Skip to content
Merged
16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ path = "src/lib.rs"
name = "mp4dump"
path = "src/bin/mp4dump.rs"

[[bin]]
name = "mp4info"
path = "src/bin/mp4info.rs"

[[bin]]
name = "mp4samples"
path = "src/bin/mp4samples.rs"

[[example]]
name = "simple"
path = "examples/simple.rs"

[[example]]
name = "boxes"
path = "examples/boxes.rs"

[dependencies]
anyhow = "1.0"
byteorder = "1.5"
Expand Down
143 changes: 143 additions & 0 deletions examples/samples.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use mp4box::{BoxValue, StructuredData, get_boxes};
use std::fs::File;

fn main() -> anyhow::Result<()> {
// Check if a file path is provided
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} <mp4_file>", args[0]);
std::process::exit(1);
}

let path = &args[1];
let mut file = File::open(path)?;
let size = file.metadata()?.len();

// Parse with decoding enabled to get structured data
let boxes = get_boxes(&mut file, size, true)?;

println!("Analyzing sample tables in: {}", path);
analyze_sample_tables(&boxes, 0);

// Also test the direct parsing example
println!("\nTesting direct parsing example:");
example_direct_parsing()?;

Ok(())
}

fn analyze_sample_tables(boxes: &[mp4box::Box], depth: usize) {
let indent = " ".repeat(depth);

for box_info in boxes {
// Look for sample table boxes
if let Some(decoded) = &box_info.decoded {
match box_info.typ.as_str() {
"stts" => {
println!("{}📊 Decoding Time-to-Sample Box (stts):", indent);
if decoded.starts_with("structured:") {
println!("{} Contains structured sample timing data", indent);
// In practice, you would parse the structured data here
// For now we show it's working with structured output
}
}
"stsc" => {
println!("{}🗂️ Sample-to-Chunk Box (stsc):", indent);
if decoded.starts_with("structured:") {
println!("{} Contains structured chunk mapping data", indent);
}
}
"stsz" => {
println!("{}📏 Sample Size Box (stsz):", indent);
if decoded.starts_with("structured:") {
println!("{} Contains structured sample size data", indent);
}
}
"stco" => {
println!("{}📍 Chunk Offset Box (stco):", indent);
if decoded.starts_with("structured:") {
println!("{} Contains structured chunk offset data", indent);
}
}
"co64" => {
println!("{}📍 64-bit Chunk Offset Box (co64):", indent);
if decoded.starts_with("structured:") {
println!("{} Contains structured 64-bit chunk offset data", indent);
}
}
"stss" => {
println!("{}🎯 Sync Sample Box (stss):", indent);
if decoded.starts_with("structured:") {
println!("{} Contains structured keyframe data", indent);
}
}
"ctts" => {
println!("{}⏰ Composition Time-to-Sample Box (ctts):", indent);
if decoded.starts_with("structured:") {
println!("{} Contains structured composition offset data", indent);
}
}
"stsd" => {
println!("{}🎬 Sample Description Box (stsd):", indent);
if decoded.starts_with("structured:") {
println!("{} Contains structured codec information", indent);
}
}
_ => {}
}
}

// Recurse into children
if let Some(children) = &box_info.children {
analyze_sample_tables(children, depth + 1);
}
}
}

/// Example of how you would access structured data directly from the registry
fn example_direct_parsing() -> anyhow::Result<()> {
use mp4box::boxes::{BoxHeader, FourCC};
use mp4box::registry::{BoxDecoder, SttsDecoder};
use std::io::Cursor;

// Example: Create a mock STTS box data
// Note: version/flags are handled by the main parser, decoder receives only payload
let mock_stts_data = vec![
0, 0, 0, 2, // entry_count = 2
0, 0, 0, 100, // sample_count = 100
0, 0, 4, 0, // sample_delta = 1024
0, 0, 0, 1, // sample_count = 1
0, 0, 2, 0, // sample_delta = 512
];

let mut cursor = Cursor::new(mock_stts_data);
let header = BoxHeader {
typ: FourCC(*b"stts"),
uuid: None,
size: 28, // 20 bytes data + 8 bytes header
header_size: 8,
start: 0,
};

let decoder = SttsDecoder;
let result = decoder.decode(&mut cursor, &header, Some(0), Some(0))?;

match result {
BoxValue::Structured(StructuredData::DecodingTimeToSample(stts_data)) => {
println!("Parsed STTS data:");
println!(" Version: {}", stts_data.version);
println!(" Flags: {}", stts_data.flags);
println!(" Entry count: {}", stts_data.entry_count);

for (i, entry) in stts_data.entries.iter().enumerate() {
println!(
" Entry {}: {} samples, delta {}",
i, entry.sample_count, entry.sample_delta
);
}
}
_ => println!("Unexpected result type"),
}

Ok(())
}
42 changes: 31 additions & 11 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub struct Box {
pub full_name: String,
/// Decoded box content if decode=true and decoder available
pub decoded: Option<String>,
/// Structured data if decode=true and structured decoder available
pub structured_data: Option<crate::registry::StructuredData>,
/// Child boxes for container types
pub children: Option<Vec<Box>>,
}
Expand Down Expand Up @@ -173,25 +175,42 @@ fn payload_geometry(b: &BoxRef) -> Option<(u64, u64)> {
}
}

fn decode_value<R: Read + Seek>(r: &mut R, b: &BoxRef, reg: &Registry) -> Option<String> {
let (key, off, len) = payload_region(b)?;
fn decode_value<R: Read + Seek>(
r: &mut R,
b: &BoxRef,
reg: &Registry,
) -> (Option<String>, Option<crate::registry::StructuredData>) {
let (key, off, len) = match payload_region(b) {
Some(region) => region,
None => return (None, None),
};
if len == 0 {
return None;
return (None, None);
}

if r.seek(SeekFrom::Start(off)).is_err() {
return None;
return (None, None);
}
let mut limited = r.take(len);

if let Some(res) = reg.decode(&key, &mut limited, &b.hdr) {
// Extract version and flags from the box if it's a FullBox
let (version, flags) = match &b.kind {
crate::boxes::NodeKind::FullBox { version, flags, .. } => (Some(*version), Some(*flags)),
_ => (None, None),
};

if let Some(res) = reg.decode(&key, &mut limited, &b.hdr, version, flags) {
match res {
Ok(BoxValue::Text(s)) => Some(s),
Ok(BoxValue::Bytes(bytes)) => Some(format!("{} bytes", bytes.len())),
Err(e) => Some(format!("[decode error: {}]", e)),
Ok(BoxValue::Text(s)) => (Some(s), None),
Ok(BoxValue::Bytes(bytes)) => (Some(format!("{} bytes", bytes.len())), None),
Ok(BoxValue::Structured(data)) => {
let debug_str = format!("structured: {:?}", data);
(Some(debug_str), Some(data))
}
Err(e) => (Some(format!("[decode error: {}]", e)), None),
}
} else {
None
(None, None)
}
}

Expand Down Expand Up @@ -222,10 +241,10 @@ fn build_box<R: Read + Seek>(r: &mut R, b: &BoxRef, decode: bool, reg: &Registry
}
};

let decoded = if decode {
let (decoded, structured_data) = if decode {
decode_value(r, b, reg)
} else {
None
(None, None)
};

Box {
Expand All @@ -242,6 +261,7 @@ fn build_box<R: Read + Seek>(r: &mut R, b: &BoxRef, decode: bool, reg: &Registry
kind: kind_str,
full_name,
decoded,
structured_data,
children,
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/bin/mp4dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,17 @@ fn decode_value(f: &mut File, b: &BoxRef, reg: &Registry) -> Option<String> {
}
let mut limited = f.take(len);

if let Some(res) = reg.decode(&key, &mut limited, &b.hdr) {
// Extract version and flags from the box if it's a FullBox
let (version, flags) = match &b.kind {
mp4box::boxes::NodeKind::FullBox { version, flags, .. } => (Some(*version), Some(*flags)),
_ => (None, None),
};

if let Some(res) = reg.decode(&key, &mut limited, &b.hdr, version, flags) {
match res {
Ok(BoxValue::Text(s)) => Some(s),
Ok(BoxValue::Bytes(bytes)) => Some(format!("{} bytes", bytes.len())),
Ok(BoxValue::Structured(data)) => Some(format!("structured: {:?}", data)),
Err(e) => Some(format!("[decode error: {}]", e)),
}
} else {
Expand Down
58 changes: 40 additions & 18 deletions src/bin/mp4info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,36 +186,58 @@ fn parse_trak(trak: &Box, index: usize, info: &mut MediaInfo) {
};

// mdhd: timescale / duration / language
if let Some(mdhd) = find_child(mdia, "mdhd")
&& let Some(decoded) = &mdhd.decoded
{
if let Some(ts) = parse_u32_field(decoded, "timescale=") {
ti.timescale = Some(ts);
if let Some(mdhd) = find_child(mdia, "mdhd") {
// Try structured data first
if let Some(mp4box::registry::StructuredData::MediaHeader(mdhd_data)) =
&mdhd.structured_data
{
ti.timescale = Some(mdhd_data.timescale);
ti.duration_ticks = Some(mdhd_data.duration as u64);
ti.duration_seconds = Some(mdhd_data.duration as f64 / mdhd_data.timescale as f64);
ti.language = Some(mdhd_data.language.clone());
}
if let Some(dur) = parse_u64_field(decoded, "duration=") {
ti.duration_ticks = Some(dur);
if let Some(ts) = ti.timescale {
ti.duration_seconds = Some(dur as f64 / ts as f64);
// Fallback to text parsing
else if let Some(decoded) = &mdhd.decoded {
if let Some(ts) = parse_u32_field(decoded, "timescale=") {
ti.timescale = Some(ts);
}
if let Some(dur) = parse_u64_field(decoded, "duration=") {
ti.duration_ticks = Some(dur);
if let Some(ts) = ti.timescale {
ti.duration_seconds = Some(dur as f64 / ts as f64);
}
}
if let Some(lang) = parse_string_field(decoded, "language=") {
ti.language = Some(lang);
}
}
if let Some(lang) = parse_string_field(decoded, "language=") {
ti.language = Some(lang);
}
}

// hdlr: determine track type (video/audio/other)
if let Some(hdlr) = find_child(mdia, "hdlr")
&& let Some(decoded) = &hdlr.decoded
{
// Ideally your hdlr decoder now prints "handler=vide name=..."
if let Some(handler) = parse_string_field(decoded, "handler=") {
let tt = match handler.as_str() {
if let Some(hdlr) = find_child(mdia, "hdlr") {
// Try structured data first
if let Some(mp4box::registry::StructuredData::HandlerReference(hdlr_data)) =
&hdlr.structured_data
{
let tt = match hdlr_data.handler_type.as_str() {
"vide" => "video",
"soun" => "audio",
_ => "other",
};
ti.track_type = Some(tt.to_string());
}
// Fallback to text parsing
else if let Some(decoded) = &hdlr.decoded {
// Ideally your hdlr decoder now prints "handler=vide name=..."
if let Some(handler) = parse_string_field(decoded, "handler=") {
let tt = match handler.as_str() {
"vide" => "video",
"soun" => "audio",
_ => "other",
};
ti.track_type = Some(tt.to_string());
}
}
}

// minf -> stbl -> stsd: codec + width/height from decoded text
Expand Down
Loading