mpn/
lib.rs

1#![deny(
2    missing_docs,
3    missing_debug_implementations,
4    missing_copy_implementations,
5    trivial_casts,
6    trivial_numeric_casts,
7    unsafe_code,
8    unstable_features,
9    unused_import_braces,
10    unused_qualifications,
11    unused_assignments
12)]
13#![allow(unused_assignments, unused_variables)]
14
15//! mpn main lib
16extern crate chrono;
17extern crate clap;
18extern crate filetime;
19extern crate mp4parse;
20
21use self::chrono::prelude::TimeZone;
22use clap::ArgMatches;
23use mp4parse::read_mp4;
24use mp4parse::AudioCodecSpecific;
25use mp4parse::SampleEntry;
26use mp4parse::TrackType;
27use mp4parse::VideoCodecSpecific;
28use no_color::is_no_color;
29use std::collections::HashMap;
30use std::error::Error;
31use std::fmt;
32use std::fmt::Debug;
33use std::fmt::Formatter;
34use std::fs;
35use std::fs::File;
36use std::io::{Cursor, Read};
37
38/// MEDIAFILE Argument constant
39pub const ARG_MEDIAFILE: &str = "MEDIAFILE";
40
41/// Media struct which holds file metadata
42pub struct Media {
43    /// filename
44    pub filename: String,
45    /// file creation time
46    pub creation_time: i64,
47    /// file last accessed time
48    pub last_accessed_time: i64,
49    /// file last modified time
50    pub last_modified_time: i64,
51    /// file preview in bytes
52    pub preview: [u8; 256],
53}
54
55impl Debug for Media {
56    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
57        write!(f, "{}", self.preview[0])
58    }
59}
60
61/// Media implementation
62impl Media {
63    /// constructor
64    pub fn new(filename: String) -> Result<Media, Box<dyn Error>> {
65        let preview: [u8; 256] = [0x0; 256];
66        let metadata = fs::metadata(filename.clone()).unwrap();
67        let ctime = filetime::FileTime::from_creation_time(&metadata).unwrap();
68        let mtime = filetime::FileTime::from_last_modification_time(&metadata);
69        let atime = filetime::FileTime::from_last_access_time(&metadata);
70
71        Ok(Media {
72            filename,
73            creation_time: ctime.seconds_relative_to_1970() as i64,
74            last_accessed_time: atime.seconds_relative_to_1970() as i64,
75            last_modified_time: mtime.seconds_relative_to_1970() as i64,
76            preview,
77        })
78    }
79}
80
81/// Inspect mp4 file and output box metadata.
82/// # Arguments
83/// * `matches` - Argument matches from the command line input
84pub fn run(matches: ArgMatches) -> Result<(), Box<dyn Error>> {
85    let mut colorize_outout = true;
86    if is_no_color() {
87        colorize_outout = false;
88    }
89    if let Some(file) = matches.get_one::<String>(ARG_MEDIAFILE).map(|s| s.as_str()) {
90        println!("[media]");
91        println!("uri = \"{}\"", file);
92        let mut fd = File::open(file)?;
93        let mut buf = Vec::new();
94        let size = fd.read_to_end(&mut buf)?;
95        let metadata = fs::metadata(file)?;
96        println!("bytes = {}", size);
97        if let Ok(time) = metadata.modified() {
98            println!(
99                "modified = {:?}",
100                chrono::Utc
101                    .timestamp_opt(
102                        time.duration_since(std::time::UNIX_EPOCH)
103                            .unwrap()
104                            .as_secs()
105                            .try_into()
106                            .unwrap(),
107                        0
108                    )
109                    .unwrap()
110            );
111        } else {
112            println!(
113                "modified = {:?}",
114                "\"error: not supported on this platform.\""
115            );
116        }
117        if let Ok(time) = metadata.created() {
118            println!(
119                "created = {:?}",
120                chrono::Utc
121                    .timestamp_opt(
122                        time.duration_since(std::time::UNIX_EPOCH)
123                            .unwrap()
124                            .as_secs()
125                            .try_into()
126                            .unwrap(),
127                        0
128                    )
129                    .unwrap()
130            );
131        } else {
132            println!(
133                "created = {:?}",
134                "\"error: not supported on this platform.\""
135            );
136        }
137        if let Ok(time) = metadata.accessed() {
138            println!(
139                "accessed = {:?}",
140                chrono::Utc
141                    .timestamp_opt(
142                        time.duration_since(std::time::UNIX_EPOCH)
143                            .unwrap()
144                            .as_secs()
145                            .try_into()
146                            .unwrap(),
147                        0
148                    )
149                    .unwrap()
150            );
151        } else {
152            println!(
153                "accessed = {:?}",
154                "\"error: not supported on this platform.\""
155            );
156        }
157        let mut c = Cursor::new(buf);
158        let context = read_mp4(&mut c).expect("read_mp4 failed");
159        for track in context.tracks {
160            match track.track_type {
161                // see https://docs.rs/mp4parse/latest/mp4parse/struct.Track.html
162                TrackType::Video => {
163                    println!("[media.track.video]");
164                    println!("track_id = {:?}", track.track_id.unwrap());
165                    println!("duration = {:?}", track.duration.unwrap());
166                    println!("empty_duration = \"{:?}\"", track.empty_duration.unwrap());
167                    println!("media_time = \"{:?}\"", track.media_time.unwrap()); // 1 = 64 bit creation and modification times. 0 = 64 bit creation and modification times.
168                    println!("timescale = \"{:?}\"", track.timescale.unwrap());
169
170                    let thb = track.tkhd.unwrap(); // TrackHeaderBox
171                    println!("[media.track.video.header]");
172                    println!("disabled = {:?}", thb.disabled);
173                    println!("duration = {:?}", thb.duration);
174                    println!("width = {:?}", thb.width);
175                    println!("height = {:?}", thb.height);
176
177                    let stsd = track
178                        .stsd
179                        .expect("TrackType::Video missing SampleDescriptionBox");
180                    let v = match stsd
181                        .descriptions
182                        .first()
183                        .expect("TrackType::Video missing SampleEntry")
184                    {
185                        SampleEntry::Video(v) => v,
186                        _ => panic!("TrackType::Video missing VideoSampleEntry"),
187                    };
188                    println!("[media.track.video.sample.entry]");
189                    println!("width = {:?}", v.width);
190                    println!("height = {:?}", v.height);
191
192                    let mut vcsd = HashMap::new(); // VideoCodecSpecific data
193                    let codec = match v.codec_specific {
194                        VideoCodecSpecific::AV1Config(ref _av1c) => "AV1",
195                        VideoCodecSpecific::AVCConfig(ref avc) => {
196                            // vcsd.insert(String::from("avc.bytes_length"), avc.len());
197                            "AVC"
198                        }
199                        VideoCodecSpecific::VPxConfig(ref vpx) => {
200                            vcsd.insert(String::from("vpx.bit_depth"), vpx.bit_depth);
201                            vcsd.insert(String::from("vpx.colour_primaries"), vpx.colour_primaries);
202                            vcsd.insert(
203                                String::from("vpx.chroma_subsampling"),
204                                vpx.chroma_subsampling,
205                            );
206                            "VPx"
207                        }
208                        VideoCodecSpecific::ESDSConfig(ref mp4v) => "MP4V",
209                        VideoCodecSpecific::H263Config(ref _h263) => "H263",
210                    };
211                    println!("[media.track.video.codec]");
212                    println!("codec_name = \"{}\"", codec);
213                    for (key, value) in &vcsd {
214                        println!("{} = {:?}", key, value);
215                    }
216                }
217                TrackType::Audio => {
218                    println!("[media.track.audio]");
219                    println!("track_id = {:?}", track.track_id.unwrap());
220                    println!("duration = \"{:?}\"", track.duration.unwrap());
221                    println!("empty_duration = \"{:?}\"", track.empty_duration.unwrap());
222                    println!("media_time = \"{:?}\"", track.media_time.unwrap());
223                    println!("timescale = \"{:?}\"", track.timescale.unwrap());
224
225                    let thb = track.tkhd.unwrap();
226                    println!("[media.track.audio.header]");
227                    println!("disabled = {:?}", thb.disabled);
228                    println!("duration = {:?}", thb.duration);
229                    println!("width = {:?}", thb.width);
230                    println!("height = {:?}", thb.height);
231
232                    let stsd = track
233                        .stsd
234                        .expect("TrackType::Audio missing SampleDescriptionBox");
235                    let a = match stsd
236                        .descriptions
237                        .first()
238                        .expect("TrackType::Audio missing SampleEntry")
239                    {
240                        SampleEntry::Audio(a) => a,
241                        _ => panic!("TrackType::Audio missing AudioSampleEntry"),
242                    };
243
244                    println!("[media.track.audio.sample.entry]");
245                    println!("channelcount = {:?}", a.channelcount);
246                    println!("samplesize = {:?}", a.samplesize);
247                    println!("samplerate = {:?}", a.samplerate);
248
249                    let mut acsd = HashMap::new(); // AudioCodecSpecific data
250                    let codec = match &a.codec_specific {
251                        AudioCodecSpecific::ES_Descriptor(esds) => {
252                            acsd.insert(
253                                String::from("esds.audio_sample_rate"),
254                                esds.audio_sample_rate.unwrap(),
255                            );
256                            acsd.insert(
257                                String::from("esds.audio_object_type"),
258                                esds.audio_object_type.unwrap() as u32,
259                            );
260                            "ES"
261                        }
262                        AudioCodecSpecific::FLACSpecificBox(flac) => {
263                            acsd.insert(
264                                String::from("flac.blocks[0].block_type"),
265                                flac.blocks[0].block_type as u32,
266                            );
267                            acsd.insert(
268                                String::from("flac.blocks[0].data.len()"),
269                                flac.blocks[0].data.len() as u32,
270                            );
271                            "FLAC"
272                        }
273                        AudioCodecSpecific::OpusSpecificBox(opus) => {
274                            acsd.insert(String::from("opus.version"), opus.version as u32);
275                            "Opus"
276                        }
277                        AudioCodecSpecific::ALACSpecificBox(alac) => {
278                            acsd.insert(String::from("alac.data.len()"), alac.data.len() as u32);
279                            "ALAC"
280                        }
281                        AudioCodecSpecific::MP3 => "MP3",
282                        AudioCodecSpecific::LPCM => "LPCM",
283                    };
284
285                    println!("[media.track.audio.codec]");
286                    println!("codec_name = \"{}\"", codec);
287                    for (key, value) in &acsd {
288                        println!("{} = {:?}", key, value);
289                    }
290                }
291                TrackType::Picture => {
292                    println!("[media.track.picture]");
293                    println!(
294                        "error = {:?}",
295                        "TrackType::Picture found, but not supported by this application."
296                    );
297                }
298                TrackType::AuxiliaryVideo => {
299                    println!("[media.track.auxiliaryvideo]");
300                    println!(
301                        "error = {:?}",
302                        "TrackType::AuxiliaryVideo found, but not supported by this application."
303                    );
304                }
305                TrackType::Metadata => {
306                    println!("[media.track.metadata]");
307                    println!(
308                        "error = {:?}",
309                        "TrackType::Metadata found, but not supported by this application."
310                    );
311                }
312                TrackType::Unknown => {
313                    println!("[media.track.unknown]");
314                    println!("error = {:?}", "TrackType::Unknown.");
315                }
316            }
317        }
318    }
319    println!();
320    Ok(())
321}
322
323/// bit array for testing
324//  pub const TESTS_SMALL: [u8; 8] = [0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70];
325/// @see (https://doc.rust-lang.org/book/second-edition/ch11-03-test-organization.html)
326#[cfg(test)]
327mod tests {
328    extern crate assert_cmd;
329    extern crate tempfile;
330
331    #[test]
332    fn unit_args() {
333        let filename = String::from("tests/files/test-bokeh-au-0t-vd-30f-854x480.mp4");
334        let args: Vec<String> = vec![String::from("mpn"), filename.clone()];
335        assert_eq!(args.len(), 2);
336    }
337}