ez_ffmpeg/core/
stream_info.rs

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