From 6b9912dd15bd282373090dc11d9b7ad3871d2306 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Sun, 10 May 2026 09:28:58 -0400 Subject: [PATCH 1/7] ID3v2: Fix `Id3v2Tag::remove_disk_total()` --- CHANGELOG.md | 4 +++- lofty/src/id3/v2/tag.rs | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f3eb3649..3ccbbcdaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **IFF**: Undersized ID3v2 chunks will no longer error outside of strict mode ([PR](https://github.com/Serial-ATA/lofty-rs/pull/644)) - **Timestamp**: Support dot-separated dates (e.g. `2024.06.03`) ([issue](https://github.com/Serial-ATA/lofty-rs/issues/647)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/648)) -- **ID3v2**: Fixed UTF-16 description string termination in `APIC` and `SYLT` frames ([issue](https://github.com/Serial-ATA/lofty-rs/issues/653)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/654)) +- **ID3v2**: + - Fixed UTF-16 description string termination in `APIC` and `SYLT` frames ([issue](https://github.com/Serial-ATA/lofty-rs/issues/653)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/654)) + - Fixed `Id3v2Tag::remove_disk_total()`, which incorrectly preserved the track number rather than disk number ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) ## [0.24.0] - 2026-04-12 diff --git a/lofty/src/id3/v2/tag.rs b/lofty/src/id3/v2/tag.rs index ed9af7c51..043be3738 100644 --- a/lofty/src/id3/v2/tag.rs +++ b/lofty/src/id3/v2/tag.rs @@ -746,11 +746,14 @@ impl Accessor for Id3v2Tag { } fn remove_disk_total(&mut self) { - let existing_track_number = self.track(); + let existing_disk_number = self.disk(); let _ = self.remove(&DISC_ID); - if let Some(track) = existing_track_number { - self.insert(Frame::text(Cow::Borrowed("TPOS"), track.to_string())); + if let Some(disk) = existing_disk_number { + self.insert(Frame::text( + Cow::Borrowed(DISC_ID.as_str()), + disk.to_string(), + )); } } From 34a914343edf29877f4855b72a5f8613032a76f9 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Sun, 10 May 2026 09:45:40 -0400 Subject: [PATCH 2/7] ID3v2: Fix extended header handling --- CHANGELOG.md | 2 + lofty/src/id3/v2/header.rs | 81 +++++++++++++++++++++----------- lofty/src/id3/v2/restrictions.rs | 4 +- lofty/src/id3/v2/write/frame.rs | 2 +- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ccbbcdaa..04f6f3c3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **ID3v2**: - Fixed UTF-16 description string termination in `APIC` and `SYLT` frames ([issue](https://github.com/Serial-ATA/lofty-rs/issues/653)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/654)) - Fixed `Id3v2Tag::remove_disk_total()`, which incorrectly preserved the track number rather than disk number ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) + - Fixed handling of encryption method symbols when writing ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) + - Fixed parsing of extended headers in ID3v2.3 ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) ## [0.24.0] - 2026-04-12 diff --git a/lofty/src/id3/v2/header.rs b/lofty/src/id3/v2/header.rs index 19a0f1f4f..efe2cae0a 100644 --- a/lofty/src/id3/v2/header.rs +++ b/lofty/src/id3/v2/header.rs @@ -1,6 +1,6 @@ use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::restrictions::TagRestrictions; -use crate::id3::v2::util::synchsafe::SynchsafeInteger; +use crate::id3::v2::util::synchsafe::{SynchsafeInteger, UnsynchronizedStream}; use crate::macros::err; use std::io::Read; @@ -141,32 +141,25 @@ impl Id3v2Header { (version == Id3v2Version::V4 || version == Id3v2Version::V3) && flags & 0x40 == 0x40; if extended_header { - extended_size = bytes.read_u32::()?.unsynch(); - - if extended_size < 6 { - return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into()); - } - - // Useless byte since there's only 1 byte for flags - let _num_flag_bytes = bytes.read_u8()?; - - let extended_flags = bytes.read_u8()?; - - // The only flags we care about here are the CRC and restrictions - - if extended_flags & 0x20 == 0x20 { - flags_parsed.crc = true; - - // We don't care about the existing CRC (5) or its length byte (1) - let mut crc = [0; 6]; - bytes.read_exact(&mut crc)?; - } - - if extended_flags & 0x10 == 0x10 { - // We don't care about the length byte, it is always 1 - let _data_length = bytes.read_u8()?; - - flags_parsed.restrictions = Some(TagRestrictions::from_byte(bytes.read_u8()?)); + match version { + // In ID3v2.3, the extended header is considered separate from the frame header, and + // thus subject to unsynchronization + Id3v2Version::V3 => { + if flags_parsed.unsynchronisation { + let mut reader = UnsynchronizedStream::new(bytes); + extended_size = reader.read_u32::()?; + read_extended_header(&mut reader, &mut flags_parsed, extended_size)?; + } else { + extended_size = bytes.read_u32::()?; + read_extended_header(bytes, &mut flags_parsed, extended_size)?; + } + }, + // In ID3v2.4, they seem to be considered one big header (?) so no flags apply to it + Id3v2Version::V4 => { + extended_size = bytes.read_u32::()?.unsynch(); + read_extended_header(bytes, &mut flags_parsed, extended_size)?; + }, + _ => unreachable!(), } } @@ -187,3 +180,37 @@ impl Id3v2Header { self.size + 10 + self.extended_size + if self.flags.footer { 10 } else { 0 } } } + +fn read_extended_header( + reader: &mut R, + flags: &mut Id3v2TagFlags, + header_size: u32, +) -> Result<()> { + if header_size < 6 { + return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into()); + } + + // Useless byte since there's only 1 byte for flags + let _num_flag_bytes = reader.read_u8()?; + + let extended_flags = reader.read_u8()?; + + // The only flags we care about here are the CRC and restrictions + + if extended_flags & 0x20 == 0x20 { + flags.crc = true; + + // We don't care about the existing CRC (5) or its length byte (1) + let mut crc = [0; 6]; + reader.read_exact(&mut crc)?; + } + + if extended_flags & 0x10 == 0x10 { + // We don't care about the length byte, it is always 1 + let _data_length = reader.read_u8()?; + + flags.restrictions = Some(TagRestrictions::from_byte(reader.read_u8()?)); + } + + Ok(()) +} diff --git a/lofty/src/id3/v2/restrictions.rs b/lofty/src/id3/v2/restrictions.rs index 89588f958..776456124 100644 --- a/lofty/src/id3/v2/restrictions.rs +++ b/lofty/src/id3/v2/restrictions.rs @@ -68,7 +68,7 @@ impl TagRestrictions { let restriction_flags = byte; // xx000000 - match restriction_flags & 0x0C { + match restriction_flags & 0xC0 { 64 => restrictions.size = TagSizeRestrictions::S_64F_128K, 128 => restrictions.size = TagSizeRestrictions::S_32F_40K, 192 => restrictions.size = TagSizeRestrictions::S_32F_4K, @@ -113,7 +113,7 @@ impl TagRestrictions { TagSizeRestrictions::S_128F_1M => {}, TagSizeRestrictions::S_64F_128K => byte |= 0x40, TagSizeRestrictions::S_32F_40K => byte |= 0x80, - TagSizeRestrictions::S_32F_4K => byte |= 0x0C, + TagSizeRestrictions::S_32F_4K => byte |= 0xC0, } if self.text_encoding { diff --git a/lofty/src/id3/v2/write/frame.rs b/lofty/src/id3/v2/write/frame.rs index 38883f30f..4ab2177b3 100644 --- a/lofty/src/id3/v2/write/frame.rs +++ b/lofty/src/id3/v2/write/frame.rs @@ -327,7 +327,7 @@ where // Guaranteed to be `Some` at this point. let method_symbol = flags.encryption.unwrap(); - if method_symbol > 0x80 { + if method_symbol <= 0x80 { return Err( Id3v2Error::new(Id3v2ErrorKind::InvalidEncryptionMethodSymbol(method_symbol)).into(), ); From 4cafe909092846c84e66e97dded0e06f2304b71b Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Sun, 10 May 2026 09:49:36 -0400 Subject: [PATCH 3/7] FLAC: Fix total sample count handling for huge files --- lofty/src/flac/properties.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lofty/src/flac/properties.rs b/lofty/src/flac/properties.rs index 138c457dd..106246720 100644 --- a/lofty/src/flac/properties.rs +++ b/lofty/src/flac/properties.rs @@ -99,8 +99,9 @@ where let bits_per_sample = ((info >> 4) & 0b11111) + 1; let channels = ((info >> 9) & 7) + 1; - // Read the remaining 32 bits of the total samples - let total_samples = stream_info.read_u32::()? | (info << 28); + // Read the remaining 32 bits of the total samples (36 bits total) + let total_samples = + (u64::from(info & 0xF) << 32) | u64::from(stream_info.read_u32::()?); let signature = stream_info.read_u128::()?; @@ -113,7 +114,7 @@ where }; if sample_rate > 0 && total_samples > 0 { - let length = (u64::from(total_samples) * 1000) / u64::from(sample_rate); + let length = (total_samples * 1000) / u64::from(sample_rate); properties.duration = Duration::from_millis(length); if length > 0 && file_length > 0 && stream_length > 0 { From 3bae6d9b1fe126241316946889d50a6fd802a937 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Fri, 15 May 2026 07:11:17 -0400 Subject: [PATCH 4/7] MP4: Cleanup property reading --- lofty/src/config/global_options.rs | 4 +- lofty/src/mp4/ilst/read.rs | 5 +- lofty/src/mp4/ilst/write.rs | 18 ++--- lofty/src/mp4/properties.rs | 115 ++++++++++++++++------------- lofty/src/mp4/read/atom_reader.rs | 5 ++ lofty/src/mp4/write.rs | 4 +- 6 files changed, 80 insertions(+), 71 deletions(-) diff --git a/lofty/src/config/global_options.rs b/lofty/src/config/global_options.rs index 4f08e1dfb..6e966fa6a 100644 --- a/lofty/src/config/global_options.rs +++ b/lofty/src/config/global_options.rs @@ -4,8 +4,8 @@ thread_local! { static GLOBAL_OPTIONS: UnsafeCell = UnsafeCell::new(GlobalOptions::default()); } -pub(crate) unsafe fn global_options() -> &'static GlobalOptions { - GLOBAL_OPTIONS.with(|global_options| unsafe { &*global_options.get() }) +pub(crate) unsafe fn global_options() -> GlobalOptions { + GLOBAL_OPTIONS.with(|global_options| unsafe { *global_options.get() }) } /// Options that control all interactions with Lofty for the current thread diff --git a/lofty/src/mp4/ilst/read.rs b/lofty/src/mp4/ilst/read.rs index 910c24c02..67cd8191a 100644 --- a/lofty/src/mp4/ilst/read.rs +++ b/lofty/src/mp4/ilst/read.rs @@ -347,7 +347,10 @@ fn parse_int(bytes: &[u8]) -> Result { Ok(match bytes.len() { 1 => i32::from(bytes[0]), 2 => i32::from(i16::from_be_bytes([bytes[0], bytes[1]])), - 3 => i32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]), + 3 => { + let pad = if bytes[0] & 0x80 != 0 { 0xFF } else { 0x00 }; + i32::from_be_bytes([pad, bytes[0], bytes[1], bytes[2]]) + }, 4 => i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), _ => err!(BadAtom( "Unexpected atom size for type \"BE signed integer\"" diff --git a/lofty/src/mp4/ilst/write.rs b/lofty/src/mp4/ilst/write.rs index 2dc10b59a..faba3dcea 100644 --- a/lofty/src/mp4/ilst/write.rs +++ b/lofty/src/mp4/ilst/write.rs @@ -458,8 +458,7 @@ fn update_offsets( write_handle.write_u32::((i64::from(read_offset) + difference) as u32)?; log::trace!( - "Updated offset from {} to {}", - read_offset, + "Updated offset from {read_offset} to {}", (i64::from(read_offset) + difference) as u32 ); } @@ -469,12 +468,7 @@ fn update_offsets( for co64 in moov.find_all_children(*b"co64", true) { log::trace!("Found `co64` atom"); - let co64_start = co64.start; - if !co64.extended { - decode_err!(@BAIL Mp4, "Expected `co64` atom to be extended"); - } - - write_handle.seek(SeekFrom::Start(co64_start + ATOM_HEADER_LEN + 8 + 4))?; + write_handle.seek(SeekFrom::Start(co64.start + ATOM_HEADER_LEN + 8 + 4))?; let count = write_handle.read_u32::()?; for _ in 0..count { @@ -487,8 +481,7 @@ fn update_offsets( write_handle.write_u64::((read_offset as i64 + difference) as u64)?; log::trace!( - "Updated offset from {} to {}", - read_offset, + "Updated offset from {read_offset} to {}", ((read_offset as i64) + difference) as u64 ); } @@ -525,8 +518,7 @@ fn update_offsets( write_handle.write_u64::((read_offset as i64 + difference) as u64)?; log::trace!( - "Updated offset from {} to {}", - read_offset, + "Updated offset from {read_offset} to {}", ((read_offset as i64) + difference) as u64 ); } @@ -643,7 +635,7 @@ where drop(write_handle); - log::trace!("Built `ilst` atom, size: {} bytes", size); + log::trace!("Built `ilst` atom, size: {size} bytes"); Ok(ilst_writer.into_contents()) } diff --git a/lofty/src/mp4/properties.rs b/lofty/src/mp4/properties.rs index 3bf065546..144fb42f6 100644 --- a/lofty/src/mp4/properties.rs +++ b/lofty/src/mp4/properties.rs @@ -493,6 +493,9 @@ fn read_stsd(reader: &mut AtomReader, properties: &mut Mp4Properties) -> R where R: Read + Seek, { + const MIN_SUPPORTED_ENTRY_VERSION: u16 = 0; + const MAX_SUPPORTED_ENTRY_VERSION: u16 = 2; + // Skipping 4 bytes // Version (1) // Flags (3) @@ -504,11 +507,64 @@ where err!(BadAtom("Expected sample entry atom in `stsd` atom")) }; - let AtomIdent::Fourcc(ref fourcc) = atom.ident else { + if atom.extended { + err!(BadAtom("Extended atoms are not supported in `stsd`")) + } + + let AtomIdent::Fourcc(ref descriptor_format) = atom.ident else { err!(BadAtom("Expected fourcc atom in `stsd` atom")) }; - match fourcc { + // Skipping 8 bytes + // Reserved (6) + // Data reference index (2) + reader.seek(SeekFrom::Current(8))?; + + let stsd_version = reader.read_u16()?; + if !(MIN_SUPPORTED_ENTRY_VERSION..=MAX_SUPPORTED_ENTRY_VERSION).contains(&stsd_version) { + err!(BadAtom("Unsupported `stsd` version")) + } + + // Skipping 6 bytes + // Revision level (2) + // Vendor (4) + reader.seek(SeekFrom::Current(6))?; + + properties.channels = reader.read_u16()? as u8; + + // Skipping 6 bytes + // Sample size (2) + // Compression ID (2) + // Packet size (2) + reader.seek(SeekFrom::Current(6))?; + + // 16.16 fixed point number + properties.sample_rate = reader.read_u32()? >> 16; + + let mut offset = reader.stream_position()?; + if stsd_version == 1 { + // Skipping 16 bytes + // Samples per packet (4) + // Bytes per packet (4) + // Bytes per frame (4) + // Bytes per sample (4) + offset = reader.seek(SeekFrom::Current(16))?; + } else if stsd_version == 2 { + let extension_struct_size = reader.read_u32()?; + + // Skipping (at least) 32 bytes + // Sample rate 64 (8) + // Channel count (4) + // Reserved (4) + // Bits per channel (4) + // Format specific flags (4) + // Bytes per audio packet (4) + // LPCM frames per audio packet (4) + // Struct size (variable) + offset = reader.seek(SeekFrom::Current(32 + i64::from(extension_struct_size)))?; + } + + match descriptor_format { b"mp4a" => mp4a_properties(reader, properties)?, b"alac" => alac_properties(reader, properties)?, b"fLaC" => flac_properties(reader, properties)?, @@ -519,15 +575,15 @@ where // Special case to detect encrypted files b"drms" => { properties.drm_protected = true; - skip_atom(reader, atom.extended, atom.len)?; + skip_atom(reader, false, offset - atom.start)?; continue; }, _ => { log::warn!( "Found unsupported sample entry: {:?}", - fourcc.escape_ascii().to_string() + descriptor_format.escape_ascii().to_string() ); - skip_atom(reader, atom.extended, atom.len)?; + skip_atom(reader, false, offset - atom.start)?; continue; }, } @@ -649,25 +705,6 @@ where // Set the codec to AAC, which is a good guess if we fail before reaching the `esds` properties.codec = Mp4Codec::AAC; - // Skipping 16 bytes - // Reserved (6) - // Data reference index (2) - // Version (2) - // Revision level (2) - // Vendor (4) - stsd.seek(SeekFrom::Current(16))?; - - properties.channels = stsd.read_u16()? as u8; - - // Skipping 4 bytes - // Sample size (2) - // Compression ID (2) - stsd.seek(SeekFrom::Current(4))?; - - properties.sample_rate = stsd.read_u32()?; - - stsd.seek(SeekFrom::Current(2))?; - // This information is often followed by an esds (elementary stream descriptor) atom containing the bitrate let Ok(Some(esds)) = stsd.next() else { return Ok(()); @@ -812,19 +849,13 @@ where R: Read + Seek, { // With ALAC, we can expect the length to be exactly 88 (80 here since we removed the size and identifier) - if stsd.seek(SeekFrom::End(0))? != 80 { + if stsd.len() != 80 { return Ok(()); } // Unlike the "mp4a" atom, we cannot read the data that immediately follows it // For ALAC, we have to skip the first "alac" atom entirely, and read the one that // immediately follows it. - // - // We are skipping over 44 bytes total - // stsd information/alac atom header (16, see `read_properties`) - // First alac atom's content (28) - stsd.seek(SeekFrom::Start(44))?; - let Ok(Some(alac)) = stsd.next() else { return Ok(()); }; @@ -870,28 +901,6 @@ where { properties.codec = Mp4Codec::FLAC; - // Skipping 16 bytes - // - // Reserved (6) - // Data reference index (2) - // Version (2) - // Revision level (2) - // Vendor (4) - stsd.seek(SeekFrom::Current(16))?; - - properties.channels = stsd.read_u16()? as u8; - properties.bit_depth = Some(stsd.read_u16()? as u8); - - // Skipping 4 bytes - // - // Compression ID (2) - // Packet size (2) - stsd.seek(SeekFrom::Current(4))?; - - properties.sample_rate = u32::from(stsd.read_u16()?); - - let _reserved = stsd.read_u16()?; - // There should be a dfla atom, but it's not worth erroring if absent. let Some(dfla) = stsd.next()? else { return Ok(()); diff --git a/lofty/src/mp4/read/atom_reader.rs b/lofty/src/mp4/read/atom_reader.rs index c24032224..5bdee88cd 100644 --- a/lofty/src/mp4/read/atom_reader.rs +++ b/lofty/src/mp4/read/atom_reader.rs @@ -42,6 +42,11 @@ where }) } + /// Get the total length of the reader, including bytes already read + pub(crate) fn len(&self) -> u64 { + self.len + } + /// Set new bounds for the reader /// /// This is useful when reading an atom such as `moov`, where we only want to read it and its diff --git a/lofty/src/mp4/write.rs b/lofty/src/mp4/write.rs index 83251257a..808a5b6e8 100644 --- a/lofty/src/mp4/write.rs +++ b/lofty/src/mp4/write.rs @@ -227,8 +227,8 @@ impl AtomWriterCompanion<'_> { // Overwrite existing extended size self.write_u64::(size)?; } else { - for i in extended_size { - self.insert((start + 8 + u64::from(i)) as usize, i); + for (index, b) in extended_size.into_iter().enumerate() { + self.insert((start + 8 + index as u64) as usize, b); } self.seek(SeekFrom::Current(8))?; From da772c3871457c20787966cc1a9fc9c62e6f2554 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Fri, 15 May 2026 07:22:07 -0400 Subject: [PATCH 5/7] ogg_pager: Fix packet offset calculation --- ogg_pager/src/packets.rs | 107 ++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/ogg_pager/src/packets.rs b/ogg_pager/src/packets.rs index 51691698e..182e0a2d1 100644 --- a/ogg_pager/src/packets.rs +++ b/ogg_pager/src/packets.rs @@ -5,6 +5,7 @@ use crate::paginate::paginate; use std::fmt::{Debug, Formatter}; use std::io::{Read, Seek, Write}; +use std::ops::Range; /// A container for packets in an OGG file pub struct Packets { @@ -78,55 +79,49 @@ impl Packets { }); } - let mut read = 0; + let mut packets_read = 0; let mut packet_size = 0_u64; let mut packet_bytes_already_read = None; let mut current_packet_content; - 'outer: loop { - if let Ok(header) = PageHeader::read(data) { - for i in header.segments { - packet_size += u64::from(i); + 'outer: while let Ok(header) = PageHeader::read(data) { + for i in header.segments { + packet_size += u64::from(i); - if i < 255 { - if count != -1 { - read += 1; - } - - let byte_count_to_read = Self::get_byte_count_to_read( - packet_size, - &mut packet_bytes_already_read, - ); - - current_packet_content = vec![0; byte_count_to_read as usize]; - data.read_exact(&mut current_packet_content)?; - - packet_sizes.push(packet_size); - packet_size = 0; - packet_bytes_already_read = None; - - content.append(&mut current_packet_content); - - if read == count { - break 'outer; - } + if i < 255 { + if count != -1 { + packets_read += 1; } - } - // The packet continues on the next page, write what we can so far - if packet_size != 0 { let byte_count_to_read = Self::get_byte_count_to_read(packet_size, &mut packet_bytes_already_read); current_packet_content = vec![0; byte_count_to_read as usize]; data.read_exact(&mut current_packet_content)?; + + packet_sizes.push(packet_size); + packet_size = 0; + packet_bytes_already_read = None; + content.append(&mut current_packet_content); + + if packets_read == count { + break 'outer; + } } + } + + // The packet continues on the next page, write what we can so far + if packet_size != 0 { + let byte_count_to_read = + Self::get_byte_count_to_read(packet_size, &mut packet_bytes_already_read); - continue; + current_packet_content = vec![0; byte_count_to_read as usize]; + data.read_exact(&mut current_packet_content)?; + content.append(&mut current_packet_content); } - break; + continue; } if count != -1 && packet_sizes.len() != count as usize { @@ -201,6 +196,24 @@ impl Packets { self.packet_sizes.is_empty() } + fn packet_span(&self, idx: usize) -> Option> { + if idx >= self.packet_sizes.len() { + return None; + } + + let start_pos = match idx { + // Packet 0 starts at pos 0 + 0 => 0, + other => self.packet_sizes[..other].iter().sum::() as usize, + }; + + if let Some(packet_size) = self.packet_sizes.get(idx) { + return Some(start_pos..start_pos + *packet_size as usize); + } + + None + } + /// Gets the packet at a specified index, returning its contents /// /// NOTES: @@ -227,22 +240,7 @@ impl Packets { /// # Ok(()) } /// ``` pub fn get(&self, idx: usize) -> Option<&[u8]> { - if idx >= self.content.len() { - return None; - } - - let start_pos = match idx { - // Packet 0 starts at pos 0 - 0 => 0, - // Anything else we have to get the size of the previous packet - other => self.packet_sizes[other - 1] as usize, - }; - - if let Some(packet_size) = self.packet_sizes.get(idx) { - return Some(&self.content[start_pos..start_pos + *packet_size as usize]); - } - - None + self.packet_span(idx).map(|range| &self.content[range]) } /// Sets the packet content, if it exists @@ -278,23 +276,14 @@ impl Packets { /// # Ok(()) } /// ``` pub fn set(&mut self, idx: usize, content: impl Into>) -> bool { - if idx >= self.packet_sizes.len() { + let Some(range) = self.packet_span(idx) else { return false; - } - - let start_pos = match idx { - // Packet 0 starts at pos 0 - 0 => 0, - // Anything else we have to get the size of the previous packet - other => self.packet_sizes[other - 1] as usize, }; let content = content.into(); let content_size = content.len(); - let end_pos = start_pos + self.packet_sizes[idx] as usize; - self.content.splice(start_pos..end_pos, content); - + self.content.splice(range, content); self.packet_sizes[idx] = content_size as u64; true From bdf8d4efa7f49dfb562ba9d4da36cd3a7a9cb196 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Fri, 15 May 2026 07:28:24 -0400 Subject: [PATCH 6/7] ogg_pager: 0.7.2 --- Cargo.toml | 2 +- ogg_pager/CHANGELOG.md | 5 +++++ ogg_pager/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 170b89688..0300245b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ license = "MIT OR Apache-2.0" [workspace.dependencies] lofty = { version = "0.24.0", path = "lofty" } lofty_attr = { version = "0.12.0", path = "lofty_attr" } -ogg_pager = { version = "0.7.1", path = "ogg_pager" } +ogg_pager = { version = "0.7.2", path = "ogg_pager" } byteorder = "1.5.0" diff --git a/ogg_pager/CHANGELOG.md b/ogg_pager/CHANGELOG.md index d02bea27f..700502020 100644 --- a/ogg_pager/CHANGELOG.md +++ b/ogg_pager/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.2] - 2026-05-15 + +### Fixed +- Fixed packet offset calculation in `Packets::{get,set}` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/657)) + ## [0.7.1] - 2026-02-08 ### Fixed diff --git a/ogg_pager/Cargo.toml b/ogg_pager/Cargo.toml index 9807f4372..ca323211f 100644 --- a/ogg_pager/Cargo.toml +++ b/ogg_pager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ogg_pager" -version = "0.7.1" +version = "0.7.2" authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"] description = "A simple OGG page reader" keywords = ["ogg", "xiph"] From e160fd59550c225753fced44c190cb278f12daa2 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Fri, 15 May 2026 07:31:17 -0400 Subject: [PATCH 7/7] misc: add PR to changelog --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04f6f3c3d..d59b89d46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Timestamp**: Support dot-separated dates (e.g. `2024.06.03`) ([issue](https://github.com/Serial-ATA/lofty-rs/issues/647)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/648)) - **ID3v2**: - Fixed UTF-16 description string termination in `APIC` and `SYLT` frames ([issue](https://github.com/Serial-ATA/lofty-rs/issues/653)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/654)) - - Fixed `Id3v2Tag::remove_disk_total()`, which incorrectly preserved the track number rather than disk number ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) - - Fixed handling of encryption method symbols when writing ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) - - Fixed parsing of extended headers in ID3v2.3 ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) + - Fixed `Id3v2Tag::remove_disk_total()`, which incorrectly preserved the track number rather than disk number ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/657)) + - Fixed handling of encryption method symbols when writing ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/657)) + - Fixed parsing of extended headers in ID3v2.3 ([issue](https://github.com/Serial-ATA/lofty-rs/issues/656)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/657)) ## [0.24.0] - 2026-04-12