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