mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-12 21:52:33 +00:00
Add copyright methods
Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
parent
f9c2321ba1
commit
cb725e882d
10 changed files with 159 additions and 45 deletions
|
@ -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, ©right_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();
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
18
tests/io.rs
18
tests/io.rs
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue