Skip to content

Commit 1433ad4

Browse files
Merge branch 'main' into Add-CountryRelease
2 parents 7c4dadc + 0805a4c commit 1433ad4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1150
-790
lines changed

.github/workflows/bench.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ jobs:
2929
run: |
3030
sudo apt-get update
3131
sudo apt-get install -y valgrind
32-
- name: Install iai-callgrind-runner
32+
- name: Install gungraun-runner
3333
run: |
3434
version=$(cargo metadata --format-version=1 |\
35-
jq '.packages[] | select(.name == "iai-callgrind").version' |\
35+
jq '.packages[] | select(.name == "gungraun").version' |\
3636
tr -d '"'
3737
)
38-
cargo install iai-callgrind-runner --version $version
38+
cargo install gungraun-runner --version $version
3939
- uses: bencherdev/bencher@main
4040
- name: Run Bencher
4141
run: |
42-
bencher run --adapter rust_iai_callgrind --err "cargo bench"
42+
bencher run --adapter rust_gungraun --err "cargo bench"

CHANGELOG.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88

99
### Added
10-
- **ItemKey**: `ItemKey::AlbumArtists`, available for ID3v2, Vorbis Comments, APE, and MP4 Ilst ([PR](https://github.com/Serial-ATA/lofty-rs/pull/523))
11-
- This is a multi-value item that stores each artist for a track. It should be retrieved with `Tag::get_strings` or `Tag::take_strings`.
12-
- For example, a track has `ItemKey::TrackArtist` = "Foo & Bar", then `ItemKey::AlbumArtists` = ["Foo", "Bar"].
10+
- **ItemKey**:
11+
- `ItemKey::AlbumArtists`, available for ID3v2, Vorbis Comments, APE, and MP4 Ilst ([PR](https://github.com/Serial-ATA/lofty-rs/pull/523))
12+
- This is a multi-value item that stores each artist for a track. It should be retrieved with `Tag::get_strings` or `Tag::take_strings`.
13+
- For example, a track has `ItemKey::TrackArtist` = "Foo & Bar", then `ItemKey::AlbumArtists` = ["Foo", "Bar"].
14+
- `ItemKey::UnsyncLyrics` ([issue](https://github.com/Serial-ATA/lofty-rs/issues/561)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/568))
15+
- In formats like Vorbis Comments, `ItemKey::Lyrics` may actually contain synchronized lyrics in LRC format. To help with the ambiguity, some
16+
apps may write a separate field containing normal, unsynchronized lyrics.
17+
- In other formats where the difference doesn't matter (like ID3v2), this will act exactly the same as `ItemKey::Lyrics`.
1318
- **Serde**: [Serde] support for `*Type` enums (`FileType`, `TagType`, `PictureType`)
1419
- Support can be enabled with the new `serde` feature (not enabled by default)
1520
- **Probe**: `Probe::read_bound()` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/557))
1621
- Same as `Probe::read()`, but returns a [`BoundTaggedFile`](https://docs.rs/lofty/latest/lofty/file/struct.BoundTaggedFile.html)
22+
- **Other**: `EXTENSIONS` list containing common file extensions for all supported audio file types ([issue](https://github.com/Serial-ATA/lofty-rs/issues/509)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/558))
23+
- This is useful for filtering files when scanning directories. If your app uses extension filtering, **please consider switching to this**, as to not
24+
miss any supported files.
1725

1826
### Changed
1927
- **ID3v2**: Check `TXXX:ALBUMARTIST` and `TXXX:ALBUM ARTIST` for `ItemKey::AlbumArtist` conversions
@@ -22,6 +30,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2230
- These fields will now properly be split into `DISCNUMBER` and `DISCTOTAL`, making it possible to use them with
2331
[Accessor::disk()](https://docs.rs/lofty/latest/lofty/tag/trait.Accessor.html#method.disk) and [Accessor::disk_total()](https://docs.rs/lofty/latest/lofty/tag/trait.Accessor.html#method.disk_total).
2432
* **ItemKey**: `ItemKey` is now `Copy` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/526))
33+
* **FileType**: Replaced `FileType::supports_tag_type()` with `FileType::tag_support()` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/566))
34+
* Rather than a simple `bool`, this now returns a `TagSupport`, which can describe three states: unsupported, read-only, and read/write
35+
* **TaggedFileExt**: Replaced `TaggedFileExt::supports_tag_type()` with `TaggedFileExt::tag_support()` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/566))
36+
* **FileResolver**: Replaced `FileResolver::supported_tag_types()` with `FileResolver::tag_support()` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/566))
2537

2638
### Fixed
2739
- **ID3v2**: Support parsing UTF-16 `COMM`/`USLT` frames with a single BOM ([issue](https://github.com/Serial-ATA/lofty-rs/issues/532)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/535))
@@ -35,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3547
* **ItemKey**: `ItemKey::Unknown` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/526))
3648
* `Tag` is now intended for generic metadata editing only, with format-specific items only being available through concrete tag types.
3749
See <https://github.com/Serial-ATA/lofty-rs/issues/521> for the rationale.
50+
* **Picture**: `Picture::new_unchecked()`, replaced with `Picture::unchecked()` returning a builder ([issue](https://github.com/Serial-ATA/lofty-rs/issues/468)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/569))
3851

3952
## [0.22.4] - 2025-04-29
4053

benches/create_tag.rs

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use lofty::tag::{Accessor, TagExt};
1313

1414
use std::borrow::Cow;
1515

16-
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
16+
use gungraun::{library_benchmark, library_benchmark_group, main};
1717

1818
const ENCODER: &str = "Lavf57.56.101";
1919

@@ -44,12 +44,10 @@ bench_tag_write!([
4444
use lofty::ape::ApeItem;
4545
use lofty::tag::ItemValue;
4646

47-
let picture = Picture::new_unchecked(
48-
PictureType::CoverFront,
49-
Some(MimeType::Jpeg),
50-
None,
51-
include_bytes!("./assets/cover.jpg").to_vec(),
52-
);
47+
let picture = Picture::unchecked(include_bytes!("./assets/cover.jpg").to_vec())
48+
.pic_type(PictureType::CoverFront)
49+
.mime_type(MimeType::Jpeg)
50+
.build();
5351

5452
tag.insert(
5553
ApeItem::new(
@@ -70,12 +68,10 @@ bench_tag_write!([
7068
use lofty::TextEncoding;
7169
use lofty::id3::v2::{Frame, TextInformationFrame};
7270

73-
let picture = Picture::new_unchecked(
74-
PictureType::CoverFront,
75-
Some(MimeType::Jpeg),
76-
None,
77-
include_bytes!("./assets/cover.jpg").to_vec(),
78-
);
71+
let picture = Picture::unchecked(include_bytes!("./assets/cover.jpg").to_vec())
72+
.pic_type(PictureType::CoverFront)
73+
.mime_type(MimeType::Jpeg)
74+
.build();
7975

8076
tag.insert_picture(picture);
8177
tag.insert(Frame::Text(TextInformationFrame::new(
@@ -88,12 +84,10 @@ bench_tag_write!([
8884
(ilst, Ilst, |tag| {
8985
use lofty::mp4::{Atom, AtomData, AtomIdent};
9086

91-
let picture = Picture::new_unchecked(
92-
PictureType::CoverFront,
93-
Some(MimeType::Jpeg),
94-
None,
95-
include_bytes!("./assets/cover.jpg").to_vec(),
96-
);
87+
let picture = Picture::unchecked(include_bytes!("./assets/cover.jpg").to_vec())
88+
.pic_type(PictureType::CoverFront)
89+
.mime_type(MimeType::Jpeg)
90+
.build();
9791

9892
tag.insert_picture(picture);
9993
tag.insert(Atom::new(
@@ -107,12 +101,10 @@ bench_tag_write!([
107101
(vorbis_comments, VorbisComments, |tag| {
108102
use lofty::ogg::OggPictureStorage;
109103

110-
let picture = Picture::new_unchecked(
111-
PictureType::CoverFront,
112-
Some(MimeType::Jpeg),
113-
None,
114-
include_bytes!("./assets/cover.jpg").to_vec(),
115-
);
104+
let picture = Picture::unchecked(include_bytes!("./assets/cover.jpg").to_vec())
105+
.pic_type(PictureType::CoverFront)
106+
.mime_type(MimeType::Jpeg)
107+
.build();
116108

117109
let _ = tag.insert_picture(picture, None).unwrap();
118110
tag.push(String::from("ENCODER"), String::from(ENCODER));

benches/read_file.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use lofty::config::ParseOptions;
44
use lofty::probe::Probe;
55

6-
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
6+
use gungraun::{library_benchmark, library_benchmark_group, main};
77

88
use std::hint::black_box;
99
use std::io::Cursor;

doc/NEW_FILE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,11 @@ Then we'll store our tests in `tests/files/{format}.rs`.
198198
There is a simple suite of tests to go in that file:
199199

200200
* `read()`: Read a file containing all possible tags (a Foo file with an ID3v2 *and* APE tag in this case), verifying
201-
all expected information is present. This can be done quickly with the `crate::verify_artist!` macro.
202-
* `write()`: Change the artist field of each tag using the `crate::set_artist!` macro, which will verify the artist
201+
all expected information is present. This can be done quickly with the `crate::verify_artist()` function.
202+
* `write()`: Change the artist field of each tag using the `crate::set_artist()` function, which will verify the artist
203203
and set a new one. Then, revert the artists using the same method.
204204
* `remove_{tag}()`: For each tag format the file supports, create a `remove_{tag}()` test that simply calls the
205-
`crate::remove_tag!` macro. For example, this format would have `remove_ape()` and `remove_id3v2()`.
205+
`crate::remove_tag_test()` function. For example, this format would have `remove_ape()` and `remove_id3v2()`.
206206

207207
#### Fuzz Tests
208208

examples/custom_resolver/src/main.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use lofty::file::FileType;
77
use lofty::id3::v2::Id3v2Tag;
88
use lofty::properties::FileProperties;
99
use lofty::resolve::FileResolver;
10-
use lofty::tag::TagType;
10+
use lofty::tag::{TagSupport, TagType};
1111
use lofty_attr::LoftyFile;
1212

1313
use std::fs::File;
@@ -73,8 +73,11 @@ impl FileResolver for MyFile {
7373

7474
// All of the `TagType`s this file supports, including the
7575
// primary one.
76-
fn supported_tag_types() -> &'static [TagType] {
77-
&[TagType::Id3v2, TagType::Ape]
76+
fn tag_support(tag_type: TagType) -> TagSupport {
77+
match tag_type {
78+
TagType::Id3v2 | TagType::Ape => TagSupport::ReadWrite,
79+
_ => TagSupport::Unsupported,
80+
}
7881
}
7982

8083
// This is used to guess the `FileType` when reading the file contents.

lofty/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ rusty-fork = "0.3.0"
4747
structopt = { version = "0.3.26", default-features = false }
4848
tempfile = "3.15.0"
4949
test-log = "0.2.16"
50-
iai-callgrind = "0.16.0"
50+
gungraun = "0.17.0"
5151

5252
[lints]
5353
workspace = true

lofty/src/ape/tag/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -920,12 +920,10 @@ mod tests {
920920

921921
#[test_log::test]
922922
fn skip_reading_cover_art() {
923-
let p = Picture::new_unchecked(
924-
PictureType::CoverFront,
925-
Some(MimeType::Jpeg),
926-
None,
927-
std::iter::repeat(0).take(50).collect::<Vec<u8>>(),
928-
);
923+
let p = Picture::unchecked(std::iter::repeat(0).take(50).collect::<Vec<u8>>())
924+
.pic_type(PictureType::CoverFront)
925+
.mime_type(MimeType::Jpeg)
926+
.build();
929927

930928
let mut tag = Tag::new(TagType::Ape);
931929
tag.push_picture(p);

lofty/src/file/file_type.rs

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,48 @@
11
use crate::config::global_options;
22
use crate::resolve::custom_resolvers;
3-
use crate::tag::TagType;
3+
use crate::tag::{TagSupport, TagType};
44

55
use std::ffi::OsStr;
66
use std::path::Path;
77

8+
/// List of common audio extensions
9+
///
10+
/// This contains a bunch of common extensions for all supported [`FileType`]s, and can be used a filter
11+
/// when scanning directories.
12+
///
13+
/// NOTE: This is **not** an exhaustive list, but it should work fine in most cases.
14+
///
15+
/// # Examples
16+
///
17+
/// ```rust,no_run
18+
/// use lofty::file::EXTENSIONS;
19+
/// use std::fs;
20+
///
21+
/// # fn main() -> lofty::error::Result<()> {
22+
/// for entry in fs::read_dir(".")? {
23+
/// let entry = entry?;
24+
///
25+
/// let path = entry.path();
26+
/// let Some(extension) = path.extension() else {
27+
/// continue;
28+
/// };
29+
///
30+
/// // Skip any non-audio file extensions
31+
/// if !EXTENSIONS.iter().any(|e| *e == extension) {
32+
/// continue;
33+
/// }
34+
///
35+
/// // `entry` is *most likely* a supported file at this point
36+
/// let parsed = lofty::read_from_path(path)?;
37+
/// }
38+
/// # Ok(()) }
39+
/// ```
40+
pub const EXTENSIONS: &[&str] = &[
41+
// Also update `FileType::from_ext()` below
42+
"aac", "ape", "aiff", "aif", "afc", "aifc", "mp3", "mp2", "mp1", "wav", "wv", "opus", "flac",
43+
"ogg", "mp4", "m4a", "m4b", "m4p", "m4r", "m4v", "3gp", "mpc", "mp+", "mpp", "spx",
44+
];
45+
846
/// The type of file read
947
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1048
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
@@ -65,11 +103,7 @@ impl FileType {
65103
}
66104
}
67105

68-
/// Returns if the target `FileType` supports a [`TagType`]
69-
///
70-
/// NOTE: This is feature dependent, meaning if you do not have the
71-
/// `id3v2` feature enabled, [`FileType::Mpeg`] will return `false` for
72-
/// [`TagType::Id3v2`].
106+
/// Describes how this `FileType` supports the given [`TagType`]
73107
///
74108
/// # Panics
75109
///
@@ -81,24 +115,48 @@ impl FileType {
81115
/// use lofty::file::FileType;
82116
/// use lofty::tag::TagType;
83117
///
84-
/// let file_type = FileType::Mpeg;
85-
/// assert!(file_type.supports_tag_type(TagType::Id3v2));
118+
/// // `FileType::Mpeg` supports both reading and writing ID3v2
119+
/// assert!(FileType::Mpeg.tag_support(TagType::Id3v2).is_writable());
86120
/// ```
87-
pub fn supports_tag_type(&self, tag_type: TagType) -> bool {
121+
pub fn tag_support(&self, tag_type: TagType) -> TagSupport {
88122
if let FileType::Custom(c) = self {
89123
let resolver = crate::resolve::lookup_resolver(c);
90-
return resolver.supported_tag_types().contains(&tag_type);
124+
return resolver.tag_support(tag_type);
91125
}
92126

93-
match tag_type {
94-
TagType::Ape => crate::ape::ApeTag::SUPPORTED_FORMATS.contains(self),
95-
TagType::Id3v1 => crate::id3::v1::Id3v1Tag::SUPPORTED_FORMATS.contains(self),
96-
TagType::Id3v2 => crate::id3::v2::Id3v2Tag::SUPPORTED_FORMATS.contains(self),
97-
TagType::Mp4Ilst => crate::mp4::Ilst::SUPPORTED_FORMATS.contains(self),
98-
TagType::VorbisComments => crate::ogg::VorbisComments::SUPPORTED_FORMATS.contains(self),
99-
TagType::RiffInfo => crate::iff::wav::RiffInfoList::SUPPORTED_FORMATS.contains(self),
100-
TagType::AiffText => crate::iff::aiff::AiffTextChunks::SUPPORTED_FORMATS.contains(self),
127+
macro_rules! tag_support {
128+
(
129+
$tag_type:ident,
130+
$(($variant:ident, $tag:path)),* $(,)?
131+
) => {
132+
match $tag_type {
133+
$(
134+
TagType::$variant => {
135+
if <$tag>::SUPPORTED_FORMATS.contains(self) {
136+
if <$tag>::READ_ONLY_FORMATS.contains(self) {
137+
return TagSupport::ReadOnly;
138+
}
139+
140+
return TagSupport::ReadWrite;
141+
}
142+
143+
TagSupport::Unsupported
144+
},
145+
)*
146+
}
147+
}
101148
}
149+
150+
tag_support!(
151+
tag_type,
152+
(Ape, crate::ape::ApeTag),
153+
(Id3v1, crate::id3::v1::Id3v1Tag),
154+
(Id3v2, crate::id3::v2::Id3v2Tag),
155+
(Mp4Ilst, crate::mp4::Ilst),
156+
(VorbisComments, crate::ogg::VorbisComments),
157+
(RiffInfo, crate::iff::wav::RiffInfoList),
158+
(AiffText, crate::iff::aiff::AiffTextChunks),
159+
)
102160
}
103161

104162
/// Attempts to extract a [`FileType`] from an extension
@@ -129,6 +187,7 @@ impl FileType {
129187
}
130188
}
131189

190+
// Also update `EXTENSIONS` above
132191
match ext.as_str() {
133192
"aac" => Some(Self::Aac),
134193
"ape" => Some(Self::Ape),

lofty/src/file/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod file_type;
55
mod tagged_file;
66

77
pub use audio_file::AudioFile;
8-
pub use file_type::FileType;
8+
pub use file_type::{EXTENSIONS, FileType};
99
pub use tagged_file::{BoundTaggedFile, TaggedFile, TaggedFileExt};
1010

1111
pub(crate) use file_type::FileTypeGuessResult;

0 commit comments

Comments
 (0)