ez_ffmpeg/core/context/
ffmpeg_context.rs

1use crate::core::context::demuxer::Demuxer;
2use crate::core::context::ffmpeg_context_builder::FfmpegContextBuilder;
3use crate::core::context::filter_complex::FilterComplex;
4use crate::core::context::filter_graph::FilterGraph;
5use crate::core::context::input::Input;
6use crate::core::context::input_filter::{InputFilter, IFILTER_FLAG_AUTOROTATE};
7use crate::core::context::muxer::Muxer;
8use crate::core::context::output::{Output, StreamMap};
9use crate::core::context::output_filter::{
10    OutputFilter, OFILTER_FLAG_AUDIO_24BIT, OFILTER_FLAG_AUTOSCALE, OFILTER_FLAG_DISABLE_CONVERT,
11};
12use crate::core::context::{frame_alloc, CodecContext};
13use crate::core::scheduler::ffmpeg_scheduler;
14use crate::core::scheduler::ffmpeg_scheduler::{FfmpegScheduler, Initialization};
15#[cfg(not(feature = "docs-rs"))]
16use crate::core::scheduler::filter_task::graph_opts_apply;
17use crate::core::scheduler::input_controller::SchNode;
18use crate::error::Error::{FileSameAsInput, FilterDescUtf8, FilterNameUtf8, FilterZeroOutputs, FrameFilterStreamTypeNoMatched, FrameFilterTypeNoMatched, ParseInteger};
19use crate::error::FilterGraphParseError::{
20    InvalidFileIndexInFg, InvalidFilterSpecifier, OutputUnconnected,
21};
22use crate::error::OpenOutputError::InvalidFileIndexInIntput;
23use crate::error::{
24    AllocOutputContextError, FilterGraphParseError, FindStreamError, OpenInputError,
25    OpenOutputError,
26};
27use crate::error::{Error, Result};
28use crate::filter::frame_pipeline::FramePipeline;
29use crate::util::ffmpeg_utils::hashmap_to_avdictionary;
30#[cfg(not(feature = "docs-rs"))]
31use ffmpeg_sys_next::AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC;
32#[cfg(not(feature = "docs-rs"))]
33use ffmpeg_sys_next::AVCodecConfig::*;
34use ffmpeg_sys_next::AVCodecID::{AV_CODEC_ID_AC3, AV_CODEC_ID_MP3, AV_CODEC_ID_NONE};
35use ffmpeg_sys_next::AVColorRange::AVCOL_RANGE_UNSPECIFIED;
36use ffmpeg_sys_next::AVColorSpace::AVCOL_SPC_UNSPECIFIED;
37use ffmpeg_sys_next::AVMediaType::{
38    AVMEDIA_TYPE_ATTACHMENT, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_DATA, AVMEDIA_TYPE_SUBTITLE,
39    AVMEDIA_TYPE_VIDEO,
40};
41use ffmpeg_sys_next::AVPixelFormat::AV_PIX_FMT_NONE;
42use ffmpeg_sys_next::AVSampleFormat::AV_SAMPLE_FMT_NONE;
43use ffmpeg_sys_next::{av_add_q, av_channel_layout_default, av_codec_get_id, av_codec_get_tag2, av_dict_free, av_freep, av_get_exact_bits_per_sample, av_guess_codec, av_guess_format, av_guess_frame_rate, av_inv_q, av_malloc, av_rescale_q, av_seek_frame, avcodec_alloc_context3, avcodec_descriptor_get, avcodec_descriptor_get_by_name, avcodec_find_encoder, avcodec_find_encoder_by_name, avcodec_get_name, avcodec_parameters_from_context, avcodec_parameters_to_context, avfilter_graph_alloc, avfilter_graph_free, avfilter_inout_free, avfilter_pad_get_name, avfilter_pad_get_type, avformat_alloc_context, avformat_alloc_output_context2, avformat_close_input, avformat_find_stream_info, avformat_flush, avformat_free_context, avformat_open_input, avio_alloc_context, avio_context_free, avio_open, AVCodec, AVCodecID, AVColorRange, AVColorSpace, AVFilterContext, AVFilterInOut, AVFilterPad, AVFormatContext, AVMediaType, AVOutputFormat, AVPixelFormat, AVRational, AVSampleFormat, AVStream, AVERROR_ENCODER_NOT_FOUND, AVFMT_FLAG_CUSTOM_IO, AVFMT_GLOBALHEADER, AVFMT_NOBINSEARCH, AVFMT_NOFILE, AVFMT_NOGENSEARCH, AVFMT_NOSTREAMS, AVIO_FLAG_WRITE, AVSEEK_FLAG_BACKWARD, AV_CODEC_PROP_BITMAP_SUB, AV_CODEC_PROP_TEXT_SUB, AV_TIME_BASE};
44#[cfg(not(feature = "docs-rs"))]
45use ffmpeg_sys_next::{av_channel_layout_copy, av_packet_side_data_new, avcodec_get_supported_config, avfilter_graph_segment_apply, avfilter_graph_segment_create_filters, avfilter_graph_segment_free, avfilter_graph_segment_parse, AVChannelLayout};
46use log::{debug, error, info, warn};
47use std::collections::HashMap;
48use std::ffi::{c_uint, c_void, CStr, CString};
49use std::ptr::{null, null_mut};
50use std::sync::Arc;
51
52pub struct FfmpegContext {
53    pub(crate) independent_readrate: bool,
54    pub(crate) demuxs: Vec<Demuxer>,
55    pub(crate) filter_graphs: Vec<FilterGraph>,
56    pub(crate) muxs: Vec<Muxer>,
57}
58
59unsafe impl Send for FfmpegContext {}
60unsafe impl Sync for FfmpegContext {}
61
62impl FfmpegContext {
63    /// Creates a new [`FfmpegContextBuilder`] which allows you to configure
64    /// and construct an [`FfmpegContext`] with custom inputs, outputs, filters,
65    /// and other parameters.
66    ///
67    /// # Examples
68    /// ```rust
69    /// let context = FfmpegContext::builder()
70    ///     .input("input.mp4")
71    ///     .output("output.mp4")
72    ///     .build()
73    ///     .unwrap();
74    /// ```
75    pub fn builder() -> FfmpegContextBuilder {
76        FfmpegContextBuilder::new()
77    }
78
79    /// Consumes this [`FfmpegContext`] and starts an FFmpeg job, returning
80    /// an [`FfmpegScheduler<ffmpeg_scheduler::Running>`] for further management.
81    ///
82    /// Internally, this method creates an [`FfmpegScheduler`] from the context
83    /// and immediately calls [`FfmpegScheduler::start()`].
84    ///
85    /// # Returns
86    /// - `Ok(FfmpegScheduler<Running>)` if the scheduling process started successfully.
87    /// - `Err(...)` if there was an error initializing or starting FFmpeg.
88    ///
89    /// # Example
90    /// ```rust
91    /// let context = FfmpegContext::builder()
92    ///     .input("input.mp4")
93    ///     .output("output.mp4")
94    ///     .build()
95    ///     .unwrap();
96    ///
97    /// // Start the FFmpeg job and get a scheduler to manage it
98    /// let scheduler = context.start().expect("Failed to start Ffmpeg job");
99    ///
100    /// // Optionally, wait for it to finish
101    /// let result = scheduler.wait();
102    /// assert!(result.is_ok());
103    /// ```
104    pub fn start(self) -> Result<FfmpegScheduler<ffmpeg_scheduler::Running>> {
105        let ffmpeg_scheduler = FfmpegScheduler::new(self);
106        ffmpeg_scheduler.start()
107    }
108
109    #[allow(dead_code)]
110    pub(crate) fn new(
111        inputs: Vec<Input>,
112        filter_complexs: Vec<FilterComplex>,
113        outputs: Vec<Output>,
114    ) -> Result<FfmpegContext> {
115        Self::new_with_options(false, inputs, filter_complexs, outputs, false)
116    }
117
118    pub(crate) fn new_with_options(
119        mut independent_readrate: bool,
120        mut inputs: Vec<Input>,
121        filter_complexs: Vec<FilterComplex>,
122        mut outputs: Vec<Output>,
123        copy_ts: bool,
124    ) -> Result<FfmpegContext> {
125        check_duplicate_inputs_outputs(&inputs, &outputs)?;
126
127        crate::core::initialize_ffmpeg();
128
129        let mut demuxs = open_input_files(&mut inputs, copy_ts)?;
130
131        if demuxs.len() <= 1 {
132            independent_readrate = false;
133        }
134
135        let mut filter_graphs = if !filter_complexs.is_empty() {
136            let mut filter_graphs = init_filter_graphs(filter_complexs)?;
137            fg_bind_inputs(&mut filter_graphs, &mut demuxs)?;
138            filter_graphs
139        } else {
140            Vec::new()
141        };
142
143        let mut muxs = open_output_files(&mut outputs, copy_ts)?;
144
145        outputs_bind(&mut muxs, &mut filter_graphs, &mut demuxs)?;
146
147        correct_input_start_times(&mut demuxs, copy_ts);
148
149        check_output_streams(&muxs)?;
150
151        check_fg_bindings(&filter_graphs)?;
152
153        check_frame_filter_pipeline(&muxs, &demuxs)?;
154
155        Ok(Self {
156            independent_readrate,
157            demuxs,
158            filter_graphs,
159            muxs,
160        })
161    }
162}
163
164const START_AT_ZERO:bool = false;
165
166fn correct_input_start_times(demuxs: &mut Vec<Demuxer>, copy_ts: bool){
167    for (i, demux) in demuxs.iter_mut().enumerate() {
168        unsafe {
169            let is = demux.in_fmt_ctx;
170
171            demux.start_time_effective = (*is).start_time;
172            if (*is).start_time == ffmpeg_sys_next::AV_NOPTS_VALUE ||
173                (*(*is).iformat).flags & ffmpeg_sys_next::AVFMT_TS_DISCONT == 0 {
174                continue;
175            }
176
177            let mut new_start_time = i64::MAX;
178            let stream_count = (*is).nb_streams;
179            for j in 0..stream_count {
180                let st = *(*is).streams.add(j as usize);
181                if (*st).discard == ffmpeg_sys_next::AVDiscard::AVDISCARD_ALL || (*st).start_time == ffmpeg_sys_next::AV_NOPTS_VALUE {
182                    continue;
183                }
184                new_start_time = std::cmp::min(new_start_time, av_rescale_q((*st).start_time, (*st).time_base, ffmpeg_sys_next::AV_TIME_BASE_Q));
185            }
186            let diff = new_start_time - (*is).start_time;
187            if diff != 0 {
188                debug!("Correcting start time of Input #{i} by {diff}us.");
189                demux.start_time_effective = new_start_time;
190                if copy_ts && START_AT_ZERO {
191                    demux.ts_offset = -new_start_time;
192                }else if !copy_ts {
193                    let abs_start_seek = (*is).start_time + demux.start_time_us.unwrap_or(0);
194                    demux.ts_offset = if abs_start_seek > new_start_time { -abs_start_seek } else { -new_start_time };
195                } else if copy_ts {
196                    demux.ts_offset = 0;
197                }
198
199                // demux.ts_offset += demux.input_ts_offset;
200            }
201
202        }
203    }
204}
205
206fn check_pipeline<T>(
207    frame_pipelines: Option<&Vec<FramePipeline>>,
208    streams: &[T],
209    tag: &str,
210    get_stream_index: impl Fn(&T) -> usize,
211    get_codec_type: impl Fn(&T) -> &AVMediaType,
212) -> Result<()> {
213    let tag_cap = {
214        let mut chars = tag.chars();
215        match chars.next() {
216            None => String::new(),
217            Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
218        }
219    };
220
221    frame_pipelines
222        .into_iter()
223        .flat_map(|pipelines| pipelines.iter())
224        .try_for_each(|pipeline| {
225            if let Some(idx) = pipeline.stream_index {
226                streams
227                    .iter()
228                    .any(|s| get_stream_index(s) == idx && get_codec_type(s) == &pipeline.media_type)
229                    .then(|| ())
230                    .ok_or_else(|| {
231                        Into::<crate::error::Error>::into(FrameFilterStreamTypeNoMatched(
232                            tag_cap.clone(),
233                            idx,
234                            format!("{:?}", pipeline.media_type),
235                        ))
236                    })
237            } else {
238                streams
239                    .iter()
240                    .any(|s| get_codec_type(s) == &pipeline.media_type)
241                    .then(|| ())
242                    .ok_or_else(|| {
243                        FrameFilterTypeNoMatched(
244                            tag.into(),
245                            format!("{:?}", pipeline.media_type),
246                        )
247                            .into()
248                    })
249            }
250        })?;
251    Ok(())
252}
253
254fn check_frame_filter_pipeline(muxs: &[Muxer], demuxs: &[Demuxer]) -> Result<()> {
255    muxs.iter().try_for_each(|mux| {
256        check_pipeline(
257            mux.frame_pipelines.as_ref(),
258            mux.get_streams(),
259            "output",
260            |s| s.stream_index,
261            |s| &s.codec_type,
262        )
263    })?;
264    demuxs.iter().try_for_each(|demux| {
265        check_pipeline(
266            demux.frame_pipelines.as_ref(),
267            demux.get_streams(),
268            "input",
269            |s| s.stream_index,
270            |s| &s.codec_type,
271        )
272    })?;
273    Ok(())
274}
275
276
277fn check_fg_bindings(filter_graphs: &Vec<FilterGraph>) -> Result<()> {
278    // check that all outputs were bound
279    for filter_graph in filter_graphs {
280        for (i, output_filter) in filter_graph.outputs.iter().enumerate() {
281            if !output_filter.has_dst() {
282                let linklabel = if output_filter.linklabel.is_empty() {
283                    "unlabeled".to_string()
284                } else {
285                    output_filter.linklabel.clone()
286                };
287                return Err(OutputUnconnected(output_filter.name.clone(), i, linklabel).into());
288            }
289        }
290    }
291    Ok(())
292}
293
294impl Into<FfmpegScheduler<Initialization>> for FfmpegContext {
295    fn into(self) -> FfmpegScheduler<Initialization> {
296        FfmpegScheduler::new(self)
297    }
298}
299
300fn check_output_streams(muxs: &Vec<Muxer>) -> Result<()> {
301    for mux in muxs {
302        unsafe {
303            let oformat = (*mux.out_fmt_ctx).oformat;
304            if !mux.has_src() && (*oformat).flags & AVFMT_NOSTREAMS == 0 {
305                warn!("Output file does not contain any stream");
306                return Err(OpenOutputError::NotContainStream.into());
307            }
308        }
309    }
310    Ok(())
311}
312
313fn outputs_bind(
314    muxs: &mut Vec<Muxer>,
315    filter_graphs: &mut Vec<FilterGraph>,
316    demuxs: &mut Vec<Demuxer>,
317) -> Result<()> {
318    for (i, mux) in muxs.iter_mut().enumerate() {
319        if mux.stream_maps.is_empty() {
320            let mut auto_disable = 0;
321            output_bind_by_unlabeled_filter(i, mux, filter_graphs, &mut auto_disable)?;
322            /* pick the first stream of each type */
323            map_auto_streams(i, mux, demuxs, filter_graphs, auto_disable)?;
324        } else {
325            for stream_map in mux.stream_maps.clone() {
326                map_manual(i, mux, &stream_map, filter_graphs, demuxs)?;
327            }
328        }
329
330        //TODO add_attachments
331        //TODO add_metadatas
332    }
333
334    Ok(())
335}
336
337fn map_manual(
338    index: usize,
339    mux: &mut Muxer,
340    stream_map: &StreamMap,
341    filter_graphs: &mut Vec<FilterGraph>,
342    demuxs: &mut Vec<Demuxer>,
343) -> Result<()> {
344    for filter_graph in filter_graphs.iter_mut() {
345        for i in 0..filter_graph.outputs.len() {
346            let option = {
347                let output_filter = &filter_graph.outputs[i];
348                if output_filter.has_dst()
349                    || output_filter.linklabel.is_empty()
350                    || output_filter.linklabel != stream_map.linklabel
351                {
352                    continue;
353                }
354
355                choose_encoder(mux, output_filter.media_type)?
356            };
357
358            match option {
359                None => {
360                    warn!(
361                        "An unexpected media_type {:?} appears in output_filter",
362                        filter_graph.outputs[i].media_type
363                    );
364                }
365                Some((codec_id, enc)) => {
366                    return ofilter_bind_ost(
367                        index,
368                        mux,
369                        filter_graph,
370                        i,
371                        codec_id,
372                        enc,
373                    );
374                }
375            }
376        }
377    }
378
379    let result = output_find_input_idx_by_linklabel(&stream_map.linklabel, demuxs, &mux.url);
380    if let Err(e) = result {
381        return match e {
382            ParseInteger => {
383                warn!("Output stream map '{}' matches no streams; Start with a number to match the input. Such as [0:v]", stream_map.linklabel);
384                Err(OpenOutputError::InvalidArgument.into())
385            }
386            Error::OpenOutput(OpenOutputError::MatchesNoStreams(stream_map)) => {
387                warn!("Output stream map '{stream_map}' matches no streams; To ignore this, add a trailing '?' to the map..");
388                Err(OpenOutputError::MatchesNoStreams(stream_map).into())
389            }
390            e => Err(e),
391        };
392    }
393
394    let option = result.unwrap();
395    if let None = option {
396        info!(
397            "Output stream map '{}' matches no streams; ignoring.",
398            stream_map.linklabel
399        );
400        return Ok(());
401    }
402    let (demux_idx, stream_index, media_type) = option.unwrap();
403
404    let demux = &mut demuxs[demux_idx];
405
406    info!(
407        "Binding output with label '{}' to input stream {stream_index}:{demux_idx}",
408        stream_map.linklabel
409    );
410
411    let demux_node = demux.node.clone();
412    let input_stream = demux.get_stream_mut(stream_index);
413
414    let option = choose_encoder(mux, media_type)?;
415
416    let input_stream_duration = input_stream.duration;
417    let input_stream_time_base = input_stream.time_base;
418
419    match option {
420        None => {
421            // copy
422            let (packet_sender, _st, output_stream_index) = mux.new_stream(demux_node)?;
423            demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
424
425            unsafe {
426                streamcopy_init(
427                    mux,
428                    *(*demux.in_fmt_ctx).streams.add(stream_index),
429                    *(*mux.out_fmt_ctx).streams.add(output_stream_index),
430                )?;
431                rescale_duration(
432                    input_stream_duration,
433                    input_stream_time_base,
434                    *(*mux.out_fmt_ctx).streams.add(output_stream_index),
435                );
436                mux.stream_ready()
437            }
438        }
439        Some((codec_id, enc)) => {
440            // connect input_stream to output
441            if stream_map.copy {
442                // copy
443                let (packet_sender, _st, output_stream_index) = mux.new_stream(demux_node)?;
444                demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
445
446                unsafe {
447                    streamcopy_init(
448                        mux,
449                        *(*demux.in_fmt_ctx).streams.add(stream_index),
450                        *(*mux.out_fmt_ctx).streams.add(output_stream_index),
451                    )?;
452                    rescale_duration(
453                        input_stream_duration,
454                        input_stream_time_base,
455                        *(*mux.out_fmt_ctx).streams.add(output_stream_index),
456                    );
457                    mux.stream_ready()
458                }
459            } else {
460                if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
461                    init_simple_filtergraph(
462                        demux,
463                        stream_index,
464                        codec_id,
465                        enc,
466                        index,
467                        mux,
468                        filter_graphs,
469                    )?;
470                } else {
471                    let (frame_sender, output_stream_index) = mux.add_enc_stream(
472                        media_type,
473                        enc,
474                        demux_node
475                    )?;
476                    input_stream.add_dst(frame_sender);
477                    demux.connect_stream(stream_index);
478
479                    unsafe {
480                        rescale_duration(
481                            input_stream_duration,
482                            input_stream_time_base,
483                            *(*mux.out_fmt_ctx).streams.add(output_stream_index),
484                        );
485                    }
486                }
487            }
488        }
489    }
490
491    Ok(())
492}
493
494#[cfg(not(feature = "docs-rs"))]
495fn set_channel_layout(
496    ch_layout: &mut AVChannelLayout,
497    ch_layouts: &Option<Vec<AVChannelLayout>>,
498    layout_requested: &AVChannelLayout,
499) -> Result<()> {
500    unsafe {
501        // Scenario 1: If the requested layout has a specified order (not UNSPEC), copy it directly
502        if layout_requested.order != AV_CHANNEL_ORDER_UNSPEC {
503            let ret = av_channel_layout_copy(ch_layout, layout_requested);
504            if ret < 0 {
505                return Err(OpenOutputError::from(ret).into());
506            }
507            return Ok(());
508        }
509
510        // Scenario 2: Requested layout is UNSPEC and no encoder-supported layouts available
511        // Use default layout based on channel count
512        if ch_layouts.is_none() {
513            av_channel_layout_default(ch_layout, layout_requested.nb_channels);
514            return Ok(());
515        }
516
517        // Scenario 3: Try to match channel count from encoder's supported layouts
518        if let Some(layouts) = ch_layouts {
519            for layout in layouts {
520                if layout.nb_channels == layout_requested.nb_channels {
521                    let ret = av_channel_layout_copy(ch_layout, layout);
522                    if ret < 0 {
523                        return Err(OpenOutputError::from(ret).into());
524                    }
525                    return Ok(());
526                }
527            }
528        }
529
530        // Scenario 4: No matching channel count found, use default layout
531        av_channel_layout_default(ch_layout, layout_requested.nb_channels);
532        Ok(())
533    }
534}
535
536#[cfg(feature = "docs-rs")]
537fn configure_output_filter_opts(
538    index: usize,
539    mux: &mut Muxer,
540    output_filter: &mut OutputFilter,
541    codec_id: AVCodecID,
542    enc: *const AVCodec,
543    output_stream_index: usize,
544) -> Result<()> {
545    Ok(())
546}
547
548#[cfg(not(feature = "docs-rs"))]
549fn configure_output_filter_opts(
550    index: usize,
551    mux: &mut Muxer,
552    output_filter: &mut OutputFilter,
553    codec_id: AVCodecID,
554    enc: *const AVCodec,
555    output_stream_index: usize,
556) -> Result<()> {
557    unsafe {
558        output_filter.opts.name = format!("#{index}:{output_stream_index}");
559        output_filter.opts.enc = enc;
560        output_filter.opts.trim_start_us = mux.start_time_us;
561        output_filter.opts.trim_duration_us = mux.recording_time_us;
562        output_filter.opts.ts_offset = mux.start_time_us;
563
564        output_filter.opts.flags = OFILTER_FLAG_DISABLE_CONVERT
565            | OFILTER_FLAG_AUTOSCALE
566            | if av_get_exact_bits_per_sample(codec_id) == 24 {
567                OFILTER_FLAG_AUDIO_24BIT
568            } else {
569                0
570            };
571
572        let enc_ctx = avcodec_alloc_context3(enc);
573        if enc_ctx.is_null() {
574            return Err(OpenOutputError::OutOfMemory.into());
575        }
576        let _codec_ctx = CodecContext::new(enc_ctx);
577
578        (*enc_ctx).thread_count = 0;
579
580        if output_filter.media_type == AVMEDIA_TYPE_VIDEO {
581            // formats
582            let mut formats: *const AVPixelFormat = null();
583            let mut ret = avcodec_get_supported_config(
584                enc_ctx,
585                null(),
586                AV_CODEC_CONFIG_PIX_FORMAT,
587                0,
588                &mut formats as *mut _ as *mut *const libc::c_void,
589                null_mut(),
590            );
591            if ret < 0 {
592                return Err(OpenOutputError::from(ret).into());
593            }
594
595            let mut current = formats;
596            let mut format_list = Vec::new();
597            let mut count = 0;
598            const MAX_FORMATS: usize = 512;
599            while !current.is_null() && *current != AV_PIX_FMT_NONE && count < MAX_FORMATS {
600                format_list.push(*current);
601                current = current.add(1);
602                count += 1;
603            }
604            if count >= MAX_FORMATS {
605                warn!("Reached maximum format limit");
606            }
607            output_filter.opts.formats = Some(format_list);
608
609            // framerates
610            let mut framerates: *const AVRational = null();
611            ret = avcodec_get_supported_config(
612                enc_ctx,
613                null(),
614                AV_CODEC_CONFIG_FRAME_RATE,
615                0,
616                &mut framerates as *mut _ as *mut *const libc::c_void,
617                null_mut(),
618            );
619            if ret < 0 {
620                return Err(OpenOutputError::from(ret).into());
621            }
622            let mut framerate_list = Vec::new();
623            let mut current = framerates;
624            let mut count = 0;
625            const MAX_FRAMERATES: usize = 64;
626            while !current.is_null() && (*current).num != 0 && (*current).den != 0 && count < MAX_FRAMERATES {
627                framerate_list.push(*current);
628                current = current.add(1);
629                count += 1;
630            }
631            if count >= MAX_FRAMERATES {
632                warn!("Reached maximum framerate limit");
633            }
634            output_filter.opts.framerates = Some(framerate_list);
635
636            if let Some(framerate) = mux.framerate {
637                output_filter.opts.framerate = framerate;
638            }
639
640            // color_spaces
641            let mut color_spaces: *const AVColorSpace = null();
642            ret = avcodec_get_supported_config(
643                enc_ctx,
644                null(),
645                AV_CODEC_CONFIG_COLOR_SPACE,
646                0,
647                &mut color_spaces as *mut _ as *mut *const libc::c_void,
648                null_mut(),
649            );
650            if ret < 0 {
651                return Err(OpenOutputError::from(ret).into());
652            }
653            let mut color_space_list = Vec::new();
654            let mut current = color_spaces;
655            let mut count = 0;
656            const MAX_COLOR_SPACES: usize = 128;
657            while !current.is_null() && *current != AVCOL_SPC_UNSPECIFIED && count < MAX_COLOR_SPACES {
658                color_space_list.push(*current);
659                current = current.add(1);
660                count += 1;
661            }
662            if count >= MAX_COLOR_SPACES {
663                warn!("Reached maximum color space limit");
664            }
665            output_filter.opts.color_spaces = Some(color_space_list);
666
667            //color_ranges
668            let mut color_ranges: *const AVColorRange = null();
669            ret = avcodec_get_supported_config(
670                enc_ctx,
671                null(),
672                AV_CODEC_CONFIG_COLOR_RANGE,
673                0,
674                &mut color_ranges as *mut _ as *mut *const libc::c_void,
675                null_mut(),
676            );
677            if ret < 0 {
678                return Err(OpenOutputError::from(ret).into());
679            }
680            let mut color_range_list = Vec::new();
681            let mut current = color_ranges;
682            let mut count = 0;
683            const MAX_COLOR_RANGES: usize = 64;
684            while !current.is_null() && *current != AVCOL_RANGE_UNSPECIFIED && count < MAX_COLOR_RANGES {
685                color_range_list.push(*current);
686                current = current.add(1);
687                count += 1;
688            }
689            if count >= MAX_COLOR_RANGES {
690                warn!("Reached maximum color range limit");
691            }
692            output_filter.opts.color_ranges = Some(color_range_list);
693
694            let stream = &mux.get_streams()[output_stream_index];
695            output_filter.opts.vsync_method = stream.vsync_method;
696        } else {
697            if let Some(sample_fmt) = &mux.audio_sample_fmt {
698                output_filter.opts.audio_format = *sample_fmt;
699            }
700            // audio formats
701            let mut audio_formats: *const AVSampleFormat = null();
702            let mut ret = avcodec_get_supported_config(
703                enc_ctx,
704                null(),
705                AV_CODEC_CONFIG_SAMPLE_FORMAT,
706                0,
707                &mut audio_formats as *mut _ as *mut _,
708                null_mut(),
709            );
710            if ret < 0 {
711                return Err(OpenOutputError::from(ret).into());
712            }
713
714            let mut current = audio_formats;
715            let mut audio_format_list = Vec::new();
716            let mut count = 0;
717            const MAX_AUDIO_FORMATS: usize = 32;
718            while !current.is_null() && *current != AV_SAMPLE_FMT_NONE && count < MAX_AUDIO_FORMATS {
719                audio_format_list.push(*current);
720                current = current.add(1);
721                count += 1;
722            }
723            if count >= MAX_AUDIO_FORMATS {
724                warn!("Reached maximum audio format limit");
725            }
726            output_filter.opts.audio_formats = Some(audio_format_list);
727
728
729            if let Some(audio_sample_rate) = &mux.audio_sample_rate {
730                output_filter.opts.sample_rate = *audio_sample_rate;
731            }
732            // sample_rates
733            let mut rates: *const i32 = null();
734            ret = avcodec_get_supported_config(
735                enc_ctx,
736                null(),
737                AV_CODEC_CONFIG_SAMPLE_RATE,
738                0,
739                &mut rates as *mut _ as *mut _,
740                null_mut(),
741            );
742            if ret < 0 {
743                return Err(OpenOutputError::from(ret).into());
744            }
745            let mut rate_list = Vec::new();
746            let mut current = rates;
747            let mut count = 0;
748            const MAX_SAMPLE_RATES: usize = 64;
749            while !current.is_null() && *current != 0 && count < MAX_SAMPLE_RATES {
750                rate_list.push(*current);
751                current = current.add(1);
752                count += 1;
753            }
754            if count >= MAX_SAMPLE_RATES {
755                warn!("Reached maximum sample rate limit");
756            }
757            output_filter.opts.sample_rates = Some(rate_list);
758
759
760            if let Some(channels) = &mux.audio_channels {
761                output_filter.opts.ch_layout.nb_channels = *channels;
762            }
763            // channel_layouts
764            let mut layouts: *const AVChannelLayout = null();
765            ret = avcodec_get_supported_config(
766                enc_ctx,
767                null(),
768                AV_CODEC_CONFIG_CHANNEL_LAYOUT,
769                0,
770                &mut layouts as *mut _ as *mut _,
771                null_mut(),
772            );
773            if ret < 0 {
774                return Err(OpenOutputError::from(ret).into());
775            }
776            let mut layout_list = Vec::new();
777            let mut current = layouts;
778            let mut count = 0;
779            const MAX_CHANNEL_LAYOUTS: usize = 128;
780            while !current.is_null() && (*current).order != AV_CHANNEL_ORDER_UNSPEC && count < MAX_CHANNEL_LAYOUTS {
781                layout_list.push(*current);
782                current = current.add(1);
783                count += 1;
784            }
785            if count >= MAX_CHANNEL_LAYOUTS {
786                warn!("Reached maximum channel layout limit");
787            }
788            output_filter.opts.ch_layouts = Some(layout_list);
789
790            // Call set_channel_layout to resolve UNSPEC layouts to proper defaults
791            // Corresponds to FFmpeg ffmpeg_filter.c:879-882
792            if output_filter.opts.ch_layout.nb_channels > 0 {
793                let layout_requested = output_filter.opts.ch_layout.clone();
794                set_channel_layout(
795                    &mut output_filter.opts.ch_layout,
796                    &output_filter.opts.ch_layouts,
797                    &layout_requested,
798                )?;
799            }
800        }
801    };
802    Ok(())
803}
804
805fn output_find_input_idx_by_linklabel(
806    linklabel: &str,
807    demuxs: &mut Vec<Demuxer>,
808    desc: &str,
809) -> Result<Option<(usize, usize, AVMediaType)>> {
810    let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
811        if linklabel.len() <= 2 {
812            warn!("Output linklabel is empty");
813            return Err(OpenOutputError::InvalidArgument.into());
814        } else {
815            &linklabel[1..linklabel.len() - 1]
816        }
817    } else {
818        linklabel
819    };
820
821    let (file_idx, remainder) = strtol(new_linklabel)?;
822    if file_idx < 0 || file_idx as usize >= demuxs.len() {
823        return Err(InvalidFileIndexInIntput(file_idx as usize, desc.to_string()).into());
824    }
825
826    let (media_type, allow_unused) = stream_specifier_parse(remainder)?;
827
828    let demux = &demuxs[file_idx as usize];
829
830    let mut stream_idx = -1i32;
831
832    for (idx, dec_stream) in demux.get_streams().iter().enumerate() {
833        if (*dec_stream).codec_type == media_type {
834            stream_idx = idx as i32;
835            break;
836        }
837    }
838
839    if stream_idx < 0 {
840        if allow_unused {
841            return Ok(None);
842        }
843
844        warn!("Stream specifier '{remainder}' in output {desc} matches no streams.");
845        return Err(OpenOutputError::MatchesNoStreams(linklabel.to_string()).into());
846    }
847    Ok(Some((file_idx as usize, stream_idx as usize, media_type)))
848}
849
850fn map_auto_streams(
851    mux_index: usize,
852    mux: &mut Muxer,
853    demuxs: &mut Vec<Demuxer>,
854    filter_graphs: &mut Vec<FilterGraph>,
855    auto_disable: i32,
856) -> Result<()> {
857    unsafe {
858        let oformat = (*mux.out_fmt_ctx).oformat;
859        map_auto_stream(
860            mux_index,
861            mux,
862            demuxs,
863            oformat,
864            AVMEDIA_TYPE_VIDEO,
865            filter_graphs,
866            auto_disable,
867        )?;
868        map_auto_stream(
869            mux_index,
870            mux,
871            demuxs,
872            oformat,
873            AVMEDIA_TYPE_AUDIO,
874            filter_graphs,
875            auto_disable,
876        )?;
877        map_auto_subtitle(
878            mux,
879            demuxs,
880            oformat,
881            auto_disable,
882        )?;
883        map_auto_data(
884            mux,
885            demuxs,
886            oformat,
887            auto_disable,
888        )?;
889    }
890    Ok(())
891}
892
893#[cfg(feature = "docs-rs")]
894unsafe fn map_auto_subtitle(
895    mux: &mut Muxer,
896    demuxs: &mut Vec<Demuxer>,
897    oformat: *const AVOutputFormat,
898    auto_disable: i32,
899) -> Result<()> {
900    Ok(())
901}
902
903#[cfg(not(feature = "docs-rs"))]
904unsafe fn map_auto_subtitle(
905    mux: &mut Muxer,
906    demuxs: &mut Vec<Demuxer>,
907    oformat: *const AVOutputFormat,
908    auto_disable: i32,
909) -> Result<()> {
910    if auto_disable & (1 << AVMEDIA_TYPE_SUBTITLE as i32) != 0 {
911        return Ok(());
912    }
913
914    let output_codec = avcodec_find_encoder((*oformat).subtitle_codec);
915    if output_codec.is_null() {
916        return Ok(());
917    }
918    let output_descriptor = avcodec_descriptor_get((*output_codec).id);
919
920    for demux in demuxs {
921        let option = demux
922            .get_streams()
923            .iter()
924            .enumerate()
925            .find_map(|(index, input_stream)| {
926                if input_stream.codec_type == AVMEDIA_TYPE_SUBTITLE {
927                    Some(index)
928                } else {
929                    None
930                }
931            });
932
933        if option.is_none() {
934            continue;
935        }
936
937        let stream_index = option.unwrap();
938
939        let input_descriptor = avcodec_descriptor_get((*demux.get_stream(stream_index).codec_parameters).codec_id);
940        let mut input_props = 0;
941        if !input_descriptor.is_null() {
942            input_props = (*input_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
943        }
944        let mut output_props = 0;
945        if !output_descriptor.is_null() {
946            output_props = (*output_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
947        }
948
949        if input_props & output_props != 0 ||
950            // Map dvb teletext which has neither property to any output subtitle encoder
951            !input_descriptor.is_null() && !output_descriptor.is_null() &&
952                ((*input_descriptor).props == 0 || (*output_descriptor).props == 0) {
953            let option = choose_encoder(mux, AVMEDIA_TYPE_SUBTITLE)?;
954
955            if let Some((_codec_id, enc)) = option {
956                let (frame_sender, output_stream_index) =
957                    mux.add_enc_stream(AVMEDIA_TYPE_SUBTITLE, enc, demux.node.clone())?;
958                demux.get_stream_mut(stream_index).add_dst(frame_sender);
959                demux.connect_stream(stream_index);
960                let input_stream = demux.get_stream(stream_index);
961                unsafe {
962                    rescale_duration(
963                        input_stream.duration,
964                        input_stream.time_base,
965                        *(*mux.out_fmt_ctx).streams.add(output_stream_index),
966                    );
967                }
968            } else {
969                error!("Error selecting an encoder(subtitle)");
970                return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
971            }
972        }
973        break;
974    }
975
976    Ok(())
977}
978
979#[cfg(feature = "docs-rs")]
980unsafe fn map_auto_data(
981    mux: &mut Muxer,
982    demuxs: &mut Vec<Demuxer>,
983    oformat: *const AVOutputFormat,
984    auto_disable: i32,
985) -> Result<()> {
986    Ok(())
987}
988
989#[cfg(not(feature = "docs-rs"))]
990unsafe fn map_auto_data(
991    mux: &mut Muxer,
992    demuxs: &mut Vec<Demuxer>,
993    oformat: *const AVOutputFormat,
994    auto_disable: i32,
995) -> Result<()> {
996    if auto_disable & (1 << AVMEDIA_TYPE_DATA as i32) != 0 {
997        return Ok(());
998    }
999
1000    /* Data only if codec id match */
1001    let codec_id = av_guess_codec(oformat, null(), (*mux.out_fmt_ctx).url, null(), AVMEDIA_TYPE_DATA);
1002
1003    if codec_id == AV_CODEC_ID_NONE {
1004        return Ok(());
1005    }
1006
1007    for demux in demuxs {
1008        let option = demux
1009            .get_streams()
1010            .iter()
1011            .enumerate()
1012            .find_map(|(index, input_stream)| {
1013                if input_stream.codec_type == AVMEDIA_TYPE_DATA && (*input_stream.codec_parameters).codec_id == codec_id {
1014                    Some(index)
1015                } else {
1016                    None
1017                }
1018            });
1019
1020        if option.is_none() {
1021            continue;
1022        }
1023
1024        let stream_index = option.unwrap();
1025        let option = choose_encoder(mux, AVMEDIA_TYPE_DATA)?;
1026
1027        if let Some((_codec_id, enc)) = option {
1028            let (frame_sender, output_stream_index) =
1029                mux.add_enc_stream(AVMEDIA_TYPE_DATA, enc, demux.node.clone())?;
1030            demux.get_stream_mut(stream_index).add_dst(frame_sender);
1031            demux.connect_stream(stream_index);
1032            let input_stream = demux.get_stream(stream_index);
1033            unsafe {
1034                rescale_duration(
1035                    input_stream.duration,
1036                    input_stream.time_base,
1037                    *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1038                );
1039            }
1040        } else {
1041            error!("Error selecting an encoder(data)");
1042            return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1043        }
1044
1045        break;
1046    }
1047
1048    Ok(())
1049}
1050
1051#[cfg(feature = "docs-rs")]
1052unsafe fn map_auto_stream(
1053    mux_index: usize,
1054    mux: &mut Muxer,
1055    demuxs: &mut Vec<Demuxer>,
1056    oformat: *const AVOutputFormat,
1057    media_type: AVMediaType,
1058    filter_graphs: &mut Vec<FilterGraph>,
1059    auto_disable: i32,
1060) -> Result<()> {
1061    Ok(())
1062}
1063
1064#[cfg(not(feature = "docs-rs"))]
1065unsafe fn map_auto_stream(
1066    mux_index: usize,
1067    mux: &mut Muxer,
1068    demuxs: &mut Vec<Demuxer>,
1069    oformat: *const AVOutputFormat,
1070    media_type: AVMediaType,
1071    filter_graphs: &mut Vec<FilterGraph>,
1072    auto_disable: i32,
1073) -> Result<()> {
1074    if auto_disable & (1 << media_type as i32) != 0 {
1075        return Ok(());
1076    }
1077    if media_type == AVMEDIA_TYPE_VIDEO
1078        || media_type == AVMEDIA_TYPE_AUDIO
1079        || media_type == AVMEDIA_TYPE_DATA
1080    {
1081        if av_guess_codec(oformat, null(), (*mux.out_fmt_ctx).url, null(), media_type)
1082            == AV_CODEC_ID_NONE
1083        {
1084            return Ok(());
1085        }
1086    }
1087
1088    for demux in demuxs {
1089        let option = demux
1090            .get_streams()
1091            .iter()
1092            .enumerate()
1093            .find_map(|(index, input_stream)| {
1094                if input_stream.codec_type == media_type {
1095                    Some(index)
1096                } else {
1097                    None
1098                }
1099            });
1100
1101        if option.is_none() {
1102            continue;
1103        }
1104
1105        let stream_index = option.unwrap();
1106        let option = choose_encoder(mux, media_type)?;
1107
1108        if let Some((codec_id, enc)) = option {
1109            if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
1110                init_simple_filtergraph(
1111                    demux,
1112                    stream_index,
1113                    codec_id,
1114                    enc,
1115                    mux_index,
1116                    mux,
1117                    filter_graphs,
1118                )?;
1119            } else {
1120                let (frame_sender, output_stream_index) =
1121                    mux.add_enc_stream(media_type, enc, demux.node.clone())?;
1122                demux.get_stream_mut(stream_index).add_dst(frame_sender);
1123                demux.connect_stream(stream_index);
1124                let input_stream = demux.get_stream(stream_index);
1125                unsafe {
1126                    rescale_duration(
1127                        input_stream.duration,
1128                        input_stream.time_base,
1129                        *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1130                    );
1131                }
1132            }
1133
1134            return Ok(());
1135        }
1136
1137        // copy
1138        let input_stream = demux.get_stream(stream_index);
1139        let input_stream_duration = input_stream.duration;
1140        let input_stream_time_base = input_stream.time_base;
1141
1142        let (packet_sender, _st, output_stream_index) = mux.new_stream(demux.node.clone())?;
1143        demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
1144
1145        unsafe {
1146            streamcopy_init(
1147                mux,
1148                *(*demux.in_fmt_ctx).streams.add(stream_index),
1149                *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1150            )?;
1151            rescale_duration(
1152                input_stream_duration,
1153                input_stream_time_base,
1154                *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1155            );
1156            mux.stream_ready()
1157        }
1158    }
1159
1160    Ok(())
1161}
1162
1163fn init_simple_filtergraph(
1164    demux: &mut Demuxer,
1165    stream_index: usize,
1166    codec_id: AVCodecID,
1167    enc: *const AVCodec,
1168    mux_index: usize,
1169    mux: &mut Muxer,
1170    filter_graphs: &mut Vec<FilterGraph>,
1171) -> Result<()> {
1172    let codec_type = demux.get_stream(stream_index).codec_type;
1173
1174    let filter_desc = if codec_type == AVMEDIA_TYPE_VIDEO {
1175        "null"
1176    } else {
1177        "anull"
1178    };
1179    let mut filter_graph = init_filter_graph(filter_graphs.len(), filter_desc, None)?;
1180
1181    // filter_graph.inputs[0].media_type = codec_type;
1182    // filter_graph.outputs[0].media_type = codec_type;
1183
1184    ifilter_bind_ist(&mut filter_graph, 0, stream_index, demux)?;
1185    ofilter_bind_ost(
1186        mux_index,
1187        mux,
1188        &mut filter_graph,
1189        0,
1190        codec_id,
1191        enc,
1192    )?;
1193
1194    filter_graphs.push(filter_graph);
1195
1196    Ok(())
1197}
1198
1199unsafe fn rescale_duration(src_duration: i64, src_time_base: AVRational, stream: *mut AVStream) {
1200    (*stream).duration = av_rescale_q(src_duration, src_time_base, (*stream).time_base);
1201}
1202
1203#[cfg(feature = "docs-rs")]
1204fn streamcopy_init(
1205    mux: &mut Muxer,
1206    input_stream: *mut AVStream,
1207    output_stream: *mut AVStream,
1208) -> Result<()> {
1209    Ok(())
1210}
1211
1212#[cfg(not(feature = "docs-rs"))]
1213fn streamcopy_init(
1214    mux: &mut Muxer,
1215    input_stream: *mut AVStream,
1216    output_stream: *mut AVStream,
1217) -> Result<()> {
1218    unsafe {
1219        let codec_ctx = avcodec_alloc_context3(null_mut());
1220        if codec_ctx.is_null() {
1221            return Err(OpenOutputError::OutOfMemory.into());
1222        }
1223        let _codec_context = CodecContext::new(codec_ctx);
1224
1225        let mut ret = avcodec_parameters_to_context(codec_ctx, (*input_stream).codecpar);
1226        if ret < 0 {
1227            error!("Error setting up codec context options.");
1228            return Err(OpenOutputError::from(ret).into());
1229        }
1230
1231        ret = avcodec_parameters_from_context((*output_stream).codecpar, codec_ctx);
1232        if ret < 0 {
1233            error!("Error getting reference codec parameters.");
1234            return Err(OpenOutputError::from(ret).into());
1235        }
1236
1237        let mut codec_tag = (*(*output_stream).codecpar).codec_tag;
1238        if codec_tag == 0 {
1239            let ct = (*(*mux.out_fmt_ctx).oformat).codec_tag;
1240            let mut codec_tag_tmp = 0;
1241            if ct.is_null()
1242                || av_codec_get_id(ct, (*(*output_stream).codecpar).codec_tag)
1243                    == (*(*output_stream).codecpar).codec_id
1244                || av_codec_get_tag2(
1245                    ct,
1246                    (*(*output_stream).codecpar).codec_id,
1247                    &mut codec_tag_tmp,
1248                ) == 0
1249            {
1250                codec_tag = (*(*output_stream).codecpar).codec_tag;
1251            }
1252        }
1253        (*(*output_stream).codecpar).codec_tag = codec_tag;
1254
1255        let mut fr = (*output_stream).r_frame_rate;
1256        if fr.num == 0 {
1257            fr = (*input_stream).r_frame_rate;
1258        }
1259
1260        if fr.num != 0 {
1261            (*output_stream).avg_frame_rate = fr;
1262        } else {
1263            (*output_stream).avg_frame_rate = (*input_stream).avg_frame_rate;
1264        }
1265
1266        // copy timebase while removing common factors
1267        if (*output_stream).time_base.num <= 0 || (*output_stream).time_base.den <= 0 {
1268            if fr.num != 0 {
1269                (*output_stream).time_base = av_inv_q(fr);
1270            } else {
1271                (*output_stream).time_base =
1272                    av_add_q((*input_stream).time_base, AVRational { num: 0, den: 1 });
1273            }
1274        }
1275
1276        for i in 0..(*(*input_stream).codecpar).nb_coded_side_data {
1277            let sd_src = (*(*input_stream).codecpar)
1278                .coded_side_data
1279                .offset(i as isize);
1280
1281            let sd_dst = av_packet_side_data_new(
1282                &mut (*(*output_stream).codecpar).coded_side_data,
1283                &mut (*(*output_stream).codecpar).nb_coded_side_data,
1284                (*sd_src).type_,
1285                (*sd_src).size,
1286                0,
1287            );
1288            if sd_dst.is_null() {
1289                return Err(OpenOutputError::OutOfMemory.into());
1290            }
1291            std::ptr::copy_nonoverlapping(
1292                (*sd_src).data as *const u8,
1293                (*sd_dst).data,
1294                (*sd_src).size,
1295            );
1296        }
1297
1298        match (*(*output_stream).codecpar).codec_type {
1299            AVMEDIA_TYPE_AUDIO => {
1300                if ((*(*output_stream).codecpar).block_align == 1
1301                    || (*(*output_stream).codecpar).block_align == 1152
1302                    || (*(*output_stream).codecpar).block_align == 576)
1303                    && (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_MP3
1304                {
1305                    (*(*output_stream).codecpar).block_align = 0;
1306                }
1307                if (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_AC3 {
1308                    (*(*output_stream).codecpar).block_align = 0;
1309                }
1310            }
1311            AVMEDIA_TYPE_VIDEO => {
1312                let sar = if (*input_stream).sample_aspect_ratio.num != 0 {
1313                    (*input_stream).sample_aspect_ratio
1314                } else {
1315                    (*(*output_stream).codecpar).sample_aspect_ratio
1316                };
1317                (*output_stream).sample_aspect_ratio = sar;
1318                (*(*output_stream).codecpar).sample_aspect_ratio = sar;
1319                (*output_stream).r_frame_rate = (*input_stream).r_frame_rate;
1320            }
1321            _ => {}
1322        }
1323    };
1324    Ok(())
1325}
1326
1327fn output_bind_by_unlabeled_filter(
1328    index: usize,
1329    mux: &mut Muxer,
1330    filter_graphs: &mut Vec<FilterGraph>,
1331    auto_disable: &mut i32,
1332) -> Result<()> {
1333    let fg_len = filter_graphs.len();
1334
1335    for i in 0..fg_len {
1336        let filter_graph = &mut filter_graphs[i];
1337
1338        for i in 0..filter_graph.outputs.len() {
1339            let option = {
1340                let output_filter = &filter_graph.outputs[i];
1341                if (!output_filter.linklabel.is_empty() && output_filter.linklabel != "out")
1342                    || output_filter.has_dst()
1343                {
1344                    continue;
1345                }
1346
1347                choose_encoder(mux, output_filter.media_type)?
1348            };
1349
1350            let media_type = filter_graph.outputs[i].media_type;
1351
1352            match option {
1353                None => {
1354                    warn!("An unexpected media_type {:?} appears in output_filter", media_type);
1355                }
1356                Some((codec_id, enc)) => {
1357                    *auto_disable |= 1 << media_type as i32;
1358                    ofilter_bind_ost(index, mux, filter_graph, i, codec_id, enc)?;
1359                }
1360            }
1361        }
1362    }
1363
1364    Ok(())
1365}
1366
1367fn ofilter_bind_ost(
1368    index: usize,
1369    mux: &mut Muxer,
1370    filter_graph: &mut FilterGraph,
1371    output_filter_index: usize,
1372    codec_id: AVCodecID,
1373    enc: *const AVCodec,
1374) -> Result<()> {
1375    let output_filter = &mut filter_graph.outputs[output_filter_index];
1376    let (frame_sender, output_stream_index) =
1377        mux.add_enc_stream(output_filter.media_type, enc, filter_graph.node.clone())?;
1378    output_filter.set_dst(frame_sender);
1379
1380    configure_output_filter_opts(
1381        index,
1382        mux,
1383        output_filter,
1384        codec_id,
1385        enc,
1386        output_stream_index,
1387    )?;
1388    Ok(())
1389}
1390
1391fn choose_encoder(
1392    mux: &Muxer,
1393    media_type: AVMediaType,
1394) -> Result<Option<(AVCodecID, *const AVCodec)>> {
1395    let media_codec = match media_type {
1396        AVMEDIA_TYPE_VIDEO => mux.video_codec.clone(),
1397        AVMEDIA_TYPE_AUDIO => mux.audio_codec.clone(),
1398        AVMEDIA_TYPE_SUBTITLE => mux.subtitle_codec.clone(),
1399        _ => return Ok(None),
1400    };
1401
1402    match media_codec {
1403        None => {
1404            let url = CString::new(&*mux.url).unwrap();
1405            unsafe {
1406                let codec_id = av_guess_codec(
1407                    (*mux.out_fmt_ctx).oformat,
1408                    null(),
1409                    url.as_ptr(),
1410                    null(),
1411                    media_type,
1412                );
1413                let enc = avcodec_find_encoder(codec_id);
1414                if enc.is_null() {
1415                    let format_name = (*(*mux.out_fmt_ctx).oformat).name;
1416                    let format_name = CStr::from_ptr(format_name).to_str();
1417                    let codec_name = avcodec_get_name(codec_id);
1418                    let codec_name = CStr::from_ptr(codec_name).to_str();
1419                    if let (Ok(format_name), Ok(codec_name)) = (format_name, codec_name) {
1420                        error!("Automatic encoder selection failed Default encoder for format {format_name} (codec {codec_name}) is probably disabled. Please choose an encoder manually.");
1421                    }
1422                    return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1423                }
1424
1425                return Ok(Some((codec_id, enc)));
1426            }
1427        }
1428        Some(media_codec) if media_codec != "copy" => unsafe {
1429            let media_codec_cstr = CString::new(media_codec.clone())?;
1430
1431            let mut enc = avcodec_find_encoder_by_name(media_codec_cstr.as_ptr());
1432            let desc = avcodec_descriptor_get_by_name(media_codec_cstr.as_ptr());
1433
1434            if enc.is_null() && !desc.is_null() {
1435                enc = avcodec_find_encoder((*desc).id);
1436                if !enc.is_null() {
1437                    let codec_name = (*enc).name;
1438                    let codec_name = CStr::from_ptr(codec_name).to_str();
1439                    let desc_name = (*desc).name;
1440                    let desc_name = CStr::from_ptr(desc_name).to_str();
1441                    if let (Ok(codec_name), Ok(desc_name)) = (codec_name, desc_name) {
1442                        debug!("Matched encoder '{codec_name}' for codec '{desc_name}'.");
1443                    }
1444                }
1445            }
1446
1447            if enc.is_null() {
1448                error!("Unknown encoder '{media_codec}'");
1449                return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1450            }
1451
1452            if (*enc).type_ != media_type {
1453                error!("Invalid encoder type '{media_codec}'");
1454                return Err(OpenOutputError::InvalidArgument.into());
1455            }
1456            let codec_id = (*enc).id;
1457            return Ok(Some((codec_id, enc)));
1458        },
1459        _ => {}
1460    };
1461
1462    Ok(None)
1463}
1464
1465fn check_duplicate_inputs_outputs(inputs: &[Input], outputs: &[Output]) -> Result<()> {
1466    for output in outputs {
1467        if let Some(output_url) = &output.url {
1468            for input in inputs {
1469                if let Some(input_url) = &input.url {
1470                    if input_url == output_url {
1471                        return Err(FileSameAsInput(input_url.clone()));
1472                    }
1473                }
1474            }
1475        }
1476    }
1477    Ok(())
1478}
1479
1480fn open_output_files(outputs: &mut Vec<Output>, copy_ts: bool) -> Result<Vec<Muxer>> {
1481    let mut muxs = Vec::new();
1482
1483    for (i, output) in outputs.iter_mut().enumerate() {
1484        unsafe {
1485            let result = open_output_file(i, output, copy_ts);
1486            if let Err(e) = result {
1487                free_output_av_format_context(muxs);
1488                return Err(e);
1489            }
1490            let mux = result.unwrap();
1491            muxs.push(mux)
1492        }
1493    }
1494    Ok(muxs)
1495}
1496
1497unsafe fn free_output_av_format_context(muxs: Vec<Muxer>) {
1498    for mut mux in muxs {
1499        avformat_close_input(&mut mux.out_fmt_ctx);
1500    }
1501}
1502
1503#[cfg(feature = "docs-rs")]
1504unsafe fn open_output_file(index: usize, output: &mut Output, copy_ts: bool) -> Result<Muxer> {
1505    Err(Error::Bug)
1506}
1507
1508#[cfg(not(feature = "docs-rs"))]
1509unsafe fn open_output_file(index: usize, output: &mut Output, copy_ts: bool) -> Result<Muxer> {
1510    let mut out_fmt_ctx = null_mut();
1511    let format = get_format(&output.format)?;
1512    match &output.url {
1513        None => {
1514            if output.write_callback.is_none() {
1515                error!("input url and write_callback is none.");
1516                return Err(OpenOutputError::InvalidSink.into());
1517            }
1518
1519            let write_callback = output.write_callback.take().unwrap();
1520
1521            let avio_ctx_buffer_size = 1024 * 64;
1522            let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
1523            if avio_ctx_buffer.is_null() {
1524                return Err(OpenOutputError::OutOfMemory.into());
1525            }
1526
1527            let have_seek_callback = output.seek_callback.is_some();
1528            let input_opaque = Box::new(OutputOpaque {
1529                write: write_callback,
1530                seek: output.seek_callback.take(),
1531            });
1532            let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
1533
1534            let mut avio_ctx = avio_alloc_context(
1535                avio_ctx_buffer as *mut libc::c_uchar,
1536                avio_ctx_buffer_size as i32,
1537                1,
1538                opaque,
1539                None,
1540                Some(write_packet_wrapper),
1541                if have_seek_callback {
1542                    Some(seek_packet_wrapper)
1543                } else {
1544                    None
1545                },
1546            );
1547            if avio_ctx.is_null() {
1548                av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
1549                return Err(OpenOutputError::OutOfMemory.into());
1550            }
1551
1552            let ret = avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), null());
1553            if out_fmt_ctx.is_null() {
1554                warn!("Error initializing the muxer for write_callback");
1555                av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
1556                avio_context_free(&mut avio_ctx);
1557                return Err(AllocOutputContextError::from(ret).into());
1558            }
1559
1560            if !have_seek_callback && output_requires_seek(out_fmt_ctx) {
1561                av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
1562                avio_context_free(&mut avio_ctx);
1563                avformat_free_context(out_fmt_ctx);
1564                warn!("The output format supports seeking, but no seek callback is provided. This may cause issues.");
1565                return Err(OpenOutputError::SeekFunctionMissing.into());
1566            }
1567
1568            (*out_fmt_ctx).pb = avio_ctx;
1569            (*out_fmt_ctx).flags |= AVFMT_FLAG_CUSTOM_IO;
1570        }
1571        Some(url) => {
1572            let url_cstr = if url == "-" {
1573                CString::new("pipe:")?
1574            } else {
1575                CString::new(url.as_str())?
1576            };
1577            let ret =
1578                avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), url_cstr.as_ptr());
1579            if out_fmt_ctx.is_null() {
1580                warn!("Error initializing the muxer for {url}");
1581                return Err(AllocOutputContextError::from(ret).into());
1582            }
1583
1584            let output_format = (*out_fmt_ctx).oformat;
1585            if (*output_format).flags & AVFMT_NOFILE == 0 {
1586                let ret = avio_open(&mut (*out_fmt_ctx).pb, url_cstr.as_ptr(), AVIO_FLAG_WRITE);
1587                if ret < 0 {
1588                    warn!("Error opening output {url}");
1589                    return Err(OpenOutputError::from(ret).into());
1590                }
1591            }
1592        }
1593    }
1594
1595    let recording_time_us = match output.stop_time_us {
1596        None => output.recording_time_us,
1597        Some(stop_time_us) => {
1598            let start_time_us = output.start_time_us.unwrap_or_else(|| 0);
1599            if stop_time_us <= start_time_us {
1600                error!("stop_time_us value smaller than start_time_us; aborting.");
1601                return Err(OpenOutputError::InvalidArgument.into());
1602            } else {
1603                Some(stop_time_us - start_time_us)
1604            }
1605        }
1606    };
1607
1608    let url = output
1609        .url
1610        .clone()
1611        .unwrap_or_else(|| format!("write_callback[{index}]"));
1612
1613    let video_codec_opts = convert_options(output.video_codec_opts.clone())?;
1614    let audio_codec_opts = convert_options(output.audio_codec_opts.clone())?;
1615    let subtitle_codec_opts = convert_options(output.subtitle_codec_opts.clone())?;
1616    let format_opts = convert_options(output.format_opts.clone())?;
1617
1618    let mux = Muxer::new(
1619        url,
1620        output.url.is_none(),
1621        out_fmt_ctx,
1622        output.frame_pipelines.take(),
1623        output.stream_maps.clone(),
1624        output.video_codec.clone(),
1625        output.audio_codec.clone(),
1626        output.subtitle_codec.clone(),
1627        output.start_time_us,
1628        recording_time_us,
1629        output.framerate,
1630        output.vsync_method,
1631        output.bits_per_raw_sample,
1632        output.audio_sample_rate,
1633        output.audio_channels,
1634        output.audio_sample_fmt,
1635        output.video_qscale,
1636        output.audio_qscale,
1637        output.max_video_frames,
1638        output.max_audio_frames,
1639        output.max_subtitle_frames,
1640        video_codec_opts,
1641        audio_codec_opts,
1642        subtitle_codec_opts,
1643        format_opts,
1644        copy_ts
1645    );
1646
1647    Ok(mux)
1648}
1649
1650fn get_format(format_option: &Option<String>) -> Result<*const AVOutputFormat> {
1651    match format_option {
1652        None => Ok(null()),
1653        Some(format_str) => unsafe {
1654            let mut format_cstr = CString::new(format_str.to_string())?;
1655            let mut format = av_guess_format(format_cstr.as_ptr(), null(), null());
1656            if format.is_null() {
1657                format_cstr = CString::new(format!("tmp.{format_str}"))?;
1658                format = av_guess_format(null(), format_cstr.as_ptr(), null());
1659            }
1660            if format.is_null() {
1661                return Err(OpenOutputError::FormatUnsupported(format_str.to_string()).into());
1662            }
1663            Ok(format)
1664        },
1665    }
1666}
1667
1668unsafe fn output_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
1669    if fmt_ctx.is_null() {
1670        return false;
1671    }
1672
1673    let mut format_name = "unknown".to_string();
1674
1675    if !(*fmt_ctx).oformat.is_null() {
1676        let oformat = (*fmt_ctx).oformat;
1677        format_name = CStr::from_ptr((*oformat).name)
1678            .to_string_lossy()
1679            .into_owned();
1680        let flags = (*oformat).flags;
1681        let no_file = flags & AVFMT_NOFILE as i32 != 0;
1682        let global_header = flags & AVFMT_GLOBALHEADER as i32 != 0;
1683
1684        log::debug!(
1685            "Output format '{format_name}' - No file: {}, Global header: {}",
1686            if no_file { "True" } else { "False" },
1687            if global_header { "True" } else { "False" }
1688        );
1689
1690        // List of formats that typically require seeking
1691        let format_names: Vec<&str> = format_name.split(',').collect();
1692        if format_names
1693            .iter()
1694            .any(|&f| matches!(f, "mp4" | "mov" | "mkv" | "avi" | "flac" | "ogg" | "webm"))
1695        {
1696            log::debug!("Output format '{format_name}' typically requires seeking.");
1697            return true;
1698        }
1699
1700        // List of streaming formats that do not require seeking
1701        if format_names.iter().any(|&f| {
1702            matches!(
1703                f,
1704                "mpegts" | "hls" | "m3u8" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
1705            )
1706        }) {
1707            log::debug!("Output format '{format_name}' does not typically require seeking.");
1708            return false;
1709        }
1710
1711        // Special handling for FLV format
1712        if format_name == "flv" {
1713            log::debug!("Output format 'flv' detected. It is highly recommended to set `seek_callback()` to avoid potential issues with 'Failed to update header with correct duration' and 'Failed to update header with correct filesize'.");
1714            return false;
1715        }
1716
1717        // If AVFMT_NOFILE is set, the format does not use standard file I/O and may not need seeking
1718        if no_file {
1719            log::debug!(
1720                "Output format '{format_name}' uses AVFMT_NOFILE. Seeking is likely unnecessary."
1721            );
1722            return false;
1723        }
1724
1725        // If the format uses global headers, it typically means the codec requires a separate metadata section
1726        if global_header {
1727            log::debug!(
1728                "Output format '{format_name}' uses AVFMT_GLOBALHEADER. Seeking may be required."
1729            );
1730            return true;
1731        }
1732    } else {
1733        log::debug!("Output format is null. Cannot determine if seeking is required.");
1734    }
1735
1736    // Default case: assume seeking is not required
1737    log::debug!("Output format '{format_name}' does not match any known rules. Assuming seeking is not required.");
1738    false
1739}
1740
1741struct InputOpaque {
1742    read: Box<dyn FnMut(&mut [u8]) -> i32>,
1743    seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1744}
1745
1746#[allow(dead_code)]
1747struct OutputOpaque {
1748    write: Box<dyn FnMut(&[u8]) -> i32>,
1749    seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1750}
1751
1752unsafe extern "C" fn write_packet_wrapper(
1753    opaque: *mut libc::c_void,
1754    buf: *const u8,
1755    buf_size: libc::c_int,
1756) -> libc::c_int {
1757    if buf.is_null() {
1758        return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1759    }
1760    let closure = &mut *(opaque as *mut Box<dyn FnMut(&[u8]) -> i32>);
1761
1762    let slice = std::slice::from_raw_parts(buf, buf_size as usize);
1763
1764    (*closure)(slice)
1765}
1766
1767unsafe extern "C" fn read_packet_wrapper(
1768    opaque: *mut libc::c_void,
1769    buf: *mut u8,
1770    buf_size: libc::c_int,
1771) -> libc::c_int {
1772    if buf.is_null() {
1773        return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1774    }
1775
1776    let context = &mut *(opaque as *mut InputOpaque);
1777
1778    let slice = std::slice::from_raw_parts_mut(buf, buf_size as usize);
1779
1780    (context.read)(slice)
1781}
1782
1783unsafe extern "C" fn seek_packet_wrapper(
1784    opaque: *mut libc::c_void,
1785    offset: i64,
1786    whence: libc::c_int,
1787) -> i64 {
1788    let context = &mut *(opaque as *mut InputOpaque);
1789
1790    if let Some(seek_func) = &mut context.seek {
1791        (*seek_func)(offset, whence)
1792    } else {
1793        ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64
1794    }
1795}
1796
1797fn fg_bind_inputs(filter_graphs: &mut Vec<FilterGraph>, demuxs: &mut Vec<Demuxer>) -> Result<()> {
1798    if filter_graphs.is_empty() {
1799        return Ok(());
1800    }
1801    bind_fg_inputs_by_fg(filter_graphs)?;
1802
1803    for filter_graph in filter_graphs.iter_mut() {
1804        for i in 0..filter_graph.inputs.len() {
1805            fg_complex_bind_input(
1806                filter_graph,
1807                i,
1808                demuxs,
1809            )?;
1810        }
1811    }
1812
1813    Ok(())
1814}
1815
1816struct FilterLabel {
1817    linklabel: String,
1818    media_type: AVMediaType,
1819}
1820
1821fn bind_fg_inputs_by_fg(filter_graphs: &mut Vec<FilterGraph>) -> Result<()> {
1822    let fg_labels = filter_graphs
1823        .iter()
1824        .map(|filter_graph| {
1825            let inputs = filter_graph
1826                .inputs
1827                .iter()
1828                .map(|input| FilterLabel {
1829                    linklabel: input.linklabel.clone(),
1830                    media_type: input.media_type,
1831                })
1832                .collect::<Vec<_>>();
1833            let outputs = filter_graph
1834                .outputs
1835                .iter()
1836                .map(|output| FilterLabel {
1837                    linklabel: output.linklabel.clone(),
1838                    media_type: output.media_type,
1839                })
1840                .collect::<Vec<_>>();
1841            (inputs, outputs)
1842        })
1843        .collect::<Vec<_>>();
1844
1845    for (i, (inputs, _outputs)) in fg_labels.iter().enumerate() {
1846        for input_filter_label in inputs.iter() {
1847            if input_filter_label.linklabel.is_empty() {
1848                continue;
1849            }
1850
1851            'outer: for (j, (_inputs, outputs)) in fg_labels.iter().enumerate() {
1852                if i == j {
1853                    continue;
1854                }
1855
1856                for (output_idx, output_filter_label) in outputs.iter().enumerate() {
1857                    if output_filter_label.linklabel != input_filter_label.linklabel {
1858                        continue;
1859                    }
1860                    if output_filter_label.media_type != input_filter_label.media_type {
1861                        warn!(
1862                            "Tried to connect {:?} output to {:?} input",
1863                            output_filter_label.media_type, input_filter_label.media_type
1864                        );
1865                        return Err(FilterGraphParseError::InvalidArgument.into());
1866                    }
1867
1868                    {
1869                        let filter_graph = &filter_graphs[j];
1870                        let output_filter = &filter_graph.outputs[output_idx];
1871                        if output_filter.has_dst() {
1872                            continue;
1873                        }
1874
1875                    }
1876
1877                    let (sender, finished_flag_list) = {
1878                        let filter_graph = &mut filter_graphs[i];
1879                        filter_graph.get_src_sender()
1880                    };
1881
1882                    {
1883                        let filter_graph = &mut filter_graphs[j];
1884                        filter_graph.outputs[output_idx].set_dst(sender);
1885                        filter_graph.outputs[output_idx].fg_input_index = i;
1886                        filter_graph.outputs[output_idx].finished_flag_list = finished_flag_list;
1887                    }
1888
1889                    break 'outer;
1890                }
1891            }
1892        }
1893    }
1894    Ok(())
1895}
1896
1897fn fg_complex_bind_input(
1898    filter_graph: &mut FilterGraph,
1899    input_filter_index: usize,
1900    demuxs: &mut Vec<Demuxer>,
1901) -> Result<()> {
1902    let graph_desc = &filter_graph.graph_desc;
1903    let input_filter = &mut filter_graph.inputs[input_filter_index];
1904    let (demux_idx, stream_idx) =
1905        if !input_filter.linklabel.is_empty() && input_filter.linklabel != "in" {
1906            let (demux_idx, stream_idx) = fg_find_input_idx_by_linklabel(
1907                &input_filter.linklabel,
1908                input_filter.media_type,
1909                demuxs,
1910                graph_desc,
1911            )?;
1912
1913            info!(
1914                "Binding filter input with label '{}' to input stream {stream_idx}:{demux_idx}",
1915                input_filter.linklabel
1916            );
1917            (demux_idx, stream_idx)
1918        } else {
1919            let mut demux_idx = -1i32;
1920            let mut stream_idx = 0;
1921            for (d_idx, demux) in demuxs.iter().enumerate() {
1922                for (st_idx, intput_stream) in demux.get_streams().iter().enumerate() {
1923                    if intput_stream.is_used() {
1924                        continue;
1925                    }
1926                    if intput_stream.codec_type == input_filter.media_type {
1927                        demux_idx = d_idx as i32;
1928                        stream_idx = st_idx;
1929                        break;
1930                    }
1931                }
1932                if demux_idx >= 0 {
1933                    break;
1934                }
1935            }
1936
1937            if demux_idx < 0 {
1938                warn!("Cannot find a matching stream for unlabeled input pad {}", input_filter.name);
1939                return Err(FilterGraphParseError::InvalidArgument.into());
1940            }
1941
1942            debug!("FilterGraph binding unlabeled input {input_filter_index} to input stream {stream_idx}:{demux_idx}");
1943
1944            (demux_idx as usize, stream_idx)
1945        };
1946
1947    let demux = &mut demuxs[demux_idx];
1948
1949    ifilter_bind_ist(filter_graph, input_filter_index, stream_idx, demux)
1950}
1951
1952#[cfg(feature = "docs-rs")]
1953fn ifilter_bind_ist(
1954    filter_graph: &mut FilterGraph,
1955    input_index: usize,
1956    stream_idx: usize,
1957    demux: &mut Demuxer,
1958) -> Result<()> {
1959    Ok(())
1960}
1961
1962#[cfg(not(feature = "docs-rs"))]
1963fn ifilter_bind_ist(
1964    filter_graph: &mut FilterGraph,
1965    input_index: usize,
1966    stream_idx: usize,
1967    demux: &mut Demuxer,
1968) -> Result<()> {
1969    unsafe {
1970        let input_filter = &mut filter_graph.inputs[input_index];
1971        let ist = *(*demux.in_fmt_ctx).streams.add(stream_idx);
1972        let par = (*ist).codecpar;
1973        if (*par).codec_type == AVMEDIA_TYPE_VIDEO {
1974            let framerate = av_guess_frame_rate(demux.in_fmt_ctx, ist, null_mut());
1975            input_filter.opts.framerate = framerate;
1976        } else if (*par).codec_type == AVMEDIA_TYPE_SUBTITLE {
1977            input_filter.opts.sub2video_width = (*par).width;
1978            input_filter.opts.sub2video_height = (*par).height;
1979
1980            if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1981                let nb_streams = (*demux.in_fmt_ctx).nb_streams;
1982                for j in 0..nb_streams {
1983                    let par1 = (**(*demux.in_fmt_ctx).streams.add(j as usize)).codecpar;
1984                    if (*par1).codec_type == AVMEDIA_TYPE_VIDEO {
1985                        input_filter.opts.sub2video_width =
1986                            std::cmp::max(input_filter.opts.sub2video_width, (*par1).width);
1987                        input_filter.opts.sub2video_height =
1988                            std::cmp::max(input_filter.opts.sub2video_height, (*par1).height);
1989                    }
1990                }
1991            }
1992
1993            if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1994                input_filter.opts.sub2video_width =
1995                    std::cmp::max(input_filter.opts.sub2video_width, 720);
1996                input_filter.opts.sub2video_height =
1997                    std::cmp::max(input_filter.opts.sub2video_height, 576);
1998            }
1999
2000            demux.get_stream_mut(stream_idx).have_sub2video = true;
2001        }
2002
2003        let dec_ctx = {
2004            let input_stream = demux.get_stream_mut(stream_idx);
2005            avcodec_alloc_context3(input_stream.codec.as_ptr())
2006        };
2007        if dec_ctx.is_null() {
2008            return Err(FilterGraphParseError::OutOfMemory.into());
2009        }
2010        let _codec_ctx = CodecContext::new(dec_ctx);
2011
2012        let fallback = input_filter.opts.fallback.as_mut_ptr();
2013        if (*dec_ctx).codec_type == AVMEDIA_TYPE_AUDIO {
2014            (*fallback).format = (*dec_ctx).sample_fmt as i32;
2015            (*fallback).sample_rate = (*dec_ctx).sample_rate;
2016
2017            let ret = av_channel_layout_copy(&mut (*fallback).ch_layout, &(*dec_ctx).ch_layout);
2018            if ret < 0 {
2019                return Err(FilterGraphParseError::from(ret).into());
2020            }
2021        } else if (*dec_ctx).codec_type == AVMEDIA_TYPE_VIDEO {
2022            (*fallback).format = (*dec_ctx).pix_fmt as i32;
2023            (*fallback).width = (*dec_ctx).width;
2024            (*fallback).height = (*dec_ctx).height;
2025            (*fallback).sample_aspect_ratio = (*dec_ctx).sample_aspect_ratio;
2026            (*fallback).colorspace = (*dec_ctx).colorspace;
2027            (*fallback).color_range = (*dec_ctx).color_range;
2028        }
2029        (*fallback).time_base = (*dec_ctx).pkt_timebase;
2030
2031        //TODO Set this flag according to the input stream parameters
2032        input_filter.opts.flags |= IFILTER_FLAG_AUTOROTATE;
2033
2034        let tsoffset = if demux.copy_ts {
2035            let mut tsoffset = if demux.start_time_us.is_some() {
2036                demux.start_time_us.unwrap()
2037            } else {
2038                0
2039            };
2040            if (*demux.in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2041                tsoffset += (*demux.in_fmt_ctx).start_time
2042            }
2043            tsoffset
2044        } else {
2045            0
2046        };
2047        if demux.start_time_us.is_some() {
2048            input_filter.opts.trim_start_us = Some(tsoffset);
2049        }
2050        input_filter.opts.trim_end_us = demux.recording_time_us;
2051
2052        let (sender, finished_flag_list) = filter_graph.get_src_sender();
2053        {
2054            let input_stream = demux.get_stream_mut(stream_idx);
2055            input_stream.add_fg_dst(sender, input_index, finished_flag_list);
2056        };
2057
2058        let node = Arc::make_mut(&mut filter_graph.node);
2059        let SchNode::Filter { inputs, .. } = node else {
2060            unreachable!()
2061        };
2062        inputs.insert(input_index, demux.node.clone());
2063
2064
2065        demux.connect_stream(stream_idx);
2066        Ok(())
2067    }
2068}
2069
2070fn fg_find_input_idx_by_linklabel(
2071    linklabel: &str,
2072    filter_media_type: AVMediaType,
2073    demuxs: &mut Vec<Demuxer>,
2074    desc: &str,
2075) -> Result<(usize, usize)> {
2076    let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
2077        if linklabel.len() <= 2 {
2078            warn!("Filter linklabel is empty");
2079            return Err(InvalidFilterSpecifier(desc.to_string()).into());
2080        } else {
2081            &linklabel[1..linklabel.len() - 1]
2082        }
2083    } else {
2084        linklabel
2085    };
2086
2087    let (file_idx, remainder) = strtol(new_linklabel)?;
2088    if file_idx < 0 || file_idx as usize >= demuxs.len() {
2089        return Err(InvalidFileIndexInFg(file_idx as usize, desc.to_string()).into());
2090    }
2091
2092    let (media_type, _allow_unused) = stream_specifier_parse(remainder)?;
2093
2094    if media_type != filter_media_type {
2095        warn!("Invalid stream label: {linklabel}");
2096        return Err(FilterGraphParseError::InvalidArgument.into());
2097    }
2098
2099    let demux = &demuxs[file_idx as usize];
2100
2101    let mut stream_idx = -1i32;
2102
2103    for (idx, dec_stream) in demux.get_streams().iter().enumerate() {
2104        if (*dec_stream).codec_type == media_type {
2105            stream_idx = idx as i32;
2106            break;
2107        }
2108    }
2109
2110    if stream_idx < 0 {
2111        warn!(
2112            "Stream specifier '{remainder}' in filtergraph description {desc} matches no streams."
2113        );
2114        return Err(FilterGraphParseError::InvalidArgument.into());
2115    }
2116    Ok((file_idx as usize, stream_idx as usize))
2117}
2118
2119fn stream_specifier_parse(specifier: &str) -> Result<(AVMediaType, bool)> {
2120    let specifier = if specifier.starts_with(':') {
2121        &specifier[1..]
2122    } else {
2123        specifier
2124    };
2125
2126    match specifier {
2127        "v" => Ok((AVMEDIA_TYPE_VIDEO, false)),
2128        "a" => Ok((AVMEDIA_TYPE_AUDIO, false)),
2129        "s" => Ok((AVMEDIA_TYPE_SUBTITLE, false)),
2130        "d" => Ok((AVMEDIA_TYPE_DATA, false)),
2131        "t" => Ok((AVMEDIA_TYPE_ATTACHMENT, false)),
2132        "v?" => Ok((AVMEDIA_TYPE_VIDEO, true)),
2133        "a?" => Ok((AVMEDIA_TYPE_AUDIO, true)),
2134        "s?" => Ok((AVMEDIA_TYPE_SUBTITLE, true)),
2135        "d?" => Ok((AVMEDIA_TYPE_DATA, true)),
2136        "t?" => Ok((AVMEDIA_TYPE_ATTACHMENT, true)),
2137        // "V" => Ok(AVMEDIA_TYPE_VIDEO),
2138        _ => Err(InvalidFilterSpecifier(specifier.to_string()).into()),
2139    }
2140}
2141
2142/// Similar to strtol() in C
2143fn strtol(input: &str) -> Result<(i64, &str)> {
2144    let mut chars = input.chars().peekable();
2145    let mut negative = false;
2146
2147    if let Some(&ch) = chars.peek() {
2148        if ch == '-' {
2149            negative = true;
2150            chars.next();
2151        } else if !ch.is_digit(10) {
2152            return Err(ParseInteger);
2153        }
2154    }
2155
2156    let number_start = input.len() - chars.clone().collect::<String>().len();
2157
2158    let number_str: String = chars.by_ref().take_while(|ch| ch.is_digit(10)).collect();
2159
2160    if number_str.is_empty() {
2161        return Err(ParseInteger);
2162    }
2163
2164    let number: i64 = number_str.parse().map_err(|_| ParseInteger)?;
2165
2166    let remainder_index = number_start + number_str.len();
2167    let remainder = &input[remainder_index..];
2168
2169    if negative {
2170        Ok((-number, remainder))
2171    } else {
2172        Ok((number, remainder))
2173    }
2174}
2175
2176fn init_filter_graphs(filter_complexs: Vec<FilterComplex>) -> Result<Vec<FilterGraph>> {
2177    let mut filter_graphs = Vec::with_capacity(filter_complexs.len());
2178    for (i, filter) in filter_complexs.iter().enumerate() {
2179        let filter_graph = init_filter_graph(i, &filter.filter_descs, filter.hw_device.clone())?;
2180        filter_graphs.push(filter_graph);
2181    }
2182    Ok(filter_graphs)
2183}
2184
2185
2186#[cfg(feature = "docs-rs")]
2187fn init_filter_graph(
2188    fg_index: usize,
2189    filter_desc: &str,
2190    hw_device: Option<String>,
2191) -> Result<FilterGraph> {
2192    Err(Error::Bug)
2193}
2194
2195#[cfg(not(feature = "docs-rs"))]
2196fn init_filter_graph(
2197    fg_index: usize,
2198    filter_desc: &str,
2199    hw_device: Option<String>,
2200) -> Result<FilterGraph> {
2201    let desc_cstr = CString::new(filter_desc)?;
2202
2203    unsafe {
2204        /* this graph is only used for determining the kinds of inputs
2205        and outputs we have, and is discarded on exit from this function */
2206        let mut graph = avfilter_graph_alloc();
2207        (*graph).nb_threads = 1;
2208
2209        let mut seg = null_mut();
2210        let mut ret = avfilter_graph_segment_parse(graph, desc_cstr.as_ptr(), 0, &mut seg);
2211        if ret < 0 {
2212            avfilter_graph_free(&mut graph);
2213            return Err(FilterGraphParseError::from(ret).into());
2214        }
2215
2216        ret = avfilter_graph_segment_create_filters(seg, 0);
2217        if ret < 0 {
2218            avfilter_graph_free(&mut graph);
2219            avfilter_graph_segment_free(&mut seg);
2220            return Err(FilterGraphParseError::from(ret).into());
2221        }
2222
2223        #[cfg(not(feature = "docs-rs"))]
2224        {
2225            ret = graph_opts_apply(seg);
2226        }
2227        if ret < 0 {
2228            avfilter_graph_segment_free(&mut seg);
2229            avfilter_graph_free(&mut graph);
2230            return Err(FilterGraphParseError::from(ret).into());
2231        }
2232
2233        let mut inputs = null_mut();
2234        let mut outputs = null_mut();
2235        ret = avfilter_graph_segment_apply(seg, 0, &mut inputs, &mut outputs);
2236        avfilter_graph_segment_free(&mut seg);
2237
2238        if ret < 0 {
2239            avfilter_inout_free(&mut inputs);
2240            avfilter_inout_free(&mut outputs);
2241            avfilter_graph_free(&mut graph);
2242            return Err(FilterGraphParseError::from(ret).into());
2243        }
2244
2245        let input_filters = inouts_to_input_filters(fg_index, inputs)?;
2246        let output_filters = inouts_to_output_filters(outputs)?;
2247
2248        if output_filters.len() == 0 {
2249            avfilter_inout_free(&mut inputs);
2250            avfilter_inout_free(&mut outputs);
2251            avfilter_graph_free(&mut graph);
2252            return Err(FilterZeroOutputs);
2253        }
2254
2255        let filter_graph = FilterGraph::new(
2256            filter_desc.to_string(),
2257            hw_device,
2258            input_filters,
2259            output_filters,
2260        );
2261
2262        avfilter_inout_free(&mut inputs);
2263        avfilter_inout_free(&mut outputs);
2264        avfilter_graph_free(&mut graph);
2265
2266        Ok(filter_graph)
2267    }
2268}
2269
2270unsafe fn inouts_to_input_filters(
2271    fg_index: usize,
2272    inouts: *mut AVFilterInOut,
2273) -> Result<Vec<InputFilter>> {
2274    let mut cur = inouts;
2275    let mut filterinouts = Vec::new();
2276    let mut filter_index = 0;
2277    while !cur.is_null() {
2278        let linklabel = if (*cur).name.is_null() {
2279            ""
2280        } else {
2281            let linklabel = CStr::from_ptr((*cur).name);
2282            let result = linklabel.to_str();
2283            if let Err(_) = result {
2284                return Err(FilterDescUtf8);
2285            }
2286            result.unwrap()
2287        };
2288
2289        let filter_ctx = (*cur).filter_ctx;
2290        let media_type = avfilter_pad_get_type((*filter_ctx).input_pads, (*cur).pad_idx);
2291
2292        let pads = (*filter_ctx).input_pads;
2293        let nb_pads = (*filter_ctx).nb_inputs;
2294
2295        let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
2296
2297        let fallback = frame_alloc()?;
2298
2299        let mut filter = InputFilter::new(linklabel.to_string(), media_type, name, fallback);
2300        filter.opts.name = format!("fg:{fg_index}:{filter_index}");
2301        filterinouts.push(filter);
2302
2303        cur = (*cur).next;
2304        filter_index += 1;
2305    }
2306    Ok(filterinouts)
2307}
2308
2309unsafe fn inouts_to_output_filters(inouts: *mut AVFilterInOut) -> Result<Vec<OutputFilter>> {
2310    let mut cur = inouts;
2311    let mut output_filters = Vec::new();
2312    while !cur.is_null() {
2313        let linklabel = if (*cur).name.is_null() {
2314            ""
2315        } else {
2316            let linklabel = CStr::from_ptr((*cur).name);
2317            let result = linklabel.to_str();
2318            if let Err(_) = result {
2319                return Err(FilterDescUtf8);
2320            }
2321            result.unwrap()
2322        };
2323
2324        let filter_ctx = (*cur).filter_ctx;
2325        let media_type = avfilter_pad_get_type((*filter_ctx).output_pads, (*cur).pad_idx);
2326
2327        let pads = (*filter_ctx).output_pads;
2328        let nb_pads = (*filter_ctx).nb_outputs;
2329
2330        let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
2331
2332        let filter = OutputFilter::new(linklabel.to_string(), media_type, name);
2333        output_filters.push(filter);
2334
2335        cur = (*cur).next;
2336    }
2337    Ok(output_filters)
2338}
2339
2340unsafe fn describe_filter_link(
2341    cur: *mut AVFilterInOut,
2342    filter_ctx: *mut AVFilterContext,
2343    pads: *mut AVFilterPad,
2344    nb_pads: c_uint,
2345) -> Result<String> {
2346    let filter = (*filter_ctx).filter;
2347    let name = (*filter).name;
2348    let name = CStr::from_ptr(name);
2349    let result = name.to_str();
2350    if let Err(_) = result {
2351        return Err(FilterNameUtf8);
2352    }
2353    let name = result.unwrap();
2354
2355    let name = if nb_pads > 1 {
2356        name.to_string()
2357    } else {
2358        let pad_name = avfilter_pad_get_name(pads, (*cur).pad_idx);
2359        let pad_name = CStr::from_ptr(pad_name);
2360        let result = pad_name.to_str();
2361        if let Err(_) = result {
2362            return Err(FilterNameUtf8);
2363        }
2364        let pad_name = result.unwrap();
2365        format!("{name}:{pad_name}")
2366    };
2367    Ok(name)
2368}
2369
2370fn open_input_files(inputs: &mut Vec<Input>, copy_ts: bool) -> Result<Vec<Demuxer>> {
2371    let mut demuxs = Vec::new();
2372    for (i, input) in inputs.iter_mut().enumerate() {
2373        unsafe {
2374            let result = open_input_file(i, input, copy_ts);
2375            if let Err(e) = result {
2376                free_input_av_format_context(demuxs);
2377                return Err(e);
2378            }
2379            let demux = result.unwrap();
2380            demuxs.push(demux)
2381        }
2382    }
2383    Ok(demuxs)
2384}
2385
2386unsafe fn free_input_av_format_context(demuxs: Vec<Demuxer>) {
2387    for mut demux in demuxs {
2388        avformat_close_input(&mut demux.in_fmt_ctx);
2389    }
2390}
2391
2392#[cfg(feature = "docs-rs")]
2393unsafe fn open_input_file(
2394    index: usize,
2395    input: &mut Input,
2396    copy_ts: bool,
2397) -> Result<Demuxer> {
2398    Err(Error::Bug)
2399}
2400
2401#[cfg(not(feature = "docs-rs"))]
2402unsafe fn open_input_file(
2403    index: usize,
2404    input: &mut Input,
2405    copy_ts: bool
2406) -> Result<Demuxer> {
2407    let mut in_fmt_ctx = avformat_alloc_context();
2408    if in_fmt_ctx.is_null() {
2409        return Err(OpenInputError::OutOfMemory.into());
2410    }
2411
2412    let recording_time_us = match input.stop_time_us {
2413        None => input.recording_time_us,
2414        Some(stop_time_us) => {
2415            let start_time_us = input.start_time_us.unwrap_or_else(|| 0);
2416            if stop_time_us <= start_time_us {
2417                error!("stop_time_us value smaller than start_time_us; aborting.");
2418                return Err(OpenOutputError::InvalidArgument.into());
2419            } else {
2420                Some(stop_time_us - start_time_us)
2421            }
2422        }
2423    };
2424
2425    let file_iformat = if let Some(format) = &input.format {
2426        let format_cstr = CString::new(format.clone())?;
2427
2428        let file_iformat = ffmpeg_sys_next::av_find_input_format(format_cstr.as_ptr());
2429        if file_iformat.is_null() {
2430            error!("Unknown input format: '{format}'");
2431            return Err(OpenInputError::InvalidFormat(format.clone()).into());
2432        }
2433        file_iformat
2434    } else {
2435        null()
2436    };
2437
2438    let input_opts = convert_options(input.input_opts.clone())?;
2439    let mut input_opts = hashmap_to_avdictionary(&input_opts);
2440
2441    match &input.url {
2442        None => {
2443            if input.read_callback.is_none() {
2444                error!("input url and read_callback is none.");
2445                return Err(OpenInputError::InvalidSource.into());
2446            }
2447
2448            let avio_ctx_buffer_size = 1024 * 64;
2449            let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
2450            if avio_ctx_buffer.is_null() {
2451                avformat_close_input(&mut in_fmt_ctx);
2452                return Err(OpenInputError::OutOfMemory.into());
2453            }
2454
2455            let have_seek_callback = input.seek_callback.is_some();
2456            let input_opaque = Box::new(InputOpaque {
2457                read: input.read_callback.take().unwrap(),
2458                seek: input.seek_callback.take(),
2459            });
2460            let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
2461
2462            let mut avio_ctx = avio_alloc_context(
2463                avio_ctx_buffer as *mut libc::c_uchar,
2464                avio_ctx_buffer_size as i32,
2465                0,
2466                opaque,
2467                Some(read_packet_wrapper),
2468                None,
2469                if have_seek_callback {
2470                    Some(seek_packet_wrapper)
2471                } else {
2472                    None
2473                },
2474            );
2475            if avio_ctx.is_null() {
2476                av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
2477                avformat_close_input(&mut in_fmt_ctx);
2478                return Err(OpenInputError::OutOfMemory.into());
2479            }
2480
2481            (*in_fmt_ctx).pb = avio_ctx;
2482            (*in_fmt_ctx).flags = AVFMT_FLAG_CUSTOM_IO;
2483
2484            let ret = avformat_open_input(&mut in_fmt_ctx, null(), file_iformat, &mut input_opts);
2485            if ret < 0 {
2486                av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2487                avio_context_free(&mut avio_ctx);
2488                avformat_close_input(&mut in_fmt_ctx);
2489                return Err(OpenInputError::from(ret).into());
2490            }
2491
2492            let ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2493            if ret < 0 {
2494                av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2495                avio_context_free(&mut avio_ctx);
2496                avformat_close_input(&mut in_fmt_ctx);
2497                return Err(FindStreamError::from(ret).into());
2498            }
2499
2500            if !have_seek_callback && input_requires_seek(in_fmt_ctx) {
2501                av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2502                avio_context_free(&mut avio_ctx);
2503                avformat_close_input(&mut in_fmt_ctx);
2504                warn!("The input format supports seeking, but no seek callback is provided. This may cause issues.");
2505                return Err(OpenInputError::SeekFunctionMissing.into());
2506            }
2507        }
2508        Some(url) => {
2509            let url_cstr = CString::new(url.as_str())?;
2510
2511            let scan_all_pmts_key = CString::new("scan_all_pmts")?;
2512            if ffmpeg_sys_next::av_dict_get(input_opts, scan_all_pmts_key.as_ptr(), null(), ffmpeg_sys_next::AV_DICT_MATCH_CASE).is_null() {
2513                let scan_all_pmts_value = CString::new("1")?;
2514                ffmpeg_sys_next::av_dict_set(&mut input_opts, scan_all_pmts_key.as_ptr(), scan_all_pmts_value.as_ptr(), ffmpeg_sys_next::AV_DICT_DONT_OVERWRITE);
2515            };
2516            (*in_fmt_ctx).flags |= ffmpeg_sys_next::AVFMT_FLAG_NONBLOCK;
2517
2518            let mut ret = avformat_open_input(&mut in_fmt_ctx, url_cstr.as_ptr(), file_iformat, &mut input_opts);
2519            av_dict_free(&mut input_opts);
2520            if ret < 0 {
2521                avformat_close_input(&mut in_fmt_ctx);
2522                return Err(OpenInputError::from(ret).into());
2523            }
2524
2525            ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2526            if ret < 0 {
2527                avformat_close_input(&mut in_fmt_ctx);
2528                return Err(FindStreamError::from(ret).into());
2529            }
2530        }
2531    }
2532
2533    let mut timestamp = input.start_time_us.unwrap_or(0);
2534    /* add the stream start time */
2535    if (*in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2536        timestamp += (*in_fmt_ctx).start_time;
2537    }
2538
2539    /* if seeking requested, we execute it */
2540    if let Some(start_time_us) = input.start_time_us {
2541        let mut seek_timestamp = timestamp;
2542
2543        if (*(*in_fmt_ctx).iformat).flags & ffmpeg_sys_next::AVFMT_SEEK_TO_PTS == 0 {
2544            let mut dts_heuristic = false;
2545            let stream_count = (*in_fmt_ctx).nb_streams;
2546
2547            for i in 0..stream_count {
2548                let stream = *(*in_fmt_ctx).streams.add(i as usize);
2549                let par = (*stream).codecpar;
2550                if (*par).video_delay != 0 {
2551                    dts_heuristic = true;
2552                    break;
2553                }
2554            }
2555            if dts_heuristic {
2556                seek_timestamp -= 3 * AV_TIME_BASE as i64 / 23;
2557            }
2558        }
2559        let ret = ffmpeg_sys_next::avformat_seek_file(in_fmt_ctx, -1, i64::MIN, seek_timestamp, seek_timestamp, 0);
2560        if ret < 0 {
2561            warn!("could not seek to position {:.3}", start_time_us as f64 / AV_TIME_BASE as f64);
2562        }
2563    }
2564
2565    let url = input
2566        .url
2567        .clone()
2568        .unwrap_or_else(|| format!("read_callback[{index}]"));
2569
2570
2571    let demux = Demuxer::new(
2572        url,
2573        input.url.is_none(),
2574        in_fmt_ctx,
2575        0 - if copy_ts { 0 } else { timestamp },
2576        input.frame_pipelines.take(),
2577        input.video_codec.clone(),
2578        input.audio_codec.clone(),
2579        input.subtitle_codec.clone(),
2580        input.readrate,
2581        input.start_time_us,
2582        recording_time_us,
2583        input.exit_on_error,
2584        input.stream_loop,
2585        input.hwaccel.clone(),
2586        input.hwaccel_device.clone(),
2587        input.hwaccel_output_format.clone(),
2588        copy_ts
2589    )?;
2590
2591    Ok(demux)
2592}
2593
2594fn convert_options(
2595    opts: Option<HashMap<String, String>>,
2596) -> Result<Option<HashMap<CString, CString>>> {
2597    if opts.is_none() {
2598        return Ok(None);
2599    }
2600
2601    let converted = opts.map(|map| {
2602        map.into_iter()
2603            .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?)))
2604            .collect::<Result<HashMap<CString, CString>, _>>() // Collect into a HashMap
2605    });
2606
2607    converted.transpose() // Convert `Result<Option<T>>` into `Option<Result<T>>`
2608}
2609
2610unsafe fn input_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
2611    if fmt_ctx.is_null() {
2612        return false;
2613    }
2614
2615    let mut format_name = "unknown".to_string();
2616    let mut format_names: Vec<&str> = Vec::with_capacity(0);
2617
2618    if !(*fmt_ctx).iformat.is_null() {
2619        let iformat = (*fmt_ctx).iformat;
2620        format_name = CStr::from_ptr((*iformat).name)
2621            .to_string_lossy()
2622            .into_owned();
2623        let flags = (*iformat).flags;
2624        let no_binsearch = flags & AVFMT_NOBINSEARCH as i32 != 0;
2625        let no_gensearch = flags & AVFMT_NOGENSEARCH as i32 != 0;
2626
2627        log::debug!(
2628            "Input format '{format_name}' - Binary search: {}, Generic search: {}",
2629            if no_binsearch { "Disabled" } else { "Enabled" },
2630            if no_gensearch { "Disabled" } else { "Enabled" }
2631        );
2632
2633        format_names = format_name.split(',').collect();
2634
2635        if format_names.iter().any(|&f| {
2636            matches!(
2637                f,
2638                "mp4" | "mkv" | "avi" | "mov" | "flac" | "wav" | "aac" | "ogg" | "mp3" | "webm"
2639            )
2640        }) {
2641            if !no_binsearch && !no_gensearch {
2642                return true;
2643            }
2644        }
2645
2646        if format_names.iter().any(|&f| {
2647            matches!(
2648                f,
2649                "hls" | "m3u8" | "mpegts" | "mms" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
2650            )
2651        }) {
2652            log::debug!("Live stream detected ({format_name}). Seeking is not possible.");
2653            return false;
2654        }
2655
2656        if no_binsearch && no_gensearch {
2657            log::debug!("Input format '{format_name}' has both NOBINSEARCH and NOGENSEARCH set. Seeking is likely restricted.");
2658        }
2659    }
2660
2661    let format_duration = (*fmt_ctx).duration;
2662
2663    if format_names.iter().any(|&f| f == "flv") {
2664        if format_duration <= 0 {
2665            log::debug!(
2666                "Input format 'flv' detected with no valid duration. Seeking is not possible."
2667            );
2668        } else {
2669            log::warn!("Input format 'flv' detected with a valid duration. While seeking may still be possible, it is highly recommended to add a `seek_callback()` for optimal input handling, especially when seeking or random access to specific segments is required.");
2670        }
2671        return false;
2672    }
2673
2674    if format_duration > 0 {
2675        log::debug!("Format '{format_name}' has a duration of {format_duration}. Seeking is likely possible.");
2676        return true;
2677    }
2678
2679    let mut video_stream_index = -1;
2680    for i in 0..(*fmt_ctx).nb_streams {
2681        let stream = *(*fmt_ctx).streams.offset(i as isize);
2682        if (*stream).codecpar.is_null() {
2683            continue;
2684        }
2685        if (*(*stream).codecpar).codec_type == AVMEDIA_TYPE_VIDEO {
2686            video_stream_index = i as i32;
2687            break;
2688        }
2689    }
2690
2691    let stream_index = if video_stream_index >= 0 {
2692        video_stream_index
2693    } else {
2694        -1
2695    };
2696
2697    let original_pos = if !(*fmt_ctx).pb.is_null() {
2698        (*(*fmt_ctx).pb).pos
2699    } else {
2700        -1
2701    };
2702
2703    if original_pos >= 0 {
2704        let seek_target = 1 * AV_TIME_BASE as i64;
2705        let seek_result = av_seek_frame(fmt_ctx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD);
2706
2707        if seek_result >= 0 {
2708            log::debug!("Seek test successful.");
2709
2710            (*(*fmt_ctx).pb).pos = original_pos;
2711            avformat_flush(fmt_ctx);
2712            log::debug!("Restored fmt_ctx.pb.pos to {original_pos} and flushed format context.",);
2713            return true;
2714        } else {
2715            log::debug!("Seek test failed (return code {seek_result}). This format likely does not support seeking.");
2716        }
2717    }
2718
2719    false
2720}
2721
2722#[cfg(test)]
2723mod tests {
2724    use std::ffi::{CStr, CString};
2725    use std::ptr::null_mut;
2726
2727    use crate::core::context::ffmpeg_context::{strtol, FfmpegContext, Output};
2728    use ffmpeg_sys_next::{
2729        avfilter_graph_alloc, avfilter_graph_free, avfilter_graph_parse_ptr, avfilter_inout_free,
2730    };
2731
2732    #[test]
2733    fn test_filter() {
2734        let desc_cstr = CString::new("[1:v][2:v]concat=n=2:v=1:a=0[vout]").unwrap();
2735        // let desc_cstr = CString::new("fps=15").unwrap();
2736
2737        unsafe {
2738            let mut graph = avfilter_graph_alloc();
2739            let mut inputs = null_mut();
2740            let mut outputs = null_mut();
2741
2742            let ret = avfilter_graph_parse_ptr(
2743                graph,
2744                desc_cstr.as_ptr(),
2745                &mut inputs,
2746                &mut outputs,
2747                null_mut(),
2748            );
2749            if ret < 0 {
2750                avfilter_inout_free(&mut inputs);
2751                avfilter_inout_free(&mut outputs);
2752                avfilter_graph_free(&mut graph);
2753                println!("err ret:{}", crate::util::ffmpeg_utils::av_err2str(ret));
2754                return;
2755            }
2756
2757            println!("inputs.is_null:{}", inputs.is_null());
2758            println!("outputs.is_null:{}", outputs.is_null());
2759
2760            let mut cur = inputs;
2761            while !cur.is_null() {
2762                let input_name = CStr::from_ptr((*cur).name);
2763                println!("Input name: {}", input_name.to_str().unwrap());
2764                cur = (*cur).next;
2765            }
2766
2767            let output_name = CStr::from_ptr((*outputs).name);
2768            println!("Output name: {}", output_name.to_str().unwrap());
2769
2770            let filter_ctx = (*outputs).filter_ctx;
2771            avfilter_inout_free(&mut outputs);
2772            println!("filter_ctx.is_null:{}", filter_ctx.is_null());
2773        }
2774    }
2775
2776    #[test]
2777    fn test_new() {
2778        let _ = env_logger::builder()
2779            .filter_level(log::LevelFilter::Debug)
2780            .is_test(true)
2781            .try_init();
2782        let _ffmpeg_context = FfmpegContext::new(
2783            vec!["test.mp4".to_string().into()],
2784            vec!["hue=s=0".to_string().into()],
2785            vec!["output.mp4".to_string().into()],
2786        )
2787        .unwrap();
2788        let _ffmpeg_context = FfmpegContext::new(
2789            vec!["test.mp4".into()],
2790            vec!["[0:v]hue=s=0".into()],
2791            vec!["output.mp4".to_string().into()],
2792        )
2793        .unwrap();
2794        let _ffmpeg_context = FfmpegContext::new(
2795            vec!["test.mp4".into()],
2796            vec!["hue=s=0[my-out]".into()],
2797            vec![Output::from("output.mp4").add_stream_map("my-out")],
2798        )
2799        .unwrap();
2800        let result = FfmpegContext::new(
2801            vec!["test.mp4".into()],
2802            vec!["hue=s=0".into()],
2803            vec![Output::from("output.mp4").add_stream_map("0:v?")],
2804        );
2805        assert!(result.is_err());
2806        let result = FfmpegContext::new(
2807            vec!["test.mp4".into()],
2808            vec!["hue=s=0".into()],
2809            vec![Output::from("output.mp4").add_stream_map_with_copy("1:v?")],
2810        );
2811        assert!(result.is_err());
2812        let result = FfmpegContext::new(
2813            vec!["test.mp4".into()],
2814            vec!["hue=s=0[fg-out]".into()],
2815            vec![
2816                Output::from("output.mp4").add_stream_map("my-out?"),
2817                Output::from("output.mp4").add_stream_map("fg-out"),
2818            ],
2819        );
2820        assert!(result.is_err());
2821        // ignore filter
2822        let result = FfmpegContext::new(
2823            vec!["test.mp4".into()],
2824            vec!["hue=s=0".into()],
2825            vec![Output::from("output.mp4").add_stream_map_with_copy("1:v")],
2826        );
2827        assert!(result.is_err());
2828        let result = FfmpegContext::new(
2829            vec!["test.mp4".into()],
2830            vec!["hue=s=0[fg-out]".into()],
2831            vec![Output::from("output.mp4").add_stream_map("fg-out?")],
2832        );
2833        assert!(result.is_err());
2834    }
2835
2836    #[test]
2837    fn test_builder() {
2838        let _ = env_logger::builder()
2839            .filter_level(log::LevelFilter::Debug)
2840            .is_test(true)
2841            .try_init();
2842
2843        let _context1 = FfmpegContext::builder()
2844            .input("test.mp4")
2845            .filter_desc("hue=s=0")
2846            .output("output.mp4")
2847            .build()
2848            .unwrap();
2849
2850        let _context2 = FfmpegContext::builder()
2851            .inputs(vec!["test.mp4"])
2852            .filter_descs(vec!["hue=s=0"])
2853            .outputs(vec!["output.mp4"])
2854            .build()
2855            .unwrap();
2856    }
2857
2858    #[test]
2859    fn test_strtol() {
2860        let input = "-123---abc";
2861        let result = strtol(input);
2862        assert_eq!(result.unwrap(), (-123, "---abc"));
2863
2864        let input = "123---abc";
2865        let result = strtol(input);
2866        assert_eq!(result.unwrap(), (123, "---abc"));
2867
2868        let input = "-123aa";
2869        let result = strtol(input);
2870        assert_eq!(result.unwrap(), (-123, "aa"));
2871
2872        let input = "-aa";
2873        let result = strtol(input);
2874        assert!(result.is_err());
2875
2876        let input = "abc";
2877        let result = strtol(input);
2878        assert!(result.is_err())
2879    }
2880}