1use std::collections::BTreeMap;
4use std::fmt;
5use std::path::PathBuf;
6use std::time::Duration;
7
8use crate::ColorSpace;
9
10#[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 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 pub format: String,
54 pub duration: Option<Duration>,
55 pub bit_rate: Option<u64>,
56 pub streams: Vec<StreamInfo>,
57 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 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}