A human play-test heard music under the boot intro and no voices. The obvious reading -- the 5.1 fold dropped the centre channel -- is wrong. `ADV.wmv` carries music and effects only; a cutscene's voice is a separate continuous XMA stream in `sound.pak`, bound to the movie by the manifest in `tables.pak`. Nothing was dropped. The exporter had never been asked for it, so every fidelity measurement in AUDIO-VERIFICATION.md would have come back clean. `audio::export_voice` resolves it with `media::resolve_movie_voice_region` and never by filename: `RT01A`'s voice lives inside `VOICE_ADV.slb`, so a name match is correct on exactly the two movies this port would have spot-checked. Decoded, not authored -- so it runs outside the `authored/audio.json` block. THE FIRST VERSION CONCATENATED THE REGION'S CHUNKS AND WAS WRONG. It produced 359 s of dialogue for a 137 s movie. Decoding and timing each chunk shows two of them equal to six decimals and each spanning the whole movie -- HANDOFF Q10's decoded two-stem shape on a second asset kind -- so they are summed at 1/n. The error was visible only because the first version recorded the decoded length against the movie's instead of clamping to it; the clamp `media`'s own doc comment invites, and which `sylpheed-viewer` applies, would have produced a file of exactly the right duration containing the wrong audio. The dropped leading chunk matches no duration in its region and is NOT closed here. It is the same signature as `BGM_103`'s third sub-wave, already open in BLOCKED.md, now corroborated on an independent asset kind. Raised with the Decoder; the manifest names every chunk dropped and its length. Also in this commit, and separable: * `--skip-at=SECONDS` -- `--script` structurally cannot press during a movie, because `_script_settled` waits while `_player != null`. That is why "does (A) skip the intro" had been read out of the source rather than measured. * MISSION section 6 pins a 5.1->stereo matrix and this exporter has shipped a different one since P4 -- the same weighting, 7.65 dB quieter -- and said so nowhere. Re-measured with the right instrument (float decode, whole file, count the samples that would clamp, not a peak reading): the pinned matrix puts ADV at +4.26 dBFS on 4406 samples, while S00A never clips. So the pin overloads one movie and the constant is over-broad for the other. NOT changed -- the level of a mix is what section 6 reserves to a human. The export now carries a warning with the numbers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
42 lines
1.9 KiB
Rust
42 lines
1.9 KiB
Rust
//! Throwaway probe: how long is each region chunk of a movie's voice?
|
|
//!
|
|
//! The question it answers is whether the chunks of a resolved voice region are
|
|
//! CONSECUTIVE SEGMENTS (concatenate them) or ALTERNATE TAKES (chunk 0 is the
|
|
//! whole track). Getting that backwards plays the dialogue three times over.
|
|
use std::process::Command;
|
|
use sylpheed_formats::{media, slb::VoiceLang};
|
|
|
|
fn main() {
|
|
let disc = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into());
|
|
let src = media::DirectorySource::new(&disc);
|
|
for movie in ["ADV", "S00A", "RT01A"] {
|
|
let Some((s, e)) = media::resolve_movie_voice_region(&src, movie, VoiceLang::English)
|
|
else {
|
|
println!("{movie}: no region");
|
|
continue;
|
|
};
|
|
let riffs = media::voice_region_riffs(&src, s, e).expect("riffs");
|
|
println!("{movie}: region [{s}, {e}) = {} bytes, {} chunk(s)", e - s, riffs.len());
|
|
for (i, r) in riffs.iter().enumerate() {
|
|
let p = std::env::temp_dir().join(format!("vc_{movie}_{i}.xma.wav"));
|
|
std::fs::write(&p, r).unwrap();
|
|
// XMA declares no duration, so DECODE it and measure the result.
|
|
let w = std::env::temp_dir().join(format!("vc_{movie}_{i}.wav"));
|
|
let _ = Command::new("ffmpeg")
|
|
.args(["-hide_banner", "-loglevel", "error", "-y", "-i"])
|
|
.arg(&p)
|
|
.arg(&w)
|
|
.output();
|
|
let out = Command::new("ffprobe")
|
|
.args(["-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0"])
|
|
.arg(&w)
|
|
.output()
|
|
.unwrap();
|
|
let dur = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
|
let _ = std::fs::remove_file(&w);
|
|
println!(" chunk {i}: {} bytes -> {dur} s", r.len());
|
|
let _ = std::fs::remove_file(&p);
|
|
}
|
|
}
|
|
}
|