1use std::time::Duration;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
5pub enum ContainerFormat {
6 Gif,
8 Png,
10 Jpeg,
12 WebP,
14 Qoi,
16 ImageSequence,
18 Avi,
20 Mp4,
22 Mkv,
24 Webm,
26 Unknown,
28}
29
30impl ContainerFormat {
31 #[must_use]
33 pub fn extension(&self) -> &'static str {
34 match self {
35 Self::Gif => "gif",
36 Self::Png => "png",
37 Self::Jpeg => "jpg",
38 Self::WebP => "webp",
39 Self::Qoi => "qoi",
40 Self::ImageSequence => "",
41 Self::Avi => "avi",
42 Self::Mp4 => "mp4",
43 Self::Mkv => "mkv",
44 Self::Webm => "webm",
45 Self::Unknown => "",
46 }
47 }
48
49 #[must_use]
51 pub fn from_path(path: &str) -> Self {
52 let lower = path.to_lowercase();
53 if lower.ends_with(".gif") {
54 Self::Gif
55 } else if lower.ends_with(".apng") || lower.ends_with(".png") {
56 Self::Png
57 } else if lower.ends_with(".jpg") || lower.ends_with(".jpeg") {
58 Self::Jpeg
59 } else if lower.ends_with(".webp") {
60 Self::WebP
61 } else if lower.ends_with(".qoi") {
62 Self::Qoi
63 } else if lower.ends_with(".avi") {
64 Self::Avi
65 } else if lower.ends_with(".mp4") || lower.ends_with(".mov") {
66 Self::Mp4
67 } else if lower.ends_with(".mkv") {
68 Self::Mkv
69 } else if lower.ends_with(".webm") {
70 Self::Webm
71 } else {
72 Self::Unknown
73 }
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub enum PixelFormat {
80 Rgb8,
82 Rgba8,
84 Gray8,
86 Rgb16,
88 Rgba16,
90 Rgb32F,
92 Rgba32F,
94}
95
96impl PixelFormat {
97 #[must_use]
99 pub fn channels(&self) -> usize {
100 match self {
101 Self::Gray8 => 1,
102 Self::Rgb8 | Self::Rgb16 | Self::Rgb32F => 3,
103 Self::Rgba8 | Self::Rgba16 | Self::Rgba32F => 4,
104 }
105 }
106}
107
108#[derive(Clone, Debug)]
110pub struct StreamInfo {
111 pub index: usize,
113 pub stream_type: StreamType,
115 pub codec: String,
117 pub width: usize,
119 pub height: usize,
121 pub fps: f64,
123 pub duration: Duration,
125 pub frame_count: usize,
127 pub pixel_format: PixelFormat,
129 pub rotation: u32,
131 pub bit_rate: u64,
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
137pub enum StreamType {
138 Video,
140 Audio,
142 Subtitle,
144}
145
146#[derive(Clone, Debug)]
148pub struct VideoMetadata {
149 pub format: ContainerFormat,
151 pub duration: Duration,
153 pub fps: f64,
155 pub width: usize,
157 pub height: usize,
159 pub frame_count: usize,
161 pub video_codec: String,
163 pub pixel_format: PixelFormat,
165 pub rotation: u32,
167 pub bit_rate: u64,
169 pub streams: Vec<StreamInfo>,
171 pub has_audio: bool,
173 pub has_subtitles: bool,
175 pub file_size: u64,
177}
178
179impl VideoMetadata {
180 #[must_use]
182 pub fn synthetic(width: usize, height: usize, fps: f64, frame_count: usize) -> Self {
183 let duration_secs = if fps > 0.0 {
184 frame_count as f64 / fps
185 } else {
186 0.0
187 };
188 Self {
189 format: ContainerFormat::Unknown,
190 duration: Duration::from_secs_f64(duration_secs),
191 fps,
192 width,
193 height,
194 frame_count,
195 video_codec: "unknown".to_string(),
196 pixel_format: PixelFormat::Rgb8,
197 rotation: 0,
198 bit_rate: 0,
199 streams: Vec::new(),
200 has_audio: false,
201 has_subtitles: false,
202 file_size: 0,
203 }
204 }
205
206 #[must_use]
208 pub fn aspect_ratio(&self) -> f64 {
209 if self.height == 0 {
210 return 0.0;
211 }
212 self.width as f64 / self.height as f64
213 }
214
215 #[must_use]
217 pub fn video_stream_count(&self) -> usize {
218 self.streams
219 .iter()
220 .filter(|s| s.stream_type == StreamType::Video)
221 .count()
222 }
223
224 #[must_use]
226 pub fn audio_stream_count(&self) -> usize {
227 self.streams
228 .iter()
229 .filter(|s| s.stream_type == StreamType::Audio)
230 .count()
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_container_format_detection() {
240 assert_eq!(ContainerFormat::from_path("test.gif"), ContainerFormat::Gif);
241 assert_eq!(ContainerFormat::from_path("test.mp4"), ContainerFormat::Mp4);
242 assert_eq!(ContainerFormat::from_path("test.MKV"), ContainerFormat::Mkv);
243 assert_eq!(
244 ContainerFormat::from_path("test.webp"),
245 ContainerFormat::WebP
246 );
247 assert_eq!(
248 ContainerFormat::from_path("test.unknown"),
249 ContainerFormat::Unknown
250 );
251 }
252
253 #[test]
254 fn test_video_metadata() {
255 let meta = VideoMetadata::synthetic(1920, 1080, 30.0, 300);
256 assert_eq!(meta.width, 1920);
257 assert_eq!(meta.height, 1080);
258 assert!((meta.fps - 30.0).abs() < 1e-6);
259 assert_eq!(meta.frame_count, 300);
260 assert!((meta.aspect_ratio() - 16.0 / 9.0).abs() < 1e-6);
261 }
262
263 #[test]
264 fn test_pixel_format_channels() {
265 assert_eq!(PixelFormat::Rgb8.channels(), 3);
266 assert_eq!(PixelFormat::Rgba8.channels(), 4);
267 assert_eq!(PixelFormat::Gray8.channels(), 1);
268 }
269}