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