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 std::borrow::Cow;
use std::fs::File;
use std::path::Path;
use std::io::{Read, Seek};
#[impl_tag(ApeInnerTag, TagType::Ape)]
pub struct ApeTag;
@ -19,12 +19,12 @@ pub struct ApeTag;
impl ApeTag {
#[allow(missing_docs)]
#[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
P: AsRef<Path>,
R: Read + Seek,
{
Ok(Self {
inner: ape::read_from_path(&path)?,
inner: ape::read_from(reader)?,
#[cfg(feature = "duration")]
duration: None, // TODO
})

View file

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

View file

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

View file

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

View file

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