2020-01-13 03:33:26 +00:00
|
|
|
use std::env;
|
|
|
|
use std::fs::File;
|
2020-07-31 17:12:26 +00:00
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::io::{self, BufReader};
|
|
|
|
use std::path::Path;
|
|
|
|
|
2022-04-21 01:43:18 +00:00
|
|
|
use mp4::{Error, Mp4Track, Result, TrackType};
|
2020-01-13 03:33:26 +00:00
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let args: Vec<String> = env::args().collect();
|
|
|
|
|
2020-07-31 17:12:26 +00:00
|
|
|
if args.len() < 2 {
|
|
|
|
println!("Usage: mp4info <filename>");
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Err(err) = info(&args[1]) {
|
|
|
|
let _ = writeln!(io::stderr(), "{}", err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|
|
|
let f = File::open(filename)?;
|
|
|
|
let size = f.metadata()?.len();
|
|
|
|
let reader = BufReader::new(f);
|
|
|
|
|
2020-08-04 23:56:59 +00:00
|
|
|
let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
2020-07-31 17:12:26 +00:00
|
|
|
|
2020-08-23 20:38:25 +00:00
|
|
|
println!("File:");
|
|
|
|
println!(" file size: {}", mp4.size());
|
|
|
|
println!(" major_brand: {}", mp4.major_brand());
|
2020-08-04 23:56:59 +00:00
|
|
|
let mut compatible_brands = String::new();
|
|
|
|
for brand in mp4.compatible_brands().iter() {
|
|
|
|
compatible_brands.push_str(&brand.to_string());
|
2022-06-01 03:50:49 +00:00
|
|
|
compatible_brands.push(' ');
|
2020-01-13 03:33:26 +00:00
|
|
|
}
|
2020-08-23 20:38:25 +00:00
|
|
|
println!(" compatible_brands: {}\n", compatible_brands);
|
2020-08-04 23:56:59 +00:00
|
|
|
|
2020-08-23 20:38:25 +00:00
|
|
|
println!("Movie:");
|
|
|
|
println!(" version: {}", mp4.moov.mvhd.version);
|
2022-04-21 01:43:18 +00:00
|
|
|
println!(
|
|
|
|
" creation time: {}",
|
|
|
|
creation_time(mp4.moov.mvhd.creation_time)
|
|
|
|
);
|
2020-08-23 20:38:25 +00:00
|
|
|
println!(" duration: {:?}", mp4.duration());
|
2020-09-04 04:33:45 +00:00
|
|
|
println!(" fragments: {:?}", mp4.is_fragmented());
|
2020-08-23 20:38:25 +00:00
|
|
|
println!(" timescale: {:?}\n", mp4.timescale());
|
|
|
|
|
|
|
|
println!("Found {} Tracks", mp4.tracks().len());
|
2021-07-30 02:57:15 +00:00
|
|
|
for track in mp4.tracks().values() {
|
2020-08-04 23:56:59 +00:00
|
|
|
let media_info = match track.track_type()? {
|
2022-04-21 01:43:18 +00:00
|
|
|
TrackType::Video => video_info(track),
|
|
|
|
TrackType::Audio => audio_info(track),
|
|
|
|
TrackType::Subtitle => subtitle_info(track),
|
2020-08-04 23:56:59 +00:00
|
|
|
};
|
2022-04-21 01:43:18 +00:00
|
|
|
|
2020-08-04 23:56:59 +00:00
|
|
|
println!(
|
|
|
|
" Track: #{}({}) {}: {}",
|
|
|
|
track.track_id(),
|
|
|
|
track.language(),
|
|
|
|
track.track_type()?,
|
2022-04-21 01:43:18 +00:00
|
|
|
media_info.unwrap_or_else(|e| e.to_string())
|
2020-08-04 23:56:59 +00:00
|
|
|
);
|
2020-01-22 05:41:51 +00:00
|
|
|
}
|
2020-08-04 23:56:59 +00:00
|
|
|
Ok(())
|
2020-01-28 05:32:53 +00:00
|
|
|
}
|
|
|
|
|
2020-08-04 23:56:59 +00:00
|
|
|
fn video_info(track: &Mp4Track) -> Result<String> {
|
2020-09-02 03:49:55 +00:00
|
|
|
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,
|
2020-09-04 04:33:45 +00:00
|
|
|
track.frame_rate()
|
2020-09-02 03:49:55 +00:00
|
|
|
))
|
|
|
|
} else {
|
|
|
|
Ok(format!(
|
|
|
|
"{} ({:?}), {}x{}, {} kb/s, {:.2} fps",
|
|
|
|
track.media_type()?,
|
|
|
|
track.box_type()?,
|
|
|
|
track.width(),
|
|
|
|
track.height(),
|
|
|
|
track.bitrate() / 1000,
|
2020-09-04 04:33:45 +00:00
|
|
|
track.frame_rate()
|
2020-09-02 03:49:55 +00:00
|
|
|
))
|
|
|
|
}
|
2020-06-05 06:10:33 +00:00
|
|
|
}
|
|
|
|
|
2020-08-04 23:56:59 +00:00
|
|
|
fn audio_info(track: &Mp4Track) -> Result<String> {
|
2020-08-23 20:38:25 +00:00
|
|
|
if let Some(ref mp4a) = track.trak.mdia.minf.stbl.stsd.mp4a {
|
|
|
|
if mp4a.esds.is_some() {
|
2020-09-05 06:09:33 +00:00
|
|
|
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(),
|
|
|
|
};
|
|
|
|
|
2020-08-23 20:38:25 +00:00
|
|
|
Ok(format!(
|
|
|
|
"{} ({}) ({:?}), {} Hz, {}, {} kb/s",
|
|
|
|
track.media_type()?,
|
2020-09-05 06:09:33 +00:00
|
|
|
profile,
|
2020-08-23 20:38:25 +00:00
|
|
|
track.box_type()?,
|
|
|
|
track.sample_freq_index()?.freq(),
|
2020-09-05 06:09:33 +00:00
|
|
|
channel_config,
|
2020-08-23 20:38:25 +00:00
|
|
|
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"))
|
|
|
|
}
|
2020-06-05 06:10:33 +00:00
|
|
|
}
|
2020-08-23 20:38:25 +00:00
|
|
|
|
2020-09-05 06:09:33 +00:00
|
|
|
fn subtitle_info(track: &Mp4Track) -> Result<String> {
|
|
|
|
if track.trak.mdia.minf.stbl.stsd.tx3g.is_some() {
|
2022-04-21 01:43:18 +00:00
|
|
|
Ok(format!("{} ({:?})", track.media_type()?, track.box_type()?,))
|
2020-09-05 06:09:33 +00:00
|
|
|
} else {
|
2020-09-05 20:15:31 +00:00
|
|
|
Err(Error::InvalidData("tx3g box not found"))
|
2020-09-05 06:09:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-23 20:38:25 +00:00
|
|
|
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
|
|
|
|
}
|
2022-04-21 01:43:18 +00:00
|
|
|
}
|