1use ffmpeg_common::Duration;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ProbeSection {
9 Format,
10 Streams,
11 Packets,
12 Frames,
13 Programs,
14 Chapters,
15 Error,
16}
17
18impl ProbeSection {
19 pub fn as_str(&self) -> &'static str {
20 match self {
21 Self::Format => "format",
22 Self::Streams => "streams",
23 Self::Packets => "packets",
24 Self::Frames => "frames",
25 Self::Programs => "programs",
26 Self::Chapters => "chapters",
27 Self::Error => "error",
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct ReadInterval {
35 pub start: Option<IntervalPosition>,
37 pub end: Option<IntervalPosition>,
39}
40
41#[derive(Debug, Clone)]
42pub enum IntervalPosition {
43 Absolute(Duration),
45 Relative(Duration),
47 Packets(u64),
49}
50
51impl ReadInterval {
52 pub fn new(start: Option<IntervalPosition>, end: Option<IntervalPosition>) -> Self {
54 Self { start, end }
55 }
56
57 pub fn to(end: IntervalPosition) -> Self {
59 Self {
60 start: None,
61 end: Some(end),
62 }
63 }
64
65 pub fn from(start: IntervalPosition) -> Self {
67 Self {
68 start: Some(start),
69 end: None,
70 }
71 }
72
73 pub fn all() -> Self {
75 Self {
76 start: None,
77 end: None,
78 }
79 }
80
81 pub fn to_string(&self) -> String {
83 let mut result = String::new();
84
85 if let Some(ref start) = self.start {
86 match start {
87 IntervalPosition::Absolute(d) => result.push_str(&d.to_ffmpeg_format()),
88 IntervalPosition::Relative(d) => result.push_str(&format!("+{}", d.to_ffmpeg_format())),
89 IntervalPosition::Packets(n) => result.push_str(&format!("#{}", n)),
90 }
91 }
92
93 result.push('%');
94
95 if let Some(ref end) = self.end {
96 match end {
97 IntervalPosition::Absolute(d) => result.push_str(&d.to_ffmpeg_format()),
98 IntervalPosition::Relative(d) => result.push_str(&format!("+{}", d.to_ffmpeg_format())),
99 IntervalPosition::Packets(n) => result.push_str(&format!("#{}", n)),
100 }
101 }
102
103 result
104 }
105}
106
107impl fmt::Display for ReadInterval {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(f, "{}", self.to_string())
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ProbeResult {
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub format: Option<FormatInfo>,
119
120 #[serde(default, skip_serializing_if = "Vec::is_empty")]
122 pub streams: Vec<StreamInfo>,
123
124 #[serde(default, skip_serializing_if = "Vec::is_empty")]
126 pub packets: Vec<PacketInfo>,
127
128 #[serde(default, skip_serializing_if = "Vec::is_empty")]
130 pub frames: Vec<FrameInfo>,
131
132 #[serde(default, skip_serializing_if = "Vec::is_empty")]
134 pub programs: Vec<ProgramInfo>,
135
136 #[serde(default, skip_serializing_if = "Vec::is_empty")]
138 pub chapters: Vec<ChapterInfo>,
139
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub error: Option<ErrorInfo>,
143}
144
145impl ProbeResult {
146 pub fn video_streams(&self) -> Vec<&StreamInfo> {
148 self.streams
149 .iter()
150 .filter(|s| s.codec_type == Some("video".to_string()))
151 .collect()
152 }
153
154 pub fn audio_streams(&self) -> Vec<&StreamInfo> {
156 self.streams
157 .iter()
158 .filter(|s| s.codec_type == Some("audio".to_string()))
159 .collect()
160 }
161
162 pub fn subtitle_streams(&self) -> Vec<&StreamInfo> {
164 self.streams
165 .iter()
166 .filter(|s| s.codec_type == Some("subtitle".to_string()))
167 .collect()
168 }
169
170 pub fn primary_video_stream(&self) -> Option<&StreamInfo> {
172 self.video_streams().into_iter().next()
173 }
174
175 pub fn primary_audio_stream(&self) -> Option<&StreamInfo> {
177 self.audio_streams().into_iter().next()
178 }
179
180 pub fn duration(&self) -> Option<f64> {
182 self.format.as_ref()?.duration.as_ref()?.parse().ok()
183 }
184
185 pub fn format_name(&self) -> Option<&str> {
187 self.format.as_ref()?.format_name.as_deref()
188 }
189
190 pub fn format_long_name(&self) -> Option<&str> {
192 self.format.as_ref()?.format_long_name.as_deref()
193 }
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct FormatInfo {
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub filename: Option<String>,
202
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub nb_streams: Option<u32>,
206
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub nb_programs: Option<u32>,
210
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub format_name: Option<String>,
214
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub format_long_name: Option<String>,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub start_time: Option<String>,
222
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub duration: Option<String>,
226
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub size: Option<String>,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub bit_rate: Option<String>,
234
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub probe_score: Option<u32>,
238
239 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
241 pub tags: HashMap<String, String>,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct StreamInfo {
247 pub index: u32,
249
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub codec_name: Option<String>,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub codec_long_name: Option<String>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub profile: Option<String>,
261
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub codec_type: Option<String>,
265
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub codec_tag_string: Option<String>,
269
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub codec_tag: Option<String>,
273
274 #[serde(skip_serializing_if = "Option::is_none")]
277 pub width: Option<u32>,
278
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub height: Option<u32>,
282
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub coded_width: Option<u32>,
286
287 #[serde(skip_serializing_if = "Option::is_none")]
289 pub coded_height: Option<u32>,
290
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub has_b_frames: Option<u32>,
294
295 #[serde(skip_serializing_if = "Option::is_none")]
297 pub sample_aspect_ratio: Option<String>,
298
299 #[serde(skip_serializing_if = "Option::is_none")]
301 pub display_aspect_ratio: Option<String>,
302
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub pix_fmt: Option<String>,
306
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub level: Option<i32>,
310
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub color_range: Option<String>,
314
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub color_space: Option<String>,
318
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub color_transfer: Option<String>,
322
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub color_primaries: Option<String>,
326
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub chroma_location: Option<String>,
330
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub field_order: Option<String>,
334
335 #[serde(skip_serializing_if = "Option::is_none")]
337 pub refs: Option<u32>,
338
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub is_avc: Option<String>,
342
343 #[serde(skip_serializing_if = "Option::is_none")]
345 pub nal_length_size: Option<String>,
346
347 #[serde(skip_serializing_if = "Option::is_none")]
350 pub sample_fmt: Option<String>,
351
352 #[serde(skip_serializing_if = "Option::is_none")]
354 pub sample_rate: Option<String>,
355
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub channels: Option<u32>,
359
360 #[serde(skip_serializing_if = "Option::is_none")]
362 pub channel_layout: Option<String>,
363
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub bits_per_sample: Option<u32>,
367
368 #[serde(skip_serializing_if = "Option::is_none")]
371 pub id: Option<String>,
372
373 #[serde(skip_serializing_if = "Option::is_none")]
375 pub r_frame_rate: Option<String>,
376
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub avg_frame_rate: Option<String>,
380
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub time_base: Option<String>,
384
385 #[serde(skip_serializing_if = "Option::is_none")]
387 pub start_pts: Option<i64>,
388
389 #[serde(skip_serializing_if = "Option::is_none")]
391 pub start_time: Option<String>,
392
393 #[serde(skip_serializing_if = "Option::is_none")]
395 pub duration_ts: Option<i64>,
396
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub duration: Option<String>,
400
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub bit_rate: Option<String>,
404
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub max_bit_rate: Option<String>,
408
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub bits_per_raw_sample: Option<u32>,
412
413 #[serde(skip_serializing_if = "Option::is_none")]
415 pub nb_frames: Option<String>,
416
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub nb_read_frames: Option<String>,
420
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub nb_read_packets: Option<String>,
424
425 #[serde(skip_serializing_if = "Option::is_none")]
427 pub disposition: Option<HashMap<String, u8>>,
428
429 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
431 pub tags: HashMap<String, String>,
432}
433
434impl StreamInfo {
435 pub fn is_video(&self) -> bool {
437 self.codec_type.as_deref() == Some("video")
438 }
439
440 pub fn is_audio(&self) -> bool {
442 self.codec_type.as_deref() == Some("audio")
443 }
444
445 pub fn is_subtitle(&self) -> bool {
447 self.codec_type.as_deref() == Some("subtitle")
448 }
449
450 pub fn language(&self) -> Option<&str> {
452 self.tags.get("language").map(|s| s.as_str())
453 }
454
455 pub fn title(&self) -> Option<&str> {
457 self.tags.get("title").map(|s| s.as_str())
458 }
459
460 pub fn resolution(&self) -> Option<(u32, u32)> {
462 match (self.width, self.height) {
463 (Some(w), Some(h)) => Some((w, h)),
464 _ => None,
465 }
466 }
467
468 pub fn frame_rate(&self) -> Option<f64> {
470 self.avg_frame_rate
471 .as_ref()
472 .or(self.r_frame_rate.as_ref())
473 .and_then(|r| parse_rational(r))
474 }
475
476 pub fn sample_rate_hz(&self) -> Option<u32> {
478 self.sample_rate.as_ref()?.parse().ok()
479 }
480
481 pub fn duration_seconds(&self) -> Option<f64> {
483 self.duration.as_ref()?.parse().ok()
484 }
485
486 pub fn bit_rate_bps(&self) -> Option<u64> {
488 self.bit_rate.as_ref()?.parse().ok()
489 }
490}
491
492#[derive(Debug, Clone, Serialize, Deserialize)]
494pub struct PacketInfo {
495 #[serde(skip_serializing_if = "Option::is_none")]
497 pub codec_type: Option<String>,
498
499 pub stream_index: u32,
501
502 #[serde(skip_serializing_if = "Option::is_none")]
504 pub pts: Option<i64>,
505
506 #[serde(skip_serializing_if = "Option::is_none")]
508 pub pts_time: Option<String>,
509
510 #[serde(skip_serializing_if = "Option::is_none")]
512 pub dts: Option<i64>,
513
514 #[serde(skip_serializing_if = "Option::is_none")]
516 pub dts_time: Option<String>,
517
518 #[serde(skip_serializing_if = "Option::is_none")]
520 pub duration: Option<i64>,
521
522 #[serde(skip_serializing_if = "Option::is_none")]
524 pub duration_time: Option<String>,
525
526 #[serde(skip_serializing_if = "Option::is_none")]
528 pub size: Option<String>,
529
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub pos: Option<String>,
533
534 #[serde(skip_serializing_if = "Option::is_none")]
536 pub flags: Option<String>,
537
538 #[serde(skip_serializing_if = "Option::is_none")]
540 pub data: Option<String>,
541
542 #[serde(skip_serializing_if = "Option::is_none")]
544 pub data_hash: Option<String>,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct FrameInfo {
550 #[serde(skip_serializing_if = "Option::is_none")]
552 pub media_type: Option<String>,
553
554 pub stream_index: u32,
556
557 #[serde(skip_serializing_if = "Option::is_none")]
559 pub key_frame: Option<u8>,
560
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub pts: Option<i64>,
564
565 #[serde(skip_serializing_if = "Option::is_none")]
567 pub pts_time: Option<String>,
568
569 #[serde(skip_serializing_if = "Option::is_none")]
571 pub pkt_pts: Option<i64>,
572
573 #[serde(skip_serializing_if = "Option::is_none")]
575 pub pkt_pts_time: Option<String>,
576
577 #[serde(skip_serializing_if = "Option::is_none")]
579 pub pkt_dts: Option<i64>,
580
581 #[serde(skip_serializing_if = "Option::is_none")]
583 pub pkt_dts_time: Option<String>,
584
585 #[serde(skip_serializing_if = "Option::is_none")]
587 pub best_effort_timestamp: Option<i64>,
588
589 #[serde(skip_serializing_if = "Option::is_none")]
591 pub best_effort_timestamp_time: Option<String>,
592
593 #[serde(skip_serializing_if = "Option::is_none")]
595 pub pkt_duration: Option<i64>,
596
597 #[serde(skip_serializing_if = "Option::is_none")]
599 pub pkt_duration_time: Option<String>,
600
601 #[serde(skip_serializing_if = "Option::is_none")]
603 pub pkt_pos: Option<String>,
604
605 #[serde(skip_serializing_if = "Option::is_none")]
607 pub pkt_size: Option<String>,
608
609 #[serde(skip_serializing_if = "Option::is_none")]
612 pub width: Option<u32>,
613
614 #[serde(skip_serializing_if = "Option::is_none")]
616 pub height: Option<u32>,
617
618 #[serde(skip_serializing_if = "Option::is_none")]
620 pub pix_fmt: Option<String>,
621
622 #[serde(skip_serializing_if = "Option::is_none")]
624 pub pict_type: Option<String>,
625
626 #[serde(skip_serializing_if = "Option::is_none")]
628 pub coded_picture_number: Option<u32>,
629
630 #[serde(skip_serializing_if = "Option::is_none")]
632 pub display_picture_number: Option<u32>,
633
634 #[serde(skip_serializing_if = "Option::is_none")]
636 pub interlaced_frame: Option<u8>,
637
638 #[serde(skip_serializing_if = "Option::is_none")]
640 pub top_field_first: Option<u8>,
641
642 #[serde(skip_serializing_if = "Option::is_none")]
644 pub repeat_pict: Option<u8>,
645
646 #[serde(skip_serializing_if = "Option::is_none")]
649 pub sample_fmt: Option<String>,
650
651 #[serde(skip_serializing_if = "Option::is_none")]
653 pub nb_samples: Option<u32>,
654
655 #[serde(skip_serializing_if = "Option::is_none")]
657 pub channels: Option<u32>,
658
659 #[serde(skip_serializing_if = "Option::is_none")]
661 pub channel_layout: Option<String>,
662}
663
664#[derive(Debug, Clone, Serialize, Deserialize)]
666pub struct ProgramInfo {
667 pub program_id: u32,
669
670 #[serde(skip_serializing_if = "Option::is_none")]
672 pub program_num: Option<u32>,
673
674 #[serde(skip_serializing_if = "Option::is_none")]
676 pub nb_streams: Option<u32>,
677
678 #[serde(skip_serializing_if = "Option::is_none")]
680 pub pmt_pid: Option<u32>,
681
682 #[serde(skip_serializing_if = "Option::is_none")]
684 pub pcr_pid: Option<u32>,
685
686 #[serde(skip_serializing_if = "Option::is_none")]
688 pub start_pts: Option<i64>,
689
690 #[serde(skip_serializing_if = "Option::is_none")]
692 pub start_time: Option<String>,
693
694 #[serde(skip_serializing_if = "Option::is_none")]
696 pub end_pts: Option<i64>,
697
698 #[serde(skip_serializing_if = "Option::is_none")]
700 pub end_time: Option<String>,
701
702 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
704 pub tags: HashMap<String, String>,
705
706 #[serde(default, skip_serializing_if = "Vec::is_empty")]
708 pub streams: Vec<StreamInfo>,
709}
710
711#[derive(Debug, Clone, Serialize, Deserialize)]
713pub struct ChapterInfo {
714 pub id: i64,
716
717 #[serde(skip_serializing_if = "Option::is_none")]
719 pub time_base: Option<String>,
720
721 pub start: i64,
723
724 #[serde(skip_serializing_if = "Option::is_none")]
726 pub start_time: Option<String>,
727
728 pub end: i64,
730
731 #[serde(skip_serializing_if = "Option::is_none")]
733 pub end_time: Option<String>,
734
735 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
737 pub tags: HashMap<String, String>,
738}
739
740impl ChapterInfo {
741 pub fn title(&self) -> Option<&str> {
743 self.tags.get("title").map(|s| s.as_str())
744 }
745}
746
747#[derive(Debug, Clone, Serialize, Deserialize)]
749pub struct ErrorInfo {
750 #[serde(skip_serializing_if = "Option::is_none")]
752 pub code: Option<i32>,
753
754 #[serde(skip_serializing_if = "Option::is_none")]
756 pub string: Option<String>,
757}
758
759fn parse_rational(s: &str) -> Option<f64> {
761 let parts: Vec<&str> = s.split('/').collect();
762 if parts.len() == 2 {
763 let num: f64 = parts[0].parse().ok()?;
764 let den: f64 = parts[1].parse().ok()?;
765 if den != 0.0 {
766 Some(num / den)
767 } else {
768 None
769 }
770 } else {
771 s.parse().ok()
772 }
773}
774
775#[cfg(test)]
776mod tests {
777 use super::*;
778
779 #[test]
780 fn test_read_interval() {
781 let interval = ReadInterval::all();
782 assert_eq!(interval.to_string(), "%");
783
784 let interval = ReadInterval::to(IntervalPosition::Absolute(Duration::from_secs(30)));
785 assert_eq!(interval.to_string(), "%00:00:30");
786
787 let interval = ReadInterval::from(IntervalPosition::Relative(Duration::from_secs(10)));
788 assert_eq!(interval.to_string(), "+00:00:10%");
789
790 let interval = ReadInterval::new(
791 Some(IntervalPosition::Absolute(Duration::from_secs(10))),
792 Some(IntervalPosition::Packets(100)),
793 );
794 assert_eq!(interval.to_string(), "00:00:10%#100");
795 }
796
797 #[test]
798 fn test_stream_info_helpers() {
799 let mut stream = StreamInfo {
800 index: 0,
801 codec_type: Some("video".to_string()),
802 width: Some(1920),
803 height: Some(1080),
804 avg_frame_rate: Some("30/1".to_string()),
805 bit_rate: Some("5000000".to_string()),
806 tags: HashMap::new(),
807 ..Default::default()
808 };
809
810 assert!(stream.is_video());
811 assert!(!stream.is_audio());
812 assert_eq!(stream.resolution(), Some((1920, 1080)));
813 assert_eq!(stream.frame_rate(), Some(30.0));
814 assert_eq!(stream.bit_rate_bps(), Some(5000000));
815
816 stream.tags.insert("language".to_string(), "eng".to_string());
817 assert_eq!(stream.language(), Some("eng"));
818 }
819
820 #[test]
821 fn test_parse_rational() {
822 assert_eq!(parse_rational("30/1"), Some(30.0));
823 assert_eq!(parse_rational("30000/1001"), Some(29.97002997002997));
824 assert_eq!(parse_rational("25"), Some(25.0));
825 assert_eq!(parse_rational("0/1"), Some(0.0));
826 assert_eq!(parse_rational("1/0"), None);
827 }
828}
829
830impl Default for StreamInfo {
831 fn default() -> Self {
832 Self {
833 index: 0,
834 codec_name: None,
835 codec_long_name: None,
836 profile: None,
837 codec_type: None,
838 codec_tag_string: None,
839 codec_tag: None,
840 width: None,
841 height: None,
842 coded_width: None,
843 coded_height: None,
844 has_b_frames: None,
845 sample_aspect_ratio: None,
846 display_aspect_ratio: None,
847 pix_fmt: None,
848 level: None,
849 color_range: None,
850 color_space: None,
851 color_transfer: None,
852 color_primaries: None,
853 chroma_location: None,
854 field_order: None,
855 refs: None,
856 is_avc: None,
857 nal_length_size: None,
858 sample_fmt: None,
859 sample_rate: None,
860 channels: None,
861 channel_layout: None,
862 bits_per_sample: None,
863 id: None,
864 r_frame_rate: None,
865 avg_frame_rate: None,
866 time_base: None,
867 start_pts: None,
868 start_time: None,
869 duration_ts: None,
870 duration: None,
871 bit_rate: None,
872 max_bit_rate: None,
873 bits_per_raw_sample: None,
874 nb_frames: None,
875 nb_read_frames: None,
876 nb_read_packets: None,
877 disposition: None,
878 tags: HashMap::new(),
879 }
880 }
881}