Update tags

Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
Serial 2021-06-26 15:35:39 -04:00
parent 730ce2ff6a
commit 0a7b327c03
5 changed files with 124 additions and 98 deletions

View file

@ -11,7 +11,7 @@ use crate::types::picture::APE_PICTYPES;
use ape::Item; use ape::Item;
use std::borrow::Cow; use std::borrow::Cow;
use std::fs::File; use std::fs::File;
use std::path::Path; use std::io::{Read, Seek};
#[impl_tag(ApeInnerTag, TagType::Ape)] #[impl_tag(ApeInnerTag, TagType::Ape)]
pub struct ApeTag; pub struct ApeTag;
@ -19,12 +19,12 @@ pub struct ApeTag;
impl ApeTag { impl ApeTag {
#[allow(missing_docs)] #[allow(missing_docs)]
#[allow(clippy::missing_errors_doc)] #[allow(clippy::missing_errors_doc)]
pub fn read_from_path<P>(path: P) -> Result<Self> pub fn read_from<R>(reader: &mut R) -> Result<Self>
where where
P: AsRef<Path>, R: Read + Seek,
{ {
Ok(Self { Ok(Self {
inner: ape::read_from_path(&path)?, inner: ape::read_from(reader)?,
#[cfg(feature = "duration")] #[cfg(feature = "duration")]
duration: None, // TODO duration: None, // TODO
}) })

View file

@ -2,20 +2,18 @@
use crate::tag::Id3Format; use crate::tag::Id3Format;
use crate::{ use crate::{
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture, Result, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, LoftyError, MimeType, Picture,
TagType, ToAny, ToAnyTag, PictureType, Result, TagType, ToAny, ToAnyTag,
}; };
use lofty_attr::impl_tag; use lofty_attr::impl_tag;
pub use id3::Tag as Id3v2InnerTag; pub use id3::Tag as Id3v2InnerTag;
use crate::types::picture::PictureType;
use filepath::FilePath; use filepath::FilePath;
use std::borrow::Cow; use std::borrow::Cow;
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::fs::File; use std::fs::File;
use std::io::{Read, Seek, SeekFrom}; use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
#[impl_tag(Id3v2InnerTag, TagType::Id3v2(Id3Format::Default))] #[impl_tag(Id3v2InnerTag, TagType::Id3v2(Id3Format::Default))]
pub struct Id3v2Tag; pub struct Id3v2Tag;
@ -23,23 +21,23 @@ pub struct Id3v2Tag;
impl Id3v2Tag { impl Id3v2Tag {
#[allow(missing_docs)] #[allow(missing_docs)]
#[allow(clippy::missing_errors_doc)] #[allow(clippy::missing_errors_doc)]
pub fn read_from_path<P>(path: P, format: &Id3Format) -> Result<Self> pub fn read_from<R>(reader: &mut R, format: &Id3Format) -> Result<Self>
where where
P: AsRef<Path>, R: Read + Seek,
{ {
match format { match format {
Id3Format::Default => Ok(Self { Id3Format::Default => Ok(Self {
inner: Id3v2InnerTag::read_from_path(&path)?, inner: Id3v2InnerTag::read_from(reader)?,
#[cfg(feature = "duration")] #[cfg(feature = "duration")]
duration: Some(mp3_duration::from_path(&path)?), duration: None, // TODO
}), }),
Id3Format::Riff => Ok(Self { Id3Format::Riff => Ok(Self {
inner: Id3v2InnerTag::read_from_wav(&path)?, inner: Id3v2InnerTag::read_from_wav_reader(reader)?,
#[cfg(feature = "duration")] #[cfg(feature = "duration")]
duration: None, duration: None,
}), }),
Id3Format::Form => Ok(Self { Id3Format::Form => Ok(Self {
inner: Id3v2InnerTag::read_from_aiff(&path)?, inner: Id3v2InnerTag::read_from_aiff_reader(reader)?,
#[cfg(feature = "duration")] #[cfg(feature = "duration")]
duration: None, duration: None,
}), }),
@ -48,7 +46,8 @@ impl Id3v2Tag {
} }
impl std::convert::TryFrom<id3::frame::Picture> for Picture { impl std::convert::TryFrom<id3::frame::Picture> for Picture {
type Error = Error; type Error = LoftyError;
fn try_from(inp: id3::frame::Picture) -> Result<Self> { fn try_from(inp: id3::frame::Picture) -> Result<Self> {
let id3::frame::Picture { let id3::frame::Picture {
ref mime_type, ref mime_type,
@ -75,7 +74,8 @@ impl std::convert::TryFrom<id3::frame::Picture> for Picture {
} }
impl TryFrom<Picture> for id3::frame::Picture { impl TryFrom<Picture> for id3::frame::Picture {
type Error = Error; type Error = LoftyError;
fn try_from(inp: Picture) -> Result<Self> { fn try_from(inp: Picture) -> Result<Self> {
Ok(Self { Ok(Self {
mime_type: String::from(inp.mime_type), mime_type: String::from(inp.mime_type),

View file

@ -1,17 +1,16 @@
#![cfg(feature = "format-mp4")] #![cfg(feature = "format-mp4")]
use crate::{ use crate::{
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture, Result, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, LoftyError, MimeType, Picture,
TagType, ToAny, ToAnyTag, PictureType, Result, TagType, ToAny, ToAnyTag,
}; };
use lofty_attr::impl_tag; use lofty_attr::impl_tag;
pub use mp4ameta::{Fourcc, Tag as Mp4InnerTag}; pub use mp4ameta::{Fourcc, Tag as Mp4InnerTag};
use crate::types::picture::PictureType;
use std::borrow::Cow; use std::borrow::Cow;
use std::fs::File; use std::fs::File;
use std::path::Path; use std::io::{Read, Seek};
#[impl_tag(Mp4InnerTag, TagType::Mp4)] #[impl_tag(Mp4InnerTag, TagType::Mp4)]
pub struct Mp4Tag {} pub struct Mp4Tag {}
@ -19,12 +18,12 @@ pub struct Mp4Tag {}
impl Mp4Tag { impl Mp4Tag {
#[allow(missing_docs)] #[allow(missing_docs)]
#[allow(clippy::missing_errors_doc)] #[allow(clippy::missing_errors_doc)]
pub fn read_from_path<P>(path: P) -> Result<Self> pub fn read_from<R>(reader: &mut R) -> Result<Self>
where where
P: AsRef<Path>, R: Read + Seek,
{ {
Ok(Self { Ok(Self {
inner: Mp4InnerTag::read_from_path(path)?, inner: Mp4InnerTag::read_from(reader)?,
#[cfg(feature = "duration")] #[cfg(feature = "duration")]
duration: None, duration: None,
}) })
@ -32,7 +31,8 @@ impl Mp4Tag {
} }
impl std::convert::TryFrom<mp4ameta::Data> for Picture { impl std::convert::TryFrom<mp4ameta::Data> for Picture {
type Error = Error; type Error = LoftyError;
fn try_from(inp: mp4ameta::Data) -> Result<Self> { fn try_from(inp: mp4ameta::Data) -> Result<Self> {
Ok(match inp { Ok(match inp {
mp4ameta::Data::Png(data) => Self { mp4ameta::Data::Png(data) => Self {
@ -53,7 +53,7 @@ impl std::convert::TryFrom<mp4ameta::Data> for Picture {
description: None, description: None,
data: Cow::from(data), data: Cow::from(data),
}, },
_ => return Err(Error::NotAPicture), _ => return Err(LoftyError::NotAPicture),
}) })
} }
} }

View file

@ -1,6 +1,6 @@
#![cfg(feature = "format-riff")] #![cfg(feature = "format-riff")]
use crate::components::logic; use crate::components::logic::riff;
use crate::{ use crate::{
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType, ToAny, ToAnyTag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType, ToAny, ToAnyTag,
}; };
@ -9,7 +9,7 @@ use lofty_attr::impl_tag;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::path::Path; use std::io::{Read, Seek};
struct RiffInnerTag { struct RiffInnerTag {
data: Option<HashMap<String, String>>, data: Option<HashMap<String, String>>,
@ -29,13 +29,13 @@ pub struct RiffTag;
impl RiffTag { impl RiffTag {
#[allow(missing_docs)] #[allow(missing_docs)]
#[allow(clippy::missing_errors_doc)] #[allow(clippy::missing_errors_doc)]
pub fn read_from_path<P>(path: P) -> Result<Self> pub fn read_from<R>(reader: &mut R) -> Result<Self>
where where
P: AsRef<Path>, R: Read + Seek,
{ {
Ok(Self { Ok(Self {
inner: RiffInnerTag { inner: RiffInnerTag {
data: logic::read::wav(File::open(path)?)?, data: riff::read_from(reader)?,
}, },
#[cfg(feature = "duration")] #[cfg(feature = "duration")]
duration: None, duration: None,
@ -235,7 +235,7 @@ impl AudioTagEdit for RiffTag {
impl AudioTagWrite for RiffTag { impl AudioTagWrite for RiffTag {
fn write_to(&self, file: &mut File) -> Result<()> { fn write_to(&self, file: &mut File) -> Result<()> {
if let Some(data) = self.inner.data.clone() { if let Some(data) = self.inner.data.clone() {
crate::components::logic::write::riff(file, data)?; riff::write_to(file, data)?;
} }
Ok(()) Ok(())

View file

@ -4,26 +4,29 @@
feature = "format-flac" feature = "format-flac"
))] ))]
use crate::components::logic::write::vorbis_generic; use crate::components::logic::constants::{
use crate::{ OPUSHEAD, OPUSTAGS, VORBIS_COMMENT_HEAD, VORBIS_IDENT_HEAD,
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, Picture, PictureType, Result,
TagType, ToAny, ToAnyTag, VorbisFormat,
}; };
use crate::components::logic::{flac, ogg_generic};
use crate::{
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, LoftyError, OggFormat, Picture,
PictureType, Result, TagType, ToAny, ToAnyTag,
};
#[cfg(feature = "format-opus")]
use crate::components::logic::ogg_generic::OGGTags;
use lofty_attr::impl_tag; use lofty_attr::impl_tag;
use lewton::inside_ogg::OggStreamReader; use lewton::inside_ogg::OggStreamReader;
use opus_headers::OpusHeaders;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::fs::File; use std::fs::File;
use std::path::Path; use std::io::{Read, Seek};
pub const VORBIS: [u8; 7] = [3, 118, 111, 114, 98, 105, 115];
const OPUSTAGS: [u8; 8] = [79, 112, 117, 115, 84, 97, 103, 115];
struct VorbisInnerTag { struct VorbisInnerTag {
format: Option<VorbisFormat>, format: Option<OggFormat>,
vendor: String, vendor: String,
comments: HashMap<String, String>, comments: HashMap<String, String>,
pictures: Option<Cow<'static, [Picture]>>, pictures: Option<Cow<'static, [Picture]>>,
@ -66,25 +69,25 @@ impl VorbisInnerTag {
self.comments = comments; self.comments = comments;
} }
fn from_path<P>(path: P, format: &VorbisFormat) -> Result<Self> fn read_from<R>(reader: &mut R, format: &OggFormat) -> Result<Self>
where where
P: AsRef<Path>, R: Read + Seek,
{ {
match format { match format {
VorbisFormat::Ogg => { OggFormat::Vorbis => {
let tag = lewton::inside_ogg::OggStreamReader::new(File::open(path)?)?; let tag = ogg_generic::read_from(reader, &VORBIS_IDENT_HEAD, &VORBIS_COMMENT_HEAD)?;
let vorbis_tag: VorbisTag = tag.try_into()?; let vorbis_tag: VorbisTag = tag.try_into()?;
Ok(vorbis_tag.inner) Ok(vorbis_tag.inner)
}, },
VorbisFormat::Opus => { OggFormat::Opus => {
let tag = opus_headers::parse_from_path(path)?; let tag = ogg_generic::read_from(reader, &OPUSHEAD, &OPUSTAGS)?;
let vorbis_tag: VorbisTag = tag.try_into()?; let vorbis_tag: VorbisTag = tag.try_into()?;
Ok(vorbis_tag.inner) Ok(vorbis_tag.inner)
}, },
VorbisFormat::Flac => { OggFormat::Flac => {
let tag = metaflac::Tag::read_from_path(path)?; let tag = metaflac::Tag::read_from(reader)?;
let vorbis_tag: VorbisTag = tag.try_into()?; let vorbis_tag: VorbisTag = tag.try_into()?;
Ok(vorbis_tag.inner) Ok(vorbis_tag.inner)
@ -93,12 +96,12 @@ impl VorbisInnerTag {
} }
} }
#[impl_tag(VorbisInnerTag, TagType::Vorbis(VorbisFormat::Ogg))] #[impl_tag(VorbisInnerTag, TagType::Ogg(OggFormat::Vorbis))]
pub struct VorbisTag; pub struct VorbisTag;
#[cfg(feature = "format-vorbis")] #[cfg(feature = "format-vorbis")]
impl TryFrom<lewton::inside_ogg::OggStreamReader<File>> for VorbisTag { impl TryFrom<lewton::inside_ogg::OggStreamReader<File>> for VorbisTag {
type Error = crate::Error; type Error = LoftyError;
fn try_from(inp: OggStreamReader<File>) -> Result<Self> { fn try_from(inp: OggStreamReader<File>) -> Result<Self> {
let mut tag = Self::default(); let mut tag = Self::default();
@ -118,7 +121,7 @@ impl TryFrom<lewton::inside_ogg::OggStreamReader<File>> for VorbisTag {
} }
tag.inner = VorbisInnerTag { tag.inner = VorbisInnerTag {
format: Some(VorbisFormat::Ogg), format: Some(OggFormat::Vorbis),
vendor: inp.comment_hdr.vendor, vendor: inp.comment_hdr.vendor,
comments: comments.into_iter().collect(), comments: comments.into_iter().collect(),
pictures: if pictures.is_empty() { pictures: if pictures.is_empty() {
@ -133,28 +136,34 @@ impl TryFrom<lewton::inside_ogg::OggStreamReader<File>> for VorbisTag {
} }
#[cfg(feature = "format-opus")] #[cfg(feature = "format-opus")]
impl TryFrom<opus_headers::OpusHeaders> for VorbisTag { impl TryFrom<OGGTags> for VorbisTag {
type Error = crate::Error; type Error = LoftyError;
fn try_from(inp: OpusHeaders) -> Result<Self> { fn try_from(inp: OGGTags) -> Result<Self> {
let mut tag = Self::default(); let mut tag = Self::default();
let mut comments = inp.comments.user_comments; let read_pictures = inp.1;
let comments = inp.2;
// TODO: opus_headers doesn't store all keys let mut pictures = Vec::new();
let mut pictures = None;
if let Some(data) = comments.remove("METADATA_BLOCK_PICTURE") { if !read_pictures.is_empty() {
if let Ok(pic) = Picture::from_apic_bytes(&data.as_bytes()) { for pic in read_pictures {
pictures = Some(Cow::from(vec![pic])) if let Ok(pic) = Picture::from_apic_bytes(&pic.as_bytes()) {
pictures.push(pic)
}
} }
} }
tag.inner = VorbisInnerTag { tag.inner = VorbisInnerTag {
format: Some(VorbisFormat::Opus), format: Some(inp.3),
vendor: inp.comments.vendor, vendor: inp.0,
comments, comments: comments.into_iter().collect(),
pictures, pictures: if pictures.is_empty() {
None
} else {
Some(Cow::from(pictures))
},
}; };
Ok(tag) Ok(tag)
@ -163,7 +172,7 @@ impl TryFrom<opus_headers::OpusHeaders> for VorbisTag {
#[cfg(feature = "format-flac")] #[cfg(feature = "format-flac")]
impl TryFrom<metaflac::Tag> for VorbisTag { impl TryFrom<metaflac::Tag> for VorbisTag {
type Error = crate::Error; type Error = LoftyError;
fn try_from(inp: metaflac::Tag) -> Result<Self> { fn try_from(inp: metaflac::Tag) -> Result<Self> {
let mut tag = Self::default(); let mut tag = Self::default();
@ -194,7 +203,7 @@ impl TryFrom<metaflac::Tag> for VorbisTag {
comment_collection.into_iter().collect(); comment_collection.into_iter().collect();
tag.inner = VorbisInnerTag { tag.inner = VorbisInnerTag {
format: Some(VorbisFormat::Flac), format: Some(OggFormat::Flac),
vendor: comments.vendor_string, vendor: comments.vendor_string,
comments: comment_collection, comments: comment_collection,
pictures: Some(Cow::from(pictures)), pictures: Some(Cow::from(pictures)),
@ -203,19 +212,19 @@ impl TryFrom<metaflac::Tag> for VorbisTag {
return Ok(tag); return Ok(tag);
} }
Err(Error::InvalidData) Err(LoftyError::InvalidData("Flac file contains invalid data"))
} }
} }
impl VorbisTag { impl VorbisTag {
#[allow(missing_docs)] #[allow(missing_docs)]
#[allow(clippy::missing_errors_doc)] #[allow(clippy::missing_errors_doc)]
pub fn read_from_path<P>(path: P, format: &VorbisFormat) -> Result<Self> pub fn read_from<R>(reader: &mut R, format: &OggFormat) -> Result<Self>
where where
P: AsRef<Path>, R: Read + Seek,
{ {
Ok(Self { Ok(Self {
inner: VorbisInnerTag::from_path(path, &format)?, inner: VorbisInnerTag::read_from(reader, format)?,
#[cfg(feature = "duration")] #[cfg(feature = "duration")]
duration: None, duration: None,
}) })
@ -314,10 +323,10 @@ impl AudioTagEdit for VorbisTag {
} }
fn set_front_cover(&mut self, cover: Picture) { fn set_front_cover(&mut self, cover: Picture) {
self.remove_front_cover(); if let Some(pic) = create_cover(&cover) {
self.remove_front_cover();
let pictures = create_cover(&cover, &self.inner.pictures); self.inner.pictures = Some(replace_pic(pic, &self.inner.pictures))
self.inner.pictures = pictures }
} }
fn remove_front_cover(&mut self) { fn remove_front_cover(&mut self) {
@ -333,10 +342,10 @@ impl AudioTagEdit for VorbisTag {
} }
fn set_back_cover(&mut self, cover: Picture) { fn set_back_cover(&mut self, cover: Picture) {
self.remove_front_cover(); if let Some(pic) = create_cover(&cover) {
self.remove_back_cover();
let pictures = create_cover(&cover, &self.inner.pictures); self.inner.pictures = Some(replace_pic(pic, &self.inner.pictures))
self.inner.pictures = pictures }
} }
fn remove_back_cover(&mut self) { fn remove_back_cover(&mut self) {
@ -425,39 +434,56 @@ fn get_cover(p_type: PictureType, pictures: &Option<Cow<'static, [Picture]>>) ->
} }
} }
fn create_cover( fn create_cover(cover: &Picture) -> Option<Picture> {
cover: &Picture,
pictures: &Option<Cow<'static, [Picture]>>,
) -> Option<Cow<'static, [Picture]>> {
if cover.pic_type == PictureType::CoverFront || cover.pic_type == PictureType::CoverBack { if cover.pic_type == PictureType::CoverFront || cover.pic_type == PictureType::CoverBack {
if let Ok(pic) = Picture::from_apic_bytes(&cover.as_apic_bytes()) { if let Ok(pic) = Picture::from_apic_bytes(&cover.as_apic_bytes()) {
if let Some(pictures) = pictures { return Some(pic);
let mut pictures = pictures.to_vec();
pictures.retain(|p| p.pic_type != PictureType::CoverBack);
pictures.push(pic);
return Some(Cow::from(pictures));
}
return Some(Cow::from(vec![pic]));
} }
} }
None None
} }
fn replace_pic(
pic: Picture,
pictures: &Option<Cow<'static, [Picture]>>,
) -> Cow<'static, [Picture]> {
if let Some(pictures) = pictures {
let mut pictures = pictures.to_vec();
pictures.retain(|p| p.pic_type != pic.pic_type);
pictures.push(pic);
Cow::from(pictures)
} else {
Cow::from(vec![pic])
}
}
impl AudioTagWrite for VorbisTag { impl AudioTagWrite for VorbisTag {
fn write_to(&self, file: &mut File) -> Result<()> { fn write_to(&self, file: &mut File) -> Result<()> {
if let Some(format) = self.inner.format.clone() { if let Some(format) = self.inner.format.clone() {
match format { match format {
VorbisFormat::Ogg => { OggFormat::Vorbis => {
vorbis_generic(file, &VORBIS, &self.inner.vendor, &self.inner.comments)?; ogg_generic::create_pages(
file,
&VORBIS_COMMENT_HEAD,
&self.inner.vendor,
&self.inner.comments,
&self.inner.pictures,
)?;
}, },
VorbisFormat::Opus => { OggFormat::Opus => {
vorbis_generic(file, &OPUSTAGS, &self.inner.vendor, &self.inner.comments)?; ogg_generic::create_pages(
file,
&OPUSTAGS,
&self.inner.vendor,
&self.inner.comments,
&self.inner.pictures,
)?;
}, },
VorbisFormat::Flac => { OggFormat::Flac => {
crate::components::logic::write::flac( flac::write_to(
file, file,
&self.inner.vendor, &self.inner.vendor,
&self.inner.comments, &self.inner.comments,