use std::env; use std::fs::File; use std::io::prelude::*; use std::io::{self, BufReader}; use std::path::Path; use mp4::{Error, Mp4Track, Result, TrackType}; fn main() { let args: Vec = env::args().collect(); if args.len() < 2 { println!("Usage: mp4info "); std::process::exit(1); } if let Err(err) = info(&args[1]) { let _ = writeln!(io::stderr(), "{}", err); } } fn info>(filename: &P) -> Result<()> { let f = File::open(filename)?; let size = f.metadata()?.len(); let reader = BufReader::new(f); let mp4 = mp4::Mp4Reader::read_header(reader, size)?; println!("File:"); println!(" file size: {}", mp4.size()); println!(" major_brand: {}", mp4.major_brand()); let mut compatible_brands = String::new(); for brand in mp4.compatible_brands().iter() { compatible_brands.push_str(&brand.to_string()); compatible_brands.push(' '); } println!(" compatible_brands: {}\n", compatible_brands); println!("Movie:"); println!(" version: {}", mp4.moov.mvhd.version); println!( " creation time: {}", creation_time(mp4.moov.mvhd.creation_time) ); println!(" duration: {:?}", mp4.duration()); println!(" fragments: {:?}", mp4.is_fragmented()); println!(" timescale: {:?}\n", mp4.timescale()); println!("Found {} Tracks", mp4.tracks().len()); for track in mp4.tracks().values() { let media_info = match track.track_type()? { TrackType::Video => video_info(track), TrackType::Audio => audio_info(track), TrackType::Subtitle => subtitle_info(track), }; println!( " Track: #{}({}) {}: {}", track.track_id(), track.language(), track.track_type()?, media_info.unwrap_or_else(|e| e.to_string()) ); } Ok(()) } fn video_info(track: &Mp4Track) -> Result { if track.trak.mdia.minf.stbl.stsd.avc1.is_some() { Ok(format!( "{} ({}) ({:?}), {}x{}, {} kb/s, {:.2} fps", track.media_type()?, track.video_profile()?, track.box_type()?, track.width(), track.height(), track.bitrate() / 1000, track.frame_rate() )) } else { Ok(format!( "{} ({:?}), {}x{}, {} kb/s, {:.2} fps", track.media_type()?, track.box_type()?, track.width(), track.height(), track.bitrate() / 1000, track.frame_rate() )) } } fn audio_info(track: &Mp4Track) -> Result { if let Some(ref mp4a) = track.trak.mdia.minf.stbl.stsd.mp4a { if mp4a.esds.is_some() { let profile = match track.audio_profile() { Ok(val) => val.to_string(), _ => "-".to_string(), }; let channel_config = match track.channel_config() { Ok(val) => val.to_string(), _ => "-".to_string(), }; Ok(format!( "{} ({}) ({:?}), {} Hz, {}, {} kb/s", track.media_type()?, profile, track.box_type()?, track.sample_freq_index()?.freq(), channel_config, track.bitrate() / 1000 )) } else { Ok(format!( "{} ({:?}), {} kb/s", track.media_type()?, track.box_type()?, track.bitrate() / 1000 )) } } else { Err(Error::InvalidData("mp4a box not found")) } } fn subtitle_info(track: &Mp4Track) -> Result { if track.trak.mdia.minf.stbl.stsd.tx3g.is_some() { Ok(format!("{} ({:?})", track.media_type()?, track.box_type()?,)) } else { Err(Error::InvalidData("tx3g box not found")) } } fn creation_time(creation_time: u64) -> u64 { // convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01) if creation_time >= 2082844800 { creation_time - 2082844800 } else { creation_time } }