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
15extern 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
38pub const ARG_MEDIAFILE: &str = "MEDIAFILE";
40
41pub struct Media {
43 pub filename: String,
45 pub creation_time: i64,
47 pub last_accessed_time: i64,
49 pub last_modified_time: i64,
51 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
61impl Media {
63 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
81pub 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 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()); println!("timescale = \"{:?}\"", track.timescale.unwrap());
169
170 let thb = track.tkhd.unwrap(); 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(); let codec = match v.codec_specific {
194 VideoCodecSpecific::AV1Config(ref _av1c) => "AV1",
195 VideoCodecSpecific::AVCConfig(ref avc) => {
196 "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(); 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#[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}