1use std::{fmt, num::NonZeroU32, str::FromStr};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
14#[cfg_attr(feature = "schema", schema(value_type = String, example = "MJPG"))]
15pub struct FourCc([u8; 4]);
16
17impl FourCc {
18 pub const fn new(bytes: [u8; 4]) -> Self {
20 Self(bytes)
21 }
22
23 pub fn to_u32(self) -> u32 {
25 u32::from_le_bytes(self.0)
26 }
27
28 pub fn as_str(&self) -> Option<&str> {
30 std::str::from_utf8(&self.0).ok()
31 }
32}
33
34impl From<u32> for FourCc {
35 fn from(value: u32) -> Self {
36 Self(value.to_le_bytes())
37 }
38}
39
40impl fmt::Display for FourCc {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 if let Some(s) = self.as_str() {
43 write!(f, "{s}")
44 } else {
45 write!(f, "0x{:08x}", self.to_u32())
46 }
47 }
48}
49
50impl FromStr for FourCc {
51 type Err = String;
52
53 fn from_str(s: &str) -> Result<Self, Self::Err> {
54 let bytes = s.as_bytes();
55 if bytes.len() != 4 {
56 return Err("fourcc must be four ASCII bytes".into());
57 }
58 let mut arr = [0u8; 4];
59 arr.copy_from_slice(bytes);
60 Ok(FourCc(arr))
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
76pub struct Resolution {
77 #[cfg_attr(feature = "schema", schema(value_type = u32, minimum = 1))]
79 pub width: NonZeroU32,
80 #[cfg_attr(feature = "schema", schema(value_type = u32, minimum = 1))]
82 pub height: NonZeroU32,
83}
84
85impl Resolution {
86 pub fn new(width: u32, height: u32) -> Option<Self> {
88 Some(Self {
89 width: NonZeroU32::new(width)?,
90 height: NonZeroU32::new(height)?,
91 })
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
111pub struct Interval {
112 #[cfg_attr(feature = "schema", schema(value_type = u32, minimum = 1))]
114 pub numerator: NonZeroU32,
115 #[cfg_attr(feature = "schema", schema(value_type = u32, minimum = 1))]
117 pub denominator: NonZeroU32,
118}
119
120impl Interval {
121 pub fn fps(&self) -> f32 {
123 self.denominator.get() as f32 / self.numerator.get() as f32
126 }
127
128 pub fn within(&self, min: Interval, max: Interval) -> bool {
129 let self_num = self.numerator.get() as u64;
131 let self_den = self.denominator.get() as u64;
132 let min_num = min.numerator.get() as u64;
133 let min_den = min.denominator.get() as u64;
134 let max_num = max.numerator.get() as u64;
135 let max_den = max.denominator.get() as u64;
136 self_num * min_den >= min_num * self_den && self_num * max_den <= max_num * self_den
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
160#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
161pub struct IntervalStepwise {
162 pub min: Interval,
163 pub max: Interval,
164 pub step: Interval,
165}
166
167impl IntervalStepwise {
168 pub fn contains(&self, candidate: Interval) -> bool {
169 if !candidate.within(self.min, self.max) {
170 return false;
171 }
172 let step_fps = self.step.fps();
174 if step_fps == 0.0 {
175 return true;
176 }
177 let min_fps = self.min.fps();
178 let cand_fps = candidate.fps();
179 let steps = ((cand_fps - min_fps) / step_fps).round();
180 ((cand_fps - min_fps) - steps * step_fps).abs() < 0.001
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
188pub enum ColorSpace {
189 Srgb,
191 Bt709,
193 Bt2020,
195 Unknown,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
211#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
212pub struct MediaFormat {
213 #[cfg_attr(feature = "schema", schema(value_type = String))]
215 pub code: FourCc,
216 pub resolution: Resolution,
218 pub color: ColorSpace,
220}
221
222impl MediaFormat {
223 pub fn new(code: FourCc, resolution: Resolution, color: ColorSpace) -> Self {
225 Self {
226 code,
227 resolution,
228 color,
229 }
230 }
231}
232
233#[cfg(feature = "serde")]
234impl serde::Serialize for FourCc {
235 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
236 where
237 S: serde::Serializer,
238 {
239 let encoded = self.as_str().unwrap_or("FFFF");
241 serializer.serialize_str(encoded)
242 }
243}
244
245#[cfg(feature = "serde")]
246impl<'de> serde::Deserialize<'de> for FourCc {
247 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
248 where
249 D: serde::Deserializer<'de>,
250 {
251 struct FourCcVisitor;
252
253 impl<'de> serde::de::Visitor<'de> for FourCcVisitor {
254 type Value = FourCc;
255
256 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
257 f.write_str("a 4-character FourCc string")
258 }
259
260 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
261 where
262 E: serde::de::Error,
263 {
264 FourCc::from_str(v).map_err(E::custom)
265 }
266 }
267
268 deserializer.deserialize_str(FourCcVisitor)
269 }
270}