ez_ffmpeg/core/
stream_info.rs

1use std::collections::HashMap;
2use std::ffi::{CStr, CString};
3use std::ptr::{null, null_mut};
4
5#[cfg(not(feature = "docs-rs"))]
6use ffmpeg_sys_next::AVChannelOrder;
7use ffmpeg_sys_next::AVMediaType::{
8    AVMEDIA_TYPE_ATTACHMENT, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_DATA, AVMEDIA_TYPE_SUBTITLE,
9    AVMEDIA_TYPE_UNKNOWN, AVMEDIA_TYPE_VIDEO,
10};
11use ffmpeg_sys_next::{
12    av_dict_get, av_find_best_stream, avcodec_get_name, avformat_find_stream_info, AVCodecID,
13    AVDictionary, AVDictionaryEntry, AVRational, AV_DICT_IGNORE_SUFFIX,
14};
15use ffmpeg_sys_next::{avformat_alloc_context, avformat_close_input, avformat_open_input};
16use crate::core::context::AVFormatContextBox;
17use crate::error::{FindStreamError, OpenInputError, Result};
18
19#[derive(Debug, Clone)]
20pub enum StreamInfo {
21    /// Video stream information
22    Video {
23        // from AVStream
24        /// The index of the stream within the media file.
25        index: i32,
26
27        /// The time base for the stream, representing the unit of time for each frame or packet.
28        time_base: AVRational,
29
30        /// The start time of the stream, in `time_base` units.
31        start_time: i64,
32
33        /// The total duration of the stream, in `time_base` units.
34        duration: i64,
35
36        /// The total number of frames in the video stream.
37        nb_frames: i64,
38
39        /// The raw frame rate (frames per second) of the video stream, represented as a rational number.
40        r_frame_rate: AVRational,
41
42        /// The sample aspect ratio of the video frames, which represents the shape of individual pixels.
43        sample_aspect_ratio: AVRational,
44
45        /// Metadata associated with the video stream, such as title, language, etc.
46        metadata: HashMap<String, String>,
47
48        /// The average frame rate of the stream, potentially accounting for variable frame rates.
49        avg_frame_rate: AVRational,
50
51        // from AVCodecParameters
52        /// The codec identifier (e.g., `AV_CODEC_ID_H264`) used to decode the video stream.
53        codec_id: AVCodecID,
54
55        /// A human-readable name of the codec used for the video stream.
56        codec_name: String,
57
58        /// The width of the video frame in pixels.
59        width: i32,
60
61        /// The height of the video frame in pixels.
62        height: i32,
63
64        /// The bitrate of the video stream, measured in bits per second (bps).
65        bit_rate: i64,
66
67        /// The pixel format of the video stream (e.g., `AV_PIX_FMT_YUV420P`).
68        pixel_format: i32,
69
70        /// Delay introduced by the video codec, measured in frames.
71        video_delay: i32,
72
73        /// The frames per second (FPS) of the video stream, represented as a floating point number.
74        /// It is calculated from the `avg_framerate` field (avg_framerate.num / avg_framerate.den).
75        fps: f64,
76
77        /// The rotation of the video stream in degrees. This value is retrieved from the metadata.
78        /// Common values are 0, 90, 180, and 270.
79        rotate: i32,
80    },
81    /// Audio stream information
82    Audio {
83        // from AVStream
84        /// The index of the audio stream within the media file.
85        index: i32,
86
87        /// The time base for the stream, representing the unit of time for each audio packet.
88        time_base: AVRational,
89
90        /// The start time of the audio stream, in `time_base` units.
91        start_time: i64,
92
93        /// The total duration of the audio stream, in `time_base` units.
94        duration: i64,
95
96        /// The total number of frames in the audio stream.
97        nb_frames: i64,
98
99        /// Metadata associated with the audio stream, such as language, title, etc.
100        metadata: HashMap<String, String>,
101
102        /// The average frame rate of the audio stream, which might not always be applicable for audio streams.
103        avg_frame_rate: AVRational,
104
105        // from AVCodecParameters
106        /// The codec identifier used to decode the audio stream (e.g., `AV_CODEC_ID_AAC`).
107        codec_id: AVCodecID,
108
109        /// A human-readable name of the codec used for the audio stream.
110        codec_name: String,
111
112        /// The audio sample rate, measured in samples per second (Hz).
113        sample_rate: i32,
114
115        /// Channel order used in this layout.
116        #[cfg(not(feature = "docs-rs"))]
117        order: AVChannelOrder,
118
119        /// Number of channels in this layout.
120        nb_channels: i32,
121
122        /// The bitrate of the audio stream, measured in bits per second (bps).
123        bit_rate: i64,
124
125        /// The format of the audio samples (e.g., `AV_SAMPLE_FMT_FLTP` for planar float samples).
126        sample_format: i32,
127
128        /// The size of each audio frame, typically representing the number of samples per channel in one frame.
129        frame_size: i32,
130    },
131    /// Subtitle stream information
132    Subtitle {
133        // from AVStream
134        /// The index of the subtitle stream within the media file.
135        index: i32,
136
137        /// The time base for the stream, representing the unit of time for each subtitle event.
138        time_base: AVRational,
139
140        /// The start time of the subtitle stream, in `time_base` units.
141        start_time: i64,
142
143        /// The total duration of the subtitle stream, in `time_base` units.
144        duration: i64,
145
146        /// The total number of subtitle events in the stream.
147        nb_frames: i64,
148
149        /// Metadata associated with the subtitle stream, such as language.
150        metadata: HashMap<String, String>,
151
152        // from AVCodecParameters
153        /// The codec identifier used to decode the subtitle stream (e.g., `AV_CODEC_ID_ASS`).
154        codec_id: AVCodecID,
155
156        /// A human-readable name of the codec used for the subtitle stream.
157        codec_name: String,
158    },
159    /// Data stream information
160    Data {
161        // From AVStream
162        /// The index of the data stream within the media file.
163        index: i32,
164
165        /// The time base for the data stream, representing the unit of time for each data packet.
166        time_base: AVRational,
167
168        /// The start time of the data stream, in `time_base` units.
169        start_time: i64,
170
171        /// The total duration of the data stream, in `time_base` units.
172        duration: i64,
173
174        /// Metadata associated with the data stream, such as additional information about the stream content.
175        metadata: HashMap<String, String>,
176    },
177    /// Attachment stream information
178    Attachment {
179        // From AVStream
180        /// The index of the attachment stream within the media file.
181        index: i32,
182
183        /// Metadata associated with the attachment stream, such as details about the attached file.
184        metadata: HashMap<String, String>,
185
186        // From AVCodecParameters
187        /// The codec identifier used to decode the attachment stream (e.g., `AV_CODEC_ID_PNG` for images).
188        codec_id: AVCodecID,
189
190        /// A human-readable name of the codec used for the attachment stream.
191        codec_name: String,
192    },
193    // Unknown stream information
194    Unknown {
195        // From AVStream
196        /// The index of the unknown stream within the media file.
197        index: i32,
198
199        /// Metadata associated with the unknown stream, which might provide further information about the stream.
200        metadata: HashMap<String, String>,
201    },
202}
203
204impl StreamInfo {
205    pub fn stream_type(&self) -> &'static str {
206        match self {
207            StreamInfo::Video { .. } => "Video",
208            StreamInfo::Audio { .. } => "Audio",
209            StreamInfo::Subtitle { .. } => "Subtitle",
210            StreamInfo::Data { .. } => "Data",
211            StreamInfo::Attachment { .. } => "Attachment",
212            StreamInfo::Unknown { .. } => "Unknown",
213        }
214    }
215}
216
217/// Retrieves video stream information from a given media URL.
218///
219/// This function opens the media file or stream specified by the URL and
220/// searches for the best video stream. If a video stream is found, it
221/// returns the relevant metadata and codec parameters wrapped in a
222/// `StreamInfo::Video` enum variant.
223///
224/// # Parameters
225/// - `url`: The URL or file path of the media file to analyze.
226///
227/// # Returns
228/// - `Ok(Some(StreamInfo::Video))`: Contains the video stream information if found.
229/// - `Ok(None)`: Returned if no video stream is found.
230/// - `Err`: If an error occurs during the operation (e.g., file cannot be opened or stream information cannot be found).
231pub fn find_video_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
232    let in_fmt_ctx_box = init_format_context(url)?;
233
234    unsafe {
235        let video_index =
236            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, null_mut(), 0);
237        if video_index < 0 {
238            return Ok(None);
239        }
240        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
241        let video_stream = *streams.offset(video_index as isize);
242
243        let index = (*video_stream).index;
244        let time_base = (*video_stream).time_base;
245        let start_time = (*video_stream).start_time;
246        let duration = (*video_stream).duration;
247        let nb_frames = (*video_stream).nb_frames;
248        let r_frame_rate = (*video_stream).r_frame_rate;
249        let sample_aspect_ratio = (*video_stream).sample_aspect_ratio;
250        let metadata = (*video_stream).metadata;
251        let metadata = av_dict_to_hashmap(metadata);
252        let avg_frame_rate = (*video_stream).avg_frame_rate;
253
254        let codec_parameters = (*video_stream).codecpar;
255        let codec_id = (*codec_parameters).codec_id;
256        let codec_name = CStr::from_ptr(avcodec_get_name(codec_id));
257        let codec_name = codec_name.to_str().unwrap_or("Unknown codec");
258        let width = (*codec_parameters).width;
259        let height = (*codec_parameters).height;
260        let bit_rate = (*codec_parameters).bit_rate;
261        let pixel_format = (*codec_parameters).format;
262        let video_delay = (*codec_parameters).video_delay;
263        let fps = if avg_frame_rate.den == 0 {
264            0.0
265        } else {
266            avg_frame_rate.num as f64 / avg_frame_rate.den as f64
267        };
268
269        // Fetch the rotation info from metadata (if present)
270        let rotate = metadata
271            .get("rotate")
272            .and_then(|rotate| rotate.parse::<i32>().ok())
273            .unwrap_or(0); // Default to 0 if no "rotate" key is found
274
275        let video_stream_info = StreamInfo::Video {
276            index,
277            time_base,
278            start_time,
279            duration,
280            nb_frames,
281            r_frame_rate,
282            sample_aspect_ratio,
283            metadata,
284            avg_frame_rate,
285            codec_id,
286            codec_name: codec_name.to_string(),
287            width,
288            height,
289            bit_rate,
290            pixel_format,
291            video_delay,
292            fps,
293            rotate,
294        };
295
296        Ok(Some(video_stream_info))
297    }
298}
299
300/// Retrieves audio stream information from a given media URL.
301///
302/// This function opens the media file or stream specified by the URL and
303/// searches for the best audio stream. If an audio stream is found, it
304/// returns the relevant metadata and codec parameters wrapped in a
305/// `StreamInfo::Audio` enum variant.
306///
307/// # Parameters
308/// - `url`: The URL or file path of the media file to analyze.
309///
310/// # Returns
311/// - `Ok(Some(StreamInfo::Audio))`: Contains the audio stream information if found.
312/// - `Ok(None)`: Returned if no audio stream is found.
313/// - `Err`: If an error occurs during the operation (e.g., file cannot be opened or stream information cannot be found).
314pub fn find_audio_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
315    let in_fmt_ctx_box = init_format_context(url)?;
316
317    unsafe {
318        let audio_index =
319            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, null_mut(), 0);
320        if audio_index < 0 {
321            return Ok(None);
322        }
323        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
324        let audio_stream = *streams.offset(audio_index as isize);
325
326        let index = (*audio_stream).index;
327        let time_base = (*audio_stream).time_base;
328        let start_time = (*audio_stream).start_time;
329        let duration = (*audio_stream).duration;
330        let nb_frames = (*audio_stream).nb_frames;
331        let metadata = (*audio_stream).metadata;
332        let metadata = av_dict_to_hashmap(metadata);
333        let avg_frame_rate = (*audio_stream).avg_frame_rate;
334
335        let codec_parameters = (*audio_stream).codecpar;
336        let codec_id = (*codec_parameters).codec_id;
337        let codec_name = CStr::from_ptr(avcodec_get_name(codec_id));
338        let codec_name = codec_name.to_str().unwrap_or("Unknown codec");
339        let sample_rate = (*codec_parameters).sample_rate;
340        #[cfg(not(feature = "docs-rs"))]
341        let ch_layout = (*codec_parameters).ch_layout;
342        let bit_rate = (*codec_parameters).bit_rate;
343        let sample_format = (*codec_parameters).format;
344        let frame_size = (*codec_parameters).frame_size;
345
346        let audio_stream_info = StreamInfo::Audio {
347            index,
348            time_base,
349            start_time,
350            duration,
351            nb_frames,
352            metadata,
353            avg_frame_rate,
354            codec_id,
355            codec_name: codec_name.to_string(),
356            sample_rate,
357            #[cfg(not(feature = "docs-rs"))]
358            order: ch_layout.order,
359            #[cfg(feature = "docs-rs")]
360            nb_channels: 0,
361            #[cfg(not(feature = "docs-rs"))]
362            nb_channels: ch_layout.nb_channels,
363            bit_rate,
364            sample_format,
365            frame_size,
366        };
367
368        Ok(Some(audio_stream_info))
369    }
370}
371
372/// Retrieves subtitle stream information from a given media URL.
373///
374/// This function opens the media file or stream specified by the URL and
375/// searches for the best subtitle stream. If a subtitle stream is found, it
376/// returns the relevant metadata and codec parameters wrapped in a
377/// `StreamInfo::Subtitle` enum variant. It also attempts to retrieve any
378/// language information from the stream metadata.
379///
380/// # Parameters
381/// - `url`: The URL or file path of the media file to analyze.
382///
383/// # Returns
384/// - `Ok(Some(StreamInfo::Subtitle))`: Contains the subtitle stream information if found.
385/// - `Ok(None)`: Returned if no subtitle stream is found.
386/// - `Err`: If an error occurs during the operation (e.g., file cannot be opened or stream information cannot be found).
387pub fn find_subtitle_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
388    let in_fmt_ctx_box = init_format_context(url)?;
389
390    unsafe {
391        let subtitle_index =
392            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, null_mut(), 0);
393        if subtitle_index < 0 {
394            return Ok(None);
395        }
396
397        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
398        let subtitle_stream = *streams.offset(subtitle_index as isize);
399
400        let index = (*subtitle_stream).index;
401        let time_base = (*subtitle_stream).time_base;
402        let start_time = (*subtitle_stream).start_time;
403        let duration = (*subtitle_stream).duration;
404        let nb_frames = (*subtitle_stream).nb_frames;
405        let metadata = (*subtitle_stream).metadata;
406        let metadata = av_dict_to_hashmap(metadata);
407
408        let codec_parameters = (*subtitle_stream).codecpar;
409        let codec_id = (*codec_parameters).codec_id;
410        let codec_name = CStr::from_ptr(avcodec_get_name(codec_id));
411        let codec_name = codec_name.to_str().unwrap_or("Unknown codec");
412
413        let subtitle_stream_info = StreamInfo::Subtitle {
414            index,
415            time_base,
416            start_time,
417            duration,
418            nb_frames,
419            metadata,
420            codec_id,
421            codec_name: codec_name.to_string(),
422        };
423
424        Ok(Some(subtitle_stream_info))
425    }
426}
427
428/// Finds the data stream information from the given media URL.
429///
430/// This function opens the media file or stream specified by the URL and
431/// searches for a data stream (`AVMEDIA_TYPE_DATA`). It returns relevant metadata
432/// wrapped in a `StreamInfo::Data` enum variant.
433///
434/// # Parameters
435/// - `url`: The URL or file path of the media file.
436///
437/// # Returns
438/// - `Ok(Some(StreamInfo::Data))`: Contains the data stream information if found.
439/// - `Ok(None)`: Returned if no data stream is found.
440/// - `Err`: If an error occurs during the operation.
441pub fn find_data_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
442    let in_fmt_ctx_box = init_format_context(url)?;
443
444    unsafe {
445        let data_index = av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_DATA, -1, -1, null_mut(), 0);
446        if data_index < 0 {
447            return Ok(None);
448        }
449
450        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
451        let data_stream = *streams.offset(data_index as isize);
452
453        let index = (*data_stream).index;
454        let time_base = (*data_stream).time_base;
455        let start_time = (*data_stream).start_time;
456        let duration = (*data_stream).duration;
457        let metadata = av_dict_to_hashmap((*data_stream).metadata);
458
459        Ok(Some(StreamInfo::Data {
460            index,
461            time_base,
462            start_time,
463            duration,
464            metadata,
465        }))
466    }
467}
468
469/// Finds the attachment stream information from the given media URL.
470///
471/// This function opens the media file or stream specified by the URL and
472/// searches for an attachment stream (`AVMEDIA_TYPE_ATTACHMENT`). It returns
473/// relevant metadata and codec information wrapped in a `StreamInfo::Attachment`
474/// enum variant.
475///
476/// # Parameters
477/// - `url`: The URL or file path of the media file.
478///
479/// # Returns
480/// - `Ok(Some(StreamInfo::Attachment))`: Contains the attachment stream information if found.
481/// - `Ok(None)`: Returned if no attachment stream is found.
482/// - `Err`: If an error occurs during the operation.
483pub fn find_attachment_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
484    let in_fmt_ctx_box = init_format_context(url)?;
485
486    unsafe {
487        let attachment_index =
488            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_ATTACHMENT, -1, -1, null_mut(), 0);
489        if attachment_index < 0 {
490            return Ok(None);
491        }
492
493        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
494        let attachment_stream = *streams.offset(attachment_index as isize);
495
496        let index = (*attachment_stream).index;
497        let metadata = av_dict_to_hashmap((*attachment_stream).metadata);
498
499        let codec_parameters = (*attachment_stream).codecpar;
500        let codec_id = (*codec_parameters).codec_id;
501        let codec_name = CStr::from_ptr(avcodec_get_name(codec_id))
502            .to_str()
503            .unwrap_or("Unknown codec")
504            .to_string();
505
506        Ok(Some(StreamInfo::Attachment {
507            index,
508            metadata,
509            codec_id,
510            codec_name,
511        }))
512    }
513}
514
515/// Finds the unknown stream information from the given media URL.
516///
517/// This function opens the media file or stream specified by the URL and
518/// searches for any unknown stream (`AVMEDIA_TYPE_UNKNOWN`). It returns
519/// relevant metadata wrapped in a `StreamInfo::Unknown` enum variant.
520///
521/// # Parameters
522/// - `url`: The URL or file path of the media file.
523///
524/// # Returns
525/// - `Ok(Some(StreamInfo::Unknown))`: Contains the unknown stream information if found.
526/// - `Ok(None)`: Returned if no unknown stream is found.
527/// - `Err`: If an error occurs during the operation.
528pub fn find_unknown_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
529    let in_fmt_ctx_box = init_format_context(url)?;
530
531    unsafe {
532        let unknown_index =
533            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_UNKNOWN, -1, -1, null_mut(), 0);
534        if unknown_index < 0 {
535            return Ok(None);
536        }
537
538        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
539        let unknown_stream = *streams.offset(unknown_index as isize);
540
541        let index = (*unknown_stream).index;
542        let metadata = av_dict_to_hashmap((*unknown_stream).metadata);
543
544        Ok(Some(StreamInfo::Unknown { index, metadata }))
545    }
546}
547
548/// Retrieves information for all streams (video, audio, subtitle, etc.) from a given media URL.
549///
550/// This function opens the media file or stream specified by the URL and
551/// retrieves information for all available streams (e.g., video, audio, subtitles).
552/// The information for each stream is wrapped in a corresponding `StreamInfo` enum
553/// variant and collected into a `Vec<StreamInfo>`.
554///
555/// # Parameters
556/// - `url`: The URL or file path of the media file to analyze.
557///
558/// # Returns
559/// - `Ok(Vec<StreamInfo>)`: A vector containing information for all detected streams.
560/// - `Err`: If an error occurs during the operation (e.g., file cannot be opened or stream information cannot be found).
561pub fn find_all_stream_infos(url: impl Into<String>) -> Result<Vec<StreamInfo>> {
562    let in_fmt_ctx_box = init_format_context(url)?;
563
564    unsafe {
565        let mut stream_infos = Vec::new();
566
567        let stream_count = (*in_fmt_ctx_box.fmt_ctx).nb_streams;
568
569        for i in 0..stream_count {
570            let stream = *(*in_fmt_ctx_box.fmt_ctx).streams.add(i as usize);
571            let codec_parameters = (*stream).codecpar;
572            let codec_id = (*codec_parameters).codec_id;
573            let codec_name = CStr::from_ptr(avcodec_get_name(codec_id))
574                .to_str()
575                .unwrap_or("Unknown codec")
576                .to_string();
577
578            let index = (*stream).index;
579            let time_base = (*stream).time_base;
580            let start_time = (*stream).start_time;
581            let duration = (*stream).duration;
582            let nb_frames = (*stream).nb_frames;
583            let avg_frame_rate = (*stream).avg_frame_rate;
584            let metadata = av_dict_to_hashmap((*stream).metadata);
585
586            match (*codec_parameters).codec_type {
587                AVMEDIA_TYPE_VIDEO => {
588                    let width = (*codec_parameters).width;
589                    let height = (*codec_parameters).height;
590                    let bit_rate = (*codec_parameters).bit_rate;
591                    let pixel_format = (*codec_parameters).format;
592                    let video_delay = (*codec_parameters).video_delay;
593                    let r_frame_rate = (*stream).r_frame_rate;
594                    let sample_aspect_ratio = (*stream).sample_aspect_ratio;
595                    let fps = if avg_frame_rate.den == 0 {
596                        0.0
597                    } else {
598                        avg_frame_rate.num as f64 / avg_frame_rate.den as f64
599                    };
600
601                    // Fetch the rotation info from metadata (if present)
602                    let rotate = metadata
603                        .get("rotate")
604                        .and_then(|rotate| rotate.parse::<i32>().ok())
605                        .unwrap_or(0); // Default to 0 if no "rotate" key is found
606
607                    stream_infos.push(StreamInfo::Video {
608                        index,
609                        time_base,
610                        start_time,
611                        duration,
612                        nb_frames,
613                        r_frame_rate,
614                        sample_aspect_ratio,
615                        metadata,
616                        avg_frame_rate,
617                        codec_id,
618                        codec_name,
619                        width,
620                        height,
621                        bit_rate,
622                        pixel_format,
623                        video_delay,
624                        fps,
625                        rotate,
626                    });
627                }
628                AVMEDIA_TYPE_AUDIO => {
629                    let sample_rate = (*codec_parameters).sample_rate;
630                    #[cfg(not(feature = "docs-rs"))]
631                    let ch_layout = (*codec_parameters).ch_layout;
632                    let sample_format = (*codec_parameters).format;
633                    let frame_size = (*codec_parameters).frame_size;
634                    let bit_rate = (*codec_parameters).bit_rate;
635
636                    stream_infos.push(StreamInfo::Audio {
637                        index,
638                        time_base,
639                        start_time,
640                        duration,
641                        nb_frames,
642                        metadata,
643                        avg_frame_rate,
644                        codec_id,
645                        codec_name,
646                        sample_rate,
647                        #[cfg(not(feature = "docs-rs"))]
648                        order: ch_layout.order,
649                        #[cfg(feature = "docs-rs")]
650                        nb_channels: 0,
651                        #[cfg(not(feature = "docs-rs"))]
652                        nb_channels: ch_layout.nb_channels,
653                        bit_rate,
654                        sample_format,
655                        frame_size,
656                    });
657                }
658                AVMEDIA_TYPE_SUBTITLE => {
659                    stream_infos.push(StreamInfo::Subtitle {
660                        index,
661                        time_base,
662                        start_time,
663                        duration,
664                        nb_frames,
665                        metadata,
666                        codec_id,
667                        codec_name,
668                    });
669                }
670                AVMEDIA_TYPE_DATA => {
671                    stream_infos.push(StreamInfo::Data {
672                        index,
673                        time_base,
674                        start_time,
675                        duration,
676                        metadata,
677                    });
678                }
679                AVMEDIA_TYPE_ATTACHMENT => {
680                    stream_infos.push(StreamInfo::Attachment {
681                        index,
682                        metadata,
683                        codec_id,
684                        codec_name,
685                    });
686                }
687                AVMEDIA_TYPE_UNKNOWN => {
688                    stream_infos.push(StreamInfo::Unknown { index, metadata });
689                }
690                _ => {}
691            }
692        }
693
694        Ok(stream_infos)
695    }
696}
697
698fn init_format_context(url: impl Into<String>) -> Result<AVFormatContextBox> {
699    unsafe {
700        let mut in_fmt_ctx = avformat_alloc_context();
701        if in_fmt_ctx.is_null() {
702            return Err(OpenInputError::OutOfMemory.into());
703        }
704
705        let url_cstr = CString::new(url.into())?;
706
707        let mut format_opts = null_mut();
708        let scan_all_pmts_key = CString::new("scan_all_pmts")?;
709        if av_dict_get(
710            format_opts,
711            scan_all_pmts_key.as_ptr(),
712            null(),
713            ffmpeg_sys_next::AV_DICT_MATCH_CASE,
714        )
715        .is_null()
716        {
717            let scan_all_pmts_value = CString::new("1")?;
718            ffmpeg_sys_next::av_dict_set(
719                &mut format_opts,
720                scan_all_pmts_key.as_ptr(),
721                scan_all_pmts_value.as_ptr(),
722                ffmpeg_sys_next::AV_DICT_DONT_OVERWRITE,
723            );
724        };
725
726        #[cfg(not(feature = "docs-rs"))]
727        let mut ret =
728            { avformat_open_input(&mut in_fmt_ctx, url_cstr.as_ptr(), null(), &mut format_opts) };
729        #[cfg(feature = "docs-rs")]
730        let mut ret = 0;
731
732        if ret < 0 {
733            avformat_close_input(&mut in_fmt_ctx);
734            return Err(OpenInputError::from(ret).into());
735        }
736
737        ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
738        if ret < 0 {
739            avformat_close_input(&mut in_fmt_ctx);
740            return Err(FindStreamError::from(ret).into());
741        }
742
743        Ok(AVFormatContextBox::new(in_fmt_ctx, true, false))
744    }
745}
746
747unsafe fn av_dict_to_hashmap(dict: *mut AVDictionary) -> HashMap<String, String> {
748    let mut map = HashMap::new();
749    let mut entry: *mut AVDictionaryEntry = null_mut();
750
751    while {
752        entry = av_dict_get(dict, null(), entry, AV_DICT_IGNORE_SUFFIX);
753        !entry.is_null()
754    } {
755        let key = CStr::from_ptr((*entry).key).to_string_lossy().into_owned();
756        let value = CStr::from_ptr((*entry).value)
757            .to_string_lossy()
758            .into_owned();
759
760        map.insert(key, value);
761    }
762
763    map
764}
765
766#[cfg(test)]
767mod tests {
768    use super::*;
769
770    #[test]
771    fn test_not_found() {
772        let result = find_all_stream_infos("not_found.mp4");
773        assert!(result.is_err());
774
775        let error = result.err().unwrap();
776        println!("{error}");
777        assert!(matches!(
778            error,
779            crate::error::Error::OpenInputStream(OpenInputError::NotFound)
780        ))
781    }
782
783    #[test]
784    fn test_find_all_stream_infos() {
785        let stream_infos = find_all_stream_infos("test.mp4").unwrap();
786        assert_eq!(2, stream_infos.len());
787        for stream_info in stream_infos {
788            println!("{:?}", stream_info);
789        }
790    }
791
792    #[test]
793    fn test_find_video_stream_info() {
794        let option = find_video_stream_info("test.mp4").unwrap();
795        assert!(option.is_some());
796        let video_stream_info = option.unwrap();
797        println!("video_stream_info:{:?}", video_stream_info);
798    }
799
800    #[test]
801    fn test_find_audio_stream_info() {
802        let option = find_audio_stream_info("test.mp4").unwrap();
803        assert!(option.is_some());
804        let audio_stream_info = option.unwrap();
805        println!("audio_stream_info:{:?}", audio_stream_info);
806    }
807
808    #[test]
809    fn test_find_subtitle_stream_info() {
810        let option = find_subtitle_stream_info("test.mp4").unwrap();
811        assert!(option.is_none())
812    }
813
814    #[test]
815    fn test_find_data_stream_info() {
816        let option = find_data_stream_info("test.mp4").unwrap();
817        assert!(option.is_none());
818    }
819
820    #[test]
821    fn test_find_attachment_stream_info() {
822        let option = find_attachment_stream_info("test.mp4").unwrap();
823        assert!(option.is_none())
824    }
825
826    #[test]
827    fn test_find_unknown_stream_info() {
828        let option = find_unknown_stream_info("test.mp4").unwrap();
829        assert!(option.is_none())
830    }
831}