collective

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

diff --git a/crates/audio-io/src/wav.rs b/crates/audio-io/src/wav.rs--- a/crates/audio-io/src/wav.rs+++ b/crates/audio-io/src/wav.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); }diff --git a/crates/audio-io/tests/it/roundtrip.rs b/crates/audio-io/tests/it/roundtrip.rs--- a/crates/audio-io/tests/it/roundtrip.rs+++ b/crates/audio-io/tests/it/roundtrip.rs@@ -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));+}