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