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