Allow removing ID3v2 tags from FLAC and APE

This commit is contained in:
Serial 2022-03-27 14:12:32 -04:00
parent b41cd32677
commit e2d0978ce1
No known key found for this signature in database
GPG key ID: DA95198DC17C4568
9 changed files with 53 additions and 10 deletions

View file

@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `lofty::read_from` will now wrap the `File` in a `BufReader`
- **FLAC**: FLAC now has its own module at `lofty::flac`
- **ID3v2**: `FrameValue` is now `#[non_exhaustive]`
- `TagType::remove_from` now works for ID3v2 tags in APE and FLAC files
- This previously verified that the `FileType` supported the tag. It now has special exceptions for these formats to
allow stripping out these unsupported tags
### Fixed
- **MP4**: Non-full `meta` atoms are now properly handled.

View file

@ -3,6 +3,8 @@ use crate::ape;
use crate::error::{ErrorKind, LoftyError, Result};
#[cfg(feature = "id3v1")]
use crate::id3::v1;
#[cfg(feature = "id3v2")]
use crate::id3::v2;
#[allow(unused_imports)]
use crate::tag::{Tag, TagType};
@ -17,6 +19,9 @@ pub(crate) fn write_to(data: &mut File, tag: &Tag) -> Result<()> {
items: ape::tag::tagitems_into_ape(tag.items()),
}
.write_to(data),
// This tag can *only* be removed in this format
#[cfg(feature = "id3v2")]
TagType::Id3v2 => v2::tag::Id3v2TagRef::empty().write_to(data),
#[cfg(feature = "id3v1")]
TagType::Id3v1 => Into::<v1::tag::Id3v1TagRef<'_>>::into(tag).write_to(data),
_ => Err(LoftyError::new(ErrorKind::UnsupportedTag)),

View file

@ -17,7 +17,7 @@ use crate::id3::v2::tag::Id3v2Tag;
#[cfg(feature = "vorbis_comments")]
use crate::ogg::VorbisComments;
use crate::properties::FileProperties;
use crate::tag::TagType;
use crate::tag::{Tag, TagType};
use std::io::{Read, Seek};
@ -44,15 +44,21 @@ pub struct FlacFile {
}
impl From<FlacFile> for TaggedFile {
#[allow(clippy::vec_init_then_push)]
fn from(input: FlacFile) -> Self {
let mut tags = Vec::<Option<Tag>>::with_capacity(2);
#[cfg(feature = "vorbis_comments")]
tags.push(input.vorbis_comments.map(Into::into));
#[cfg(feature = "id3v2")]
tags.push(input.id3v2_tag.map(Into::into));
Self {
ty: FileType::FLAC,
properties: input.properties,
#[cfg(feature = "vorbis_comments")]
tags: input
.vorbis_comments
.map_or_else(Vec::new, |t| vec![t.into()]),
#[cfg(not(feature = "vorbis_comments"))]
#[cfg(any(feature = "vorbis_comments", feature = "id3v2"))]
tags: tags.into_iter().flatten().collect(),
#[cfg(not(any(feature = "vorbis_comments", feature = "id3v2")))]
tags: Vec::new(),
}
}

View file

@ -428,6 +428,15 @@ pub(crate) struct Id3v2TagRef<'a, I: Iterator<Item = FrameRef<'a>> + 'a> {
pub(crate) frames: I,
}
impl<'a> Id3v2TagRef<'a, std::iter::Empty<FrameRef<'a>>> {
pub(crate) fn empty() -> Self {
Self {
flags: Id3v2TagFlags::default(),
frames: std::iter::empty(),
}
}
}
// Create an iterator of FrameRef from a Tag's items for Id3v2TagRef::new
pub(crate) fn tag_frames(tag: &Tag) -> impl Iterator<Item = FrameRef<'_>> + '_ {
let items = tag

View file

@ -41,7 +41,7 @@ pub(crate) fn write_id3v2<'a, I: Iterator<Item = FrameRef<'a>> + 'a>(
let data = probe.into_inner();
match file_type {
Some(FileType::APE | FileType::MP3) => {},
Some(FileType::APE | FileType::MP3 | FileType::FLAC) => {},
// Formats such as WAV and AIFF store the ID3v2 tag in an 'ID3 ' chunk rather than at the beginning of the file
Some(FileType::WAV) => {
tag.flags.footer = false;
@ -77,6 +77,7 @@ pub(super) fn create_tag<'a, I: Iterator<Item = FrameRef<'a>> + 'a>(
let frames = &mut tag.frames;
let mut peek = frames.peekable();
// We are stripping the tag
if peek.peek().is_none() {
return Ok(Vec::new());
}

View file

@ -45,7 +45,7 @@ pub(in crate) fn write_to(file: &mut File, tag: &Tag, file_type: FileType) -> Re
pictures,
};
if let FileType::FLAC = file_type {
if file_type == FileType::FLAC {
return flac::write::write_to(file, &mut comments_ref);
}
@ -58,6 +58,11 @@ pub(in crate) fn write_to(file: &mut File, tag: &Tag, file_type: FileType) -> Re
write(file, &mut comments_ref, format)
},
#[cfg(feature = "id3v2")]
TagType::Id3v2 if file_type == FileType::FLAC => {
// This tag can *only* be removed in this format
crate::id3::v2::tag::Id3v2TagRef::empty().write_to(file)
},
_ => Err(LoftyError::new(ErrorKind::UnsupportedTag)),
}
}

View file

@ -2,6 +2,7 @@ pub(crate) mod item;
pub(crate) mod utils;
use crate::error::{ErrorKind, LoftyError, Result};
use crate::file::FileType;
use crate::picture::{Picture, PictureType};
use crate::probe::Probe;
use crate::traits::{Accessor, TagExt};
@ -448,7 +449,10 @@ impl TagType {
None => return Err(LoftyError::new(ErrorKind::UnknownFormat)),
};
if !file_type.supports_tag_type(*self) {
let special_exceptions =
(file_type == FileType::APE || file_type == FileType::FLAC) && *self == TagType::Id3v2;
if !special_exceptions && !file_type.supports_tag_type(*self) {
return Err(LoftyError::new(ErrorKind::UnsupportedTag));
}

View file

@ -52,3 +52,8 @@ fn remove_ape() {
fn remove_id3v1() {
crate::remove_tag!("tests/files/assets/minimal/full_test.ape", TagType::Id3v1);
}
#[test]
fn remove_id3v2() {
crate::remove_tag!("tests/files/assets/minimal/full_test.ape", TagType::Id3v2);
}

View file

@ -35,7 +35,7 @@ fn flac_write() {
}
#[test]
fn flac_remove() {
fn flac_remove_vorbis_comments() {
crate::remove_tag!(
"tests/files/assets/minimal/full_test.flac",
TagType::VorbisComments
@ -135,3 +135,8 @@ fn flac_with_id3v2() {
assert!(flac_file.vorbis_comments().is_some());
}
#[test]
fn flac_remove_id3v2() {
crate::remove_tag!("tests/files/assets/flac_with_id3v2.flac", TagType::Id3v2);
}