Skip to main content

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