ez_ffmpeg/core/context/
ffmpeg_context.rs

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