use crate::ffmpeg;
use anyhow::{bail, Context, Result};
use cavs_format::{Reader, TrackKind};
use std::fs;
use std::path::{Path, PathBuf};
pub fn unpack(input: &Path, output: &Path, combined_mp4: bool) -> Result<Vec<PathBuf>> {
let mut r = Reader::open(input).with_context(|| format!("cannot open {}", input.display()))?;
fs::create_dir_all(output)?;
let tracks = r.tracks().to_vec();
let mut primaries = Vec::new();
for track in &tracks {
match track.kind {
TrackKind::Video | TrackKind::Audio => {
let dir = output.join(&track.name);
fs::create_dir_all(&dir)?;
let init = r.track_init_bytes(track.track_id)?;
fs::write(dir.join("init.mp4"), &init)?;
let segs: Vec<_> = r
.segments_for_track(track.track_id)
.into_iter()
.cloned()
.collect();
let mut combined = if combined_mp4 {
init.clone()
} else {
Vec::new()
};
for (ordinal, seg) in segs.iter().enumerate() {
let bytes = r.segment_bytes(seg)?;
fs::write(dir.join(format!("seg_{ordinal:05}.m4s")), &bytes)?;
if combined_mp4 {
combined.extend_from_slice(&bytes);
}
}
eprintln!(
"[unpack] track {} ({}): {} segments -> {}/",
track.track_id,
track.name,
segs.len(),
dir.display()
);
if combined_mp4 {
let mp4_path = output.join(format!("{}.mp4", track.name));
fs::write(&mp4_path, &combined)?;
eprintln!("[unpack] combined mp4 -> {}", mp4_path.display());
primaries.push(mp4_path);
}
}
TrackKind::Subtitle | TrackKind::Data => {
if track.name.contains("..") || track.name.starts_with('/') {
bail!(
"track {} has an unsafe name: {}",
track.track_id,
track.name
);
}
let path = output.join(&track.name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let segs: Vec<_> = r
.segments_for_track(track.track_id)
.into_iter()
.cloned()
.collect();
let mut bytes = Vec::new();
for seg in &segs {
bytes.extend_from_slice(&r.segment_bytes(seg)?);
}
fs::write(&path, &bytes)?;
eprintln!(
"[unpack] data track {} -> {} ({} bytes)",
track.track_id,
path.display(),
bytes.len()
);
if track.codec == "raw" {
primaries.push(path);
}
}
}
}
Ok(primaries)
}
pub fn play(input: &Path) -> Result<()> {
let tmp = tempfile::tempdir().context("cannot create temp dir")?;
let primaries = unpack(input, tmp.path(), true)?;
let Some(target) = primaries.first() else {
bail!("no playable track found in {}", input.display());
};
eprintln!("[play] launching ffplay on {}", target.display());
ffmpeg::play_file(target)
}