mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
Add support for AIFF chunks
Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
parent
ca52551807
commit
e949d2bb85
14 changed files with 304 additions and 31 deletions
|
@ -38,9 +38,10 @@ format-opus = ["ogg_pager"]
|
||||||
format-vorbis = ["ogg_pager"]
|
format-vorbis = ["ogg_pager"]
|
||||||
format-ape = ["ape"]
|
format-ape = ["ape"]
|
||||||
format-id3 = ["id3", "filepath"]
|
format-id3 = ["id3", "filepath"]
|
||||||
|
format-aiff = []
|
||||||
format-riff = []
|
format-riff = []
|
||||||
format-ogg = ["format-flac", "format-opus", "format-vorbis"]
|
format-ogg = ["format-flac", "format-opus", "format-vorbis"]
|
||||||
all_tags = ["format-ogg", "format-mp4", "format-id3", "format-riff", "format-ape"]
|
all_tags = ["format-ogg", "format-mp4", "format-id3", "format-riff", "format-ape", "format-aiff"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
|
|
127
src/components/logic/aiff.rs
Normal file
127
src/components/logic/aiff.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
use crate::{LoftyError, Result};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||||
|
use std::cmp::{max, min};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
|
pub(crate) fn read_from<T>(data: &mut T) -> Result<(Option<String>, Option<String>)>
|
||||||
|
where
|
||||||
|
T: Read + Seek,
|
||||||
|
{
|
||||||
|
let mut name_id: Option<String> = None;
|
||||||
|
let mut author_id: Option<String> = None;
|
||||||
|
|
||||||
|
data.seek(SeekFrom::Start(12))?;
|
||||||
|
|
||||||
|
while let (Ok(fourcc), Ok(size)) = (
|
||||||
|
data.read_u32::<LittleEndian>(),
|
||||||
|
data.read_u32::<BigEndian>(),
|
||||||
|
) {
|
||||||
|
match &fourcc.to_le_bytes() {
|
||||||
|
f if f == b"NAME" && name_id.is_none() => {
|
||||||
|
let mut name = vec![0; size as usize];
|
||||||
|
data.read_exact(&mut name)?;
|
||||||
|
|
||||||
|
name_id = Some(String::from_utf8(name)?);
|
||||||
|
},
|
||||||
|
f if f == b"AUTH" && author_id.is_none() => {
|
||||||
|
let mut auth = vec![0; size as usize];
|
||||||
|
data.read_exact(&mut auth)?;
|
||||||
|
|
||||||
|
author_id = Some(String::from_utf8(auth)?);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
data.seek(SeekFrom::Current(i64::from(size)))?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (&None, &None) == (&name_id, &author_id) {
|
||||||
|
return Err(LoftyError::InvalidData("AIFF file contains no text chunks"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((name_id, author_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write_to(
|
||||||
|
data: &mut File,
|
||||||
|
metadata: (Option<&String>, Option<&String>),
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut text_chunks = Vec::new();
|
||||||
|
|
||||||
|
if let Some(name_id) = metadata.0 {
|
||||||
|
let len = (name_id.len() as u32).to_be_bytes();
|
||||||
|
|
||||||
|
text_chunks.extend(b"NAME".iter());
|
||||||
|
text_chunks.extend(len.iter());
|
||||||
|
text_chunks.extend(name_id.as_bytes().iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(author_id) = metadata.1 {
|
||||||
|
let len = (author_id.len() as u32).to_be_bytes();
|
||||||
|
|
||||||
|
text_chunks.extend(b"AUTH".iter());
|
||||||
|
text_chunks.extend(len.iter());
|
||||||
|
text_chunks.extend(author_id.as_bytes().iter());
|
||||||
|
}
|
||||||
|
|
||||||
|
data.seek(SeekFrom::Start(12))?;
|
||||||
|
|
||||||
|
let mut name: Option<(usize, usize)> = None;
|
||||||
|
let mut auth: Option<(usize, usize)> = None;
|
||||||
|
|
||||||
|
while let (Ok(fourcc), Ok(size)) = (
|
||||||
|
data.read_u32::<LittleEndian>(),
|
||||||
|
data.read_u32::<BigEndian>(),
|
||||||
|
) {
|
||||||
|
let pos = (data.seek(SeekFrom::Current(0))? - 8) as usize;
|
||||||
|
|
||||||
|
match &fourcc.to_le_bytes() {
|
||||||
|
f if f == b"NAME" && name.is_none() => name = Some((pos, (pos + 8 + size as usize))),
|
||||||
|
f if f == b"AUTH" && auth.is_none() => auth = Some((pos, (pos + 8 + size as usize))),
|
||||||
|
_ => {
|
||||||
|
data.seek(SeekFrom::Current(i64::from(size)))?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.seek(SeekFrom::Start(0))?;
|
||||||
|
|
||||||
|
let mut file_bytes = Vec::new();
|
||||||
|
data.read_to_end(&mut file_bytes)?;
|
||||||
|
|
||||||
|
match (name, auth) {
|
||||||
|
(Some((n_pos, n_end)), Some((a_pos, a_end))) => {
|
||||||
|
let first_start = min(n_pos, a_pos);
|
||||||
|
let first_end = min(n_end, a_end);
|
||||||
|
|
||||||
|
let last_start = max(n_pos, a_pos);
|
||||||
|
let last_end = max(n_end, a_end);
|
||||||
|
|
||||||
|
file_bytes.drain(last_start..last_end);
|
||||||
|
file_bytes.splice(first_start..first_end, text_chunks);
|
||||||
|
},
|
||||||
|
(Some((start, end)), None) | (None, Some((start, end))) => {
|
||||||
|
file_bytes.splice(start..end, text_chunks);
|
||||||
|
},
|
||||||
|
(None, None) => {
|
||||||
|
data.seek(SeekFrom::Start(16))?;
|
||||||
|
|
||||||
|
let mut size = [0; 4];
|
||||||
|
data.read_exact(&mut size)?;
|
||||||
|
|
||||||
|
let comm_end = (20 + u32::from_le_bytes(size)) as usize;
|
||||||
|
file_bytes.splice(comm_end..comm_end, text_chunks);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_size = ((file_bytes.len() - 8) as u32).to_be_bytes();
|
||||||
|
file_bytes.splice(4..8, total_size.to_vec());
|
||||||
|
|
||||||
|
data.seek(SeekFrom::Start(0))?;
|
||||||
|
data.set_len(0)?;
|
||||||
|
data.write_all(&*file_bytes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -6,3 +6,6 @@ pub(crate) mod riff;
|
||||||
|
|
||||||
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
|
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
|
||||||
pub(crate) mod ogg;
|
pub(crate) mod ogg;
|
||||||
|
|
||||||
|
#[cfg(feature = "format-aiff")]
|
||||||
|
pub(crate) mod aiff;
|
||||||
|
|
|
@ -86,7 +86,6 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "format-riff")]
|
|
||||||
pub(crate) fn write_to(data: &mut File, metadata: HashMap<String, String>) -> Result<()> {
|
pub(crate) fn write_to(data: &mut File, metadata: HashMap<String, String>) -> Result<()> {
|
||||||
let mut packet = Vec::new();
|
let mut packet = Vec::new();
|
||||||
|
|
||||||
|
|
68
src/components/tags/aiff_tag.rs
Normal file
68
src/components/tags/aiff_tag.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use crate::components::logic::aiff;
|
||||||
|
use crate::{
|
||||||
|
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Result, TagType, ToAny, ToAnyTag,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use lofty_attr::impl_tag;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AiffInnerTag {
|
||||||
|
name_id: Option<String>,
|
||||||
|
author_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[impl_tag(AiffInnerTag, TagType::AiffText)]
|
||||||
|
pub struct AiffTag;
|
||||||
|
|
||||||
|
impl AiffTag {
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[allow(clippy::missing_errors_doc)]
|
||||||
|
pub fn read_from<R>(reader: &mut R) -> Result<Self>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
let (name_id, author_id) = aiff::read_from(reader)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner: AiffInnerTag { name_id, author_id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioTagEdit for AiffTag {
|
||||||
|
fn title(&self) -> Option<&str> {
|
||||||
|
self.inner.name_id.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_title(&mut self, title: &str) {
|
||||||
|
self.inner.name_id = Some(title.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_title(&mut self) {
|
||||||
|
self.inner.name_id = None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artist_str(&self) -> Option<&str> {
|
||||||
|
self.inner.author_id.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_artist(&mut self, artist: &str) {
|
||||||
|
self.inner.author_id = Some(artist.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_artist(&mut self) {
|
||||||
|
self.inner.author_id = None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioTagWrite for AiffTag {
|
||||||
|
fn write_to(&self, file: &mut File) -> Result<()> {
|
||||||
|
aiff::write_to(
|
||||||
|
file,
|
||||||
|
(self.inner.name_id.as_ref(), self.inner.author_id.as_ref()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -123,8 +123,8 @@ impl AudioTagEdit for ApeTag {
|
||||||
fn album_title(&self) -> Option<&str> {
|
fn album_title(&self) -> Option<&str> {
|
||||||
self.get_value("Album")
|
self.get_value("Album")
|
||||||
}
|
}
|
||||||
fn set_album_title(&mut self, v: &str) {
|
fn set_album_title(&mut self, title: &str) {
|
||||||
self.set_value("Album", v)
|
self.set_value("Album", title)
|
||||||
}
|
}
|
||||||
fn remove_album_title(&mut self) {
|
fn remove_album_title(&mut self) {
|
||||||
self.remove_key("Album")
|
self.remove_key("Album")
|
||||||
|
|
|
@ -143,8 +143,8 @@ impl AudioTagEdit for Id3v2Tag {
|
||||||
fn album_title(&self) -> Option<&str> {
|
fn album_title(&self) -> Option<&str> {
|
||||||
self.inner.album()
|
self.inner.album()
|
||||||
}
|
}
|
||||||
fn set_album_title(&mut self, v: &str) {
|
fn set_album_title(&mut self, title: &str) {
|
||||||
self.inner.set_album(v)
|
self.inner.set_album(title)
|
||||||
}
|
}
|
||||||
fn remove_album_title(&mut self) {
|
fn remove_album_title(&mut self) {
|
||||||
self.inner.remove_album();
|
self.inner.remove_album();
|
||||||
|
|
|
@ -24,6 +24,12 @@ pub use riff_tag::RiffTag;
|
||||||
feature = "format-flac"
|
feature = "format-flac"
|
||||||
))]
|
))]
|
||||||
pub(crate) mod ogg_tag;
|
pub(crate) mod ogg_tag;
|
||||||
|
|
||||||
|
#[cfg(feature = "format-aiff")]
|
||||||
|
pub(crate) mod aiff_tag;
|
||||||
|
#[cfg(feature = "format-aiff")]
|
||||||
|
pub use aiff_tag::AiffTag;
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "format-vorbis",
|
feature = "format-vorbis",
|
||||||
feature = "format-opus",
|
feature = "format-opus",
|
||||||
|
|
|
@ -90,8 +90,8 @@ impl AudioTagEdit for Mp4Tag {
|
||||||
self.inner.album()
|
self.inner.album()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_album_title(&mut self, v: &str) {
|
fn set_album_title(&mut self, title: &str) {
|
||||||
self.inner.set_album(v)
|
self.inner.set_album(title)
|
||||||
}
|
}
|
||||||
fn remove_album_title(&mut self) {
|
fn remove_album_title(&mut self) {
|
||||||
self.inner.remove_album();
|
self.inner.remove_album();
|
||||||
|
|
|
@ -177,7 +177,9 @@ impl TryFrom<metaflac::Tag> for OggTag {
|
||||||
return Ok(tag);
|
return Ok(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(LoftyError::InvalidData("Flac file contains no vorbis comment blocks"))
|
Err(LoftyError::InvalidData(
|
||||||
|
"Flac file contains no vorbis comment blocks",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use crate::components::logic::riff;
|
use crate::components::logic::riff;
|
||||||
use crate::{
|
use crate::{
|
||||||
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType, ToAny, ToAnyTag,
|
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Result, TagType, ToAny, ToAnyTag,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek};
|
use std::io::{Read, Seek};
|
||||||
|
@ -86,8 +85,8 @@ impl AudioTagEdit for RiffTag {
|
||||||
self.get_value("IPRD").or_else(|| self.get_value("ALBU"))
|
self.get_value("IPRD").or_else(|| self.get_value("ALBU"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_album_title(&mut self, v: &str) {
|
fn set_album_title(&mut self, title: &str) {
|
||||||
self.set_value("IPRD", v)
|
self.set_value("IPRD", title)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_album_title(&mut self) {
|
fn remove_album_title(&mut self) {
|
||||||
|
|
23
src/tag.rs
23
src/tag.rs
|
@ -131,6 +131,8 @@ impl Tag {
|
||||||
feature = "format-opus"
|
feature = "format-opus"
|
||||||
))]
|
))]
|
||||||
TagType::Ogg(format) => Ok(Box::new(OggTag::read_from(reader, &format)?)),
|
TagType::Ogg(format) => Ok(Box::new(OggTag::read_from(reader, &format)?)),
|
||||||
|
#[cfg(feature = "format-aiff")]
|
||||||
|
TagType::AiffText => Ok(Box::new(AiffTag::read_from(reader)?)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,6 +160,10 @@ pub enum TagType {
|
||||||
/// Metadata stored in a RIFF INFO chunk
|
/// Metadata stored in a RIFF INFO chunk
|
||||||
/// Common file extensions: `.wav, .wave, .riff`
|
/// Common file extensions: `.wav, .wave, .riff`
|
||||||
RiffInfo,
|
RiffInfo,
|
||||||
|
#[cfg(feature = "format-aiff")]
|
||||||
|
/// Metadata stored in AIFF text chunks
|
||||||
|
/// Common file extensions: `.aiff, .aif`
|
||||||
|
AiffText,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -238,17 +244,21 @@ impl TagType {
|
||||||
70 if sig.starts_with(&FORM) => {
|
70 if sig.starts_with(&FORM) => {
|
||||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||||
|
|
||||||
|
data.seek(SeekFrom::Start(8))?;
|
||||||
|
|
||||||
|
let mut id = [0; 4];
|
||||||
|
data.read_exact(&mut id)?;
|
||||||
|
|
||||||
|
if &id != b"AIFF" && &id != b"AIFC" {
|
||||||
|
return Err(LoftyError::UnknownFormat);
|
||||||
|
}
|
||||||
|
|
||||||
let mut found_id3 = false;
|
let mut found_id3 = false;
|
||||||
|
|
||||||
while let (Ok(fourcc), Ok(size)) = (
|
while let (Ok(fourcc), Ok(size)) = (
|
||||||
data.read_u32::<LittleEndian>(),
|
data.read_u32::<LittleEndian>(),
|
||||||
data.read_u32::<BigEndian>(),
|
data.read_u32::<BigEndian>(),
|
||||||
) {
|
) {
|
||||||
if fourcc.to_le_bytes() == FORM {
|
|
||||||
data.seek(SeekFrom::Current(4))?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if fourcc.to_le_bytes()[..3] == ID3 {
|
if fourcc.to_le_bytes()[..3] == ID3 {
|
||||||
found_id3 = true;
|
found_id3 = true;
|
||||||
break;
|
break;
|
||||||
|
@ -265,8 +275,7 @@ impl TagType {
|
||||||
return Ok(Self::Id3v2(Id3Format::Form));
|
return Ok(Self::Id3v2(Id3Format::Form));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: support AIFF chunks?
|
Ok(Self::AiffText)
|
||||||
Err(LoftyError::UnknownFormat)
|
|
||||||
},
|
},
|
||||||
#[cfg(feature = "format-flac")]
|
#[cfg(feature = "format-flac")]
|
||||||
102 if sig.starts_with(&FLAC) => Ok(Self::Ogg(OggFormat::Flac)),
|
102 if sig.starts_with(&FLAC) => Ok(Self::Ogg(OggFormat::Flac)),
|
||||||
|
|
|
@ -47,7 +47,9 @@ pub trait AudioTagEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the track year
|
/// Returns the track year
|
||||||
fn year(&self) -> Option<i32> { None }
|
fn year(&self) -> Option<i32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Sets the track year
|
/// Sets the track year
|
||||||
fn set_year(&mut self, _year: i32) {}
|
fn set_year(&mut self, _year: i32) {}
|
||||||
/// Removes the track year
|
/// Removes the track year
|
||||||
|
@ -63,14 +65,18 @@ pub trait AudioTagEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the album title
|
/// Returns the album title
|
||||||
fn album_title(&self) -> Option<&str> { None }
|
fn album_title(&self) -> Option<&str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Sets the album title
|
/// Sets the album title
|
||||||
fn set_album_title(&mut self, v: &str) {}
|
fn set_album_title(&mut self, _title: &str) {}
|
||||||
/// Removes the album title
|
/// Removes the album title
|
||||||
fn remove_album_title(&mut self) {}
|
fn remove_album_title(&mut self) {}
|
||||||
|
|
||||||
/// Returns the album artist string
|
/// Returns the album artist string
|
||||||
fn album_artist_str(&self) -> Option<&str> { None }
|
fn album_artist_str(&self) -> Option<&str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Splits the artist string into a `Vec`
|
/// Splits the artist string into a `Vec`
|
||||||
fn album_artists(&self, delimiter: &str) -> Option<Vec<&str>> {
|
fn album_artists(&self, delimiter: &str) -> Option<Vec<&str>> {
|
||||||
self.album_artist_str()
|
self.album_artist_str()
|
||||||
|
@ -92,21 +98,27 @@ pub trait AudioTagEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the front cover
|
/// Returns the front cover
|
||||||
fn front_cover(&self) -> Option<Picture> { None }
|
fn front_cover(&self) -> Option<Picture> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Sets the front cover
|
/// Sets the front cover
|
||||||
fn set_front_cover(&mut self, _cover: Picture) {}
|
fn set_front_cover(&mut self, _cover: Picture) {}
|
||||||
/// Removes the front cover
|
/// Removes the front cover
|
||||||
fn remove_front_cover(&mut self) {}
|
fn remove_front_cover(&mut self) {}
|
||||||
|
|
||||||
/// Returns the front cover
|
/// Returns the front cover
|
||||||
fn back_cover(&self) -> Option<Picture> { None }
|
fn back_cover(&self) -> Option<Picture> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Sets the front cover
|
/// Sets the front cover
|
||||||
fn set_back_cover(&mut self, _cover: Picture) {}
|
fn set_back_cover(&mut self, _cover: Picture) {}
|
||||||
/// Removes the front cover
|
/// Removes the front cover
|
||||||
fn remove_back_cover(&mut self) {}
|
fn remove_back_cover(&mut self) {}
|
||||||
|
|
||||||
/// Returns an `Iterator` over all pictures stored in the track
|
/// Returns an `Iterator` over all pictures stored in the track
|
||||||
fn pictures(&self) -> Option<Cow<'static, [Picture]>> { None }
|
fn pictures(&self) -> Option<Cow<'static, [Picture]>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the track number and total tracks
|
/// Returns the track number and total tracks
|
||||||
fn track(&self) -> (Option<u32>, Option<u32>) {
|
fn track(&self) -> (Option<u32>, Option<u32>) {
|
||||||
|
@ -124,14 +136,18 @@ pub trait AudioTagEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the track number
|
/// Returns the track number
|
||||||
fn track_number(&self) -> Option<u32> { None }
|
fn track_number(&self) -> Option<u32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Sets the track number
|
/// Sets the track number
|
||||||
fn set_track_number(&mut self, _track_number: u32) {}
|
fn set_track_number(&mut self, _track_number: u32) {}
|
||||||
/// Removes the track number
|
/// Removes the track number
|
||||||
fn remove_track_number(&mut self) {}
|
fn remove_track_number(&mut self) {}
|
||||||
|
|
||||||
/// Returns the total tracks
|
/// Returns the total tracks
|
||||||
fn total_tracks(&self) -> Option<u32> { None }
|
fn total_tracks(&self) -> Option<u32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Sets the total tracks
|
/// Sets the total tracks
|
||||||
fn set_total_tracks(&mut self, _total_track: u32) {}
|
fn set_total_tracks(&mut self, _total_track: u32) {}
|
||||||
/// Removes the total tracks
|
/// Removes the total tracks
|
||||||
|
@ -148,14 +164,18 @@ pub trait AudioTagEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the disc number
|
/// Returns the disc number
|
||||||
fn disc_number(&self) -> Option<u32> { None }
|
fn disc_number(&self) -> Option<u32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Sets the disc number
|
/// Sets the disc number
|
||||||
fn set_disc_number(&mut self, _disc_number: u32) {}
|
fn set_disc_number(&mut self, _disc_number: u32) {}
|
||||||
/// Removes the disc number
|
/// Removes the disc number
|
||||||
fn remove_disc_number(&mut self) {}
|
fn remove_disc_number(&mut self) {}
|
||||||
|
|
||||||
/// Returns the total discs
|
/// Returns the total discs
|
||||||
fn total_discs(&self) -> Option<u32> { None }
|
fn total_discs(&self) -> Option<u32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Sets the total discs
|
/// Sets the total discs
|
||||||
fn set_total_discs(&mut self, _total_discs: u32) {}
|
fn set_total_discs(&mut self, _total_discs: u32) {}
|
||||||
/// Removes the total discs
|
/// Removes the total discs
|
||||||
|
@ -206,6 +226,8 @@ pub trait ToAnyTag: ToAny {
|
||||||
TagType::Ogg(_) => Box::new(OggTag::from(self.to_anytag())),
|
TagType::Ogg(_) => Box::new(OggTag::from(self.to_anytag())),
|
||||||
#[cfg(feature = "format-riff")]
|
#[cfg(feature = "format-riff")]
|
||||||
TagType::RiffInfo => Box::new(RiffTag::from(self.to_anytag())),
|
TagType::RiffInfo => Box::new(RiffTag::from(self.to_anytag())),
|
||||||
|
#[cfg(feature = "format-aiff")]
|
||||||
|
TagType::AiffText => Box::new(AiffTag::from(self.to_anytag())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
37
tests/io.rs
37
tests/io.rs
|
@ -210,3 +210,40 @@ full_test!(test_flac, "tests/assets/a.flac");
|
||||||
full_test!(test_m4a, "tests/assets/a.m4a");
|
full_test!(test_m4a, "tests/assets/a.m4a");
|
||||||
full_test!(test_ogg, "tests/assets/a.ogg");
|
full_test!(test_ogg, "tests/assets/a.ogg");
|
||||||
full_test!(test_opus, "tests/assets/a.opus");
|
full_test!(test_opus, "tests/assets/a.opus");
|
||||||
|
|
||||||
|
// AIFF text chunks only provide 2 values
|
||||||
|
#[test]
|
||||||
|
fn test_aiff_text() {
|
||||||
|
let file = "tests/assets/a_text.aiff";
|
||||||
|
println!("-- Adding tags --");
|
||||||
|
|
||||||
|
println!("Reading file");
|
||||||
|
let mut tag = Tag::default().read_from_path_signature(file).unwrap();
|
||||||
|
|
||||||
|
println!("Setting title");
|
||||||
|
tag.set_title("foo title");
|
||||||
|
println!("Setting artist");
|
||||||
|
tag.set_artist("foo artist");
|
||||||
|
|
||||||
|
println!("Writing");
|
||||||
|
tag.write_to_path(file).unwrap();
|
||||||
|
|
||||||
|
println!("-- Verifying tags --");
|
||||||
|
println!("Reading file");
|
||||||
|
let mut tag = Tag::default().read_from_path_signature(file).unwrap();
|
||||||
|
|
||||||
|
println!("Verifying title");
|
||||||
|
assert_eq!(tag.title(), Some("foo title"));
|
||||||
|
println!("Verifying artist");
|
||||||
|
assert_eq!(tag.artist_str(), Some("foo artist"));
|
||||||
|
|
||||||
|
println!("-- Removing tags --");
|
||||||
|
|
||||||
|
println!("Removing title");
|
||||||
|
tag.remove_title();
|
||||||
|
println!("Removing artist");
|
||||||
|
tag.remove_artist();
|
||||||
|
|
||||||
|
println!("Writing");
|
||||||
|
tag.write_to_path(file).unwrap()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue