Add copyright methods

Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
Serial 2021-07-08 19:16:38 -04:00
parent f9c2321ba1
commit cb725e882d
10 changed files with 159 additions and 45 deletions

View file

@ -1,16 +1,17 @@
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>)>
pub(crate) fn read_from<T>(data: &mut T) -> Result<(Option<String>, Option<String>, Option<String>)>
where
T: Read + Seek,
{
let mut name_id: Option<String> = None;
let mut author_id: Option<String> = None;
let mut copyright_id: Option<String> = None;
data.seek(SeekFrom::Start(12))?;
@ -31,22 +32,28 @@ where
author_id = Some(String::from_utf8(auth)?);
},
f if f == b"(c) " && copyright_id.is_none() => {
let mut copy = vec![0; size as usize];
data.read_exact(&mut copy)?;
copyright_id = Some(String::from_utf8(copy)?);
},
_ => {
data.seek(SeekFrom::Current(i64::from(size)))?;
},
}
}
if (&None, &None) == (&name_id, &author_id) {
if (&None, &None, &None) == (&name_id, &author_id, &copyright_id) {
return Err(LoftyError::InvalidData("AIFF file contains no text chunks"));
}
Ok((name_id, author_id))
Ok((name_id, author_id, copyright_id))
}
pub(crate) fn write_to(
data: &mut File,
metadata: (Option<&String>, Option<&String>),
metadata: (Option<&String>, Option<&String>, Option<&String>),
) -> Result<()> {
let mut text_chunks = Vec::new();
@ -66,10 +73,19 @@ pub(crate) fn write_to(
text_chunks.extend(author_id.as_bytes().iter());
}
if let Some(copyright_id) = metadata.2 {
let len = (copyright_id.len() as u32).to_be_bytes();
text_chunks.extend(b"(c) ".iter());
text_chunks.extend(len.iter());
text_chunks.extend(copyright_id.as_bytes().iter());
}
data.seek(SeekFrom::Start(12))?;
let mut name: Option<(usize, usize)> = None;
let mut auth: Option<(usize, usize)> = None;
let mut copy: Option<(usize, usize)> = None;
while let (Ok(fourcc), Ok(size)) = (
data.read_u32::<LittleEndian>(),
@ -80,6 +96,7 @@ pub(crate) fn write_to(
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))),
f if f == b"(c) " && copy.is_none() => copy = Some((pos, (pos + 8 + size as usize))),
_ => {
data.seek(SeekFrom::Current(i64::from(size)))?;
},
@ -91,21 +108,8 @@ pub(crate) fn write_to(
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) => {
match (name, auth, copy) {
(None, None, None) => {
data.seek(SeekFrom::Start(16))?;
let mut size = [0; 4];
@ -114,6 +118,23 @@ pub(crate) fn write_to(
let comm_end = (20 + u32::from_le_bytes(size)) as usize;
file_bytes.splice(comm_end..comm_end, text_chunks);
},
(Some(single_value), None, None)
| (None, Some(single_value), None)
| (None, None, Some(single_value)) => {
file_bytes.splice(single_value.0..single_value.1, text_chunks);
},
(title, author, copyright) => {
let items: Vec<(usize, usize)> = vec![title, author, copyright]
.iter()
.filter(|i| i.is_some())
.map(|v| v.unwrap())
.collect();
if let (Some(first), Some(last)) = (items.iter().min(), items.iter().max()) {
file_bytes.drain(last.0..last.1);
file_bytes.splice(first.0..first.1, text_chunks);
}
},
}
let total_size = ((file_bytes.len() - 8) as u32).to_be_bytes();

View file

@ -28,12 +28,15 @@ where
#[allow(clippy::cast_lossless)]
while cursor.position() < info_list_size as u64 {
if cursor.read_u8()? != 0 {
cursor.seek(SeekFrom::Current(-1))?;
}
let mut fourcc = vec![0; 4];
cursor.read_exact(&mut fourcc)?;
let size = cursor.read_u32::<LittleEndian>()?;
let key = String::from_utf8(fourcc)?;
let size = cursor.read_u32::<LittleEndian>()?;
let mut buf = vec![0; size as usize];
cursor.read_exact(&mut buf)?;

View file

@ -12,6 +12,7 @@ use lofty_attr::impl_tag;
struct AiffInnerTag {
name_id: Option<String>,
author_id: Option<String>,
copyright_id: Option<String>,
}
#[impl_tag(AiffInnerTag, TagType::AiffText)]
@ -24,10 +25,14 @@ impl AiffTag {
where
R: Read + Seek,
{
let (name_id, author_id) = aiff::read_from(reader)?;
let (name_id, author_id, copyright_id) = aiff::read_from(reader)?;
Ok(Self {
inner: AiffInnerTag { name_id, author_id },
inner: AiffInnerTag {
name_id,
author_id,
copyright_id,
},
})
}
}
@ -56,13 +61,27 @@ impl AudioTagEdit for AiffTag {
fn remove_artist(&mut self) {
self.inner.author_id = None
}
fn copyright(&self) -> Option<&str> {
self.inner.copyright_id.as_deref()
}
fn set_copyright(&mut self, copyright: &str) {
self.inner.copyright_id = Some(copyright.to_string())
}
fn remove_copyright(&mut self) {
self.inner.copyright_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()),
(
self.inner.name_id.as_ref(),
self.inner.author_id.as_ref(),
self.inner.copyright_id.as_ref(),
),
)
}
}

View file

@ -120,6 +120,16 @@ impl AudioTagEdit for ApeTag {
self.remove_key("Year")
}
fn copyright(&self) -> Option<&str> {
self.get_value("Copyright")
}
fn set_copyright(&mut self, copyright: &str) {
self.set_value("Copyright", copyright)
}
fn remove_copyright(&mut self) {
self.remove_key("Copyright")
}
fn album_title(&self) -> Option<&str> {
self.get_value("Album")
}

View file

@ -105,15 +105,17 @@ impl AudioTagEdit for Id3v2Tag {
fn date(&self) -> Option<String> {
if let Some(released) = self.inner.get("TDRL") {
if let id3::frame::Content::Text(date) = &released.content() {
return Some(date.clone());
}
return released
.content()
.text()
.map(std::string::ToString::to_string);
}
if let Some(recorded) = self.inner.get("TRDC") {
if let id3::frame::Content::Text(date) = &recorded.content() {
return Some(date.clone());
}
return recorded
.content()
.text()
.map(std::string::ToString::to_string);
}
None
@ -140,6 +142,20 @@ impl AudioTagEdit for Id3v2Tag {
self.inner.remove_year()
}
fn copyright(&self) -> Option<&str> {
if let Some(frame) = self.inner.get("TCOP") {
return frame.content().text();
}
None
}
fn set_copyright(&mut self, copyright: &str) {
self.inner.set_text("TCOP", copyright)
}
fn remove_copyright(&mut self) {
self.inner.remove("TCOP")
}
fn album_title(&self) -> Option<&str> {
self.inner.album()
}

View file

@ -61,17 +61,16 @@ impl AudioTagEdit for Mp4Tag {
fn set_title(&mut self, title: &str) {
self.inner.set_title(title)
}
fn remove_title(&mut self) {
self.inner.remove_title();
}
fn artist_str(&self) -> Option<&str> {
self.inner.artist()
}
fn set_artist(&mut self, artist: &str) {
self.inner.set_artist(artist)
}
fn remove_artist(&mut self) {
self.inner.remove_artists();
}
@ -82,14 +81,23 @@ impl AudioTagEdit for Mp4Tag {
fn set_year(&mut self, year: i32) {
self.inner.set_year(year.to_string())
}
fn remove_year(&mut self) {
self.inner.remove_year();
}
fn copyright(&self) -> Option<&str> {
self.inner.copyright()
}
fn set_copyright(&mut self, copyright: &str) {
self.inner.set_copyright(copyright)
}
fn remove_copyright(&mut self) {
self.inner.remove_copyright()
}
fn album_title(&self) -> Option<&str> {
self.inner.album()
}
fn set_album_title(&mut self, title: &str) {
self.inner.set_album(title)
}
@ -100,11 +108,9 @@ impl AudioTagEdit for Mp4Tag {
fn album_artist_str(&self) -> Option<&str> {
self.inner.album_artist()
}
fn set_album_artist(&mut self, artists: &str) {
self.inner.set_album_artist(artists)
}
fn remove_album_artists(&mut self) {
self.inner.remove_album_artists();
}
@ -155,11 +161,9 @@ impl AudioTagEdit for Mp4Tag {
fn back_cover(&self) -> Option<Picture> {
self.front_cover()
}
fn set_back_cover(&mut self, cover: Picture) {
self.set_front_cover(cover)
}
fn remove_back_cover(&mut self) {
self.inner.remove_artwork();
}

View file

@ -236,6 +236,16 @@ impl AudioTagEdit for OggTag {
self.inner.remove_key("YEAR");
}
fn copyright(&self) -> Option<&str> {
self.inner.get_value("COPYRIGHT")
}
fn set_copyright(&mut self, copyright: &str) {
self.inner.set_value("COPYRIGHT", copyright)
}
fn remove_copyright(&mut self) {
self.inner.remove_key("COPYRIGHT")
}
fn album_title(&self) -> Option<&str> {
self.inner.get_value("ALBUM")
}

View file

@ -60,11 +60,9 @@ impl AudioTagEdit for RiffTag {
fn title(&self) -> Option<&str> {
self.get_value("INAM")
}
fn set_title(&mut self, title: &str) {
self.set_value("INAM", title)
}
fn remove_title(&mut self) {
self.remove_key("INAM")
}
@ -72,23 +70,29 @@ impl AudioTagEdit for RiffTag {
fn artist_str(&self) -> Option<&str> {
self.get_value("IART")
}
fn set_artist(&mut self, artist: &str) {
self.set_value("IART", artist)
}
fn remove_artist(&mut self) {
self.remove_key("IART")
}
fn copyright(&self) -> Option<&str> {
self.get_value("ICOP")
}
fn set_copyright(&mut self, copyright: &str) {
self.set_value("ICOP", copyright)
}
fn remove_copyright(&mut self) {
self.remove_key("ICOP")
}
fn album_title(&self) -> Option<&str> {
self.get_value("IPRD").or_else(|| self.get_value("ALBU"))
}
fn set_album_title(&mut self, title: &str) {
self.set_value("IPRD", title)
}
fn remove_album_title(&mut self) {
self.remove_key("IPRD")
}

View file

@ -55,6 +55,15 @@ pub trait AudioTagEdit {
/// Removes the track year
fn remove_year(&mut self) {}
/// Returns the copyright
fn copyright(&self) -> Option<&str> {
None
}
/// Sets the copyright
fn set_copyright(&mut self, _copyright: &str) {}
/// Removes the copyright
fn remove_copyright(&mut self) {}
/// Returns the track's [`Album`]
fn album(&self) -> Album<'_> {
Album {

View file

@ -31,6 +31,9 @@ macro_rules! add_tags {
println!("Setting year");
tag.set_year(2020);
println!("Setting copyright");
tag.set_copyright("1988");
println!("Setting album title");
tag.set_album_title("foo album title");
@ -103,6 +106,9 @@ macro_rules! verify_write {
assert_eq!(tag.year(), Some(2020));
}
println!("Verifying copyright");
assert_eq!(tag.copyright(), Some("1988"));
println!("Verifying album title");
assert_eq!(tag.album_title(), Some("foo album title"));
@ -174,6 +180,11 @@ macro_rules! remove_tags {
assert!(tag.year().is_none());
tag.remove_year();
println!("Removing copyright");
tag.remove_copyright();
assert!(tag.copyright().is_none());
tag.remove_copyright();
println!("Removing album title");
tag.remove_album_title();
assert!(tag.album_title().is_none());
@ -224,11 +235,14 @@ fn test_aiff_text() {
tag.set_title("foo title");
println!("Setting artist");
tag.set_artist("foo artist");
println!("Setting copyright");
tag.set_copyright("1988");
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();
@ -236,6 +250,8 @@ fn test_aiff_text() {
assert_eq!(tag.title(), Some("foo title"));
println!("Verifying artist");
assert_eq!(tag.artist_str(), Some("foo artist"));
println!("Verifying copyright");
assert_eq!(tag.copyright(), Some("1988"));
println!("-- Removing tags --");
@ -243,6 +259,8 @@ fn test_aiff_text() {
tag.remove_title();
println!("Removing artist");
tag.remove_artist();
println!("Removing copyright");
tag.remove_copyright();
println!("Writing");
tag.write_to_path(file).unwrap()