mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
Support new pictures and proc macro
This commit is contained in:
parent
25368a428f
commit
a6d49fd9ce
11 changed files with 388 additions and 390 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -335,6 +335,7 @@ dependencies = [
|
|||
"filepath",
|
||||
"id3",
|
||||
"lewton",
|
||||
"lofty_attr",
|
||||
"metaflac",
|
||||
"mp3-duration",
|
||||
"mp4ameta",
|
||||
|
@ -344,6 +345,14 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lofty_attr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
|
@ -640,9 +649,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.68"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87"
|
||||
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -1,8 +1,63 @@
|
|||
use crate::vorbis_tag::VORBIS;
|
||||
use crate::{Error, Result};
|
||||
|
||||
#[cfg(feature = "ogg")]
|
||||
use ogg::PacketWriteEndInfo;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
|
||||
pub(crate) fn vorbis_generic(
|
||||
file: &mut File,
|
||||
sig: &[u8],
|
||||
vendor: &str,
|
||||
comments: &HashMap<String, String>,
|
||||
) -> Result<()> {
|
||||
let mut packet = Vec::new();
|
||||
packet.extend(sig.iter());
|
||||
|
||||
let comments: Vec<(String, String)> = comments
|
||||
.iter()
|
||||
.map(|(a, b)| (a.to_string(), b.to_string()))
|
||||
.collect();
|
||||
|
||||
let vendor_len = vendor.len() as u32;
|
||||
packet.extend(vendor_len.to_le_bytes().iter());
|
||||
packet.extend(vendor.as_bytes().iter());
|
||||
|
||||
let comments_len = comments.len() as u32;
|
||||
packet.extend(comments_len.to_le_bytes().iter());
|
||||
|
||||
let mut comment_str = Vec::new();
|
||||
|
||||
for (a, b) in comments {
|
||||
comment_str.push(format!("{}={}", a, b));
|
||||
let last = comment_str.last().unwrap();
|
||||
let len = last.as_bytes().len() as u32;
|
||||
packet.extend(len.to_le_bytes().iter());
|
||||
packet.extend(last.as_bytes().iter());
|
||||
}
|
||||
|
||||
if sig == VORBIS {
|
||||
packet.push(1);
|
||||
}
|
||||
|
||||
let mut file_bytes = Vec::new();
|
||||
file.read_to_end(&mut file_bytes)?;
|
||||
|
||||
let data = if sig == VORBIS {
|
||||
ogg(Cursor::new(file_bytes), &*packet)?
|
||||
} else {
|
||||
opus(Cursor::new(file_bytes), &*packet)?
|
||||
};
|
||||
|
||||
file.seek(SeekFrom::Start(0))?;
|
||||
file.set_len(0)?;
|
||||
file.write_all(&data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn ogg<T>(data: T, packet: &[u8]) -> Result<Vec<u8>>
|
||||
where
|
||||
T: Read + Seek,
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
#![cfg(feature = "format-ape")]
|
||||
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, MimeType, Picture, PictureType,
|
||||
Result, TagType, ToAny, ToAnyTag,
|
||||
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType, ToAny, ToAnyTag,
|
||||
};
|
||||
use lofty_attr::impl_tag;
|
||||
|
||||
pub use ape::Tag as ApeInnerTag;
|
||||
|
||||
use crate::types::picture::APE_PICTYPES;
|
||||
use ape::Item;
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use filepath::FilePath;
|
||||
use std::borrow::Cow;
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "duration")]
|
||||
use std::time::Duration;
|
||||
|
||||
impl_tag!(ApeTag, ApeInnerTag, TagType::Ape);
|
||||
#[impl_tag(ApeInnerTag, TagType::Ape)]
|
||||
pub struct ApeTag;
|
||||
|
||||
impl ApeTag {
|
||||
#[allow(missing_docs)]
|
||||
|
@ -45,6 +43,17 @@ impl ApeTag {
|
|||
None
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
fn get_picture(&self, item: &Item) -> Option<Picture> {
|
||||
if let ape::ItemValue::Binary(bin) = &item.value {
|
||||
if let Ok(pic) = Picture::from_ape_bytes(&item.key, bin) {
|
||||
return Some(pic);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn set_value<V>(&mut self, key: &str, val: V)
|
||||
where
|
||||
V: Into<String>,
|
||||
|
@ -148,7 +157,7 @@ impl AudioTagEdit for ApeTag {
|
|||
|
||||
fn front_cover(&self) -> Option<Picture> {
|
||||
if let Some(val) = self.inner.item("Cover Art (Front)") {
|
||||
return get_picture(val);
|
||||
return self.get_picture(val);
|
||||
}
|
||||
|
||||
None
|
||||
|
@ -157,7 +166,7 @@ impl AudioTagEdit for ApeTag {
|
|||
fn set_front_cover(&mut self, cover: Picture) {
|
||||
self.remove_front_cover();
|
||||
|
||||
if let Ok(item) = ape::Item::from_binary("Cover Art (Front)", cover.data) {
|
||||
if let Ok(item) = ape::Item::from_binary("Cover Art (Front)", cover.as_ape_bytes()) {
|
||||
self.inner.set_item(item)
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +177,7 @@ impl AudioTagEdit for ApeTag {
|
|||
|
||||
fn back_cover(&self) -> Option<Picture> {
|
||||
if let Some(val) = self.inner.item("Cover Art (Back)") {
|
||||
return get_picture(val);
|
||||
return self.get_picture(val);
|
||||
}
|
||||
|
||||
None
|
||||
|
@ -177,7 +186,7 @@ impl AudioTagEdit for ApeTag {
|
|||
fn set_back_cover(&mut self, cover: Picture) {
|
||||
self.remove_back_cover();
|
||||
|
||||
if let Ok(item) = ape::Item::from_binary("Cover Art (Back)", cover.data) {
|
||||
if let Ok(item) = ape::Item::from_binary("Cover Art (Back)", cover.as_ape_bytes()) {
|
||||
self.inner.set_item(item)
|
||||
}
|
||||
}
|
||||
|
@ -186,9 +195,22 @@ impl AudioTagEdit for ApeTag {
|
|||
self.remove_key("Cover Art (Back)")
|
||||
}
|
||||
|
||||
fn pictures(&self) -> Option<Vec<Picture>> {
|
||||
// TODO
|
||||
None
|
||||
fn pictures(&self) -> Option<Cow<'static, [Picture]>> {
|
||||
let mut pics = Vec::new();
|
||||
|
||||
for pic_type in &APE_PICTYPES {
|
||||
if let Some(item) = self.inner.item(pic_type) {
|
||||
if let Some(pic) = self.get_picture(item) {
|
||||
pics.push(pic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pics.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Cow::from(pics))
|
||||
}
|
||||
}
|
||||
|
||||
// Track number and total tracks are stored together as num/total?
|
||||
|
@ -283,49 +305,6 @@ impl AudioTagEdit for ApeTag {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_picture(item: &Item) -> Option<Picture> {
|
||||
if let ape::ItemValue::Binary(bin) = &item.value {
|
||||
if !bin.is_empty() {
|
||||
let pic_type = match &*item.key {
|
||||
"Cover Art (Front)" => PictureType::CoverFront,
|
||||
"Cover Art (Back)" => PictureType::CoverBack,
|
||||
_ => PictureType::Other,
|
||||
};
|
||||
|
||||
let data_pos: Option<usize> =
|
||||
if bin.starts_with(&[b'\xff']) || bin.starts_with(&[b'\x89']) {
|
||||
Some(0)
|
||||
} else {
|
||||
bin.iter().find(|x| x == &&b'\0').map(|pos| *pos as usize)
|
||||
};
|
||||
|
||||
if let Some(pos) = data_pos {
|
||||
let mut cursor = Cursor::new(bin.clone());
|
||||
|
||||
if cursor.seek(SeekFrom::Start((pos + 1) as u64)).is_ok() {
|
||||
if let Ok(mime) = cursor.read_u32::<LittleEndian>() {
|
||||
if let Some(mime_type) = match &mime.to_le_bytes() {
|
||||
b"PNG\0" => Some(MimeType::Png),
|
||||
b"JPEG" => Some(MimeType::Jpeg),
|
||||
_ => None,
|
||||
} {
|
||||
cursor.set_position(0_u64);
|
||||
|
||||
return Some(Picture {
|
||||
pic_type,
|
||||
mime_type,
|
||||
data: cursor.into_inner(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl AudioTagWrite for ApeTag {
|
||||
fn write_to(&self, file: &mut File) -> Result<()> {
|
||||
// Write only uses paths, this is annoying
|
||||
|
|
|
@ -2,23 +2,23 @@
|
|||
|
||||
use crate::tag::Id3Format;
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture,
|
||||
Result, TagType, ToAny, ToAnyTag,
|
||||
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture, 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::convert::TryInto;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "duration")]
|
||||
use std::time::Duration;
|
||||
|
||||
impl_tag!(Id3v2Tag, Id3v2InnerTag, TagType::Id3v2(Id3Format::Default));
|
||||
#[impl_tag(Id3v2InnerTag, TagType::Id3v2(Id3Format::Default))]
|
||||
pub struct Id3v2Tag;
|
||||
|
||||
impl Id3v2Tag {
|
||||
#[allow(missing_docs)]
|
||||
|
@ -36,45 +36,54 @@ impl Id3v2Tag {
|
|||
Id3Format::Riff => Ok(Self {
|
||||
inner: Id3v2InnerTag::read_from_wav(&path)?,
|
||||
#[cfg(feature = "duration")]
|
||||
duration: None, // TODO
|
||||
duration: None,
|
||||
}),
|
||||
Id3Format::Form => Ok(Self {
|
||||
inner: Id3v2InnerTag::read_from_aiff(&path)?,
|
||||
#[cfg(feature = "duration")]
|
||||
duration: None, // TODO
|
||||
duration: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&id3::frame::Picture> for Picture {
|
||||
impl std::convert::TryFrom<id3::frame::Picture> for Picture {
|
||||
type Error = Error;
|
||||
fn try_from(inp: &id3::frame::Picture) -> Result<Self> {
|
||||
fn try_from(inp: id3::frame::Picture) -> Result<Self> {
|
||||
let id3::frame::Picture {
|
||||
ref mime_type,
|
||||
data,
|
||||
ref picture_type,
|
||||
description,
|
||||
..
|
||||
} = inp;
|
||||
let mime_type: MimeType = mime_type.as_str().try_into()?;
|
||||
let pic_type: PictureType = picture_type.into();
|
||||
let pic_type = *picture_type;
|
||||
let description = if description == String::new() {
|
||||
None
|
||||
} else {
|
||||
Some(Cow::from(description))
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
pic_type,
|
||||
mime_type,
|
||||
data: data.clone(),
|
||||
description,
|
||||
data: Cow::from(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Picture> for id3::frame::Picture {
|
||||
impl TryFrom<Picture> for id3::frame::Picture {
|
||||
type Error = Error;
|
||||
fn try_from(inp: Picture) -> Result<Self> {
|
||||
Ok(Self {
|
||||
mime_type: String::from(inp.mime_type),
|
||||
picture_type: inp.pic_type.into(),
|
||||
description: "".to_string(),
|
||||
data: inp.data,
|
||||
picture_type: inp.pic_type,
|
||||
description: inp
|
||||
.description
|
||||
.map_or_else(|| "".to_string(), |d| d.to_string()),
|
||||
data: Vec::from(inp.data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -176,8 +185,13 @@ impl AudioTagEdit for Id3v2Tag {
|
|||
.and_then(|pic| {
|
||||
Some(Picture {
|
||||
pic_type: PictureType::CoverFront,
|
||||
data: pic.data.clone(),
|
||||
data: Cow::from(pic.data.clone()),
|
||||
mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
description: if pic.description == String::new() {
|
||||
None
|
||||
} else {
|
||||
Some(Cow::from(pic.description.clone()))
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -202,8 +216,13 @@ impl AudioTagEdit for Id3v2Tag {
|
|||
.and_then(|pic| {
|
||||
Some(Picture {
|
||||
pic_type: PictureType::CoverBack,
|
||||
data: pic.data.clone(),
|
||||
data: Cow::from(pic.data.clone()),
|
||||
mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
description: if pic.description == String::new() {
|
||||
None
|
||||
} else {
|
||||
Some(Cow::from(pic.description.clone()))
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -221,20 +240,20 @@ impl AudioTagEdit for Id3v2Tag {
|
|||
.remove_picture_by_type(id3::frame::PictureType::CoverBack);
|
||||
}
|
||||
|
||||
fn pictures(&self) -> Option<Vec<Picture>> {
|
||||
fn pictures(&self) -> Option<Cow<'static, [Picture]>> {
|
||||
let mut pictures = self.inner.pictures().peekable();
|
||||
|
||||
if pictures.peek().is_some() {
|
||||
let mut collection = Vec::new();
|
||||
|
||||
for pic in pictures {
|
||||
match TryInto::<Picture>::try_into(pic) {
|
||||
match TryInto::<Picture>::try_into(pic.clone()) {
|
||||
Ok(p) => collection.push(p),
|
||||
Err(_) => return None,
|
||||
}
|
||||
}
|
||||
|
||||
return Some(collection);
|
||||
return Some(Cow::from(collection));
|
||||
}
|
||||
|
||||
None
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
#![cfg(feature = "format-mp4")]
|
||||
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture,
|
||||
Result, TagType, ToAny, ToAnyTag,
|
||||
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture, 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;
|
||||
#[cfg(feature = "duration")]
|
||||
use std::time::Duration;
|
||||
|
||||
impl_tag!(Mp4Tag, Mp4InnerTag, TagType::Mp4);
|
||||
#[impl_tag(Mp4InnerTag, TagType::Mp4)]
|
||||
pub struct Mp4Tag {}
|
||||
|
||||
impl Mp4Tag {
|
||||
#[allow(missing_docs)]
|
||||
|
@ -36,18 +37,21 @@ impl std::convert::TryFrom<mp4ameta::Data> for Picture {
|
|||
Ok(match inp {
|
||||
mp4ameta::Data::Png(data) => Self {
|
||||
pic_type: PictureType::Other,
|
||||
data,
|
||||
mime_type: MimeType::Png,
|
||||
description: None,
|
||||
data: Cow::from(data),
|
||||
},
|
||||
mp4ameta::Data::Jpeg(data) => Self {
|
||||
pic_type: PictureType::Other,
|
||||
data,
|
||||
mime_type: MimeType::Jpeg,
|
||||
description: None,
|
||||
data: Cow::from(data),
|
||||
},
|
||||
mp4ameta::Data::Bmp(data) => Self {
|
||||
pic_type: PictureType::Other,
|
||||
mime_type: MimeType::Bmp,
|
||||
data,
|
||||
description: None,
|
||||
data: Cow::from(data),
|
||||
},
|
||||
_ => return Err(Error::NotAPicture),
|
||||
})
|
||||
|
@ -110,33 +114,40 @@ impl AudioTagEdit for Mp4Tag {
|
|||
}
|
||||
|
||||
fn front_cover(&self) -> Option<Picture> {
|
||||
self.inner.artwork().and_then(|data| match data {
|
||||
mp4ameta::Data::Jpeg(d) => Some(Picture {
|
||||
pic_type: PictureType::Other,
|
||||
data: d.clone(),
|
||||
mime_type: MimeType::Jpeg,
|
||||
}),
|
||||
mp4ameta::Data::Png(d) => Some(Picture {
|
||||
pic_type: PictureType::Other,
|
||||
data: d.clone(),
|
||||
mime_type: MimeType::Png,
|
||||
}),
|
||||
mp4ameta::Data::Bmp(d) => Some(Picture {
|
||||
pic_type: PictureType::Other,
|
||||
data: d.clone(),
|
||||
mime_type: MimeType::Bmp,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
if let Some(picture) = &self.inner.artwork() {
|
||||
return match picture {
|
||||
mp4ameta::Data::Jpeg(d) => Some(Picture {
|
||||
pic_type: PictureType::Other,
|
||||
mime_type: MimeType::Jpeg,
|
||||
description: None,
|
||||
data: Cow::from(d.clone()),
|
||||
}),
|
||||
mp4ameta::Data::Png(d) => Some(Picture {
|
||||
pic_type: PictureType::Other,
|
||||
mime_type: MimeType::Png,
|
||||
description: None,
|
||||
data: Cow::from(d.clone()),
|
||||
}),
|
||||
mp4ameta::Data::Bmp(d) => Some(Picture {
|
||||
pic_type: PictureType::Other,
|
||||
mime_type: MimeType::Bmp,
|
||||
description: None,
|
||||
data: Cow::from(d.clone()),
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn set_front_cover(&mut self, cover: Picture) {
|
||||
self.inner.remove_artwork();
|
||||
|
||||
self.inner.add_artwork(match cover.mime_type {
|
||||
MimeType::Png => mp4ameta::Data::Png(cover.data),
|
||||
MimeType::Jpeg => mp4ameta::Data::Jpeg(cover.data),
|
||||
MimeType::Bmp => mp4ameta::Data::Bmp(cover.data),
|
||||
MimeType::Png => mp4ameta::Data::Png(Vec::from(cover.data)),
|
||||
MimeType::Jpeg => mp4ameta::Data::Jpeg(Vec::from(cover.data)),
|
||||
MimeType::Bmp => mp4ameta::Data::Bmp(Vec::from(cover.data)),
|
||||
_ => panic!("Attempt to add an invalid image format to MP4"),
|
||||
});
|
||||
}
|
||||
|
@ -157,7 +168,7 @@ impl AudioTagEdit for Mp4Tag {
|
|||
self.inner.remove_artwork();
|
||||
}
|
||||
|
||||
fn pictures(&self) -> Option<Vec<Picture>> {
|
||||
fn pictures(&self) -> Option<Cow<'static, [Picture]>> {
|
||||
let mut pictures = Vec::new();
|
||||
|
||||
for art in self.inner.artworks() {
|
||||
|
@ -172,7 +183,8 @@ impl AudioTagEdit for Mp4Tag {
|
|||
pictures.push(Picture {
|
||||
pic_type: PictureType::Other,
|
||||
mime_type,
|
||||
data,
|
||||
description: None,
|
||||
data: Cow::from(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +192,7 @@ impl AudioTagEdit for Mp4Tag {
|
|||
if pictures.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(pictures)
|
||||
Some(Cow::from(pictures))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,19 +2,16 @@
|
|||
|
||||
use crate::components::logic;
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType,
|
||||
ToAny, ToAnyTag,
|
||||
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType, ToAny, ToAnyTag,
|
||||
};
|
||||
use lofty_attr::impl_tag;
|
||||
|
||||
use std::borrow::BorrowMut;
|
||||
use std::borrow::{BorrowMut, Cow};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(feature = "duration")]
|
||||
use std::time::Duration;
|
||||
|
||||
struct RiffInnerTag {
|
||||
data: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
@ -27,6 +24,9 @@ impl Default for RiffInnerTag {
|
|||
}
|
||||
}
|
||||
|
||||
#[impl_tag(RiffInnerTag, TagType::RiffInfo)]
|
||||
pub struct RiffTag;
|
||||
|
||||
impl RiffTag {
|
||||
#[allow(missing_docs)]
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
|
@ -44,8 +44,6 @@ impl RiffTag {
|
|||
}
|
||||
}
|
||||
|
||||
impl_tag!(RiffTag, RiffInnerTag, TagType::RiffInfo);
|
||||
|
||||
impl RiffTag {
|
||||
fn get_value(&self, key: &str) -> Option<&str> {
|
||||
self.inner
|
||||
|
@ -166,7 +164,7 @@ impl AudioTagEdit for RiffTag {
|
|||
fn remove_back_cover(&mut self) {}
|
||||
|
||||
/// This will always return `None`, as this is non-standard
|
||||
fn pictures(&self) -> Option<Vec<Picture>> {
|
||||
fn pictures(&self) -> Option<Cow<'static, [Picture]>> {
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
@ -4,29 +4,30 @@
|
|||
feature = "format-flac"
|
||||
))]
|
||||
|
||||
use crate::components::logic;
|
||||
use crate::tag::VorbisFormat;
|
||||
use crate::components::logic::write::vorbis_generic;
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture,
|
||||
PictureType, Result, TagType, ToAny, ToAnyTag,
|
||||
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, Picture, PictureType, Result,
|
||||
TagType, ToAny, ToAnyTag, VorbisFormat,
|
||||
};
|
||||
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::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "duration")]
|
||||
use std::time::Duration;
|
||||
|
||||
const VORBIS: [u8; 7] = [3, 118, 111, 114, 98, 105, 115];
|
||||
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 {
|
||||
format: Option<VorbisFormat>,
|
||||
vendor: String,
|
||||
comments: HashMap<String, String>,
|
||||
pictures: Option<Vec<Picture>>,
|
||||
pictures: Option<Cow<'static, [Picture]>>,
|
||||
}
|
||||
|
||||
impl Default for VorbisInnerTag {
|
||||
|
@ -66,147 +67,109 @@ impl VorbisInnerTag {
|
|||
self.comments = comments;
|
||||
}
|
||||
|
||||
fn from_path<P>(path: P, format: VorbisFormat) -> Result<Self>
|
||||
fn from_path<P>(path: P, format: &VorbisFormat) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match format {
|
||||
VorbisFormat::Ogg => {
|
||||
let headers = lewton::inside_ogg::OggStreamReader::new(File::open(path)?).unwrap();
|
||||
let tag = lewton::inside_ogg::OggStreamReader::new(File::open(path)?)?;
|
||||
let vorbis_tag: VorbisTag = tag.try_into()?;
|
||||
|
||||
let vendor = headers.comment_hdr.vendor;
|
||||
|
||||
let mut comments = headers.comment_hdr.comment_list;
|
||||
|
||||
let mut pictures: Vec<Picture> = Vec::new();
|
||||
|
||||
if let Some(p) = comments
|
||||
.iter()
|
||||
.position(|(k, _)| *k == "METADATA_BLOCK_PICTURE")
|
||||
{
|
||||
let kv = comments.remove(p);
|
||||
if let Some(pic) = picture_from_data(&*kv.1)? {
|
||||
pictures.push(pic)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
format: Some(format),
|
||||
vendor,
|
||||
comments: comments.into_iter().collect(),
|
||||
pictures: if pictures.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(pictures)
|
||||
},
|
||||
})
|
||||
Ok(vorbis_tag.inner)
|
||||
},
|
||||
VorbisFormat::Opus => {
|
||||
let headers = opus_headers::parse_from_path(path)?;
|
||||
let vendor = headers.comments.vendor;
|
||||
let tag = opus_headers::parse_from_path(path)?;
|
||||
let vorbis_tag: VorbisTag = tag.try_into()?;
|
||||
|
||||
let mut comments = headers.comments.user_comments;
|
||||
|
||||
// TODO: opus_headers doesn't store all keys
|
||||
let pictures = if let Some(data) = comments.remove("METADATA_BLOCK_PICTURE") {
|
||||
picture_from_data(&*data)?.map(|pic| vec![pic])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
format: Some(format),
|
||||
vendor,
|
||||
comments,
|
||||
pictures,
|
||||
})
|
||||
Ok(vorbis_tag.inner)
|
||||
},
|
||||
VorbisFormat::Flac => {
|
||||
let headers = metaflac::Tag::read_from_path(path)?;
|
||||
let as_vorbis: VorbisTag = headers.into();
|
||||
let tag = metaflac::Tag::read_from_path(path)?;
|
||||
let vorbis_tag: VorbisTag = tag.try_into()?;
|
||||
|
||||
Ok(as_vorbis.inner)
|
||||
Ok(vorbis_tag.inner)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn picture_from_data(data: &str) -> Result<Option<Picture>> {
|
||||
let data = match base64::decode(data) {
|
||||
Ok(o) => o,
|
||||
Err(_) => data.as_bytes().to_vec(),
|
||||
};
|
||||
#[impl_tag(VorbisInnerTag, TagType::Vorbis(VorbisFormat::Ogg))]
|
||||
pub struct VorbisTag;
|
||||
|
||||
let mut i = 0;
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
impl TryFrom<lewton::inside_ogg::OggStreamReader<File>> for VorbisTag {
|
||||
type Error = crate::Error;
|
||||
|
||||
let picture_type_b = u32::from_le_bytes(match (&data[i..i + 4]).try_into() {
|
||||
Ok(o) => o,
|
||||
Err(_) => return Err(Error::InvalidData),
|
||||
});
|
||||
|
||||
let picture_type = match picture_type_b {
|
||||
3 => PictureType::CoverFront,
|
||||
4 => PictureType::CoverBack,
|
||||
_ => PictureType::Other,
|
||||
};
|
||||
|
||||
i += 4;
|
||||
|
||||
match data[i..i + 4].try_into() {
|
||||
Ok(mime_len) => {
|
||||
i += 4;
|
||||
let mime_len = u32::from_le_bytes(mime_len);
|
||||
|
||||
match String::from_utf8(data[i..i + mime_len as usize].to_vec()) {
|
||||
Ok(mime_type) => {
|
||||
let mime_type = MimeType::try_from(&*mime_type);
|
||||
|
||||
match mime_type {
|
||||
Ok(mime_type) => {
|
||||
let content = data[(8 + mime_len) as usize..].to_vec();
|
||||
|
||||
Ok(Some(Picture {
|
||||
pic_type: picture_type,
|
||||
data: content,
|
||||
mime_type,
|
||||
}))
|
||||
},
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
},
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
},
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
impl_tag!(
|
||||
VorbisTag,
|
||||
VorbisInnerTag,
|
||||
TagType::Vorbis(VorbisFormat::Ogg)
|
||||
);
|
||||
|
||||
impl VorbisTag {
|
||||
#[allow(missing_docs)]
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub fn read_from_path<P>(path: P, format: VorbisFormat) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self {
|
||||
inner: VorbisInnerTag::from_path(path, format)?,
|
||||
#[cfg(feature = "duration")]
|
||||
duration: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<metaflac::Tag> for VorbisTag {
|
||||
fn from(inp: metaflac::Tag) -> Self {
|
||||
fn try_from(inp: OggStreamReader<File>) -> Result<Self> {
|
||||
let mut tag = Self::default();
|
||||
|
||||
let (comments, vendor, pictures) = if let Some(comments) = inp.vorbis_comments() {
|
||||
let mut comments = inp.comment_hdr.comment_list;
|
||||
|
||||
let mut pictures: Vec<Picture> = Vec::new();
|
||||
|
||||
if let Some(p) = comments
|
||||
.iter()
|
||||
.position(|(k, _)| *k == "METADATA_BLOCK_PICTURE")
|
||||
{
|
||||
let kv = comments.remove(p);
|
||||
if let Ok(pic) = Picture::from_apic_bytes(&kv.1.as_bytes()) {
|
||||
pictures.push(pic)
|
||||
}
|
||||
}
|
||||
|
||||
tag.inner = VorbisInnerTag {
|
||||
format: Some(VorbisFormat::Ogg),
|
||||
vendor: inp.comment_hdr.vendor,
|
||||
comments: comments.into_iter().collect(),
|
||||
pictures: if pictures.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Cow::from(pictures))
|
||||
},
|
||||
};
|
||||
|
||||
Ok(tag)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "format-opus")]
|
||||
impl TryFrom<opus_headers::OpusHeaders> for VorbisTag {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(inp: OpusHeaders) -> Result<Self> {
|
||||
let mut tag = Self::default();
|
||||
|
||||
let mut comments = inp.comments.user_comments;
|
||||
|
||||
// TODO: opus_headers doesn't store all keys
|
||||
let mut pictures = None;
|
||||
|
||||
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]))
|
||||
}
|
||||
}
|
||||
|
||||
tag.inner = VorbisInnerTag {
|
||||
format: Some(VorbisFormat::Opus),
|
||||
vendor: inp.comments.vendor,
|
||||
comments,
|
||||
pictures,
|
||||
};
|
||||
|
||||
Ok(tag)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "format-flac")]
|
||||
impl TryFrom<metaflac::Tag> for VorbisTag {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(inp: metaflac::Tag) -> Result<Self> {
|
||||
let mut tag = Self::default();
|
||||
|
||||
if let Some(comments) = inp.vorbis_comments() {
|
||||
let comments = comments.clone();
|
||||
let mut user_comments = comments.comments;
|
||||
|
||||
|
@ -214,7 +177,7 @@ impl From<metaflac::Tag> for VorbisTag {
|
|||
|
||||
if let Some(data) = user_comments.remove("METADATA_BLOCK_PICTURE") {
|
||||
for item in data {
|
||||
if let Ok(Some(pic)) = picture_from_data(&*item) {
|
||||
if let Ok(pic) = Picture::from_apic_bytes(&item.as_bytes()) {
|
||||
pictures.push(pic)
|
||||
}
|
||||
}
|
||||
|
@ -231,48 +194,32 @@ impl From<metaflac::Tag> for VorbisTag {
|
|||
let comment_collection: HashMap<String, String> =
|
||||
comment_collection.into_iter().collect();
|
||||
|
||||
let vendor = comments.vendor_string;
|
||||
tag.inner = VorbisInnerTag {
|
||||
format: Some(VorbisFormat::Flac),
|
||||
vendor: comments.vendor_string,
|
||||
comments: comment_collection,
|
||||
pictures: Some(Cow::from(pictures)),
|
||||
};
|
||||
|
||||
(comment_collection, vendor, Some(pictures))
|
||||
} else {
|
||||
let comments: HashMap<String, String> = HashMap::new();
|
||||
let vendor = String::new();
|
||||
return Ok(tag);
|
||||
}
|
||||
|
||||
(comments, vendor, None)
|
||||
};
|
||||
|
||||
tag.inner = VorbisInnerTag {
|
||||
format: Some(VorbisFormat::Flac),
|
||||
vendor,
|
||||
comments,
|
||||
pictures,
|
||||
};
|
||||
|
||||
tag
|
||||
Err(Error::InvalidData)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&VorbisTag> for metaflac::Tag {
|
||||
fn from(inp: &VorbisTag) -> Self {
|
||||
let mut tag = Self::default();
|
||||
|
||||
tag.remove_blocks(metaflac::BlockType::VorbisComment);
|
||||
|
||||
let vendor = inp.inner.vendor.clone();
|
||||
let mut comment_collection: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
for (k, v) in inp.inner.comments.clone() {
|
||||
comment_collection.insert(k, vec![v]);
|
||||
}
|
||||
|
||||
tag.push_block(metaflac::Block::VorbisComment(
|
||||
metaflac::block::VorbisComment {
|
||||
vendor_string: vendor,
|
||||
comments: comment_collection,
|
||||
},
|
||||
));
|
||||
|
||||
tag
|
||||
impl VorbisTag {
|
||||
#[allow(missing_docs)]
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub fn read_from_path<P>(path: P, format: &VorbisFormat) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self {
|
||||
inner: VorbisInnerTag::from_path(path, &format)?,
|
||||
#[cfg(feature = "duration")]
|
||||
duration: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,13 +317,15 @@ impl AudioTagEdit for VorbisTag {
|
|||
fn set_front_cover(&mut self, cover: Picture) {
|
||||
self.remove_front_cover();
|
||||
|
||||
let pictures = create_cover(cover, self.inner.pictures.clone());
|
||||
let pictures = create_cover(&cover, &self.inner.pictures);
|
||||
self.inner.pictures = pictures
|
||||
}
|
||||
|
||||
fn remove_front_cover(&mut self) {
|
||||
if let Some(mut p) = self.inner.pictures.clone() {
|
||||
p.retain(|pic| Some(pic) != self.front_cover().as_ref())
|
||||
if let Some(p) = self.inner.pictures.clone() {
|
||||
let mut p = p.to_vec();
|
||||
p.retain(|pic| Some(pic) != self.front_cover().as_ref());
|
||||
self.inner.pictures = Some(Cow::from(p));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -387,17 +336,19 @@ impl AudioTagEdit for VorbisTag {
|
|||
fn set_back_cover(&mut self, cover: Picture) {
|
||||
self.remove_front_cover();
|
||||
|
||||
let pictures = create_cover(cover, self.inner.pictures.clone());
|
||||
let pictures = create_cover(&cover, &self.inner.pictures);
|
||||
self.inner.pictures = pictures
|
||||
}
|
||||
|
||||
fn remove_back_cover(&mut self) {
|
||||
if let Some(mut p) = self.inner.pictures.clone() {
|
||||
p.retain(|pic| Some(pic) != self.back_cover().as_ref())
|
||||
if let Some(p) = self.inner.pictures.clone() {
|
||||
let mut p = p.to_vec();
|
||||
p.retain(|pic| Some(pic) != self.back_cover().as_ref());
|
||||
self.inner.pictures = Some(Cow::from(p));
|
||||
}
|
||||
}
|
||||
|
||||
fn pictures(&self) -> Option<Vec<Picture>> {
|
||||
fn pictures(&self) -> Option<Cow<'static, [Picture]>> {
|
||||
self.inner.pictures.clone()
|
||||
}
|
||||
|
||||
|
@ -460,11 +411,11 @@ impl AudioTagEdit for VorbisTag {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_cover(p_type: PictureType, pictures: &Option<Vec<Picture>>) -> Option<Picture> {
|
||||
fn get_cover(p_type: PictureType, pictures: &Option<Cow<'static, [Picture]>>) -> Option<Picture> {
|
||||
match pictures {
|
||||
None => None,
|
||||
Some(pictures) => {
|
||||
for pic in pictures {
|
||||
for pic in pictures.iter() {
|
||||
if pic.pic_type == p_type {
|
||||
return Some(pic.clone());
|
||||
}
|
||||
|
@ -475,37 +426,25 @@ fn get_cover(p_type: PictureType, pictures: &Option<Vec<Picture>>) -> Option<Pic
|
|||
}
|
||||
}
|
||||
|
||||
fn create_cover(cover: Picture, pictures: Option<Vec<Picture>>) -> Option<Vec<Picture>> {
|
||||
let mime = String::from(cover.mime_type);
|
||||
let mime_len = (mime.len() as u32).to_le_bytes();
|
||||
fn create_cover(
|
||||
cover: &Picture,
|
||||
pictures: &Option<Cow<'static, [Picture]>>,
|
||||
) -> Option<Cow<'static, [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);
|
||||
|
||||
let picture_type = match cover.pic_type {
|
||||
PictureType::CoverFront => 3_u32.to_le_bytes(),
|
||||
PictureType::CoverBack => 4_u32.to_le_bytes(),
|
||||
PictureType::Other => unreachable!(),
|
||||
};
|
||||
pictures.push(pic);
|
||||
return Some(Cow::from(pictures));
|
||||
}
|
||||
|
||||
let data = cover.data;
|
||||
|
||||
let mut encoded = Vec::new();
|
||||
encoded.extend(picture_type.iter());
|
||||
encoded.extend(mime_len.iter());
|
||||
encoded.extend(mime.as_bytes().iter());
|
||||
encoded.extend(data.iter());
|
||||
|
||||
let encoded = base64::encode(encoded);
|
||||
|
||||
if let Ok(Some(pic)) = picture_from_data(&*encoded) {
|
||||
if let Some(mut pictures) = pictures {
|
||||
pictures.retain(|p| p.pic_type != PictureType::CoverBack);
|
||||
pictures.push(pic);
|
||||
Some(pictures)
|
||||
} else {
|
||||
Some(vec![pic])
|
||||
return Some(Cow::from(vec![pic]));
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
impl AudioTagWrite for VorbisTag {
|
||||
|
@ -513,15 +452,47 @@ impl AudioTagWrite for VorbisTag {
|
|||
if let Some(format) = self.inner.format.clone() {
|
||||
match format {
|
||||
VorbisFormat::Ogg => {
|
||||
write(file, &VORBIS, &self.inner.vendor, &self.inner.comments)?;
|
||||
},
|
||||
VorbisFormat::Flac => {
|
||||
let mut flac_tag: metaflac::Tag = self.into();
|
||||
|
||||
flac_tag.write_to(file)?;
|
||||
vorbis_generic(file, &VORBIS, &self.inner.vendor, &self.inner.comments)?;
|
||||
},
|
||||
VorbisFormat::Opus => {
|
||||
write(file, &OPUSTAGS, &self.inner.vendor, &self.inner.comments)?;
|
||||
vorbis_generic(file, &OPUSTAGS, &self.inner.vendor, &self.inner.comments)?;
|
||||
},
|
||||
VorbisFormat::Flac => {
|
||||
// TODO
|
||||
let tag = metaflac::Tag::read_from(file)?;
|
||||
|
||||
let mut blocks: Vec<metaflac::Block> =
|
||||
tag.blocks().map(std::borrow::ToOwned::to_owned).collect();
|
||||
|
||||
let mut pictures: Vec<metaflac::Block> = Vec::new();
|
||||
let mut comment_collection: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
if let Some(pics) = self.inner.pictures.clone() {
|
||||
for pic in pics.iter() {
|
||||
pictures.push(metaflac::Block::Picture(
|
||||
metaflac::block::Picture::from_bytes(&*pic.as_apic_bytes())?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
for (k, v) in self.inner.comments.clone() {
|
||||
comment_collection.insert(k, vec![v]);
|
||||
}
|
||||
|
||||
blocks[1] = metaflac::Block::VorbisComment(metaflac::block::VorbisComment {
|
||||
vendor_string: self.inner.vendor.clone(),
|
||||
comments: comment_collection,
|
||||
});
|
||||
|
||||
blocks.append(&mut pictures);
|
||||
|
||||
file.write_all(b"fLaC")?;
|
||||
|
||||
let total = blocks.len();
|
||||
|
||||
for (i, block) in blocks.iter().enumerate() {
|
||||
block.write_to(i == total - 1, file)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -529,54 +500,3 @@ impl AudioTagWrite for VorbisTag {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
file: &mut File,
|
||||
sig: &[u8],
|
||||
vendor: &str,
|
||||
comments: &HashMap<String, String>,
|
||||
) -> Result<()> {
|
||||
let mut packet = Vec::new();
|
||||
packet.extend(sig.iter());
|
||||
|
||||
let comments: Vec<(String, String)> = comments
|
||||
.iter()
|
||||
.map(|(a, b)| (a.to_string(), b.to_string()))
|
||||
.collect();
|
||||
|
||||
let vendor_len = vendor.len() as u32;
|
||||
packet.extend(vendor_len.to_le_bytes().iter());
|
||||
packet.extend(vendor.as_bytes().iter());
|
||||
|
||||
let comments_len = comments.len() as u32;
|
||||
packet.extend(comments_len.to_le_bytes().iter());
|
||||
|
||||
let mut comment_str = Vec::new();
|
||||
|
||||
for (a, b) in comments {
|
||||
comment_str.push(format!("{}={}", a, b));
|
||||
let last = comment_str.last().unwrap();
|
||||
let len = last.as_bytes().len() as u32;
|
||||
packet.extend(len.to_le_bytes().iter());
|
||||
packet.extend(last.as_bytes().iter());
|
||||
}
|
||||
|
||||
if sig == VORBIS {
|
||||
packet.push(1);
|
||||
}
|
||||
|
||||
let mut file_bytes = Vec::new();
|
||||
file.read_to_end(&mut file_bytes)?;
|
||||
|
||||
let data = if sig == VORBIS {
|
||||
logic::write::ogg(Cursor::new(file_bytes), &*packet)?
|
||||
} else {
|
||||
logic::write::opus(Cursor::new(file_bytes), &*packet)?
|
||||
};
|
||||
|
||||
file.seek(SeekFrom::Start(0))?;
|
||||
file.set_len(0)?;
|
||||
file.write_all(&data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ impl Tag {
|
|||
feature = "format-flac",
|
||||
feature = "format-opus"
|
||||
))]
|
||||
TagType::Vorbis(format) => Ok(Box::new(VorbisTag::read_from_path(path, format)?)),
|
||||
TagType::Vorbis(format) => Ok(Box::new(VorbisTag::read_from_path(path, &format)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
use crate::components::tags::*;
|
||||
use crate::{Album, AnyTag, Picture, Result, TagType};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fs::{File, OpenOptions};
|
||||
|
||||
/// Combination of [`AudioTagEdit`], [`AudioTagWrite`], and [`ToAnyTag`]
|
||||
|
@ -104,7 +105,7 @@ pub trait AudioTagEdit {
|
|||
fn remove_back_cover(&mut self);
|
||||
|
||||
/// Returns an `Iterator` over all pictures stored in the track
|
||||
fn pictures(&self) -> Option<Vec<Picture>>;
|
||||
fn pictures(&self) -> Option<Cow<'static, [Picture]>>;
|
||||
|
||||
/// Returns the track number and total tracks
|
||||
fn track(&self) -> (Option<u32>, Option<u32>) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use lofty::{Id3Format, Tag, TagType, ToAnyTag, VorbisTag};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "format-id3", feature = "format-flac"))]
|
||||
|
@ -12,7 +13,7 @@ fn test_inner() {
|
|||
.set_title(vec!["title from metaflac::Tag"]);
|
||||
|
||||
// Turn the flac tag into a VorbisTag
|
||||
let tag: VorbisTag = innertag.into();
|
||||
let tag: VorbisTag = innertag.try_into().unwrap();
|
||||
|
||||
// Turn the VorbisTag into a Box<dyn AudioTag>
|
||||
let id3tag = tag.to_dyn_tag(TagType::Id3v2(Id3Format::Default));
|
||||
|
|
10
tests/io.rs
10
tests/io.rs
|
@ -1,5 +1,6 @@
|
|||
#![cfg(feature = "default")]
|
||||
use lofty::{MimeType, Picture, PictureType, Tag};
|
||||
use std::borrow::Cow;
|
||||
|
||||
macro_rules! full_test {
|
||||
($function:ident, $file:expr) => {
|
||||
|
@ -39,12 +40,14 @@ macro_rules! add_tags {
|
|||
Picture {
|
||||
pic_type: PictureType::CoverFront,
|
||||
mime_type: MimeType::Jpeg,
|
||||
data: vec![0, 74, 80, 69, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
description: Some(Cow::from("test")),
|
||||
data: Cow::from(vec![0; 11]),
|
||||
},
|
||||
Picture {
|
||||
pic_type: PictureType::CoverBack,
|
||||
mime_type: MimeType::Jpeg,
|
||||
data: vec![0, 74, 80, 69, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
description: Some(Cow::from("test")),
|
||||
data: Cow::from(vec![0; 11]),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -66,7 +69,8 @@ macro_rules! add_tags {
|
|||
let cover = Picture {
|
||||
pic_type: PictureType::Other,
|
||||
mime_type: MimeType::Jpeg,
|
||||
data: vec![0, 74, 80, 69, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
description: None,
|
||||
data: Cow::from(vec![0; 11]),
|
||||
};
|
||||
|
||||
println!("Setting cover");
|
||||
|
|
Loading…
Reference in a new issue