1use std::path::Path;
9
10use anyhow::{Context, Result};
11
12use container::streaming;
13
14#[derive(Debug, Clone)]
16pub struct MediaInfo {
17 pub container: String,
19 pub video_codec: String,
21 pub width: u32,
23 pub height: u32,
25 pub frame_rate: f64,
27 pub duration: f64,
29 pub pixel_format: String,
31 pub audio: Option<AudioStreamInfo>,
33}
34
35#[derive(Debug, Clone)]
37pub struct AudioStreamInfo {
38 pub codec: String,
40 pub sample_rate: u32,
42 pub channels: u16,
44}
45
46pub fn probe_file(input: impl AsRef<Path>) -> Result<MediaInfo> {
48 let input = input.as_ref();
49 let bytes = std::fs::read(input)
50 .with_context(|| format!("reading input file {}", input.display()))?;
51 probe_bytes(&bytes)
52}
53
54pub fn probe_bytes(input: &[u8]) -> Result<MediaInfo> {
56 let demuxer = streaming::demux_streaming(input).context("demux")?;
57 let header = demuxer.header();
58
59 let audio = demuxer.audio().map(|t| AudioStreamInfo {
60 codec: t.codec.to_ascii_lowercase(),
61 sample_rate: t.sample_rate,
62 channels: t.channels,
63 });
64
65 Ok(MediaInfo {
66 container: detect_container(input).to_string(),
67 video_codec: header.codec.to_ascii_lowercase(),
68 width: header.info.width,
69 height: header.info.height,
70 frame_rate: header.info.frame_rate,
71 duration: header.info.duration,
72 pixel_format: format!("{:?}", header.info.pixel_format),
73 audio,
74 })
75}
76
77fn detect_container(data: &[u8]) -> &'static str {
81 if data.len() < 12 {
82 return "unknown";
83 }
84 if &data[4..8] == b"ftyp" || &data[4..8] == b"moov" || &data[4..8] == b"mdat" {
85 return "mp4";
86 }
87 if data[0] == 0x1A && data[1] == 0x45 && data[2] == 0xDF && data[3] == 0xA3 {
88 return "mkv";
89 }
90 if &data[..4] == b"RIFF" && &data[8..12] == b"AVI " {
91 return "avi";
92 }
93 if data[0] == 0x47
94 && data.len() > 188
95 && data[188] == 0x47
96 && (data.len() <= 376 || data[376] == 0x47)
97 {
98 return "ts";
99 }
100 "unknown"
101}