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.video_qscale,
1280        output.audio_qscale,
1281        output.max_video_frames,
1282        output.max_audio_frames,
1283        output.max_subtitle_frames,
1284        video_codec_opts,
1285        audio_codec_opts,
1286        subtitle_codec_opts,
1287        format_opts,
1288    );
1289
1290    Ok(mux)
1291}
1292
1293fn get_format(format_option: &Option<String>) -> Result<*const AVOutputFormat> {
1294    match format_option {
1295        None => Ok(null()),
1296        Some(format_str) => unsafe {
1297            let mut format_cstr = CString::new(format_str.to_string())?;
1298            let mut format = av_guess_format(format_cstr.as_ptr(), null(), null());
1299            if format.is_null() {
1300                format_cstr = CString::new(format!("tmp.{format_str}"))?;
1301                format = av_guess_format(null(), format_cstr.as_ptr(), null());
1302            }
1303            if format.is_null() {
1304                return Err(OpenOutputError::FormatUnsupported(format_str.to_string()).into());
1305            }
1306            Ok(format)
1307        },
1308    }
1309}
1310
1311unsafe fn output_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
1312    if fmt_ctx.is_null() {
1313        return false;
1314    }
1315
1316    let mut format_name = "unknown".to_string();
1317
1318    if !(*fmt_ctx).oformat.is_null() {
1319        let oformat = (*fmt_ctx).oformat;
1320        format_name = CStr::from_ptr((*oformat).name)
1321            .to_string_lossy()
1322            .into_owned();
1323        let flags = (*oformat).flags;
1324        let no_file = flags & AVFMT_NOFILE as i32 != 0;
1325        let global_header = flags & AVFMT_GLOBALHEADER as i32 != 0;
1326
1327        log::debug!(
1328            "Output format '{format_name}' - No file: {}, Global header: {}",
1329            if no_file { "True" } else { "False" },
1330            if global_header { "True" } else { "False" }
1331        );
1332
1333        // List of formats that typically require seeking
1334        let format_names: Vec<&str> = format_name.split(',').collect();
1335        if format_names
1336            .iter()
1337            .any(|&f| matches!(f, "mp4" | "mov" | "mkv" | "avi" | "flac" | "ogg" | "webm"))
1338        {
1339            log::debug!("Output format '{format_name}' typically requires seeking.");
1340            return true;
1341        }
1342
1343        // List of streaming formats that do not require seeking
1344        if format_names.iter().any(|&f| {
1345            matches!(
1346                f,
1347                "mpegts" | "hls" | "m3u8" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
1348            )
1349        }) {
1350            log::debug!("Output format '{format_name}' does not typically require seeking.");
1351            return false;
1352        }
1353
1354        // Special handling for FLV format
1355        if format_name == "flv" {
1356            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'.");
1357            return false;
1358        }
1359
1360        // If AVFMT_NOFILE is set, the format does not use standard file I/O and may not need seeking
1361        if no_file {
1362            log::debug!(
1363                "Output format '{format_name}' uses AVFMT_NOFILE. Seeking is likely unnecessary."
1364            );
1365            return false;
1366        }
1367
1368        // If the format uses global headers, it typically means the codec requires a separate metadata section
1369        if global_header {
1370            log::debug!(
1371                "Output format '{format_name}' uses AVFMT_GLOBALHEADER. Seeking may be required."
1372            );
1373            return true;
1374        }
1375    } else {
1376        log::debug!("Output format is null. Cannot determine if seeking is required.");
1377    }
1378
1379    // Default case: assume seeking is not required
1380    log::debug!("Output format '{format_name}' does not match any known rules. Assuming seeking is not required.");
1381    false
1382}
1383
1384struct InputOpaque {
1385    read: Box<dyn FnMut(&mut [u8]) -> i32>,
1386    seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1387}
1388
1389#[allow(dead_code)]
1390struct OutputOpaque {
1391    write: Box<dyn FnMut(&[u8]) -> i32>,
1392    seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1393}
1394
1395unsafe extern "C" fn write_packet_wrapper(
1396    opaque: *mut libc::c_void,
1397    buf: *const u8,
1398    buf_size: libc::c_int,
1399) -> libc::c_int {
1400    if buf.is_null() {
1401        return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1402    }
1403    let closure = &mut *(opaque as *mut Box<dyn FnMut(&[u8]) -> i32>);
1404
1405    let slice = std::slice::from_raw_parts(buf, buf_size as usize);
1406
1407    (*closure)(slice)
1408}
1409
1410unsafe extern "C" fn read_packet_wrapper(
1411    opaque: *mut libc::c_void,
1412    buf: *mut u8,
1413    buf_size: libc::c_int,
1414) -> libc::c_int {
1415    if buf.is_null() {
1416        return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1417    }
1418
1419    let context = &mut *(opaque as *mut InputOpaque);
1420
1421    let slice = std::slice::from_raw_parts_mut(buf, buf_size as usize);
1422
1423    (context.read)(slice)
1424}
1425
1426unsafe extern "C" fn seek_packet_wrapper(
1427    opaque: *mut libc::c_void,
1428    offset: i64,
1429    whence: libc::c_int,
1430) -> i64 {
1431    let context = &mut *(opaque as *mut InputOpaque);
1432
1433    if let Some(seek_func) = &mut context.seek {
1434        (*seek_func)(offset, whence)
1435    } else {
1436        ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64
1437    }
1438}
1439
1440fn fg_bind_inputs(filter_graphs: &mut Vec<FilterGraph>, demuxs: &mut Vec<Demuxer>) -> Result<()> {
1441    if filter_graphs.is_empty() {
1442        return Ok(());
1443    }
1444    bind_fg_inputs_by_fg(filter_graphs)?;
1445
1446    for filter_graph in filter_graphs.iter_mut() {
1447        for i in 0..filter_graph.inputs.len() {
1448            fg_complex_bind_input(
1449                filter_graph,
1450                i,
1451                demuxs,
1452            )?;
1453        }
1454    }
1455
1456    Ok(())
1457}
1458
1459struct FilterLabel {
1460    linklabel: String,
1461    media_type: AVMediaType,
1462}
1463
1464fn bind_fg_inputs_by_fg(filter_graphs: &mut Vec<FilterGraph>) -> Result<()> {
1465    let fg_labels = filter_graphs
1466        .iter()
1467        .map(|filter_graph| {
1468            let inputs = filter_graph
1469                .inputs
1470                .iter()
1471                .map(|input| FilterLabel {
1472                    linklabel: input.linklabel.clone(),
1473                    media_type: input.media_type,
1474                })
1475                .collect::<Vec<_>>();
1476            let outputs = filter_graph
1477                .outputs
1478                .iter()
1479                .map(|output| FilterLabel {
1480                    linklabel: output.linklabel.clone(),
1481                    media_type: output.media_type,
1482                })
1483                .collect::<Vec<_>>();
1484            (inputs, outputs)
1485        })
1486        .collect::<Vec<_>>();
1487
1488    for (i, (inputs, _outputs)) in fg_labels.iter().enumerate() {
1489        for input_filter_label in inputs.iter() {
1490            if input_filter_label.linklabel.is_empty() {
1491                continue;
1492            }
1493
1494            'outer: for (j, (_inputs, outputs)) in fg_labels.iter().enumerate() {
1495                if i == j {
1496                    continue;
1497                }
1498
1499                for (output_idx, output_filter_label) in outputs.iter().enumerate() {
1500                    if output_filter_label.linklabel != input_filter_label.linklabel {
1501                        continue;
1502                    }
1503                    if output_filter_label.media_type != input_filter_label.media_type {
1504                        warn!(
1505                            "Tried to connect {:?} output to {:?} input",
1506                            output_filter_label.media_type, input_filter_label.media_type
1507                        );
1508                        return Err(FilterGraphParseError::InvalidArgument.into());
1509                    }
1510
1511                    {
1512                        let filter_graph = &filter_graphs[j];
1513                        let output_filter = &filter_graph.outputs[output_idx];
1514                        if output_filter.has_dst() {
1515                            continue;
1516                        }
1517
1518                    }
1519
1520                    let sender = {
1521                        let filter_graph = &mut filter_graphs[i];
1522                        let sender = filter_graph.get_src_sender();
1523                        sender
1524                    };
1525
1526                    {
1527                        let filter_graph = &mut filter_graphs[j];
1528                        filter_graph.outputs[output_idx].set_dst(sender);
1529                        filter_graph.outputs[output_idx].fg_input_index = i;
1530                    }
1531
1532                    break 'outer;
1533                }
1534            }
1535        }
1536    }
1537    Ok(())
1538}
1539
1540fn fg_complex_bind_input(
1541    filter_graph: &mut FilterGraph,
1542    input_filter_index: usize,
1543    demuxs: &mut Vec<Demuxer>,
1544) -> Result<()> {
1545    let graph_desc = &filter_graph.graph_desc;
1546    let input_filter = &mut filter_graph.inputs[input_filter_index];
1547    let (demux_idx, stream_idx) =
1548        if !input_filter.linklabel.is_empty() && input_filter.linklabel != "in" {
1549            let (demux_idx, stream_idx) = fg_find_input_idx_by_linklabel(
1550                &input_filter.linklabel,
1551                input_filter.media_type,
1552                demuxs,
1553                graph_desc,
1554            )?;
1555
1556            info!(
1557                "Binding filter input with label '{}' to input stream {stream_idx}:{demux_idx}",
1558                input_filter.linklabel
1559            );
1560            (demux_idx, stream_idx)
1561        } else {
1562            let mut demux_idx = -1i32;
1563            let mut stream_idx = 0;
1564            for (d_idx, demux) in demuxs.iter().enumerate() {
1565                for (st_idx, intput_stream) in demux.get_streams().iter().enumerate() {
1566                    if intput_stream.is_used() {
1567                        continue;
1568                    }
1569                    if intput_stream.codec_type == input_filter.media_type {
1570                        demux_idx = d_idx as i32;
1571                        stream_idx = st_idx;
1572                        break;
1573                    }
1574                }
1575                if demux_idx >= 0 {
1576                    break;
1577                }
1578            }
1579
1580            if demux_idx < 0 {
1581                warn!("Cannot find a matching stream for unlabeled input pad {}", input_filter.name);
1582                return Err(FilterGraphParseError::InvalidArgument.into());
1583            }
1584
1585            debug!("FilterGraph binding unlabeled input {input_filter_index} to input stream {stream_idx}:{demux_idx}");
1586
1587            (demux_idx as usize, stream_idx)
1588        };
1589
1590    let demux = &mut demuxs[demux_idx];
1591
1592    ifilter_bind_ist(filter_graph, input_filter_index, stream_idx, demux)
1593}
1594
1595#[cfg(feature = "docs-rs")]
1596fn ifilter_bind_ist(
1597    filter_graph: &mut FilterGraph,
1598    input_index: usize,
1599    stream_idx: usize,
1600    demux: &mut Demuxer,
1601) -> Result<()> {
1602    Ok(())
1603}
1604
1605#[cfg(not(feature = "docs-rs"))]
1606fn ifilter_bind_ist(
1607    filter_graph: &mut FilterGraph,
1608    input_index: usize,
1609    stream_idx: usize,
1610    demux: &mut Demuxer,
1611) -> Result<()> {
1612    unsafe {
1613        let input_filter = &mut filter_graph.inputs[input_index];
1614        let ist = *(*demux.in_fmt_ctx).streams.add(stream_idx);
1615        let par = (*ist).codecpar;
1616        if (*par).codec_type == AVMEDIA_TYPE_VIDEO {
1617            let framerate = av_guess_frame_rate(demux.in_fmt_ctx, ist, null_mut());
1618            input_filter.opts.framerate = framerate;
1619        } else if (*par).codec_type == AVMEDIA_TYPE_SUBTITLE {
1620            input_filter.opts.sub2video_width = (*par).width;
1621            input_filter.opts.sub2video_height = (*par).height;
1622
1623            if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1624                let nb_streams = (*demux.in_fmt_ctx).nb_streams;
1625                for j in 0..nb_streams {
1626                    let par1 = (**(*demux.in_fmt_ctx).streams.add(j as usize)).codecpar;
1627                    if (*par1).codec_type == AVMEDIA_TYPE_VIDEO {
1628                        input_filter.opts.sub2video_width =
1629                            std::cmp::max(input_filter.opts.sub2video_width, (*par1).width);
1630                        input_filter.opts.sub2video_height =
1631                            std::cmp::max(input_filter.opts.sub2video_height, (*par1).height);
1632                    }
1633                }
1634            }
1635
1636            if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1637                input_filter.opts.sub2video_width =
1638                    std::cmp::max(input_filter.opts.sub2video_width, 720);
1639                input_filter.opts.sub2video_height =
1640                    std::cmp::max(input_filter.opts.sub2video_height, 576);
1641            }
1642
1643            demux.get_stream_mut(stream_idx).have_sub2video = true;
1644        }
1645
1646        let dec_ctx = {
1647            let input_stream = demux.get_stream_mut(stream_idx);
1648            avcodec_alloc_context3(input_stream.codec.as_ptr())
1649        };
1650        if dec_ctx.is_null() {
1651            return Err(FilterGraphParseError::OutOfMemory.into());
1652        }
1653        let _codec_ctx = CodecContext::new(dec_ctx);
1654
1655        let fallback = input_filter.opts.fallback.as_mut_ptr();
1656        if (*dec_ctx).codec_type == AVMEDIA_TYPE_AUDIO {
1657            (*fallback).format = (*dec_ctx).sample_fmt as i32;
1658            (*fallback).sample_rate = (*dec_ctx).sample_rate;
1659
1660            let ret = av_channel_layout_copy(&mut (*fallback).ch_layout, &(*dec_ctx).ch_layout);
1661            if ret < 0 {
1662                return Err(FilterGraphParseError::from(ret).into());
1663            }
1664        } else if (*dec_ctx).codec_type == AVMEDIA_TYPE_VIDEO {
1665            (*fallback).format = (*dec_ctx).pix_fmt as i32;
1666            (*fallback).width = (*dec_ctx).width;
1667            (*fallback).height = (*dec_ctx).height;
1668            (*fallback).sample_aspect_ratio = (*dec_ctx).sample_aspect_ratio;
1669            (*fallback).colorspace = (*dec_ctx).colorspace;
1670            (*fallback).color_range = (*dec_ctx).color_range;
1671        }
1672        (*fallback).time_base = (*dec_ctx).pkt_timebase;
1673
1674        //TODO Set this flag according to the input stream parameters
1675        input_filter.opts.flags |= IFILTER_FLAG_AUTOROTATE;
1676
1677        if demux.start_time_us.is_some() {
1678            input_filter.opts.trim_start_us = Some(0);
1679        }
1680        input_filter.opts.trim_end_us = demux.recording_time_us;
1681
1682        let sender = filter_graph.get_src_sender();
1683        {
1684            let input_stream = demux.get_stream_mut(stream_idx);
1685            input_stream.add_dst(sender);
1686            input_stream.fg_input_index = input_index;
1687        };
1688
1689        let node = Arc::make_mut(&mut filter_graph.node);
1690        let SchNode::Filter { inputs, .. } = node else {
1691            unreachable!()
1692        };
1693        inputs.insert(input_index, demux.node.clone());
1694
1695
1696        demux.connect_stream(stream_idx);
1697        Ok(())
1698    }
1699}
1700
1701fn fg_find_input_idx_by_linklabel(
1702    linklabel: &str,
1703    filter_media_type: AVMediaType,
1704    demuxs: &mut Vec<Demuxer>,
1705    desc: &str,
1706) -> Result<(usize, usize)> {
1707    let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
1708        if linklabel.len() <= 2 {
1709            warn!("Filter linklabel is empty");
1710            return Err(InvalidFilterSpecifier(desc.to_string()).into());
1711        } else {
1712            &linklabel[1..linklabel.len() - 1]
1713        }
1714    } else {
1715        linklabel
1716    };
1717
1718    let (file_idx, remainder) = strtol(new_linklabel)?;
1719    if file_idx < 0 || file_idx as usize >= demuxs.len() {
1720        return Err(InvalidFileIndexInFg(file_idx as usize, desc.to_string()).into());
1721    }
1722
1723    let (media_type, _allow_unused) = stream_specifier_parse(remainder)?;
1724
1725    if media_type != filter_media_type {
1726        warn!("Invalid stream label: {linklabel}");
1727        return Err(FilterGraphParseError::InvalidArgument.into());
1728    }
1729
1730    let demux = &demuxs[file_idx as usize];
1731
1732    let mut stream_idx = -1i32;
1733
1734    for (idx, dec_stream) in demux.get_streams().iter().enumerate() {
1735        if (*dec_stream).codec_type == media_type {
1736            stream_idx = idx as i32;
1737            break;
1738        }
1739    }
1740
1741    if stream_idx < 0 {
1742        warn!(
1743            "Stream specifier '{remainder}' in filtergraph description {desc} matches no streams."
1744        );
1745        return Err(FilterGraphParseError::InvalidArgument.into());
1746    }
1747    Ok((file_idx as usize, stream_idx as usize))
1748}
1749
1750fn stream_specifier_parse(specifier: &str) -> Result<(AVMediaType, bool)> {
1751    let specifier = if specifier.starts_with(':') {
1752        &specifier[1..]
1753    } else {
1754        specifier
1755    };
1756
1757    match specifier {
1758        "v" => Ok((AVMEDIA_TYPE_VIDEO, false)),
1759        "a" => Ok((AVMEDIA_TYPE_AUDIO, false)),
1760        "s" => Ok((AVMEDIA_TYPE_SUBTITLE, false)),
1761        "d" => Ok((AVMEDIA_TYPE_DATA, false)),
1762        "t" => Ok((AVMEDIA_TYPE_ATTACHMENT, false)),
1763        "v?" => Ok((AVMEDIA_TYPE_VIDEO, true)),
1764        "a?" => Ok((AVMEDIA_TYPE_AUDIO, true)),
1765        "s?" => Ok((AVMEDIA_TYPE_SUBTITLE, true)),
1766        "d?" => Ok((AVMEDIA_TYPE_DATA, true)),
1767        "t?" => Ok((AVMEDIA_TYPE_ATTACHMENT, true)),
1768        // "V" => Ok(AVMEDIA_TYPE_VIDEO),
1769        _ => Err(InvalidFilterSpecifier(specifier.to_string()).into()),
1770    }
1771}
1772
1773/// Similar to strtol() in C
1774fn strtol(input: &str) -> Result<(i64, &str)> {
1775    let mut chars = input.chars().peekable();
1776    let mut negative = false;
1777
1778    if let Some(&ch) = chars.peek() {
1779        if ch == '-' {
1780            negative = true;
1781            chars.next();
1782        } else if !ch.is_digit(10) {
1783            return Err(ParseInteger);
1784        }
1785    }
1786
1787    let number_start = input.len() - chars.clone().collect::<String>().len();
1788
1789    let number_str: String = chars.by_ref().take_while(|ch| ch.is_digit(10)).collect();
1790
1791    if number_str.is_empty() {
1792        return Err(ParseInteger);
1793    }
1794
1795    let number: i64 = number_str.parse().map_err(|_| ParseInteger)?;
1796
1797    let remainder_index = number_start + number_str.len();
1798    let remainder = &input[remainder_index..];
1799
1800    if negative {
1801        Ok((-number, remainder))
1802    } else {
1803        Ok((number, remainder))
1804    }
1805}
1806
1807fn init_filter_graphs(filter_complexs: Vec<FilterComplex>) -> Result<Vec<FilterGraph>> {
1808    let mut filter_graphs = Vec::with_capacity(filter_complexs.len());
1809    for (i, filter) in filter_complexs.iter().enumerate() {
1810        let filter_graph = init_filter_graph(i, &filter.filter_descs, filter.hw_device.clone())?;
1811        filter_graphs.push(filter_graph);
1812    }
1813    Ok(filter_graphs)
1814}
1815
1816
1817#[cfg(feature = "docs-rs")]
1818fn init_filter_graph(
1819    fg_index: usize,
1820    filter_desc: &str,
1821    hw_device: Option<String>,
1822) -> Result<FilterGraph> {
1823    Err(Bug)
1824}
1825
1826#[cfg(not(feature = "docs-rs"))]
1827fn init_filter_graph(
1828    fg_index: usize,
1829    filter_desc: &str,
1830    hw_device: Option<String>,
1831) -> Result<FilterGraph> {
1832    let desc_cstr = CString::new(filter_desc)?;
1833
1834    unsafe {
1835        /* this graph is only used for determining the kinds of inputs
1836        and outputs we have, and is discarded on exit from this function */
1837        let mut graph = avfilter_graph_alloc();
1838        (*graph).nb_threads = 1;
1839
1840        let mut seg = null_mut();
1841        let mut ret = avfilter_graph_segment_parse(graph, desc_cstr.as_ptr(), 0, &mut seg);
1842        if ret < 0 {
1843            avfilter_graph_free(&mut graph);
1844            return Err(FilterGraphParseError::from(ret).into());
1845        }
1846
1847        ret = avfilter_graph_segment_create_filters(seg, 0);
1848        if ret < 0 {
1849            avfilter_graph_free(&mut graph);
1850            avfilter_graph_segment_free(&mut seg);
1851            return Err(FilterGraphParseError::from(ret).into());
1852        }
1853
1854        #[cfg(not(feature = "docs-rs"))]
1855        {
1856            ret = graph_opts_apply(seg);
1857        }
1858        if ret < 0 {
1859            avfilter_graph_segment_free(&mut seg);
1860            avfilter_graph_free(&mut graph);
1861            return Err(FilterGraphParseError::from(ret).into());
1862        }
1863
1864        let mut inputs = null_mut();
1865        let mut outputs = null_mut();
1866        ret = avfilter_graph_segment_apply(seg, 0, &mut inputs, &mut outputs);
1867        avfilter_graph_segment_free(&mut seg);
1868
1869        if ret < 0 {
1870            avfilter_inout_free(&mut inputs);
1871            avfilter_inout_free(&mut outputs);
1872            avfilter_graph_free(&mut graph);
1873            return Err(FilterGraphParseError::from(ret).into());
1874        }
1875
1876        let input_filters = inouts_to_input_filters(fg_index, inputs)?;
1877        let output_filters = inouts_to_output_filters(outputs)?;
1878
1879        if output_filters.len() == 0 {
1880            avfilter_inout_free(&mut inputs);
1881            avfilter_inout_free(&mut outputs);
1882            avfilter_graph_free(&mut graph);
1883            return Err(FilterZeroOutputs);
1884        }
1885
1886        let filter_graph = FilterGraph::new(
1887            filter_desc.to_string(),
1888            hw_device,
1889            input_filters,
1890            output_filters,
1891        );
1892
1893        avfilter_inout_free(&mut inputs);
1894        avfilter_inout_free(&mut outputs);
1895        avfilter_graph_free(&mut graph);
1896
1897        Ok(filter_graph)
1898    }
1899}
1900
1901unsafe fn inouts_to_input_filters(
1902    fg_index: usize,
1903    inouts: *mut AVFilterInOut,
1904) -> Result<Vec<InputFilter>> {
1905    let mut cur = inouts;
1906    let mut filterinouts = Vec::new();
1907    let mut filter_index = 0;
1908    while !cur.is_null() {
1909        let linklabel = if (*cur).name.is_null() {
1910            ""
1911        } else {
1912            let linklabel = CStr::from_ptr((*cur).name);
1913            let result = linklabel.to_str();
1914            if let Err(_) = result {
1915                return Err(FilterDescUtf8);
1916            }
1917            result.unwrap()
1918        };
1919
1920        let filter_ctx = (*cur).filter_ctx;
1921        let media_type = avfilter_pad_get_type((*filter_ctx).input_pads, (*cur).pad_idx);
1922
1923        let pads = (*filter_ctx).input_pads;
1924        let nb_pads = (*filter_ctx).nb_inputs;
1925
1926        let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
1927
1928        let fallback = frame_alloc()?;
1929
1930        let mut filter = InputFilter::new(linklabel.to_string(), media_type, name, fallback);
1931        filter.opts.name = format!("fg:{fg_index}:{filter_index}");
1932        filterinouts.push(filter);
1933
1934        cur = (*cur).next;
1935        filter_index += 1;
1936    }
1937    Ok(filterinouts)
1938}
1939
1940unsafe fn inouts_to_output_filters(inouts: *mut AVFilterInOut) -> Result<Vec<OutputFilter>> {
1941    let mut cur = inouts;
1942    let mut output_filters = Vec::new();
1943    while !cur.is_null() {
1944        let linklabel = if (*cur).name.is_null() {
1945            ""
1946        } else {
1947            let linklabel = CStr::from_ptr((*cur).name);
1948            let result = linklabel.to_str();
1949            if let Err(_) = result {
1950                return Err(FilterDescUtf8);
1951            }
1952            result.unwrap()
1953        };
1954
1955        let filter_ctx = (*cur).filter_ctx;
1956        let media_type = avfilter_pad_get_type((*filter_ctx).output_pads, (*cur).pad_idx);
1957
1958        let pads = (*filter_ctx).output_pads;
1959        let nb_pads = (*filter_ctx).nb_outputs;
1960
1961        let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
1962
1963        let filter = OutputFilter::new(linklabel.to_string(), media_type, name);
1964        output_filters.push(filter);
1965
1966        cur = (*cur).next;
1967    }
1968    Ok(output_filters)
1969}
1970
1971unsafe fn describe_filter_link(
1972    cur: *mut AVFilterInOut,
1973    filter_ctx: *mut AVFilterContext,
1974    pads: *mut AVFilterPad,
1975    nb_pads: c_uint,
1976) -> Result<String> {
1977    let filter = (*filter_ctx).filter;
1978    let name = (*filter).name;
1979    let name = CStr::from_ptr(name);
1980    let result = name.to_str();
1981    if let Err(_) = result {
1982        return Err(FilterNameUtf8);
1983    }
1984    let name = result.unwrap();
1985
1986    let name = if nb_pads > 1 {
1987        name.to_string()
1988    } else {
1989        let pad_name = avfilter_pad_get_name(pads, (*cur).pad_idx);
1990        let pad_name = CStr::from_ptr(pad_name);
1991        let result = pad_name.to_str();
1992        if let Err(_) = result {
1993            return Err(FilterNameUtf8);
1994        }
1995        let pad_name = result.unwrap();
1996        format!("{name}:{pad_name}")
1997    };
1998    Ok(name)
1999}
2000
2001fn open_input_files(inputs: &mut Vec<Input>) -> Result<Vec<Demuxer>> {
2002    let mut demuxs = Vec::new();
2003    for (i, input) in inputs.iter_mut().enumerate() {
2004        unsafe {
2005            let result = open_input_file(i, input);
2006            if let Err(e) = result {
2007                free_input_av_format_context(demuxs);
2008                return Err(e);
2009            }
2010            let demux = result.unwrap();
2011            demuxs.push(demux)
2012        }
2013    }
2014    Ok(demuxs)
2015}
2016
2017unsafe fn free_input_av_format_context(demuxs: Vec<Demuxer>) {
2018    for mut demux in demuxs {
2019        avformat_close_input(&mut demux.in_fmt_ctx);
2020    }
2021}
2022
2023#[cfg(feature = "docs-rs")]
2024unsafe fn open_input_file(
2025    index: usize,
2026    input: &mut Input,
2027) -> Result<Demuxer> {
2028    Err(Bug)
2029}
2030
2031#[cfg(not(feature = "docs-rs"))]
2032unsafe fn open_input_file(
2033    index: usize,
2034    input: &mut Input,
2035) -> Result<Demuxer> {
2036    let mut in_fmt_ctx = null_mut();
2037
2038    let input_opts = convert_options(input.input_opts.clone())?;
2039    let mut input_opts = hashmap_to_avdictionary(&input_opts);
2040
2041    let recording_time_us = match input.stop_time_us {
2042        None => input.recording_time_us,
2043        Some(stop_time_us) => {
2044            let start_time_us = input.start_time_us.unwrap_or_else(|| 0);
2045            if stop_time_us <= start_time_us {
2046                error!("stop_time_us value smaller than start_time_us; aborting.");
2047                return Err(OpenOutputError::InvalidArgument.into());
2048            } else {
2049                Some(stop_time_us - start_time_us)
2050            }
2051        }
2052    };
2053
2054    match &input.url {
2055        None => {
2056            if input.read_callback.is_none() {
2057                error!("input url and read_callback is none.");
2058                return Err(OpenInputError::InvalidSource.into());
2059            }
2060
2061            in_fmt_ctx = avformat_alloc_context();
2062            if in_fmt_ctx.is_null() {
2063                return Err(OpenInputError::OutOfMemory.into());
2064            }
2065
2066            let avio_ctx_buffer_size = 1024 * 64;
2067            let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
2068            if avio_ctx_buffer.is_null() {
2069                avformat_close_input(&mut in_fmt_ctx);
2070                return Err(OpenInputError::OutOfMemory.into());
2071            }
2072
2073            let have_seek_callback = input.seek_callback.is_some();
2074            let input_opaque = Box::new(InputOpaque {
2075                read: input.read_callback.take().unwrap(),
2076                seek: input.seek_callback.take(),
2077            });
2078            let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
2079
2080            let mut avio_ctx = avio_alloc_context(
2081                avio_ctx_buffer as *mut libc::c_uchar,
2082                avio_ctx_buffer_size as i32,
2083                0,
2084                opaque,
2085                Some(read_packet_wrapper),
2086                None,
2087                if have_seek_callback {
2088                    Some(seek_packet_wrapper)
2089                } else {
2090                    None
2091                },
2092            );
2093            if avio_ctx.is_null() {
2094                av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
2095                avformat_close_input(&mut in_fmt_ctx);
2096                return Err(OpenInputError::OutOfMemory.into());
2097            }
2098
2099            (*in_fmt_ctx).pb = avio_ctx;
2100            (*in_fmt_ctx).flags = AVFMT_FLAG_CUSTOM_IO;
2101
2102            let ret = avformat_open_input(&mut in_fmt_ctx, null(), null(), &mut input_opts);
2103            if ret < 0 {
2104                av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2105                avio_context_free(&mut avio_ctx);
2106                avformat_close_input(&mut in_fmt_ctx);
2107                return Err(OpenInputError::from(ret).into());
2108            }
2109
2110            let ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2111            if ret < 0 {
2112                av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2113                avio_context_free(&mut avio_ctx);
2114                avformat_close_input(&mut in_fmt_ctx);
2115                return Err(FindStreamError::from(ret).into());
2116            }
2117
2118            if !have_seek_callback && input_requires_seek(in_fmt_ctx) {
2119                av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2120                avio_context_free(&mut avio_ctx);
2121                avformat_close_input(&mut in_fmt_ctx);
2122                warn!("The input format supports seeking, but no seek callback is provided. This may cause issues.");
2123                return Err(OpenInputError::SeekFunctionMissing.into());
2124            }
2125        }
2126        Some(url) => {
2127            let url_cstr = CString::new(url.as_str())?;
2128
2129            let mut ret =
2130                avformat_open_input(&mut in_fmt_ctx, url_cstr.as_ptr(), null(), &mut input_opts);
2131            if ret < 0 {
2132                avformat_close_input(&mut in_fmt_ctx);
2133                return Err(OpenInputError::from(ret).into());
2134            }
2135
2136            ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2137            if ret < 0 {
2138                avformat_close_input(&mut in_fmt_ctx);
2139                return Err(FindStreamError::from(ret).into());
2140            }
2141        }
2142    }
2143
2144    /* if seeking requested, we execute it */
2145    if let Some(start_time_us) = input.start_time_us {
2146        let mut seek_timestamp = start_time_us;
2147        /* add the stream start time */
2148        if (*in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2149            seek_timestamp += (*in_fmt_ctx).start_time;
2150        }
2151
2152        if (*(*in_fmt_ctx).iformat).flags & ffmpeg_sys_next::AVFMT_SEEK_TO_PTS == 0 {
2153            let mut dts_heuristic = false;
2154            let stream_count = (*in_fmt_ctx).nb_streams;
2155
2156            for i in 0..stream_count {
2157                let stream = *(*in_fmt_ctx).streams.add(i as usize);
2158                let par = (*stream).codecpar;
2159                if (*par).video_delay != 0 {
2160                    dts_heuristic = true;
2161                    break;
2162                }
2163            }
2164            if dts_heuristic {
2165                seek_timestamp -= 3 * AV_TIME_BASE as i64 / 23;
2166            }
2167        }
2168        let ret = ffmpeg_sys_next::avformat_seek_file(in_fmt_ctx, -1, i64::MIN, seek_timestamp, seek_timestamp, 0);
2169        if ret < 0 {
2170            warn!("could not seek to position {:.3}", start_time_us as f64 / AV_TIME_BASE as f64);
2171        }
2172    }
2173
2174    let url = input
2175        .url
2176        .clone()
2177        .unwrap_or_else(|| format!("read_callback[{index}]"));
2178
2179    let demux = Demuxer::new(
2180        index,
2181        url,
2182        input.url.is_none(),
2183        in_fmt_ctx,
2184        0 - input.start_time_us.unwrap_or(0),
2185        input.frame_pipelines.take(),
2186        input.video_codec.clone(),
2187        input.audio_codec.clone(),
2188        input.subtitle_codec.clone(),
2189        input.readrate,
2190        input.start_time_us,
2191        recording_time_us,
2192        input.exit_on_error,
2193        input.stream_loop,
2194        input.hwaccel.clone(),
2195        input.hwaccel_device.clone(),
2196        input.hwaccel_output_format.clone(),
2197    )?;
2198
2199    Ok(demux)
2200}
2201
2202fn convert_options(
2203    opts: Option<HashMap<String, String>>,
2204) -> Result<Option<HashMap<CString, CString>>> {
2205    if opts.is_none() {
2206        return Ok(None);
2207    }
2208
2209    let converted = opts.map(|map| {
2210        map.into_iter()
2211            .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?)))
2212            .collect::<Result<HashMap<CString, CString>, _>>() // Collect into a HashMap
2213    });
2214
2215    converted.transpose() // Convert `Result<Option<T>>` into `Option<Result<T>>`
2216}
2217
2218unsafe fn input_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
2219    if fmt_ctx.is_null() {
2220        return false;
2221    }
2222
2223    let mut format_name = "unknown".to_string();
2224    let mut format_names: Vec<&str> = Vec::with_capacity(0);
2225
2226    if !(*fmt_ctx).iformat.is_null() {
2227        let iformat = (*fmt_ctx).iformat;
2228        format_name = CStr::from_ptr((*iformat).name)
2229            .to_string_lossy()
2230            .into_owned();
2231        let flags = (*iformat).flags;
2232        let no_binsearch = flags & AVFMT_NOBINSEARCH as i32 != 0;
2233        let no_gensearch = flags & AVFMT_NOGENSEARCH as i32 != 0;
2234
2235        log::debug!(
2236            "Input format '{format_name}' - Binary search: {}, Generic search: {}",
2237            if no_binsearch { "Disabled" } else { "Enabled" },
2238            if no_gensearch { "Disabled" } else { "Enabled" }
2239        );
2240
2241        format_names = format_name.split(',').collect();
2242
2243        if format_names.iter().any(|&f| {
2244            matches!(
2245                f,
2246                "mp4" | "mkv" | "avi" | "mov" | "flac" | "wav" | "aac" | "ogg" | "mp3" | "webm"
2247            )
2248        }) {
2249            if !no_binsearch && !no_gensearch {
2250                return true;
2251            }
2252        }
2253
2254        if format_names.iter().any(|&f| {
2255            matches!(
2256                f,
2257                "hls" | "m3u8" | "mpegts" | "mms" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
2258            )
2259        }) {
2260            log::debug!("Live stream detected ({format_name}). Seeking is not possible.");
2261            return false;
2262        }
2263
2264        if no_binsearch && no_gensearch {
2265            log::debug!("Input format '{format_name}' has both NOBINSEARCH and NOGENSEARCH set. Seeking is likely restricted.");
2266        }
2267    }
2268
2269    let format_duration = (*fmt_ctx).duration;
2270
2271    if format_names.iter().any(|&f| f == "flv") {
2272        if format_duration <= 0 {
2273            log::debug!(
2274                "Input format 'flv' detected with no valid duration. Seeking is not possible."
2275            );
2276        } else {
2277            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.");
2278        }
2279        return false;
2280    }
2281
2282    if format_duration > 0 {
2283        log::debug!("Format '{format_name}' has a duration of {format_duration}. Seeking is likely possible.");
2284        return true;
2285    }
2286
2287    let mut video_stream_index = -1;
2288    for i in 0..(*fmt_ctx).nb_streams {
2289        let stream = *(*fmt_ctx).streams.offset(i as isize);
2290        if (*stream).codecpar.is_null() {
2291            continue;
2292        }
2293        if (*(*stream).codecpar).codec_type == AVMEDIA_TYPE_VIDEO {
2294            video_stream_index = i as i32;
2295            break;
2296        }
2297    }
2298
2299    let stream_index = if video_stream_index >= 0 {
2300        video_stream_index
2301    } else {
2302        -1
2303    };
2304
2305    let original_pos = if !(*fmt_ctx).pb.is_null() {
2306        (*(*fmt_ctx).pb).pos
2307    } else {
2308        -1
2309    };
2310
2311    if original_pos >= 0 {
2312        let seek_target = 1 * AV_TIME_BASE as i64;
2313        let seek_result = av_seek_frame(fmt_ctx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD);
2314
2315        if seek_result >= 0 {
2316            log::debug!("Seek test successful.");
2317
2318            (*(*fmt_ctx).pb).pos = original_pos;
2319            avformat_flush(fmt_ctx);
2320            log::debug!("Restored fmt_ctx.pb.pos to {original_pos} and flushed format context.",);
2321            return true;
2322        } else {
2323            log::debug!("Seek test failed (return code {seek_result}). This format likely does not support seeking.");
2324        }
2325    }
2326
2327    false
2328}
2329
2330#[cfg(test)]
2331mod tests {
2332    use std::ffi::{CStr, CString};
2333    use std::ptr::null_mut;
2334
2335    use crate::core::context::ffmpeg_context::{strtol, FfmpegContext, Output};
2336    use ffmpeg_sys_next::{
2337        avfilter_graph_alloc, avfilter_graph_free, avfilter_graph_parse_ptr, avfilter_inout_free,
2338    };
2339
2340    #[test]
2341    fn test_filter() {
2342        let desc_cstr = CString::new("[1:v][2:v]concat=n=2:v=1:a=0[vout]").unwrap();
2343        // let desc_cstr = CString::new("fps=15").unwrap();
2344
2345        unsafe {
2346            let mut graph = avfilter_graph_alloc();
2347            let mut inputs = null_mut();
2348            let mut outputs = null_mut();
2349
2350            let ret = avfilter_graph_parse_ptr(
2351                graph,
2352                desc_cstr.as_ptr(),
2353                &mut inputs,
2354                &mut outputs,
2355                null_mut(),
2356            );
2357            if ret < 0 {
2358                avfilter_inout_free(&mut inputs);
2359                avfilter_inout_free(&mut outputs);
2360                avfilter_graph_free(&mut graph);
2361                println!("err ret:{}", av_err2str(ret));
2362                return;
2363            }
2364
2365            println!("inputs.is_null:{}", inputs.is_null());
2366            println!("outputs.is_null:{}", outputs.is_null());
2367
2368            let mut cur = inputs;
2369            while !cur.is_null() {
2370                let input_name = CStr::from_ptr((*cur).name);
2371                println!("Input name: {}", input_name.to_str().unwrap());
2372                cur = (*cur).next;
2373            }
2374
2375            let output_name = CStr::from_ptr((*outputs).name);
2376            println!("Output name: {}", output_name.to_str().unwrap());
2377
2378            let filter_ctx = (*outputs).filter_ctx;
2379            avfilter_inout_free(&mut outputs);
2380            println!("filter_ctx.is_null:{}", filter_ctx.is_null());
2381        }
2382    }
2383
2384    #[test]
2385    fn test_new() {
2386        let _ = env_logger::builder()
2387            .filter_level(log::LevelFilter::Debug)
2388            .is_test(true)
2389            .try_init();
2390        let ffmpeg_context = FfmpegContext::new(
2391            vec!["test.mp4".to_string().into()],
2392            vec!["hue=s=0".to_string().into()],
2393            vec!["output.mp4".to_string().into()],
2394        )
2395        .unwrap();
2396        let ffmpeg_context = FfmpegContext::new(
2397            vec!["test.mp4".into()],
2398            vec!["[0:v]hue=s=0".into()],
2399            vec!["output.mp4".to_string().into()],
2400        )
2401        .unwrap();
2402        let ffmpeg_context = FfmpegContext::new(
2403            vec!["test.mp4".into()],
2404            vec!["hue=s=0[my-out]".into()],
2405            vec![Output::from("output.mp4").add_stream_map("my-out")],
2406        )
2407        .unwrap();
2408        let ffmpeg_context = FfmpegContext::new(
2409            vec!["test.mp4".into()],
2410            vec!["hue=s=0".into()],
2411            vec![Output::from("output.mp4").add_stream_map_with_copy("0:v?")],
2412        )
2413        .unwrap();
2414        let result = FfmpegContext::new(
2415            vec!["test.mp4".into()],
2416            vec!["hue=s=0".into()],
2417            vec![Output::from("output.mp4").add_stream_map_with_copy("1:v?")],
2418        );
2419        assert!(result.is_err());
2420        let result = FfmpegContext::new(
2421            vec!["test.mp4".into()],
2422            vec!["hue=s=0[fg-out]".into()],
2423            vec![
2424                Output::from("output.mp4").add_stream_map("my-out?"),
2425                Output::from("output.mp4").add_stream_map("fg-out"),
2426            ],
2427        );
2428        assert!(result.is_err());
2429        // ignore filter
2430        let result = FfmpegContext::new(
2431            vec!["test.mp4".into()],
2432            vec!["hue=s=0".into()],
2433            vec![Output::from("output.mp4").add_stream_map_with_copy("1:v")],
2434        );
2435        assert!(result.is_err());
2436        let result = FfmpegContext::new(
2437            vec!["test.mp4".into()],
2438            vec!["hue=s=0[fg-out]".into()],
2439            vec![Output::from("output.mp4").add_stream_map("fg-out?")],
2440        );
2441        assert!(result.is_err());
2442    }
2443
2444    #[test]
2445    fn test_builder() {
2446        let _ = env_logger::builder()
2447            .filter_level(log::LevelFilter::Debug)
2448            .is_test(true)
2449            .try_init();
2450
2451        let context1 = FfmpegContext::builder()
2452            .input("test.mp4")
2453            .filter_desc("hue=s=0")
2454            .output("output.mp4")
2455            .build()
2456            .unwrap();
2457
2458        let context2 = FfmpegContext::builder()
2459            .inputs(vec!["test.mp4"])
2460            .filter_descs(vec!["hue=s=0"])
2461            .outputs(vec!["output.mp4"])
2462            .build()
2463            .unwrap();
2464    }
2465
2466    #[test]
2467    fn test_strtol() {
2468        let input = "-123---abc";
2469        let result = strtol(input);
2470        assert_eq!(result.unwrap(), (-123, "---abc"));
2471
2472        let input = "123---abc";
2473        let result = strtol(input);
2474        assert_eq!(result.unwrap(), (123, "---abc"));
2475
2476        let input = "-123aa";
2477        let result = strtol(input);
2478        assert_eq!(result.unwrap(), (-123, "aa"));
2479
2480        let input = "-aa";
2481        let result = strtol(input);
2482        assert!(result.is_err());
2483
2484        let input = "abc";
2485        let result = strtol(input);
2486        assert!(result.is_err())
2487    }
2488}