Skip to main content

ffcine_core/
probe.rs

1//! What a media file contains, as reported by a backend (`ffcine_render::Backend`).
2
3use std::collections::BTreeMap;
4use std::fmt;
5use std::path::PathBuf;
6use std::time::Duration;
7
8use crate::ColorSpace;
9
10/// An exact ratio (frame rates, time bases).
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct Rational {
13    pub num: i32,
14    pub den: i32,
15}
16
17impl Rational {
18    pub fn as_f64(self) -> f64 {
19        f64::from(self.num) / f64::from(self.den)
20    }
21}
22
23impl std::str::FromStr for Rational {
24    type Err = crate::Error;
25    /// `24000/1001` or `25`.
26    fn from_str(s: &str) -> Result<Self, crate::Error> {
27        let invalid = || crate::Error::Invalid {
28            what: "rational (N/D)",
29            value: s.to_owned(),
30        };
31        let (n, d) = s.split_once('/').unwrap_or((s, "1"));
32        let r = Self {
33            num: n.parse().map_err(|_| invalid())?,
34            den: d.parse().map_err(|_| invalid())?,
35        };
36        if r.num <= 0 || r.den <= 0 {
37            return Err(invalid());
38        }
39        Ok(r)
40    }
41}
42
43impl fmt::Display for Rational {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{}/{}", self.num, self.den)
46    }
47}
48
49#[derive(Debug, Clone, PartialEq)]
50pub struct MediaInfo {
51    pub path: PathBuf,
52    /// Container (`mov,mp4,m4a,3gp,3g2,mj2`, `matroska,webm`, ...).
53    pub format: String,
54    pub duration: Option<Duration>,
55    pub bit_rate: Option<u64>,
56    pub streams: Vec<StreamInfo>,
57    /// Container metadata (`encoder`, `major_brand`, `compatible_brands`,
58    /// `com.apple.quicktime.make`, ...): what [`Profile::detect`](crate::camera::Profile::detect)
59    /// reads besides the colour tags.
60    pub tags: BTreeMap<String, String>,
61}
62
63#[derive(Debug, Clone, PartialEq)]
64pub struct StreamInfo {
65    pub index: usize,
66    pub codec: String,
67    pub bit_rate: Option<u64>,
68    pub kind: StreamKind,
69}
70
71#[derive(Debug, Clone, PartialEq)]
72#[non_exhaustive]
73pub enum StreamKind {
74    Video(VideoInfo),
75    Audio(AudioInfo),
76    Subtitle,
77    Data,
78    Other,
79}
80
81#[derive(Debug, Clone, PartialEq)]
82pub struct VideoInfo {
83    pub width: u32,
84    pub height: u32,
85    pub pixel_format: String,
86    pub frame_rate: Option<Rational>,
87    pub frames: Option<u64>,
88    pub color: ColorSpace,
89}
90
91#[derive(Debug, Clone, PartialEq)]
92pub struct AudioInfo {
93    pub sample_rate: u32,
94    pub channels: u16,
95    pub sample_format: String,
96}
97
98impl fmt::Display for MediaInfo {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(f, "{}: {}", self.path.display(), self.format)?;
101        if let Some(d) = self.duration {
102            write!(f, ", {:.3}s", d.as_secs_f64())?;
103        }
104        if let Some(b) = self.bit_rate {
105            write!(f, ", {} kb/s", b / 1000)?;
106        }
107        for s in &self.streams {
108            write!(f, "\n  #{} {s}", s.index)?;
109        }
110        Ok(())
111    }
112}
113
114impl fmt::Display for StreamInfo {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match &self.kind {
117            StreamKind::Video(v) => {
118                write!(
119                    f,
120                    "video {} {}x{} {}",
121                    self.codec, v.width, v.height, v.pixel_format
122                )?;
123                write!(f, " {}", v.color)?;
124                if let Some(r) = v.frame_rate {
125                    write!(f, ", {:.3} fps", r.as_f64())?;
126                }
127                if let Some(n) = v.frames {
128                    write!(f, ", {n} frames")?;
129                }
130            }
131            StreamKind::Audio(a) => write!(
132                f,
133                "audio {} {} Hz {}ch {}",
134                self.codec, a.sample_rate, a.channels, a.sample_format
135            )?,
136            StreamKind::Subtitle => write!(f, "subtitle {}", self.codec)?,
137            StreamKind::Data => write!(f, "data {}", self.codec)?,
138            StreamKind::Other => write!(f, "other {}", self.codec)?,
139        }
140        if let Some(b) = self.bit_rate {
141            write!(f, ", {} kb/s", b / 1000)?;
142        }
143        Ok(())
144    }
145}
146
147impl MediaInfo {
148    /// The first video stream, if any.
149    pub fn video(&self) -> Option<(&StreamInfo, &VideoInfo)> {
150        self.streams.iter().find_map(|s| match &s.kind {
151            StreamKind::Video(v) => Some((s, v)),
152            _ => None,
153        })
154    }
155}