Merge remote-tracking branch 'origin/archive/local-work-pre-movie-sync-2026-07-20'
# Conflicts: # crates/sylpheed-viewer/src/iso_loader.rs
This commit is contained in:
@@ -103,6 +103,21 @@ enum Commands {
|
||||
#[command(subcommand)]
|
||||
cmd: MeshCommands,
|
||||
},
|
||||
|
||||
/// Audio tools (identify WAV/XMA/XMA2 + metadata)
|
||||
Audio {
|
||||
#[command(subcommand)]
|
||||
cmd: AudioCommands,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum AudioCommands {
|
||||
/// Identify an audio file/stream and print its metadata
|
||||
Info {
|
||||
/// Path to a WAV / XMA / raw audio stream
|
||||
file: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -219,9 +234,41 @@ async fn main() -> Result<()> {
|
||||
cmd_pak_textures(&pak, &output, verbose)
|
||||
}
|
||||
},
|
||||
Commands::Audio { cmd } => match cmd {
|
||||
AudioCommands::Info { file } => cmd_audio_info(&file),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ── audio info ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn cmd_audio_info(file: &Path) -> Result<()> {
|
||||
use sylpheed_formats::AudioInfo;
|
||||
let bytes = std::fs::read(file).with_context(|| format!("Cannot read {}", file.display()))?;
|
||||
let info = AudioInfo::probe(&bytes);
|
||||
|
||||
println!("{} {}", "Audio:".green().bold(), file.display());
|
||||
println!(" Codec : {}", info.codec.label().yellow());
|
||||
let opt = |v: Option<String>| v.unwrap_or_else(|| "—".dimmed().to_string());
|
||||
println!(" Channels : {}", opt(info.channels.map(|c| c.to_string())));
|
||||
println!(" Sample rate: {}", opt(info.sample_rate.map(|r| format!("{r} Hz"))));
|
||||
println!(" Bit depth : {}", opt(info.bits_per_sample.map(|b| format!("{b}-bit"))));
|
||||
if let Some(d) = info.duration_secs {
|
||||
println!(" Duration : {d:.2} s");
|
||||
}
|
||||
if let Some(p) = info.xma_packets {
|
||||
println!(" XMA packets: {} (2048 B each)", p.to_string().yellow());
|
||||
}
|
||||
println!(" Size : {} bytes", info.size_bytes.to_string().yellow());
|
||||
if info.codec.needs_decoder() {
|
||||
println!(
|
||||
" {} decode not supported (needs an XMA2 decoder + the sound-bank descriptor)",
|
||||
"note:".dimmed()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── extract ────────────────────────────────────────────────────────────────
|
||||
|
||||
async fn cmd_extract(iso_path: &Path, output_dir: &Path) -> Result<()> {
|
||||
@@ -376,9 +423,16 @@ fn cmd_texture_info(file: &Path) -> Result<()> {
|
||||
let tex = X360Texture::from_xpr2(&bytes)
|
||||
.with_context(|| format!("Failed to parse texture: {}", file.display()))?;
|
||||
|
||||
let d = tex.format.desc();
|
||||
println!("{} {}", "Texture:".green().bold(), file.display());
|
||||
println!(" Resolution : {}×{}", tex.width.to_string().yellow(), tex.height.to_string().yellow());
|
||||
println!(" Format : {:?}", tex.format);
|
||||
println!(
|
||||
" Format : {} ({:?}) · {} bpp, {}",
|
||||
tex.format.gpu_name().yellow(),
|
||||
tex.format,
|
||||
d.bpp,
|
||||
if d.compressed { "compressed" } else { "uncompressed" },
|
||||
);
|
||||
println!(" Mip levels : {}", tex.mip_levels);
|
||||
println!(" Data size : {} bytes", tex.data.len().to_string().yellow());
|
||||
|
||||
@@ -913,24 +967,6 @@ fn decode_to_rgba8(tex: &sylpheed_formats::texture::X360Texture) -> Result<Vec<u
|
||||
|
||||
use sylpheed_formats::pak::inner_format_label as inner_label;
|
||||
|
||||
/// Try to recover an IDXD entry's original TOC path from its identity tokens.
|
||||
/// Uses the entry's ID/Name/Model fields plus identifier-like pool tokens as
|
||||
/// candidates for [`sylpheed_formats::hash::recover_toc_name`].
|
||||
fn idxd_toc_name(obj: &IdxdObject, name_hash: u32) -> Option<String> {
|
||||
let mut cands: Vec<&str> = Vec::new();
|
||||
for key in ["ID", "Name", "Model"] {
|
||||
if let Some(v) = obj.get_raw(key) {
|
||||
cands.push(v);
|
||||
}
|
||||
}
|
||||
for t in obj.tokens() {
|
||||
if t.contains('_') || t.len() >= 5 {
|
||||
cands.push(t.as_str());
|
||||
}
|
||||
}
|
||||
sylpheed_formats::hash::recover_toc_name(name_hash, &cands)
|
||||
}
|
||||
|
||||
fn cmd_pak_list(pak: &Path, idxd_only: bool) -> Result<()> {
|
||||
let arc = PakArchive::open(pak).with_context(|| format!("opening {}", pak.display()))?;
|
||||
println!(
|
||||
@@ -958,7 +994,7 @@ fn cmd_pak_list(pak: &Path, idxd_only: bool) -> Result<()> {
|
||||
}
|
||||
let (detail, name) = if is_idxd {
|
||||
match IdxdObject::parse(&payload) {
|
||||
Ok(o) => (o.identity(), idxd_toc_name(&o, e.name_hash)),
|
||||
Ok(o) => (o.identity(), o.recover_toc_path(e.name_hash)),
|
||||
Err(_) => (String::new(), None),
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user