Make OggTag::write_to guess the format from the file content, finish up fixing features

Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
Serial 2021-06-30 01:00:40 -04:00
parent 579902ceb5
commit 445d5751b6
15 changed files with 176 additions and 166 deletions

9
Cargo.lock generated
View file

@ -319,6 +319,7 @@ dependencies = [
"ape",
"base64",
"byteorder",
"cfg-if",
"criterion",
"filepath",
"id3",
@ -332,9 +333,9 @@ dependencies = [
[[package]]
name = "lofty_attr"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28725103e1b62ada9bbeb65859b2707dbc1d1e285fac8c2657f7617741743ea2"
checksum = "730965dbb1c84caad9679a67cd70eaadd874fbd79eb7dda2bf369fbf80c22cee"
dependencies = [
"quote",
"syn",
@ -422,9 +423,9 @@ dependencies = [
[[package]]
name = "ogg_pager"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf5bd48d1b8abee3f76817b94092f8928ff08eaa78e3c2333cc4954d4b25663"
checksum = "219b1f570935d705bde1e81ae7131f87e5feec84b2e7a82198e9927dab48f0f2"
dependencies = [
"byteorder",
]

View file

@ -18,7 +18,7 @@ riff = {version = "1.0.1", optional = true}
id3 = {version = "0.6.4", optional = true} # De/Encoding
filepath = { version = "0.1.1", optional = true } # wav/aiff only supports paths for some reason
# Ogg
ogg_pager = { version = "0.1.5", optional = true }
ogg_pager = { version = "0.1.6", optional = true }
# Mp4
mp4ameta = {version = "0.10.2", optional = true}
# Flac
@ -28,8 +28,9 @@ thiserror = "1.0.25"
base64 = "0.13.0"
byteorder = "1.4.3"
cfg-if = "1.0.0"
lofty_attr = "0.1.3"
lofty_attr = "0.1.4"
[features]
default = ["all_tags"]

View file

@ -1,31 +0,0 @@
// RIFF
// Used to determine the RIFF metadata format
pub const LIST_ID: &[u8; 4] = b"LIST";
// FourCC
// Standard
pub const IART: [u8; 4] = [73, 65, 82, 84];
pub const ICMT: [u8; 4] = [73, 67, 77, 84];
pub const ICRD: [u8; 4] = [73, 67, 82, 68];
pub const INAM: [u8; 4] = [73, 78, 65, 77];
pub const IPRD: [u8; 4] = [73, 80, 82, 68]; // Represents album title
// Non-standard
pub const ITRK: [u8; 4] = [73, 84, 82, 75]; // Can represent track number
pub const IPRT: [u8; 4] = [73, 80, 82, 84]; // Can also represent track number
pub const IFRM: [u8; 4] = [73, 70, 82, 77]; // Can represent total tracks
// Very non-standard
pub const ALBU: [u8; 4] = [65, 76, 66, 85]; // Can album artist OR album title
pub const TRAC: [u8; 4] = [84, 82, 65, 67]; // Can represent track number OR total tracks
pub const DISC: [u8; 4] = [68, 73, 83, 67]; // Can represent disc number OR total discs
// OGG
pub const VORBIS_IDENT_HEAD: [u8; 7] = [1, 118, 111, 114, 98, 105, 115];
pub const VORBIS_COMMENT_HEAD: [u8; 7] = [3, 118, 111, 114, 98, 105, 115];
pub const VORBIS_SETUP_HEAD: [u8; 7] = [5, 118, 111, 114, 98, 105, 115];
pub const OPUSTAGS: [u8; 8] = [79, 112, 117, 115, 84, 97, 103, 115];
pub const OPUSHEAD: [u8; 8] = [79, 112, 117, 115, 72, 101, 97, 100];

View file

@ -1,10 +1,3 @@
#[cfg(any(
feature = "format-opus",
feature = "format-vorbis",
feature = "format-riff"
))]
pub(crate) mod constants;
#[cfg(feature = "format-flac")]
pub(crate) mod flac;

View file

@ -0,0 +1,12 @@
// OGG
#[cfg(feature = "format-vorbis")]
pub const VORBIS_IDENT_HEAD: [u8; 7] = [1, 118, 111, 114, 98, 105, 115];
#[cfg(feature = "format-vorbis")]
pub const VORBIS_COMMENT_HEAD: [u8; 7] = [3, 118, 111, 114, 98, 105, 115];
#[cfg(feature = "format-vorbis")]
pub const VORBIS_SETUP_HEAD: [u8; 7] = [5, 118, 111, 114, 98, 105, 115];
#[cfg(feature = "format-opus")]
pub const OPUSTAGS: [u8; 8] = [79, 112, 117, 115, 84, 97, 103, 115];
#[cfg(feature = "format-opus")]
pub const OPUSHEAD: [u8; 8] = [79, 112, 117, 115, 72, 101, 97, 100];

View file

@ -1,9 +1,12 @@
pub(crate) mod read;
pub(crate) mod write;
use std::io::{Read, Seek};
use ogg_pager::Page;
use crate::{LoftyError, Result};
use ogg_pager::Page;
use std::io::{Read, Seek};
pub(crate) mod constants;
pub(crate) mod read;
pub(crate) mod write;
pub fn page_from_packet(packet: &mut [u8]) -> Result<Vec<Page>> {
let mut pages: Vec<Page> = Vec::new();

View file

@ -1,4 +1,3 @@
use crate::components::logic::constants::OPUSHEAD;
use crate::components::logic::ogg::{is_metadata, reach_metadata};
use crate::{LoftyError, OggFormat, Picture, Result};
@ -10,7 +9,12 @@ use ogg_pager::Page;
pub type OGGTags = (String, Vec<Picture>, HashMap<String, String>, OggFormat);
pub(crate) fn read_from<T>(mut data: T, header_sig: &[u8], comment_sig: &[u8]) -> Result<OGGTags>
pub(crate) fn read_from<T>(
mut data: T,
header_sig: &[u8],
comment_sig: &[u8],
format: OggFormat,
) -> Result<OGGTags>
where
T: Read + Seek,
{
@ -72,11 +76,5 @@ where
}
}
let vorbis_format = if header_sig == OPUSHEAD {
OggFormat::Opus
} else {
OggFormat::Vorbis
};
Ok((vendor_str, pictures, md, vorbis_format))
Ok((vendor_str, pictures, md, format))
}

View file

@ -1,6 +1,9 @@
use crate::{LoftyError, Picture, Result};
use crate::components::logic::constants::{VORBIS_COMMENT_HEAD, VORBIS_SETUP_HEAD};
#[cfg(feature = "format-opus")]
use crate::components::logic::ogg::constants::OPUSTAGS;
#[cfg(feature = "format-vorbis")]
use crate::components::logic::ogg::constants::{VORBIS_COMMENT_HEAD, VORBIS_SETUP_HEAD};
use byteorder::{LittleEndian, ReadBytesExt};
use std::borrow::Cow;
@ -60,6 +63,7 @@ pub(crate) fn create_pages(
Ok(())
}
#[cfg(feature = "format-vorbis")]
fn vorbis_write(
mut data: &mut File,
writer: &mut Vec<u8>,
@ -186,7 +190,6 @@ fn vorbis_write(
}
fn write_to(mut data: &mut File, pages: &mut [Page], sig: &[u8]) -> Result<()> {
let vorbis = sig == VORBIS_COMMENT_HEAD;
let first_page = Page::read(&mut data)?;
let ser = first_page.serial;
@ -197,9 +200,13 @@ fn write_to(mut data: &mut File, pages: &mut [Page], sig: &[u8]) -> Result<()> {
let first_md_page = Page::read(&mut data)?;
is_metadata(&first_md_page, sig)?;
if vorbis {
#[cfg(feature = "format-vorbis")]
if sig == VORBIS_COMMENT_HEAD {
vorbis_write(data, &mut writer, first_md_page.content, ser, pages)?;
} else {
}
#[cfg(feature = "format-opus")]
if sig == OPUSTAGS {
let reached_md_end: bool;
let mut remaining = Vec::new();
@ -227,7 +234,7 @@ fn write_to(mut data: &mut File, pages: &mut [Page], sig: &[u8]) -> Result<()> {
}
writer.write_all(&*remaining)?;
};
}
data.seek(SeekFrom::Start(0))?;
data.set_len(0)?;

View file

@ -1,4 +1,3 @@
use super::constants::LIST_ID;
use crate::{LoftyError, Result};
use byteorder::{LittleEndian, ReadBytesExt};
@ -7,6 +6,28 @@ use std::collections::HashMap;
use std::fs::File;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
// Used to determine the RIFF metadata format
pub const LIST_ID: &[u8; 4] = b"LIST";
// FourCC
// Standard
pub const IART: [u8; 4] = [73, 65, 82, 84];
pub const ICMT: [u8; 4] = [73, 67, 77, 84];
pub const ICRD: [u8; 4] = [73, 67, 82, 68];
pub const INAM: [u8; 4] = [73, 78, 65, 77];
pub const IPRD: [u8; 4] = [73, 80, 82, 68]; // Represents album title
// Non-standard
pub const ITRK: [u8; 4] = [73, 84, 82, 75]; // Can represent track number
pub const IPRT: [u8; 4] = [73, 80, 82, 84]; // Can also represent track number
pub const IFRM: [u8; 4] = [73, 70, 82, 77]; // Can represent total tracks
// Very non-standard
pub const ALBU: [u8; 4] = [65, 76, 66, 85]; // Can album artist OR album title
pub const TRAC: [u8; 4] = [84, 82, 65, 67]; // Can represent track number OR total tracks
pub const DISC: [u8; 4] = [68, 73, 83, 67]; // Can represent disc number OR total discs
pub(crate) fn read_from<T>(mut data: T) -> Result<Option<HashMap<String, String>>>
where
T: Read + Seek,
@ -91,34 +112,32 @@ where
fn create_key(fourcc: &[u8]) -> Option<String> {
match fourcc {
fcc if fcc == super::constants::IART => Some("Artist".to_string()),
fcc if fcc == super::constants::ICMT => Some("Comment".to_string()),
fcc if fcc == super::constants::ICRD => Some("Date".to_string()),
fcc if fcc == super::constants::INAM => Some("Title".to_string()),
fcc if fcc == super::constants::IPRD => Some("Album".to_string()),
fcc if fcc == IART => Some("Artist".to_string()),
fcc if fcc == ICMT => Some("Comment".to_string()),
fcc if fcc == ICRD => Some("Date".to_string()),
fcc if fcc == INAM => Some("Title".to_string()),
fcc if fcc == IPRD => Some("Album".to_string()),
// Non-standard
fcc if fcc == super::constants::ITRK || fcc == super::constants::IPRT => {
Some("TrackNumber".to_string())
},
fcc if fcc == super::constants::IFRM => Some("TrackTotal".to_string()),
fcc if fcc == super::constants::ALBU => Some("Album".to_string()),
fcc if fcc == super::constants::TRAC => Some("TrackNumber".to_string()),
fcc if fcc == super::constants::DISC => Some("DiscNumber".to_string()),
fcc if fcc == ITRK || fcc == IPRT => Some("TrackNumber".to_string()),
fcc if fcc == IFRM => Some("TrackTotal".to_string()),
fcc if fcc == ALBU => Some("Album".to_string()),
fcc if fcc == TRAC => Some("TrackNumber".to_string()),
fcc if fcc == DISC => Some("DiscNumber".to_string()),
_ => None,
}
}
pub fn key_to_fourcc(key: &str) -> Option<[u8; 4]> {
match key {
"Artist" => Some(super::constants::IART),
"Comment" => Some(super::constants::ICMT),
"Date" => Some(super::constants::ICRD),
"Title" => Some(super::constants::INAM),
"Album" => Some(super::constants::IPRD),
"TrackTotal" => Some(super::constants::IFRM),
"TrackNumber" => Some(super::constants::TRAC),
"DiscNumber" | "DiscTotal" => Some(super::constants::DISC),
"Artist" => Some(IART),
"Comment" => Some(ICMT),
"Date" => Some(ICRD),
"Title" => Some(INAM),
"Album" => Some(IPRD),
"TrackTotal" => Some(IFRM),
"TrackNumber" => Some(TRAC),
"DiscNumber" | "DiscTotal" => Some(DISC),
_ => None,
}
}

View file

@ -4,10 +4,14 @@
feature = "format-flac"
))]
use crate::components::logic::constants::{
OPUSHEAD, OPUSTAGS, VORBIS_COMMENT_HEAD, VORBIS_IDENT_HEAD,
};
use crate::components::logic::{flac, ogg};
#[cfg(feature = "format-flac")]
use crate::components::logic::flac;
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
use crate::components::logic::ogg;
#[cfg(feature = "format-opus")]
use crate::components::logic::ogg::constants::{OPUSHEAD, OPUSTAGS};
#[cfg(feature = "format-vorbis")]
use crate::components::logic::ogg::constants::{VORBIS_COMMENT_HEAD, VORBIS_IDENT_HEAD};
use crate::{
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, LoftyError, OggFormat, Picture,
PictureType, Result, TagType, ToAny, ToAnyTag,
@ -22,10 +26,9 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::io::{Read, Seek};
use std::io::{Read, Seek, SeekFrom};
struct OggInnerTag {
format: Option<OggFormat>,
vendor: String,
comments: HashMap<String, String>,
pictures: Option<Cow<'static, [Picture]>>,
@ -34,7 +37,6 @@ struct OggInnerTag {
impl Default for OggInnerTag {
fn default() -> Self {
Self {
format: None,
vendor: String::new(),
comments: HashMap::default(),
pictures: None,
@ -73,18 +75,26 @@ impl OggInnerTag {
R: Read + Seek,
{
match format {
#[cfg(feature = "format-vorbis")]
OggFormat::Vorbis => {
let tag = ogg::read::read_from(reader, &VORBIS_IDENT_HEAD, &VORBIS_COMMENT_HEAD)?;
let tag = ogg::read::read_from(
reader,
&VORBIS_IDENT_HEAD,
&VORBIS_COMMENT_HEAD,
OggFormat::Vorbis,
)?;
let vorbis_tag: OggTag = tag.try_into()?;
Ok(vorbis_tag.inner)
},
#[cfg(feature = "format-opus")]
OggFormat::Opus => {
let tag = ogg::read::read_from(reader, &OPUSHEAD, &OPUSTAGS)?;
let tag = ogg::read::read_from(reader, &OPUSHEAD, &OPUSTAGS, OggFormat::Opus)?;
let vorbis_tag: OggTag = tag.try_into()?;
Ok(vorbis_tag.inner)
},
#[cfg(feature = "format-flac")]
OggFormat::Flac => {
let tag = metaflac::Tag::read_from(reader)?;
let vorbis_tag: OggTag = tag.try_into()?;
@ -95,47 +105,16 @@ impl OggInnerTag {
}
}
#[impl_tag(OggInnerTag, TagType::Ogg(OggFormat::Vorbis))]
#[custom_convert]
pub struct OggTag;
impl<'a> From<(AnyTag<'a>, OggFormat)> for OggTag {
fn from(inp: (AnyTag<'a>, OggFormat)) -> Self {
let mut tag = OggTag::default();
let anytag = inp.0;
if let Some(v) = anytag.title() {
tag.set_title(v)
}
if let Some(v) = anytag.artists_as_string() {
tag.set_artist(&v)
}
if let Some(v) = anytag.year {
tag.set_year(v)
}
if let Some(v) = anytag.album().title {
tag.set_album_title(v)
}
if let Some(v) = anytag.album().artists {
tag.set_album_artist(&v.join("/"))
}
if let Some(v) = anytag.track_number() {
tag.set_track_number(v)
}
if let Some(v) = anytag.total_tracks() {
tag.set_total_tracks(v)
}
if let Some(v) = anytag.disc_number() {
tag.set_disc_number(v)
}
if let Some(v) = anytag.total_discs() {
tag.set_total_discs(v)
}
tag.inner.format = Some(inp.1);
tag
cfg_if::cfg_if! {
if #[cfg(feature = "format-opus")] {
#[impl_tag(OggInnerTag, TagType::Ogg(OggFormat::Opus))]
pub struct OggTag;
} else if #[cfg(feature = "format-vorbis")] {
#[impl_tag(OggInnerTag, TagType::Ogg(OggFormat::Vorbis))]
pub struct OggTag;
} else {
#[impl_tag(OggInnerTag, TagType::Ogg(OggFormat::Flac))]
pub struct OggTag;
}
}
@ -150,7 +129,6 @@ impl TryFrom<OGGTags> for OggTag {
let comments = inp.2;
tag.inner = OggInnerTag {
format: Some(inp.3),
vendor: inp.0,
comments: comments.into_iter().collect(),
pictures: if pictures.is_empty() {
@ -197,7 +175,6 @@ impl TryFrom<metaflac::Tag> for OggTag {
comment_collection.into_iter().collect();
tag.inner = OggInnerTag {
format: Some(OggFormat::Flac),
vendor: comments.vendor_string,
comments: comment_collection,
pictures: Some(Cow::from(pictures)),
@ -454,34 +431,45 @@ fn replace_pic(
impl AudioTagWrite for OggTag {
fn write_to(&self, file: &mut File) -> Result<()> {
if let Some(format) = self.inner.format.clone() {
match format {
OggFormat::Vorbis => {
ogg::write::create_pages(
file,
&VORBIS_COMMENT_HEAD,
&self.inner.vendor,
&self.inner.comments,
&self.inner.pictures,
)?;
},
OggFormat::Opus => {
ogg::write::create_pages(
file,
&OPUSTAGS,
&self.inner.vendor,
&self.inner.comments,
&self.inner.pictures,
)?;
},
OggFormat::Flac => {
flac::write_to(
file,
&self.inner.vendor,
&self.inner.comments,
&self.inner.pictures,
)?;
},
let mut sig = [0; 4];
file.read_exact(&mut sig)?;
file.seek(SeekFrom::Start(0))?;
#[cfg(feature = "format-flac")]
if &sig == b"fLaC" {
return flac::write_to(
file,
&self.inner.vendor,
&self.inner.comments,
&self.inner.pictures,
);
}
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
{
let p = ogg_pager::Page::read(file)?;
file.seek(SeekFrom::Start(0))?;
#[cfg(feature = "format-opus")]
if p.content.starts_with(&OPUSHEAD) {
return ogg::write::create_pages(
file,
&OPUSTAGS,
&self.inner.vendor,
&self.inner.comments,
&self.inner.pictures,
);
}
#[cfg(feature = "format-vorbis")]
if p.content.starts_with(&VORBIS_IDENT_HEAD) {
return ogg::write::create_pages(
file,
&VORBIS_COMMENT_HEAD,
&self.inner.vendor,
&self.inner.comments,
&self.inner.pictures,
);
}
}

View file

@ -278,10 +278,12 @@ impl TagType {
data.seek(SeekFrom::Start(0))?;
#[cfg(feature = "format-vorbis")]
if ident_sig[1..7] == VORBIS {
return Ok(Self::Ogg(OggFormat::Vorbis));
}
#[cfg(feature = "format-opus")]
if ident_sig[..] == OPUSHEAD {
return Ok(Self::Ogg(OggFormat::Opus));
}

View file

@ -202,7 +202,7 @@ pub trait ToAnyTag: ToAny {
feature = "format-flac",
feature = "format-opus"
))]
TagType::Ogg(f) => Box::new(OggTag::from((self.to_anytag(), f))),
TagType::Ogg(_) => Box::new(OggTag::from(self.to_anytag())),
#[cfg(feature = "format-riff")]
TagType::RiffInfo => Box::new(RiffTag::from(self.to_anytag())),
}

View file

@ -1,9 +1,22 @@
use crate::{LoftyError, Result};
#[cfg(any(
feature = "format-id3",
feature = "format-opus",
feature = "format-vorbis",
feature = "format-flac",
))]
use byteorder::{BigEndian, ReadBytesExt};
#[cfg(any(
feature = "format-id3",
feature = "format-opus",
feature = "format-vorbis",
feature = "format-flac",
feature = "format-ape",
))]
use std::io::{Cursor, Read};
use std::borrow::Cow;
use std::convert::TryFrom;
use std::io::{Cursor, Read};
#[cfg(feature = "format-ape")]
pub const APE_PICTYPES: [&str; 21] = [
@ -114,6 +127,7 @@ pub trait PicType {
/// The picture type
#[cfg(not(feature = "format-id3"))]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PictureType {
Other,

View file

@ -1,3 +1,5 @@
#![cfg(feature = "default")]
use lofty::{Id3Format, OggFormat, Tag, TagType};
macro_rules! convert_tag {

View file

@ -1,8 +1,9 @@
#![cfg(feature = "default")]
use lofty::{Id3Format, OggTag, Tag, TagType, ToAnyTag};
use std::convert::TryInto;
#[test]
#[cfg(all(feature = "format-id3", feature = "format-flac"))]
fn test_inner() {
// New flac tag
let mut innertag = metaflac::Tag::new();