[WIP] Audio codec probe/detection + CLI audio-info + viewer audio panel
Snapshot of local WIP before rebasing onto the movie/voice remote work. - audio.rs: AudioCodec enum, AudioInfo::probe (RIFF/WAVE parse, raw-XMA2 Shannon-entropy heuristic looks_like_raw_xma2), from_wav, riff_data_span. - cli/main.rs: `audio-info` subcommand (cmd_audio_info). - viewer ui.rs/iso_loader.rs: draw_audio_detail panel wiring. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -165,6 +165,8 @@ pub enum PakContent {
|
||||
Ratc(Vec<RatcEntry>),
|
||||
/// Plain-text / XML payload, with its encoding label.
|
||||
Text { text: String, encoding: String },
|
||||
/// An audio stream (WAV / XMA / raw XMA2) — metadata only; XMA isn't decoded.
|
||||
Audio(sylpheed_formats::AudioInfo),
|
||||
}
|
||||
|
||||
/// One child in a presented RATC bundle.
|
||||
@@ -978,6 +980,12 @@ fn classify_content(payload: &[u8]) -> PakContent {
|
||||
encoding: encoding.to_string(),
|
||||
};
|
||||
}
|
||||
// Last resort: a self-describing WAV/XMA header, or a header-less high-
|
||||
// entropy blob (the game's raw XMA2 sound-bank streams).
|
||||
let audio = sylpheed_formats::AudioInfo::probe(payload);
|
||||
if audio.codec != sylpheed_formats::AudioCodec::Unknown {
|
||||
return PakContent::Audio(audio);
|
||||
}
|
||||
PakContent::None
|
||||
}
|
||||
|
||||
|
||||
@@ -506,6 +506,7 @@ fn draw_pak_browser(ui: &mut egui::Ui, pak: &mut PakView) {
|
||||
PakContent::Text { text, encoding } => {
|
||||
draw_text_detail(ui, row, text, encoding)
|
||||
}
|
||||
PakContent::Audio(info) => draw_audio_detail(ui, row, info),
|
||||
PakContent::None => draw_idxd_detail(ui, row),
|
||||
},
|
||||
});
|
||||
@@ -531,6 +532,10 @@ fn row_kind(row: &crate::iso_loader::PakRow) -> String {
|
||||
PakContent::Lsta(f) => format!("LSTA · {} sprite(s)", f.len()),
|
||||
PakContent::Ratc(c) => format!("RATC · {} item(s)", c.len()),
|
||||
PakContent::Text { .. } => "text".into(),
|
||||
PakContent::Audio(a) => match a.xma_packets {
|
||||
Some(p) => format!("audio · {} · {p} pkts", a.codec.label()),
|
||||
None => format!("audio · {}", a.codec.label()),
|
||||
},
|
||||
PakContent::None => row.identity.clone(),
|
||||
}
|
||||
}
|
||||
@@ -836,6 +841,59 @@ fn draw_text_detail(
|
||||
);
|
||||
}
|
||||
|
||||
/// An audio stream entry: codec + whatever metadata is derivable. The game's
|
||||
/// `sound.pak` streams are raw XMA2 (no embedded channels/rate), so most fields
|
||||
/// read "—" until the sound-bank descriptor + an XMA2 decoder are added.
|
||||
fn draw_audio_detail(
|
||||
ui: &mut egui::Ui,
|
||||
row: &crate::iso_loader::PakRow,
|
||||
info: &sylpheed_formats::AudioInfo,
|
||||
) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.heading("Audio stream");
|
||||
ui.separator();
|
||||
ui.label(info.codec.label());
|
||||
ui.separator();
|
||||
ui.label(format!("hash {:08x}", row.hash));
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
let dash = "—".to_string();
|
||||
egui::Grid::new("audio_meta").striped(true).num_columns(2).show(ui, |ui| {
|
||||
ui.strong("Codec");
|
||||
ui.label(info.codec.label());
|
||||
ui.end_row();
|
||||
ui.strong("Channels");
|
||||
ui.label(info.channels.map(|c| c.to_string()).unwrap_or_else(|| dash.clone()));
|
||||
ui.end_row();
|
||||
ui.strong("Sample rate");
|
||||
ui.label(info.sample_rate.map(|r| format!("{r} Hz")).unwrap_or_else(|| dash.clone()));
|
||||
ui.end_row();
|
||||
if let Some(d) = info.duration_secs {
|
||||
ui.strong("Duration");
|
||||
ui.label(format!("{d:.2} s"));
|
||||
ui.end_row();
|
||||
}
|
||||
if let Some(p) = info.xma_packets {
|
||||
ui.strong("XMA packets");
|
||||
ui.label(format!("{p} (2048 B each)"));
|
||||
ui.end_row();
|
||||
}
|
||||
ui.strong("Size");
|
||||
ui.label(format!("{} bytes", info.size_bytes));
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
if info.codec.needs_decoder() {
|
||||
ui.separator();
|
||||
ui.colored_label(
|
||||
egui::Color32::from_gray(150),
|
||||
"Raw XMA2 stream — playback needs an XMA2 decoder and the (un-RE'd) \
|
||||
sound-bank descriptor for per-stream channels/sample-rate.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Video player (WMV cutscenes) ──────────────────────────────────────────────
|
||||
|
||||
/// Format seconds as `mm:ss`.
|
||||
|
||||
Reference in New Issue
Block a user