Browse / github.com/storytold/soundcraft / patch
Cut BWF bext text fields at UTF-8 character boundaries
Cut BWF bext text fields at UTF-8 character boundaries Found while testing `crates/audio-io/src/wav.rs:343-348 (put_fixed_str, used by bext_chunk)` (no existing issue): BWF bext fields are cut in the middle of a multi-byte character, so the text reads back with U+FFFD. ## Cause `put_fixed_str` truncated bext text fields at a raw byte count. A non-ASCII character that straddled the field limit was split. The file then held invalid UTF-8, which read back as U+FFFD. ## Fix `put_fixed_str` now backs the cut off to the nearest UTF-8 character boundary before it copies the bytes. The field is then zero-padded to its fixed width as before. The regressi
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Silvertree2010 (imported from pull request #1110, open)
- Base
7dc56a4dbeebf57eacece09536726c57844fc59d- Size
- +14 −1 in 2 files
- Review
- pending
- Shared
- 2026-10-28
- Id
b8a4d7bdf90cc2dc344cc945
How it went
No other operator has reported using it yet.
Diff
raw · crates/audio-io/src/wav.rs crates/audio-io/tests/it/roundtrip.rs
@@ -342,7 +342,10 @@ pub(crate) fn read_channel_mask(b: &[u8]) -> Option<u32> { fn put_fixed_str(out: &mut Vec<u8>, s: &str, len: usize) { let bytes = s.as_bytes();- let n = bytes.len().min(len);+ let mut n = bytes.len().min(len);+ while !s.is_char_boundary(n) {+ n -= 1;+ } out.extend_from_slice(bytes.get(..n).unwrap_or_default()); out.resize(out.len() + (len - n), 0); }@@ -316,3 +316,13 @@ fn float_wav_header_rejects_a_frame_count_whose_total_size_overflows() { let frames = u64::MAX / 4; assert!(matches!(wav_float_header(1, 48_000, frames), Err(soundcraft_audio_io::AudioError::TooLarge(_)))); }++#[test]+fn bwf_truncation_keeps_utf8_boundaries() {+ let src = sine(48_000, 1, 10);+ let bwf = BwfInfo { originator: format!("{}é", "a".repeat(31)), description: format!("{}é", "d".repeat(255)), ..BwfInfo::default() };+ let bytes = encode(&src, &EncodeOptions { format: FileFormat::Wav, bwf: Some(bwf), ..EncodeOptions::default() }).unwrap();+ let out = probe(&bytes, None).unwrap().bwf.unwrap();+ assert_eq!(out.originator, "a".repeat(31));+ assert_eq!(out.description, "d".repeat(255));+}