1#![allow(dead_code)]
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum StreamType {
12 Video,
14 Audio,
16 Subtitle,
18 Data,
20 Unknown,
22}
23
24#[derive(Debug, Clone)]
26pub struct StreamInfo {
27 pub index: u32,
29 pub stream_type: StreamType,
31 pub codec_id: String,
33 pub bitrate_bps: u64,
35 pub duration_ms_val: u64,
37 pub rate_num: u32,
39 pub rate_den: u32,
41 pub width: u32,
43 pub height: u32,
45 pub channels: u32,
47}
48
49impl StreamInfo {
50 pub fn new(index: u32, stream_type: StreamType, codec_id: impl Into<String>) -> Self {
52 Self {
53 index,
54 stream_type,
55 codec_id: codec_id.into(),
56 bitrate_bps: 0,
57 duration_ms_val: 0,
58 rate_num: 0,
59 rate_den: 1,
60 width: 0,
61 height: 0,
62 channels: 0,
63 }
64 }
65
66 pub fn is_video(&self) -> bool {
68 self.stream_type == StreamType::Video
69 }
70
71 pub fn is_audio(&self) -> bool {
73 self.stream_type == StreamType::Audio
74 }
75
76 pub fn duration_ms(&self) -> u64 {
78 self.duration_ms_val
79 }
80
81 pub fn frame_rate(&self) -> f64 {
83 if self.rate_den == 0 {
84 return 0.0;
85 }
86 self.rate_num as f64 / self.rate_den as f64
87 }
88
89 pub fn with_bitrate(mut self, bps: u64) -> Self {
91 self.bitrate_bps = bps;
92 self
93 }
94
95 pub fn with_duration_ms(mut self, ms: u64) -> Self {
97 self.duration_ms_val = ms;
98 self
99 }
100
101 pub fn with_video_dims(mut self, width: u32, height: u32) -> Self {
103 self.width = width;
104 self.height = height;
105 self
106 }
107
108 pub fn with_frame_rate(mut self, num: u32, den: u32) -> Self {
110 self.rate_num = num;
111 self.rate_den = den;
112 self
113 }
114
115 pub fn with_audio(mut self, sample_rate: u32, channels: u32) -> Self {
117 self.rate_num = sample_rate;
118 self.channels = channels;
119 self
120 }
121}
122
123#[derive(Debug, Default)]
130pub struct StreamInfoParser;
131
132impl StreamInfoParser {
133 pub fn new() -> Self {
135 Self
136 }
137
138 pub fn parse_header(&self, header: &str) -> Vec<StreamInfo> {
142 let mut streams = Vec::new();
143 for line in header.lines() {
144 let trimmed = line.trim();
145 if trimmed.is_empty() || trimmed.starts_with('#') {
146 continue;
147 }
148 let parts: Vec<&str> = trimmed.split(',').collect();
149 if parts.len() < 10 {
150 continue;
151 }
152 let index: u32 = match parts[0].trim().parse() {
153 Ok(v) => v,
154 Err(_) => continue,
155 };
156 let stream_type = match parts[1].trim() {
157 "video" => StreamType::Video,
158 "audio" => StreamType::Audio,
159 "subtitle" => StreamType::Subtitle,
160 "data" => StreamType::Data,
161 _ => StreamType::Unknown,
162 };
163 let codec_id = parts[2].trim().to_string();
164 let bitrate_bps: u64 = parts[3].trim().parse().unwrap_or(0);
165 let duration_ms: u64 = parts[4].trim().parse().unwrap_or(0);
166 let rate_num: u32 = parts[5].trim().parse().unwrap_or(0);
167 let rate_den: u32 = parts[6].trim().parse().unwrap_or(1);
168 let width: u32 = parts[7].trim().parse().unwrap_or(0);
169 let height: u32 = parts[8].trim().parse().unwrap_or(0);
170 let channels: u32 = parts[9].trim().parse().unwrap_or(0);
171 streams.push(StreamInfo {
172 index,
173 stream_type,
174 codec_id,
175 bitrate_bps,
176 duration_ms_val: duration_ms,
177 rate_num,
178 rate_den,
179 width,
180 height,
181 channels,
182 });
183 }
184 streams
185 }
186}
187
188#[derive(Debug, Default)]
190pub struct MediaInfo {
191 pub streams: Vec<StreamInfo>,
193 pub container_format: String,
195 pub file_size_bytes: u64,
197}
198
199impl MediaInfo {
200 pub fn new() -> Self {
202 Self::default()
203 }
204
205 pub fn stream_count(&self) -> usize {
207 self.streams.len()
208 }
209
210 pub fn video_streams(&self) -> Vec<&StreamInfo> {
212 self.streams.iter().filter(|s| s.is_video()).collect()
213 }
214
215 pub fn audio_streams(&self) -> Vec<&StreamInfo> {
217 self.streams.iter().filter(|s| s.is_audio()).collect()
218 }
219
220 pub fn primary_video(&self) -> Option<&StreamInfo> {
222 self.video_streams().into_iter().next()
223 }
224
225 pub fn primary_audio(&self) -> Option<&StreamInfo> {
227 self.audio_streams().into_iter().next()
228 }
229
230 pub fn total_bitrate_kbps(&self) -> u64 {
232 self.streams.iter().map(|s| s.bitrate_bps / 1000).sum()
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 fn make_video_stream() -> StreamInfo {
241 StreamInfo::new(0, StreamType::Video, "h264")
242 .with_video_dims(1920, 1080)
243 .with_frame_rate(30, 1)
244 .with_bitrate(5_000_000)
245 .with_duration_ms(60_000)
246 }
247
248 fn make_audio_stream() -> StreamInfo {
249 StreamInfo::new(1, StreamType::Audio, "aac")
250 .with_audio(48_000, 2)
251 .with_bitrate(128_000)
252 .with_duration_ms(60_000)
253 }
254
255 #[test]
256 fn test_stream_info_is_video() {
257 let s = make_video_stream();
258 assert!(s.is_video());
259 assert!(!s.is_audio());
260 }
261
262 #[test]
263 fn test_stream_info_is_audio() {
264 let s = make_audio_stream();
265 assert!(s.is_audio());
266 assert!(!s.is_video());
267 }
268
269 #[test]
270 fn test_stream_info_duration_ms() {
271 let s = make_video_stream();
272 assert_eq!(s.duration_ms(), 60_000);
273 }
274
275 #[test]
276 fn test_stream_info_frame_rate() {
277 let s = make_video_stream();
278 assert!((s.frame_rate() - 30.0).abs() < 0.001);
279 }
280
281 #[test]
282 fn test_stream_info_zero_den_frame_rate() {
283 let s = StreamInfo::new(0, StreamType::Video, "av1").with_frame_rate(30, 0);
284 assert!((s.frame_rate()).abs() < 0.001);
285 }
286
287 #[test]
288 fn test_parser_parses_video_line() {
289 let header = "0,video,h264,5000000,60000,30,1,1920,1080,0";
290 let parser = StreamInfoParser::new();
291 let streams = parser.parse_header(header);
292 assert_eq!(streams.len(), 1);
293 assert!(streams[0].is_video());
294 assert_eq!(streams[0].codec_id, "h264");
295 assert_eq!(streams[0].width, 1920);
296 }
297
298 #[test]
299 fn test_parser_parses_audio_line() {
300 let header = "1,audio,aac,128000,60000,48000,0,0,0,2";
301 let parser = StreamInfoParser::new();
302 let streams = parser.parse_header(header);
303 assert_eq!(streams.len(), 1);
304 assert!(streams[0].is_audio());
305 assert_eq!(streams[0].channels, 2);
306 }
307
308 #[test]
309 fn test_parser_skips_comment_lines() {
310 let header = "# This is a comment\n0,video,av1,4000000,30000,24,1,1280,720,0";
311 let parser = StreamInfoParser::new();
312 let streams = parser.parse_header(header);
313 assert_eq!(streams.len(), 1);
314 }
315
316 #[test]
317 fn test_parser_skips_malformed_lines() {
318 let header = "bad,line\n0,video,vp9,0,0,30,1,1920,1080,0";
319 let parser = StreamInfoParser::new();
320 let streams = parser.parse_header(header);
321 assert_eq!(streams.len(), 1);
322 }
323
324 #[test]
325 fn test_media_info_stream_count() {
326 let mut info = MediaInfo::new();
327 info.streams.push(make_video_stream());
328 info.streams.push(make_audio_stream());
329 assert_eq!(info.stream_count(), 2);
330 }
331
332 #[test]
333 fn test_media_info_video_streams() {
334 let mut info = MediaInfo::new();
335 info.streams.push(make_video_stream());
336 info.streams.push(make_audio_stream());
337 assert_eq!(info.video_streams().len(), 1);
338 }
339
340 #[test]
341 fn test_media_info_primary_video() {
342 let mut info = MediaInfo::new();
343 info.streams.push(make_video_stream());
344 assert!(info.primary_video().is_some());
345 }
346
347 #[test]
348 fn test_media_info_primary_audio() {
349 let mut info = MediaInfo::new();
350 info.streams.push(make_audio_stream());
351 assert!(info.primary_audio().is_some());
352 }
353
354 #[test]
355 fn test_media_info_total_bitrate_kbps() {
356 let mut info = MediaInfo::new();
357 info.streams.push(make_video_stream()); info.streams.push(make_audio_stream()); assert_eq!(info.total_bitrate_kbps(), 5128);
360 }
361}