From f766084b31682288d90d9aa95b7612de10a80546 Mon Sep 17 00:00:00 2001 From: Cui Hao Date: Fri, 26 Jun 2026 20:29:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(media):=20=E6=8B=92=E7=BB=9D=E6=97=A0?= =?UTF-8?q?=E9=9F=B3=E9=A2=91=E8=BD=A8=E6=96=87=E4=BB=B6=20+=20=E6=B8=85?= =?UTF-8?q?=E7=90=86=E4=B8=AD=E6=AF=92=E6=B3=A2=E5=BD=A2=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/opentake-media/src/decode/pcm.rs | 6 ++++ crates/opentake-media/src/waveform/store.rs | 25 +++++++++++++++++ .../tests/ffmpeg_integration.rs | 28 +++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/crates/opentake-media/src/decode/pcm.rs b/crates/opentake-media/src/decode/pcm.rs index 2820a3f..4bba61c 100644 --- a/crates/opentake-media/src/decode/pcm.rs +++ b/crates/opentake-media/src/decode/pcm.rs @@ -147,6 +147,12 @@ pub fn extract_pcm(path: &Path, spec: &PcmSpec, range: Option<(f64, f64)>) -> Re if !status.success() && raw.is_empty() { return Err(MediaError::no_track("audio", path)); } + // ffmpeg can exit 0 with empty stdout when metadata says audio exists but + // no decodable samples: treat as no audio track so the waveform cache + // isn't poisoned with all-1.0 silence. + if raw.is_empty() { + return Err(MediaError::no_track("audio", path)); + } let samples = raw_to_mono_f32(&raw, spec); Ok(PcmBuffer { diff --git a/crates/opentake-media/src/waveform/store.rs b/crates/opentake-media/src/waveform/store.rs index 8b86562..b140351 100644 --- a/crates/opentake-media/src/waveform/store.rs +++ b/crates/opentake-media/src/waveform/store.rs @@ -34,6 +34,11 @@ pub fn load_waveform(cache_root: &Path, key: &str) -> Option> { while let Ok(v) = cursor.read_f32::() { out.push(v); } + // Reject poison files: every bucket == 1.0 silence is what an older build + // cached when extract_pcm returned an empty Vec. Force regeneration. + if !out.is_empty() && out.iter().all(|&v| v == 1.0) { + return None; + } Some(out) } @@ -97,6 +102,26 @@ mod tests { assert!(load_waveform(dir.path(), "bad").is_none()); } + #[test] + fn load_all_ones_poison_cache_is_none() { + // An older build cached all-1.0 buckets when extract_pcm returned an + // empty Vec for a "silent" file; treat such a file as poison so the + // waveform is regenerated instead of rendered as a flat green band. + let dir = tempfile::tempdir().unwrap(); + save_waveform(dir.path(), "poison", &[1.0f32; 8]).unwrap(); + assert!(load_waveform(dir.path(), "poison").is_none()); + } + + #[test] + fn load_mixed_values_with_some_ones_is_kept() { + // A real waveform whose peaks happen to reach 1.0 must NOT be discarded; + // only an all-1.0 file is poison. + let dir = tempfile::tempdir().unwrap(); + let samples = vec![1.0f32, 0.5, 1.0, 0.0]; + save_waveform(dir.path(), "real", &samples).unwrap(); + assert_eq!(load_waveform(dir.path(), "real").unwrap(), samples); + } + #[test] fn little_endian_byte_layout_is_fixed() { let dir = tempfile::tempdir().unwrap(); diff --git a/crates/opentake-media/tests/ffmpeg_integration.rs b/crates/opentake-media/tests/ffmpeg_integration.rs index b6649dc..b41b8e5 100644 --- a/crates/opentake-media/tests/ffmpeg_integration.rs +++ b/crates/opentake-media/tests/ffmpeg_integration.rs @@ -216,6 +216,34 @@ fn extract_pcm_no_audio_track_errors() { ); } +#[test] +fn extract_pcm_empty_stdout_errors_not_silent_buffer() { + // Regression for the "ffmpeg exits 0 but emits no PCM" case: a range window + // entirely past the media end seeks past EOF, so ffmpeg succeeds yet writes + // zero bytes. extract_pcm must surface NoTrack rather than return an empty + // buffer — an empty buffer downstream poisons the waveform cache with an + // all-1.0 flat band. + if !ffmpeg_available() { + return; + } + let dir = tempfile::tempdir().unwrap(); + let av = dir.path().join("av.mp4"); + if !make_av(&av) { + return; + } + let spec = PcmSpec { + sample_rate: 16_000, + channels: 1, + format: PcmFormat::F32, + }; + // The clip is ~2 s long; ask for [5.0, 6.0) → past EOF → empty stdout. + let res = extract_pcm(&av, &spec, Some((5.0, 6.0))); + assert!( + res.is_err(), + "expected an error for an empty PCM window, not a silent buffer" + ); +} + #[test] fn waveform_has_expected_bucket_count() { if !ffmpeg_available() {