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        if video_stream.is_null() {
243            return Err(FindStreamError::NoStreamFound.into());
244        }
245
246        let index = (*video_stream).index;
247        let time_base = (*video_stream).time_base;
248        let start_time = (*video_stream).start_time;
249        let duration = (*video_stream).duration;
250        let nb_frames = (*video_stream).nb_frames;
251        let r_frame_rate = (*video_stream).r_frame_rate;
252        let sample_aspect_ratio = (*video_stream).sample_aspect_ratio;
253        let metadata = (*video_stream).metadata;
254        let metadata = dict_to_hashmap(metadata);
255        let avg_frame_rate = (*video_stream).avg_frame_rate;
256
257        let codec_parameters = (*video_stream).codecpar;
258        if codec_parameters.is_null() {
259            return Err(FindStreamError::NoCodecparFound.into());
260        }
261        let codec_id = (*codec_parameters).codec_id;
262        let codec_name = codec_name(codec_id);
263        let width = (*codec_parameters).width;
264        let height = (*codec_parameters).height;
265        let bit_rate = (*codec_parameters).bit_rate;
266        let pixel_format = (*codec_parameters).format;
267        let video_delay = (*codec_parameters).video_delay;
268        let fps = if avg_frame_rate.den == 0 {
269            0.0
270        } else {
271            avg_frame_rate.num as f64 / avg_frame_rate.den as f64
272        };
273
274        // Fetch the rotation info from metadata (if present)
275        let rotate = metadata
276            .get("rotate")
277            .and_then(|rotate| rotate.parse::<i32>().ok())
278            .unwrap_or(0); // Default to 0 if no "rotate" key is found
279
280        let video_stream_info = StreamInfo::Video {
281            index,
282            time_base,
283            start_time,
284            duration,
285            nb_frames,
286            r_frame_rate,
287            sample_aspect_ratio,
288            metadata,
289            avg_frame_rate,
290            codec_id,
291            codec_name,
292            width,
293            height,
294            bit_rate,
295            pixel_format,
296            video_delay,
297            fps,
298            rotate,
299        };
300
301        Ok(Some(video_stream_info))
302    }
303}
304
305/// Retrieves audio stream information from a given media URL.
306///
307/// This function opens the media file or stream specified by the URL and
308/// searches for the best audio stream. If an audio stream is found, it
309/// returns the relevant metadata and codec parameters wrapped in a
310/// `StreamInfo::Audio` enum variant.
311///
312/// # Parameters
313/// - `url`: The URL or file path of the media file to analyze.
314///
315/// # Returns
316/// - `Ok(Some(StreamInfo::Audio))`: Contains the audio stream information if found.
317/// - `Ok(None)`: Returned if no audio stream is found.
318/// - `Err`: If an error occurs during the operation (e.g., file cannot be opened or stream information cannot be found).
319pub fn find_audio_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
320    let in_fmt_ctx_box = init_format_context(url)?;
321
322    unsafe {
323        let audio_index =
324            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, null_mut(), 0);
325        if audio_index < 0 {
326            return Ok(None);
327        }
328        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
329        let audio_stream = *streams.offset(audio_index as isize);
330        if audio_stream.is_null() {
331            return Err(FindStreamError::NoStreamFound.into());
332        }
333
334        let index = (*audio_stream).index;
335        let time_base = (*audio_stream).time_base;
336        let start_time = (*audio_stream).start_time;
337        let duration = (*audio_stream).duration;
338        let nb_frames = (*audio_stream).nb_frames;
339        let metadata = (*audio_stream).metadata;
340        let metadata = dict_to_hashmap(metadata);
341        let avg_frame_rate = (*audio_stream).avg_frame_rate;
342
343        let codec_parameters = (*audio_stream).codecpar;
344        if codec_parameters.is_null() {
345            return Err(FindStreamError::NoCodecparFound.into());
346        }
347        let codec_id = (*codec_parameters).codec_id;
348        let codec_name = codec_name(codec_id);
349        let sample_rate = (*codec_parameters).sample_rate;
350        #[cfg(not(feature = "docs-rs"))]
351        let ch_layout = (*codec_parameters).ch_layout;
352        let bit_rate = (*codec_parameters).bit_rate;
353        let sample_format = (*codec_parameters).format;
354        let frame_size = (*codec_parameters).frame_size;
355
356        let audio_stream_info = StreamInfo::Audio {
357            index,
358            time_base,
359            start_time,
360            duration,
361            nb_frames,
362            metadata,
363            avg_frame_rate,
364            codec_id,
365            codec_name,
366            sample_rate,
367            #[cfg(not(feature = "docs-rs"))]
368            order: ch_layout.order,
369            #[cfg(feature = "docs-rs")]
370            nb_channels: 0,
371            #[cfg(not(feature = "docs-rs"))]
372            nb_channels: ch_layout.nb_channels,
373            bit_rate,
374            sample_format,
375            frame_size,
376        };
377
378        Ok(Some(audio_stream_info))
379    }
380}
381
382/// Retrieves subtitle stream information from a given media URL.
383///
384/// This function opens the media file or stream specified by the URL and
385/// searches for the best subtitle stream. If a subtitle stream is found, it
386/// returns the relevant metadata and codec parameters wrapped in a
387/// `StreamInfo::Subtitle` enum variant. It also attempts to retrieve any
388/// language information from the stream metadata.
389///
390/// # Parameters
391/// - `url`: The URL or file path of the media file to analyze.
392///
393/// # Returns
394/// - `Ok(Some(StreamInfo::Subtitle))`: Contains the subtitle stream information if found.
395/// - `Ok(None)`: Returned if no subtitle stream is found.
396/// - `Err`: If an error occurs during the operation (e.g., file cannot be opened or stream information cannot be found).
397pub fn find_subtitle_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
398    let in_fmt_ctx_box = init_format_context(url)?;
399
400    unsafe {
401        let subtitle_index =
402            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, null_mut(), 0);
403        if subtitle_index < 0 {
404            return Ok(None);
405        }
406
407        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
408        let subtitle_stream = *streams.offset(subtitle_index as isize);
409        if subtitle_stream.is_null() {
410            return Err(FindStreamError::NoStreamFound.into());
411        }
412
413        let index = (*subtitle_stream).index;
414        let time_base = (*subtitle_stream).time_base;
415        let start_time = (*subtitle_stream).start_time;
416        let duration = (*subtitle_stream).duration;
417        let nb_frames = (*subtitle_stream).nb_frames;
418        let metadata = (*subtitle_stream).metadata;
419        let metadata = dict_to_hashmap(metadata);
420
421        let codec_parameters = (*subtitle_stream).codecpar;
422        if codec_parameters.is_null() {
423            return Err(FindStreamError::NoCodecparFound.into());
424        }
425        let codec_id = (*codec_parameters).codec_id;
426        let codec_name = codec_name(codec_id);
427
428        let subtitle_stream_info = StreamInfo::Subtitle {
429            index,
430            time_base,
431            start_time,
432            duration,
433            nb_frames,
434            metadata,
435            codec_id,
436            codec_name,
437        };
438
439        Ok(Some(subtitle_stream_info))
440    }
441}
442
443/// Finds the data stream information from the given media URL.
444///
445/// This function opens the media file or stream specified by the URL and
446/// searches for a data stream (`AVMEDIA_TYPE_DATA`). It returns relevant metadata
447/// wrapped in a `StreamInfo::Data` enum variant.
448///
449/// # Parameters
450/// - `url`: The URL or file path of the media file.
451///
452/// # Returns
453/// - `Ok(Some(StreamInfo::Data))`: Contains the data stream information if found.
454/// - `Ok(None)`: Returned if no data stream is found.
455/// - `Err`: If an error occurs during the operation.
456pub fn find_data_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
457    let in_fmt_ctx_box = init_format_context(url)?;
458
459    unsafe {
460        let data_index = av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_DATA, -1, -1, null_mut(), 0);
461        if data_index < 0 {
462            return Ok(None);
463        }
464
465        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
466        let data_stream = *streams.offset(data_index as isize);
467        if data_stream.is_null() {
468            return Err(FindStreamError::NoStreamFound.into());
469        }
470
471        let index = (*data_stream).index;
472        let time_base = (*data_stream).time_base;
473        let start_time = (*data_stream).start_time;
474        let duration = (*data_stream).duration;
475        let metadata = dict_to_hashmap((*data_stream).metadata);
476
477        Ok(Some(StreamInfo::Data {
478            index,
479            time_base,
480            start_time,
481            duration,
482            metadata,
483        }))
484    }
485}
486
487/// Finds the attachment stream information from the given media URL.
488///
489/// This function opens the media file or stream specified by the URL and
490/// searches for an attachment stream (`AVMEDIA_TYPE_ATTACHMENT`). It returns
491/// relevant metadata and codec information wrapped in a `StreamInfo::Attachment`
492/// enum variant.
493///
494/// # Parameters
495/// - `url`: The URL or file path of the media file.
496///
497/// # Returns
498/// - `Ok(Some(StreamInfo::Attachment))`: Contains the attachment stream information if found.
499/// - `Ok(None)`: Returned if no attachment stream is found.
500/// - `Err`: If an error occurs during the operation.
501pub fn find_attachment_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
502    let in_fmt_ctx_box = init_format_context(url)?;
503
504    unsafe {
505        let attachment_index =
506            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_ATTACHMENT, -1, -1, null_mut(), 0);
507        if attachment_index < 0 {
508            return Ok(None);
509        }
510
511        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
512        let attachment_stream = *streams.offset(attachment_index as isize);
513        if attachment_stream.is_null() {
514            return Err(FindStreamError::NoStreamFound.into());
515        }
516
517        let index = (*attachment_stream).index;
518        let metadata = dict_to_hashmap((*attachment_stream).metadata);
519
520        let codec_parameters = (*attachment_stream).codecpar;
521        if codec_parameters.is_null() {
522            return Err(FindStreamError::NoCodecparFound.into());
523        }
524        let codec_id = (*codec_parameters).codec_id;
525        let codec_name = codec_name(codec_id);
526
527        Ok(Some(StreamInfo::Attachment {
528            index,
529            metadata,
530            codec_id,
531            codec_name,
532        }))
533    }
534}
535
536/// Finds the unknown stream information from the given media URL.
537///
538/// This function opens the media file or stream specified by the URL and
539/// searches for any unknown stream (`AVMEDIA_TYPE_UNKNOWN`). It returns
540/// relevant metadata wrapped in a `StreamInfo::Unknown` enum variant.
541///
542/// # Parameters
543/// - `url`: The URL or file path of the media file.
544///
545/// # Returns
546/// - `Ok(Some(StreamInfo::Unknown))`: Contains the unknown stream information if found.
547/// - `Ok(None)`: Returned if no unknown stream is found.
548/// - `Err`: If an error occurs during the operation.
549pub fn find_unknown_stream_info(url: impl Into<String>) -> Result<Option<StreamInfo>> {
550    let in_fmt_ctx_box = init_format_context(url)?;
551
552    unsafe {
553        let unknown_index =
554            av_find_best_stream(in_fmt_ctx_box.fmt_ctx, AVMEDIA_TYPE_UNKNOWN, -1, -1, null_mut(), 0);
555        if unknown_index < 0 {
556            return Ok(None);
557        }
558
559        let streams = (*in_fmt_ctx_box.fmt_ctx).streams;
560        let unknown_stream = *streams.offset(unknown_index as isize);
561        if unknown_stream.is_null() {
562            return Err(FindStreamError::NoStreamFound.into());
563        }
564
565        let index = (*unknown_stream).index;
566        let metadata = dict_to_hashmap((*unknown_stream).metadata);
567
568        Ok(Some(StreamInfo::Unknown { index, metadata }))
569    }
570}
571
572/// Retrieves information for all streams (video, audio, subtitle, etc.) from a given media URL.
573///
574/// This function opens the media file or stream specified by the URL and
575/// retrieves information for all available streams (e.g., video, audio, subtitles).
576/// The information for each stream is wrapped in a corresponding `StreamInfo` enum
577/// variant and collected into a `Vec<StreamInfo>`.
578///
579/// # Parameters
580/// - `url`: The URL or file path of the media file to analyze.
581///
582/// # Returns
583/// - `Ok(Vec<StreamInfo>)`: A vector containing information for all detected streams.
584/// - `Err`: If an error occurs during the operation (e.g., file cannot be opened or stream information cannot be found).
585pub fn find_all_stream_infos(url: impl Into<String>) -> Result<Vec<StreamInfo>> {
586    let in_fmt_ctx_box = init_format_context(url)?;
587
588    unsafe {
589        let fmt_ctx_ptr = in_fmt_ctx_box.fmt_ctx;
590        if fmt_ctx_ptr.is_null() {
591            return Err(OpenInputError::OutOfMemory.into());
592        }
593        let fmt_ctx = &*fmt_ctx_ptr;
594        let nb_streams = fmt_ctx.nb_streams as usize;
595
596        let mut infos = Vec::with_capacity(nb_streams);
597
598        for i in 0..nb_streams {
599            let raw_stream = *fmt_ctx.streams.add(i);
600            if raw_stream.is_null() {
601                infos.push(StreamInfo::Unknown {
602                    index: i as i32,
603                    metadata: HashMap::new(),
604                });
605                continue;
606            }
607            let stream = &*raw_stream;
608
609            let metadata = dict_to_hashmap(stream.metadata);
610
611            if stream.codecpar.is_null() {
612                infos.push(StreamInfo::Unknown {
613                    index: stream.index,
614                    metadata,
615                });
616                continue;
617            }
618
619            let codecpar = &*stream.codecpar;
620            let codec_id = codecpar.codec_id;
621            let codec_name = codec_name(codec_id);
622
623            let index = stream.index;
624            let time_base = stream.time_base;
625            let start_time = stream.start_time;
626            let duration = stream.duration;
627            let nb_frames = stream.nb_frames;
628            let avg_frame_rate = stream.avg_frame_rate;
629
630            match codecpar.codec_type {
631                AVMEDIA_TYPE_VIDEO => {
632                    let width = codecpar.width;
633                    let height = codecpar.height;
634                    let bit_rate = codecpar.bit_rate;
635                    let pixel_format = codecpar.format;
636                    let video_delay = codecpar.video_delay;
637                    let r_frame_rate = (*stream).r_frame_rate;
638                    let sample_aspect_ratio = (*stream).sample_aspect_ratio;
639                    let fps = if avg_frame_rate.den == 0 {
640                        0.0
641                    } else {
642                        avg_frame_rate.num as f64 / avg_frame_rate.den as f64
643                    };
644
645                    // Fetch the rotation info from metadata (if present)
646                    let rotate = metadata
647                        .get("rotate")
648                        .and_then(|rotate| rotate.parse::<i32>().ok())
649                        .unwrap_or(0); // Default to 0 if no "rotate" key is found
650
651                    infos.push(StreamInfo::Video {
652                        index,
653                        time_base,
654                        start_time,
655                        duration,
656                        nb_frames,
657                        r_frame_rate,
658                        sample_aspect_ratio,
659                        metadata,
660                        avg_frame_rate,
661                        codec_id,
662                        codec_name,
663                        width,
664                        height,
665                        bit_rate,
666                        pixel_format,
667                        video_delay,
668                        fps,
669                        rotate,
670                    });
671                }
672                AVMEDIA_TYPE_AUDIO => {
673                    let sample_rate = codecpar.sample_rate;
674                    #[cfg(not(feature = "docs-rs"))]
675                    let ch_layout = codecpar.ch_layout;
676                    let sample_format = codecpar.format;
677                    let frame_size = codecpar.frame_size;
678                    let bit_rate = codecpar.bit_rate;
679
680                    infos.push(StreamInfo::Audio {
681                        index,
682                        time_base,
683                        start_time,
684                        duration,
685                        nb_frames,
686                        metadata,
687                        avg_frame_rate,
688                        codec_id,
689                        codec_name,
690                        sample_rate,
691                        #[cfg(not(feature = "docs-rs"))]
692                        order: ch_layout.order,
693                        #[cfg(feature = "docs-rs")]
694                        nb_channels: 0,
695                        #[cfg(not(feature = "docs-rs"))]
696                        nb_channels: ch_layout.nb_channels,
697                        bit_rate,
698                        sample_format,
699                        frame_size,
700                    });
701                }
702                AVMEDIA_TYPE_SUBTITLE => {
703                    infos.push(StreamInfo::Subtitle {
704                        index,
705                        time_base,
706                        start_time,
707                        duration,
708                        nb_frames,
709                        metadata,
710                        codec_id,
711                        codec_name,
712                    });
713                }
714                AVMEDIA_TYPE_DATA => {
715                    infos.push(StreamInfo::Data {
716                        index,
717                        time_base,
718                        start_time,
719                        duration,
720                        metadata,
721                    });
722                }
723                AVMEDIA_TYPE_ATTACHMENT => {
724                    infos.push(StreamInfo::Attachment {
725                        index,
726                        metadata,
727                        codec_id,
728                        codec_name,
729                    });
730                }
731                AVMEDIA_TYPE_UNKNOWN => {
732                    infos.push(StreamInfo::Unknown { index, metadata });
733                }
734                _ => {}
735            }
736        }
737
738        if infos.iter().all(|i| matches!(i, StreamInfo::Unknown { .. })) {
739            return Err(FindStreamError::NoStreamFound.into());
740        }
741
742        Ok(infos)
743    }
744}
745
746#[inline]
747fn codec_name(id: AVCodecID) -> String {
748    unsafe {
749        let ptr = avcodec_get_name(id);
750        if ptr.is_null() {
751            "Unknown codec".into()
752        } else {
753            CStr::from_ptr(ptr).to_string_lossy().into_owned()
754        }
755    }
756}
757
758fn init_format_context(url: impl Into<String>) -> Result<AVFormatContextBox> {
759    crate::core::initialize_ffmpeg();
760
761    unsafe {
762        let mut in_fmt_ctx = avformat_alloc_context();
763        if in_fmt_ctx.is_null() {
764            return Err(OpenInputError::OutOfMemory.into());
765        }
766
767        let url_cstr = CString::new(url.into())?;
768
769        let mut format_opts = null_mut();
770        let scan_all_pmts_key = CString::new("scan_all_pmts")?;
771        if av_dict_get(
772            format_opts,
773            scan_all_pmts_key.as_ptr(),
774            null(),
775            ffmpeg_sys_next::AV_DICT_MATCH_CASE,
776        )
777        .is_null()
778        {
779            let scan_all_pmts_value = CString::new("1")?;
780            ffmpeg_sys_next::av_dict_set(
781                &mut format_opts,
782                scan_all_pmts_key.as_ptr(),
783                scan_all_pmts_value.as_ptr(),
784                ffmpeg_sys_next::AV_DICT_DONT_OVERWRITE,
785            );
786        };
787
788        #[cfg(not(feature = "docs-rs"))]
789        let mut ret =
790            { avformat_open_input(&mut in_fmt_ctx, url_cstr.as_ptr(), null(), &mut format_opts) };
791        #[cfg(feature = "docs-rs")]
792        let mut ret = 0;
793
794        if ret < 0 {
795            avformat_close_input(&mut in_fmt_ctx);
796            return Err(OpenInputError::from(ret).into());
797        }
798
799        ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
800        if ret < 0 {
801            avformat_close_input(&mut in_fmt_ctx);
802            return Err(FindStreamError::from(ret).into());
803        }
804
805        Ok(AVFormatContextBox::new(in_fmt_ctx, true, false))
806    }
807}
808
809fn dict_to_hashmap(dict: *mut AVDictionary) -> HashMap<String, String> {
810    if dict.is_null() {
811        return HashMap::new();
812    }
813    let mut map = HashMap::new();
814    unsafe {
815        let mut e: *mut AVDictionaryEntry = null_mut();
816        while {
817            e = av_dict_get(dict, null(), e, AV_DICT_IGNORE_SUFFIX);
818            !e.is_null()
819        } {
820            let k = CStr::from_ptr((*e).key).to_string_lossy().into_owned();
821            let v = CStr::from_ptr((*e).value).to_string_lossy().into_owned();
822            map.insert(k, v);
823        }
824    }
825    map
826}
827
828#[cfg(test)]
829mod tests {
830    use super::*;
831
832    #[test]
833    fn test_not_found() {
834        let result = find_all_stream_infos("not_found.mp4");
835        assert!(result.is_err());
836
837        let error = result.err().unwrap();
838        println!("{error}");
839        assert!(matches!(
840            error,
841            crate::error::Error::OpenInputStream(OpenInputError::NotFound)
842        ))
843    }
844
845    #[test]
846    fn test_find_all_stream_infos() {
847        let stream_infos = find_all_stream_infos("test.mp4").unwrap();
848        assert_eq!(2, stream_infos.len());
849        for stream_info in stream_infos {
850            println!("{:?}", stream_info);
851        }
852    }
853
854    #[test]
855    fn test_find_video_stream_info() {
856        let option = find_video_stream_info("test.mp4").unwrap();
857        assert!(option.is_some());
858        let video_stream_info = option.unwrap();
859        println!("video_stream_info:{:?}", video_stream_info);
860    }
861
862    #[test]
863    fn test_find_audio_stream_info() {
864        let option = find_audio_stream_info("test.mp4").unwrap();
865        assert!(option.is_some());
866        let audio_stream_info = option.unwrap();
867        println!("audio_stream_info:{:?}", audio_stream_info);
868    }
869
870    #[test]
871    fn test_find_subtitle_stream_info() {
872        let option = find_subtitle_stream_info("test.mp4").unwrap();
873        assert!(option.is_none())
874    }
875
876    #[test]
877    fn test_find_data_stream_info() {
878        let option = find_data_stream_info("test.mp4").unwrap();
879        assert!(option.is_none());
880    }
881
882    #[test]
883    fn test_find_attachment_stream_info() {
884        let option = find_attachment_stream_info("test.mp4").unwrap();
885        assert!(option.is_none())
886    }
887
888    #[test]
889    fn test_find_unknown_stream_info() {
890        let option = find_unknown_stream_info("test.mp4").unwrap();
891        assert!(option.is_none())
892    }
893}