Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ env:
CARGO_TERM_COLOR: always

jobs:
build_msrv:
name: Build MSRV
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
toolchain: 1.89.0
- run: |
cargo build --all-features --workspace --tests --examples

test:
name: Tests
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- **MP4**: `Mp4File::ftyp()` was moved to `Mp4Properties::ftyp()` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/650))
- **MSRV**: Bumped to **1.89.0** ([PR](https://github.com/Serial-ATA/lofty-rs/pull/652))

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ members = [

[workspace.package]
edition = "2024"
rust-version = "1.85"
rust-version = "1.89"
repository = "https://github.com/Serial-ATA/lofty-rs"
license = "MIT OR Apache-2.0"

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Serial-ATA/lofty-rs/ci.yml?branch=main&logo=github&style=for-the-badge)](https://github.com/Serial-ATA/lofty-rs/actions/workflows/ci.yml)
[![Downloads](https://img.shields.io/crates/d/lofty?style=for-the-badge&logo=rust)](https://crates.io/crates/lofty)
[![Version](https://img.shields.io/crates/v/lofty?style=for-the-badge&logo=rust)](https://crates.io/crates/lofty)
[![MSRV](https://img.shields.io/crates/msrv/lofty?style=for-the-badge&color=lightgray)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/)
[![MSRV](https://img.shields.io/crates/msrv/lofty?style=for-the-badge&color=lightgray)](https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/)
[![Documentation](https://img.shields.io/badge/docs.rs-lofty-informational?style=for-the-badge&logo=read-the-docs)](https://docs.rs/lofty/)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/Serial-ATA?style=for-the-badge&logo=githubsponsors)](https://github.com/sponsors/Serial-ATA)

Expand Down
33 changes: 16 additions & 17 deletions lofty/src/ape/tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,19 +568,19 @@ impl SplitTag for ApeTag {
let item = std::mem::replace(item, ApeItem::EMPTY);

// Multi-value string
if let Some(text) = item.value.text() {
if text.contains('\0') {
for value in text.split('\0') {
let item_value = match &item.value {
ItemValue::Text(_) => ItemValue::Text(value.to_string()),
ItemValue::Locator(_) => ItemValue::Locator(value.to_string()),
_ => unreachable!(),
};

tag.items.push(TagItem::new(k, item_value))
}
return false; // Item consumed
if let Some(text) = item.value.text()
&& text.contains('\0')
{
for value in text.split('\0') {
let item_value = match &item.value {
ItemValue::Text(_) => ItemValue::Text(value.to_string()),
ItemValue::Locator(_) => ItemValue::Locator(value.to_string()),
_ => unreachable!(),
};

tag.items.push(TagItem::new(k, item_value))
}
return false; // Item consumed
}

tag.items.push(TagItem::new(k, item.value));
Expand All @@ -604,12 +604,11 @@ impl MergeTag for SplitTagRemainder {
}

for pic in tag.pictures {
if let Some(key) = pic.pic_type.as_ape_key() {
if let Ok(item) =
if let Some(key) = pic.pic_type.as_ape_key()
&& let Ok(item) =
ApeItem::new(key.to_string(), ItemValue::Binary(pic.as_ape_bytes()))
{
merged.insert(item)
}
{
merged.insert(item)
}
}

Expand Down
9 changes: 4 additions & 5 deletions lofty/src/file/file_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,14 @@ impl FileType {
let ext = ext.as_ref().to_str()?.to_ascii_lowercase();

// Give custom resolvers priority
if unsafe { global_options().use_custom_resolvers } {
if let Some((ty, _)) = CUSTOM_RESOLVERS
if unsafe { global_options().use_custom_resolvers }
&& let Some((ty, _)) = CUSTOM_RESOLVERS
.lock()
.ok()?
.iter()
.find(|(_, f)| f.extension() == Some(ext.as_str()))
{
return Some(Self::Custom(ty));
}
{
return Some(Self::Custom(ty));
}

// Also update `EXTENSIONS` above
Expand Down
14 changes: 6 additions & 8 deletions lofty/src/flac/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,14 @@ where

blocks.extend(metadata_blocks);

if will_write_padding {
if let Some(preferred_padding) = write_options.preferred_padding {
log::warn!("File is missing a PADDING block. Adding one");
if will_write_padding && let Some(preferred_padding) = write_options.preferred_padding {
log::warn!("File is missing a PADDING block. Adding one");

// `PADDING` always goes last
let mut padding_block = Block::new_padding(preferred_padding as usize)?;
padding_block.last = true;
// `PADDING` always goes last
let mut padding_block = Block::new_padding(preferred_padding as usize)?;
padding_block.last = true;

blocks.push(padding_block);
}
blocks.push(padding_block);
}

if let Some(block) = blocks.last_mut() {
Expand Down
8 changes: 4 additions & 4 deletions lofty/src/id3/v1/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,10 @@ impl SplitTag for Id3v1Tag {
))
}

if let Some(genre_index) = self.genre.take() {
if let Some(genre) = GENRES.get(genre_index as usize) {
tag.insert_text(ItemKey::Genre, (*genre).to_string());
}
if let Some(genre_index) = self.genre.take()
&& let Some(genre) = GENRES.get(genre_index as usize)
{
tag.insert_text(ItemKey::Genre, (*genre).to_string());
}

(SplitTagRemainder, tag)
Expand Down
10 changes: 5 additions & 5 deletions lofty/src/id3/v2/frame/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,11 @@ impl TryFrom<ItemKey> for FrameId<'_> {
type Error = LoftyError;

fn try_from(value: ItemKey) -> std::prelude::rust_2015::Result<Self, Self::Error> {
if let Some(mapped) = value.map_key(TagType::Id3v2) {
if mapped.len() == 4 {
Self::verify_id(mapped)?;
return Ok(Self::Valid(Cow::Borrowed(mapped)));
}
if let Some(mapped) = value.map_key(TagType::Id3v2)
&& mapped.len() == 4
{
Self::verify_id(mapped)?;
return Ok(Self::Valid(Cow::Borrowed(mapped)));
}

Err(Id3v2Error::new(Id3v2ErrorKind::UnsupportedFrameId(value)).into())
Expand Down
8 changes: 4 additions & 4 deletions lofty/src/id3/v2/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1302,10 +1302,10 @@ impl From<Id3v2Tag> for Tag {

impl From<Tag> for Id3v2Tag {
fn from(mut input: Tag) -> Self {
if unsafe { global_options().preserve_format_specific_items } {
if let Some(companion) = input.companion_tag.take().and_then(CompanionTag::id3v2) {
return SplitTagRemainder(companion).merge_tag(input);
}
if unsafe { global_options().preserve_format_specific_items }
&& let Some(companion) = input.companion_tag.take().and_then(CompanionTag::id3v2)
{
return SplitTagRemainder(companion).merge_tag(input);
}

SplitTagRemainder::default().merge_tag(input)
Expand Down
2 changes: 1 addition & 1 deletion lofty/src/id3/v2/write/chunk_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ where

// It is required an odd length chunk be padded with a 0
// The 0 isn't included in the chunk size, however
if tag.len() % 2 != 0 {
if !tag.len().is_multiple_of(2) {
tag_bytes.write_u8(0)?;
}

Expand Down
24 changes: 12 additions & 12 deletions lofty/src/id3/v2/write/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,19 +333,19 @@ where
);
}

if let Some(mut len) = flags.data_length_indicator {
if len > 0 {
write_frame_header(writer, name, (value.len() + 1) as u32, flags, write_options)?;
if !write_options.use_id3v23 {
len = len.synch()?;
}

writer.write_u32::<BigEndian>(len)?;
writer.write_u8(method_symbol)?;
writer.write_all(value)?;

return Ok(());
if let Some(mut len) = flags.data_length_indicator
&& len > 0
{
write_frame_header(writer, name, (value.len() + 1) as u32, flags, write_options)?;
if !write_options.use_id3v23 {
len = len.synch()?;
}

writer.write_u32::<BigEndian>(len)?;
writer.write_u8(method_symbol)?;
writer.write_all(value)?;

return Ok(());
}

Err(Id3v2Error::new(Id3v2ErrorKind::MissingDataLengthIndicator).into())
Expand Down
30 changes: 15 additions & 15 deletions lofty/src/iff/aiff/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ impl Accessor for AiffTextChunks {
}

fn comment(&self) -> Option<Cow<'_, str>> {
if let Some(ref anno) = self.annotations {
if !anno.is_empty() {
return anno.first().map(String::as_str).map(Cow::Borrowed);
}
if let Some(ref anno) = self.annotations
&& !anno.is_empty()
{
return anno.first().map(String::as_str).map(Cow::Borrowed);
}

if let Some(ref comm) = self.comments {
Expand Down Expand Up @@ -339,17 +339,17 @@ where

fn create_text_chunks(tag: &mut AiffTextChunksRef<'_, T, AI>) -> Result<Vec<u8>> {
fn write_chunk(writer: &mut Vec<u8>, key: &str, value: Option<&str>) {
if let Some(val) = value {
if let Ok(len) = u32::try_from(val.len()) {
writer.extend(key.as_bytes());
writer.extend(len.to_be_bytes());
writer.extend(val.as_bytes());

// AIFF only needs a terminator if the string is on an odd boundary,
// unlike RIFF, which makes use of both C-strings and even boundaries
if len % 2 != 0 {
writer.push(0);
}
if let Some(val) = value
&& let Ok(len) = u32::try_from(val.len())
{
writer.extend(key.as_bytes());
writer.extend(len.to_be_bytes());
writer.extend(val.as_bytes());

// AIFF only needs a terminator if the string is on an odd boundary,
// unlike RIFF, which makes use of both C-strings and even boundaries
if len % 2 != 0 {
writer.push(0);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lofty/src/iff/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl<R: Read + Seek, B: ByteOrder> Chunks<R, B> {
// and it is NOT included in the chunk's size

let mut padding_size = 1;
if current_chunk_size % 2 == 0 || self.remaining_size < padding_size {
if current_chunk_size.is_multiple_of(2) || self.remaining_size < padding_size {
return Ok(());
}

Expand Down
8 changes: 4 additions & 4 deletions lofty/src/iff/wav/tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,10 @@ impl From<Tag> for RiffInfoList {
let mut riff_info = RiffInfoList::default();

for item in input.items {
if let ItemValue::Text(val) | ItemValue::Locator(val) = item.item_value {
if let Some(key) = item.item_key.map_key(TagType::RiffInfo) {
riff_info.items.push((key.to_string(), val))
}
if let ItemValue::Text(val) | ItemValue::Locator(val) = item.item_value
&& let Some(key) = item.item_key.map_key(TagType::RiffInfo)
{
riff_info.items.push((key.to_string(), val))
}
}

Expand Down
2 changes: 1 addition & 1 deletion lofty/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Serial-ATA/lofty-rs/ci.yml?branch=main&logo=github&style=for-the-badge)](https://github.com/Serial-ATA/lofty-rs/actions/workflows/ci.yml)
//! [![Downloads](https://img.shields.io/crates/d/lofty?style=for-the-badge&logo=rust)](https://crates.io/crates/lofty)
//! [![Version](https://img.shields.io/crates/v/lofty?style=for-the-badge&logo=rust)](https://crates.io/crates/lofty)
//! [![MSRV](https://img.shields.io/crates/msrv/lofty?style=for-the-badge&color=lightgray)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/)
//! [![MSRV](https://img.shields.io/crates/msrv/lofty?style=for-the-badge&color=lightgray)](https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/)
//!
//! Parse, convert, and write metadata to audio formats.
//!
Expand Down
16 changes: 8 additions & 8 deletions lofty/src/mp4/ilst/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,10 +583,10 @@ impl Accessor for Ilst {
}

fn date(&self) -> Option<Timestamp> {
if let Some(atom) = self.get(&AtomIdent::Fourcc(*b"\xa9day")) {
if let Some(AtomData::UTF8(text)) = atom.data().next() {
return try_parse_timestamp(text);
}
if let Some(atom) = self.get(&AtomIdent::Fourcc(*b"\xa9day"))
&& let Some(AtomData::UTF8(text)) = atom.data().next()
{
return try_parse_timestamp(text);
}

None
Expand Down Expand Up @@ -885,10 +885,10 @@ impl From<Ilst> for Tag {

impl From<Tag> for Ilst {
fn from(mut input: Tag) -> Self {
if unsafe { global_options().preserve_format_specific_items } {
if let Some(companion) = input.companion_tag.take().and_then(CompanionTag::ilst) {
return SplitTagRemainder(companion).merge_tag(input);
}
if unsafe { global_options().preserve_format_specific_items }
&& let Some(companion) = input.companion_tag.take().and_then(CompanionTag::ilst)
{
return SplitTagRemainder(companion).merge_tag(input);
}

SplitTagRemainder::default().merge_tag(input)
Expand Down
19 changes: 9 additions & 10 deletions lofty/src/mp4/ilst/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,16 @@ where
b"cpil" | b"hdvd" | b"pcst" | b"pgap" | b"shwm" => {
if let Some(atom_data) =
parse_data_inner(&mut ilst_reader, parsing_mode, &atom)?
&& let Some((_, content)) = atom_data.first()
{
if let Some((_, content)) = atom_data.first() {
// Any size integer is technically valid, we'll correct it on write.
let is_true = content.iter().any(|&b| b != 0);
let data = AtomData::Bool(is_true);

tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*fourcc),
data: AtomDataStorage::Single(data),
})
}
// Any size integer is technically valid, we'll correct it on write.
let is_true = content.iter().any(|&b| b != 0);
let data = AtomData::Bool(is_true);

tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*fourcc),
data: AtomDataStorage::Single(data),
})
}

continue;
Expand Down
25 changes: 13 additions & 12 deletions lofty/src/mpeg/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,23 +148,24 @@ where
2
};

if let Some(vbr_header) = vbr_header {
if first_frame_header.sample_rate > 0 && vbr_header.is_valid() {
log::debug!("MPEG: Valid VBR header; using it to calculate duration");
if let Some(vbr_header) = vbr_header
&& first_frame_header.sample_rate > 0
&& vbr_header.is_valid()
{
log::debug!("MPEG: Valid VBR header; using it to calculate duration");

let sample_rate = u64::from(first_frame_header.sample_rate);
let samples_per_frame = u64::from(first_frame_header.samples);
let sample_rate = u64::from(first_frame_header.sample_rate);
let samples_per_frame = u64::from(first_frame_header.samples);

let total_frames = u64::from(vbr_header.frames);
let total_frames = u64::from(vbr_header.frames);

let length = (samples_per_frame * 1000 * total_frames).div_round(sample_rate);
let length = (samples_per_frame * 1000 * total_frames).div_round(sample_rate);

properties.duration = Duration::from_millis(length);
properties.overall_bitrate = ((file_length * 8) / length) as u32;
properties.audio_bitrate = ((u64::from(vbr_header.size) * 8) / length) as u32;
properties.duration = Duration::from_millis(length);
properties.overall_bitrate = ((file_length * 8) / length) as u32;
properties.audio_bitrate = ((u64::from(vbr_header.size) * 8) / length) as u32;

return Ok(());
}
return Ok(());
}

// Nothing more we can do
Expand Down
Loading