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, 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_CODEC_PROP_BITMAP_SUB, AV_CODEC_PROP_TEXT_SUB, 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 pub fn builder() -> FfmpegContextBuilder {
76 FfmpegContextBuilder::new()
77 }
78
79 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 }
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 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 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 }
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 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 if stream_map.copy {
442 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 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 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 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 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 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 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 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_subtitle(
783 mux,
784 demuxs,
785 oformat,
786 auto_disable,
787 )?;
788 map_auto_data(
789 mux,
790 demuxs,
791 oformat,
792 auto_disable,
793 )?;
794 }
795 Ok(())
796}
797
798#[cfg(feature = "docs-rs")]
799unsafe fn map_auto_subtitle(
800 mux: &mut Muxer,
801 demuxs: &mut Vec<Demuxer>,
802 oformat: *const AVOutputFormat,
803 auto_disable: i32,
804) -> Result<()> {
805 Ok(())
806}
807
808#[cfg(not(feature = "docs-rs"))]
809unsafe fn map_auto_subtitle(
810 mux: &mut Muxer,
811 demuxs: &mut Vec<Demuxer>,
812 oformat: *const AVOutputFormat,
813 auto_disable: i32,
814) -> Result<()> {
815 if auto_disable & (1 << AVMEDIA_TYPE_SUBTITLE as i32) != 0 {
816 return Ok(());
817 }
818
819 let output_codec = avcodec_find_encoder((*oformat).subtitle_codec);
820 if output_codec.is_null() {
821 return Ok(());
822 }
823 let output_descriptor = avcodec_descriptor_get((*output_codec).id);
824
825 for demux in demuxs {
826 let option = demux
827 .get_streams()
828 .iter()
829 .enumerate()
830 .find_map(|(index, input_stream)| {
831 if input_stream.codec_type == AVMEDIA_TYPE_SUBTITLE {
832 Some(index)
833 } else {
834 None
835 }
836 });
837
838 if option.is_none() {
839 continue;
840 }
841
842 let stream_index = option.unwrap();
843
844 let input_descriptor = avcodec_descriptor_get((*demux.get_stream(stream_index).codec_parameters).codec_id);
845 let mut input_props = 0;
846 if !input_descriptor.is_null() {
847 input_props = (*input_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
848 }
849 let mut output_props = 0;
850 if !output_descriptor.is_null() {
851 output_props = (*output_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
852 }
853
854 if input_props & output_props != 0 ||
855 !input_descriptor.is_null() && !output_descriptor.is_null() &&
857 ((*input_descriptor).props == 0 || (*output_descriptor).props == 0) {
858 let option = choose_encoder(mux, AVMEDIA_TYPE_SUBTITLE)?;
859
860 if let Some((_codec_id, enc)) = option {
861 let (frame_sender, output_stream_index) =
862 mux.add_enc_stream(AVMEDIA_TYPE_SUBTITLE, enc, demux.node.clone())?;
863 demux.get_stream_mut(stream_index).add_dst(frame_sender);
864 demux.connect_stream(stream_index);
865 let input_stream = demux.get_stream(stream_index);
866 unsafe {
867 rescale_duration(
868 input_stream.duration,
869 input_stream.time_base,
870 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
871 );
872 }
873 } else {
874 error!("Error selecting an encoder(subtitle)");
875 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
876 }
877 }
878 break;
879 }
880
881 Ok(())
882}
883
884#[cfg(feature = "docs-rs")]
885unsafe fn map_auto_data(
886 mux: &mut Muxer,
887 demuxs: &mut Vec<Demuxer>,
888 oformat: *const AVOutputFormat,
889 auto_disable: i32,
890) -> Result<()> {
891 Ok(())
892}
893
894#[cfg(not(feature = "docs-rs"))]
895unsafe fn map_auto_data(
896 mux: &mut Muxer,
897 demuxs: &mut Vec<Demuxer>,
898 oformat: *const AVOutputFormat,
899 auto_disable: i32,
900) -> Result<()> {
901 if auto_disable & (1 << AVMEDIA_TYPE_DATA as i32) != 0 {
902 return Ok(());
903 }
904
905 let codec_id = av_guess_codec(oformat, null(), (*mux.out_fmt_ctx).url, null(), AVMEDIA_TYPE_DATA);
907
908 if codec_id == AV_CODEC_ID_NONE {
909 return Ok(());
910 }
911
912 for demux in demuxs {
913 let option = demux
914 .get_streams()
915 .iter()
916 .enumerate()
917 .find_map(|(index, input_stream)| {
918 if input_stream.codec_type == AVMEDIA_TYPE_DATA && (*input_stream.codec_parameters).codec_id == codec_id {
919 Some(index)
920 } else {
921 None
922 }
923 });
924
925 if option.is_none() {
926 continue;
927 }
928
929 let stream_index = option.unwrap();
930 let option = choose_encoder(mux, AVMEDIA_TYPE_DATA)?;
931
932 if let Some((_codec_id, enc)) = option {
933 let (frame_sender, output_stream_index) =
934 mux.add_enc_stream(AVMEDIA_TYPE_DATA, enc, demux.node.clone())?;
935 demux.get_stream_mut(stream_index).add_dst(frame_sender);
936 demux.connect_stream(stream_index);
937 let input_stream = demux.get_stream(stream_index);
938 unsafe {
939 rescale_duration(
940 input_stream.duration,
941 input_stream.time_base,
942 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
943 );
944 }
945 } else {
946 error!("Error selecting an encoder(data)");
947 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
948 }
949
950 break;
951 }
952
953 Ok(())
954}
955
956#[cfg(feature = "docs-rs")]
957unsafe fn map_auto_stream(
958 mux_index: usize,
959 mux: &mut Muxer,
960 demuxs: &mut Vec<Demuxer>,
961 oformat: *const AVOutputFormat,
962 media_type: AVMediaType,
963 filter_graphs: &mut Vec<FilterGraph>,
964 auto_disable: i32,
965) -> Result<()> {
966 Ok(())
967}
968
969#[cfg(not(feature = "docs-rs"))]
970unsafe fn map_auto_stream(
971 mux_index: usize,
972 mux: &mut Muxer,
973 demuxs: &mut Vec<Demuxer>,
974 oformat: *const AVOutputFormat,
975 media_type: AVMediaType,
976 filter_graphs: &mut Vec<FilterGraph>,
977 auto_disable: i32,
978) -> Result<()> {
979 if auto_disable & (1 << media_type as i32) != 0 {
980 return Ok(());
981 }
982 if media_type == AVMEDIA_TYPE_VIDEO
983 || media_type == AVMEDIA_TYPE_AUDIO
984 || media_type == AVMEDIA_TYPE_DATA
985 {
986 if av_guess_codec(oformat, null(), (*mux.out_fmt_ctx).url, null(), media_type)
987 == AV_CODEC_ID_NONE
988 {
989 return Ok(());
990 }
991 }
992
993 for demux in demuxs {
994 let option = demux
995 .get_streams()
996 .iter()
997 .enumerate()
998 .find_map(|(index, input_stream)| {
999 if input_stream.codec_type == media_type {
1000 Some(index)
1001 } else {
1002 None
1003 }
1004 });
1005
1006 if option.is_none() {
1007 continue;
1008 }
1009
1010 let stream_index = option.unwrap();
1011 let option = choose_encoder(mux, media_type)?;
1012
1013 if let Some((codec_id, enc)) = option {
1014 if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
1015 init_simple_filtergraph(
1016 demux,
1017 stream_index,
1018 codec_id,
1019 enc,
1020 mux_index,
1021 mux,
1022 filter_graphs,
1023 )?;
1024 } else {
1025 let (frame_sender, output_stream_index) =
1026 mux.add_enc_stream(media_type, enc, demux.node.clone())?;
1027 demux.get_stream_mut(stream_index).add_dst(frame_sender);
1028 demux.connect_stream(stream_index);
1029 let input_stream = demux.get_stream(stream_index);
1030 unsafe {
1031 rescale_duration(
1032 input_stream.duration,
1033 input_stream.time_base,
1034 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1035 );
1036 }
1037 }
1038
1039 return Ok(());
1040 }
1041
1042 let input_stream = demux.get_stream(stream_index);
1044 let input_stream_duration = input_stream.duration;
1045 let input_stream_time_base = input_stream.time_base;
1046
1047 let (packet_sender, _st, output_stream_index) = mux.new_stream(demux.node.clone())?;
1048 demux.add_packet_dst(packet_sender, stream_index, output_stream_index);
1049
1050 unsafe {
1051 streamcopy_init(
1052 mux,
1053 *(*demux.in_fmt_ctx).streams.add(stream_index),
1054 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1055 )?;
1056 rescale_duration(
1057 input_stream_duration,
1058 input_stream_time_base,
1059 *(*mux.out_fmt_ctx).streams.add(output_stream_index),
1060 );
1061 mux.stream_ready()
1062 }
1063 }
1064
1065 Ok(())
1066}
1067
1068fn init_simple_filtergraph(
1069 demux: &mut Demuxer,
1070 stream_index: usize,
1071 codec_id: AVCodecID,
1072 enc: *const AVCodec,
1073 mux_index: usize,
1074 mux: &mut Muxer,
1075 filter_graphs: &mut Vec<FilterGraph>,
1076) -> Result<()> {
1077 let codec_type = demux.get_stream(stream_index).codec_type;
1078
1079 let filter_desc = if codec_type == AVMEDIA_TYPE_VIDEO {
1080 "null"
1081 } else {
1082 "anull"
1083 };
1084 let mut filter_graph = init_filter_graph(filter_graphs.len(), filter_desc, None)?;
1085
1086 ifilter_bind_ist(&mut filter_graph, 0, stream_index, demux)?;
1090 ofilter_bind_ost(
1091 mux_index,
1092 mux,
1093 &mut filter_graph,
1094 0,
1095 codec_id,
1096 enc,
1097 )?;
1098
1099 filter_graphs.push(filter_graph);
1100
1101 Ok(())
1102}
1103
1104unsafe fn rescale_duration(src_duration: i64, src_time_base: AVRational, stream: *mut AVStream) {
1105 (*stream).duration = av_rescale_q(src_duration, src_time_base, (*stream).time_base);
1106}
1107
1108#[cfg(feature = "docs-rs")]
1109fn streamcopy_init(
1110 mux: &mut Muxer,
1111 input_stream: *mut AVStream,
1112 output_stream: *mut AVStream,
1113) -> Result<()> {
1114 Ok(())
1115}
1116
1117#[cfg(not(feature = "docs-rs"))]
1118fn streamcopy_init(
1119 mux: &mut Muxer,
1120 input_stream: *mut AVStream,
1121 output_stream: *mut AVStream,
1122) -> Result<()> {
1123 unsafe {
1124 let codec_ctx = avcodec_alloc_context3(null_mut());
1125 if codec_ctx.is_null() {
1126 return Err(OpenOutputError::OutOfMemory.into());
1127 }
1128 let _codec_context = CodecContext::new(codec_ctx);
1129
1130 let mut ret = avcodec_parameters_to_context(codec_ctx, (*input_stream).codecpar);
1131 if ret < 0 {
1132 error!("Error setting up codec context options.");
1133 return Err(OpenOutputError::from(ret).into());
1134 }
1135
1136 ret = avcodec_parameters_from_context((*output_stream).codecpar, codec_ctx);
1137 if ret < 0 {
1138 error!("Error getting reference codec parameters.");
1139 return Err(OpenOutputError::from(ret).into());
1140 }
1141
1142 let mut codec_tag = (*(*output_stream).codecpar).codec_tag;
1143 if codec_tag == 0 {
1144 let ct = (*(*mux.out_fmt_ctx).oformat).codec_tag;
1145 let mut codec_tag_tmp = 0;
1146 if ct.is_null()
1147 || av_codec_get_id(ct, (*(*output_stream).codecpar).codec_tag)
1148 == (*(*output_stream).codecpar).codec_id
1149 || av_codec_get_tag2(
1150 ct,
1151 (*(*output_stream).codecpar).codec_id,
1152 &mut codec_tag_tmp,
1153 ) == 0
1154 {
1155 codec_tag = (*(*output_stream).codecpar).codec_tag;
1156 }
1157 }
1158 (*(*output_stream).codecpar).codec_tag = codec_tag;
1159
1160 let mut fr = (*output_stream).r_frame_rate;
1161 if fr.num == 0 {
1162 fr = (*input_stream).r_frame_rate;
1163 }
1164
1165 if fr.num != 0 {
1166 (*output_stream).avg_frame_rate = fr;
1167 } else {
1168 (*output_stream).avg_frame_rate = (*input_stream).avg_frame_rate;
1169 }
1170
1171 if (*output_stream).time_base.num <= 0 || (*output_stream).time_base.den <= 0 {
1173 if fr.num != 0 {
1174 (*output_stream).time_base = av_inv_q(fr);
1175 } else {
1176 (*output_stream).time_base =
1177 av_add_q((*input_stream).time_base, AVRational { num: 0, den: 1 });
1178 }
1179 }
1180
1181 for i in 0..(*(*input_stream).codecpar).nb_coded_side_data {
1182 let sd_src = (*(*input_stream).codecpar)
1183 .coded_side_data
1184 .offset(i as isize);
1185
1186 let sd_dst = av_packet_side_data_new(
1187 &mut (*(*output_stream).codecpar).coded_side_data,
1188 &mut (*(*output_stream).codecpar).nb_coded_side_data,
1189 (*sd_src).type_,
1190 (*sd_src).size,
1191 0,
1192 );
1193 if sd_dst.is_null() {
1194 return Err(OpenOutputError::OutOfMemory.into());
1195 }
1196 std::ptr::copy_nonoverlapping(
1197 (*sd_src).data as *const u8,
1198 (*sd_dst).data,
1199 (*sd_src).size,
1200 );
1201 }
1202
1203 match (*(*output_stream).codecpar).codec_type {
1204 AVMEDIA_TYPE_AUDIO => {
1205 if ((*(*output_stream).codecpar).block_align == 1
1206 || (*(*output_stream).codecpar).block_align == 1152
1207 || (*(*output_stream).codecpar).block_align == 576)
1208 && (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_MP3
1209 {
1210 (*(*output_stream).codecpar).block_align = 0;
1211 }
1212 if (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_AC3 {
1213 (*(*output_stream).codecpar).block_align = 0;
1214 }
1215 }
1216 AVMEDIA_TYPE_VIDEO => {
1217 let sar = if (*input_stream).sample_aspect_ratio.num != 0 {
1218 (*input_stream).sample_aspect_ratio
1219 } else {
1220 (*(*output_stream).codecpar).sample_aspect_ratio
1221 };
1222 (*output_stream).sample_aspect_ratio = sar;
1223 (*(*output_stream).codecpar).sample_aspect_ratio = sar;
1224 (*output_stream).r_frame_rate = (*input_stream).r_frame_rate;
1225 }
1226 _ => {}
1227 }
1228 };
1229 Ok(())
1230}
1231
1232fn output_bind_by_unlabeled_filter(
1233 index: usize,
1234 mux: &mut Muxer,
1235 filter_graphs: &mut Vec<FilterGraph>,
1236 auto_disable: &mut i32,
1237) -> Result<()> {
1238 let fg_len = filter_graphs.len();
1239
1240 for i in 0..fg_len {
1241 let filter_graph = &mut filter_graphs[i];
1242
1243 for i in 0..filter_graph.outputs.len() {
1244 let option = {
1245 let output_filter = &filter_graph.outputs[i];
1246 if (!output_filter.linklabel.is_empty() && output_filter.linklabel != "out")
1247 || output_filter.has_dst()
1248 {
1249 continue;
1250 }
1251
1252 choose_encoder(mux, output_filter.media_type)?
1253 };
1254
1255 let media_type = filter_graph.outputs[i].media_type;
1256
1257 match option {
1258 None => {
1259 warn!("An unexpected media_type {:?} appears in output_filter", media_type);
1260 }
1261 Some((codec_id, enc)) => {
1262 *auto_disable |= 1 << media_type as i32;
1263 ofilter_bind_ost(index, mux, filter_graph, i, codec_id, enc)?;
1264 }
1265 }
1266 }
1267 }
1268
1269 Ok(())
1270}
1271
1272fn ofilter_bind_ost(
1273 index: usize,
1274 mux: &mut Muxer,
1275 filter_graph: &mut FilterGraph,
1276 output_filter_index: usize,
1277 codec_id: AVCodecID,
1278 enc: *const AVCodec,
1279) -> Result<()> {
1280 let output_filter = &mut filter_graph.outputs[output_filter_index];
1281 let (frame_sender, output_stream_index) =
1282 mux.add_enc_stream(output_filter.media_type, enc, filter_graph.node.clone())?;
1283 output_filter.set_dst(frame_sender);
1284
1285 configure_output_filter_opts(
1286 index,
1287 mux,
1288 output_filter,
1289 codec_id,
1290 enc,
1291 output_stream_index,
1292 )?;
1293 Ok(())
1294}
1295
1296fn choose_encoder(
1297 mux: &Muxer,
1298 media_type: AVMediaType,
1299) -> Result<Option<(AVCodecID, *const AVCodec)>> {
1300 let media_codec = match media_type {
1301 AVMEDIA_TYPE_VIDEO => mux.video_codec.clone(),
1302 AVMEDIA_TYPE_AUDIO => mux.audio_codec.clone(),
1303 AVMEDIA_TYPE_SUBTITLE => mux.subtitle_codec.clone(),
1304 _ => return Ok(None),
1305 };
1306
1307 match media_codec {
1308 None => {
1309 let url = CString::new(&*mux.url).unwrap();
1310 unsafe {
1311 let codec_id = av_guess_codec(
1312 (*mux.out_fmt_ctx).oformat,
1313 null(),
1314 url.as_ptr(),
1315 null(),
1316 media_type,
1317 );
1318 let enc = avcodec_find_encoder(codec_id);
1319 if enc.is_null() {
1320 let format_name = (*(*mux.out_fmt_ctx).oformat).name;
1321 let format_name = CStr::from_ptr(format_name).to_str();
1322 let codec_name = avcodec_get_name(codec_id);
1323 let codec_name = CStr::from_ptr(codec_name).to_str();
1324 if let (Ok(format_name), Ok(codec_name)) = (format_name, codec_name) {
1325 error!("Automatic encoder selection failed Default encoder for format {format_name} (codec {codec_name}) is probably disabled. Please choose an encoder manually.");
1326 }
1327 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1328 }
1329
1330 return Ok(Some((codec_id, enc)));
1331 }
1332 }
1333 Some(media_codec) if media_codec != "copy" => unsafe {
1334 let media_codec_cstr = CString::new(media_codec.clone())?;
1335
1336 let mut enc = avcodec_find_encoder_by_name(media_codec_cstr.as_ptr());
1337 let desc = avcodec_descriptor_get_by_name(media_codec_cstr.as_ptr());
1338
1339 if enc.is_null() && !desc.is_null() {
1340 enc = avcodec_find_encoder((*desc).id);
1341 if !enc.is_null() {
1342 let codec_name = (*enc).name;
1343 let codec_name = CStr::from_ptr(codec_name).to_str();
1344 let desc_name = (*desc).name;
1345 let desc_name = CStr::from_ptr(desc_name).to_str();
1346 if let (Ok(codec_name), Ok(desc_name)) = (codec_name, desc_name) {
1347 debug!("Matched encoder '{codec_name}' for codec '{desc_name}'.");
1348 }
1349 }
1350 }
1351
1352 if enc.is_null() {
1353 error!("Unknown encoder '{media_codec}'");
1354 return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
1355 }
1356
1357 if (*enc).type_ != media_type {
1358 error!("Invalid encoder type '{media_codec}'");
1359 return Err(OpenOutputError::InvalidArgument.into());
1360 }
1361 let codec_id = (*enc).id;
1362 return Ok(Some((codec_id, enc)));
1363 },
1364 _ => {}
1365 };
1366
1367 Ok(None)
1368}
1369
1370fn check_duplicate_inputs_outputs(inputs: &[Input], outputs: &[Output]) -> Result<()> {
1371 for output in outputs {
1372 if let Some(output_url) = &output.url {
1373 for input in inputs {
1374 if let Some(input_url) = &input.url {
1375 if input_url == output_url {
1376 return Err(FileSameAsInput(input_url.clone()));
1377 }
1378 }
1379 }
1380 }
1381 }
1382 Ok(())
1383}
1384
1385fn open_output_files(outputs: &mut Vec<Output>, copy_ts: bool) -> Result<Vec<Muxer>> {
1386 let mut muxs = Vec::new();
1387
1388 for (i, output) in outputs.iter_mut().enumerate() {
1389 unsafe {
1390 let result = open_output_file(i, output, copy_ts);
1391 if let Err(e) = result {
1392 free_output_av_format_context(muxs);
1393 return Err(e);
1394 }
1395 let mux = result.unwrap();
1396 muxs.push(mux)
1397 }
1398 }
1399 Ok(muxs)
1400}
1401
1402unsafe fn free_output_av_format_context(muxs: Vec<Muxer>) {
1403 for mut mux in muxs {
1404 avformat_close_input(&mut mux.out_fmt_ctx);
1405 }
1406}
1407
1408#[cfg(feature = "docs-rs")]
1409unsafe fn open_output_file(index: usize, output: &mut Output) -> Result<Muxer> {
1410 Err(Bug)
1411}
1412
1413#[cfg(not(feature = "docs-rs"))]
1414unsafe fn open_output_file(index: usize, output: &mut Output, copy_ts: bool) -> Result<Muxer> {
1415 let mut out_fmt_ctx = null_mut();
1416 let format = get_format(&output.format)?;
1417 match &output.url {
1418 None => {
1419 if output.write_callback.is_none() {
1420 error!("input url and write_callback is none.");
1421 return Err(OpenOutputError::InvalidSink.into());
1422 }
1423
1424 let write_callback = output.write_callback.take().unwrap();
1425
1426 let avio_ctx_buffer_size = 1024 * 64;
1427 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
1428 if avio_ctx_buffer.is_null() {
1429 return Err(OpenOutputError::OutOfMemory.into());
1430 }
1431
1432 let have_seek_callback = output.seek_callback.is_some();
1433 let input_opaque = Box::new(OutputOpaque {
1434 write: write_callback,
1435 seek: output.seek_callback.take(),
1436 });
1437 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
1438
1439 let mut avio_ctx = avio_alloc_context(
1440 avio_ctx_buffer as *mut libc::c_uchar,
1441 avio_ctx_buffer_size as i32,
1442 1,
1443 opaque,
1444 None,
1445 Some(write_packet_wrapper),
1446 if have_seek_callback {
1447 Some(seek_packet_wrapper)
1448 } else {
1449 None
1450 },
1451 );
1452 if avio_ctx.is_null() {
1453 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
1454 return Err(OpenOutputError::OutOfMemory.into());
1455 }
1456
1457 let ret = avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), null());
1458 if out_fmt_ctx.is_null() {
1459 warn!("Error initializing the muxer for write_callback");
1460 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
1461 avio_context_free(&mut avio_ctx);
1462 return Err(AllocOutputContextError::from(ret).into());
1463 }
1464
1465 if !have_seek_callback && output_requires_seek(out_fmt_ctx) {
1466 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
1467 avio_context_free(&mut avio_ctx);
1468 avformat_free_context(out_fmt_ctx);
1469 warn!("The output format supports seeking, but no seek callback is provided. This may cause issues.");
1470 return Err(OpenOutputError::SeekFunctionMissing.into());
1471 }
1472
1473 (*out_fmt_ctx).pb = avio_ctx;
1474 (*out_fmt_ctx).flags |= AVFMT_FLAG_CUSTOM_IO;
1475 }
1476 Some(url) => {
1477 let url_cstr = if url == "-" {
1478 CString::new("pipe:")?
1479 } else {
1480 CString::new(url.as_str())?
1481 };
1482 let ret =
1483 avformat_alloc_output_context2(&mut out_fmt_ctx, format, null(), url_cstr.as_ptr());
1484 if out_fmt_ctx.is_null() {
1485 warn!("Error initializing the muxer for {url}");
1486 return Err(AllocOutputContextError::from(ret).into());
1487 }
1488
1489 let output_format = (*out_fmt_ctx).oformat;
1490 if (*output_format).flags & AVFMT_NOFILE == 0 {
1491 let ret = avio_open(&mut (*out_fmt_ctx).pb, url_cstr.as_ptr(), AVIO_FLAG_WRITE);
1492 if ret < 0 {
1493 warn!("Error opening output {url}");
1494 return Err(OpenOutputError::from(ret).into());
1495 }
1496 }
1497 }
1498 }
1499
1500 let recording_time_us = match output.stop_time_us {
1501 None => output.recording_time_us,
1502 Some(stop_time_us) => {
1503 let start_time_us = output.start_time_us.unwrap_or_else(|| 0);
1504 if stop_time_us <= start_time_us {
1505 error!("stop_time_us value smaller than start_time_us; aborting.");
1506 return Err(OpenOutputError::InvalidArgument.into());
1507 } else {
1508 Some(stop_time_us - start_time_us)
1509 }
1510 }
1511 };
1512
1513 let url = output
1514 .url
1515 .clone()
1516 .unwrap_or_else(|| format!("write_callback[{index}]"));
1517
1518 let video_codec_opts = convert_options(output.video_codec_opts.clone())?;
1519 let audio_codec_opts = convert_options(output.audio_codec_opts.clone())?;
1520 let subtitle_codec_opts = convert_options(output.subtitle_codec_opts.clone())?;
1521 let format_opts = convert_options(output.format_opts.clone())?;
1522
1523 let mux = Muxer::new(
1524 url,
1525 output.url.is_none(),
1526 out_fmt_ctx,
1527 output.frame_pipelines.take(),
1528 output.stream_maps.clone(),
1529 output.video_codec.clone(),
1530 output.audio_codec.clone(),
1531 output.subtitle_codec.clone(),
1532 output.start_time_us,
1533 recording_time_us,
1534 output.framerate,
1535 output.vsync_method,
1536 output.bits_per_raw_sample,
1537 output.audio_sample_rate,
1538 output.audio_channels,
1539 output.audio_sample_fmt,
1540 output.video_qscale,
1541 output.audio_qscale,
1542 output.max_video_frames,
1543 output.max_audio_frames,
1544 output.max_subtitle_frames,
1545 video_codec_opts,
1546 audio_codec_opts,
1547 subtitle_codec_opts,
1548 format_opts,
1549 copy_ts
1550 );
1551
1552 Ok(mux)
1553}
1554
1555fn get_format(format_option: &Option<String>) -> Result<*const AVOutputFormat> {
1556 match format_option {
1557 None => Ok(null()),
1558 Some(format_str) => unsafe {
1559 let mut format_cstr = CString::new(format_str.to_string())?;
1560 let mut format = av_guess_format(format_cstr.as_ptr(), null(), null());
1561 if format.is_null() {
1562 format_cstr = CString::new(format!("tmp.{format_str}"))?;
1563 format = av_guess_format(null(), format_cstr.as_ptr(), null());
1564 }
1565 if format.is_null() {
1566 return Err(OpenOutputError::FormatUnsupported(format_str.to_string()).into());
1567 }
1568 Ok(format)
1569 },
1570 }
1571}
1572
1573unsafe fn output_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
1574 if fmt_ctx.is_null() {
1575 return false;
1576 }
1577
1578 let mut format_name = "unknown".to_string();
1579
1580 if !(*fmt_ctx).oformat.is_null() {
1581 let oformat = (*fmt_ctx).oformat;
1582 format_name = CStr::from_ptr((*oformat).name)
1583 .to_string_lossy()
1584 .into_owned();
1585 let flags = (*oformat).flags;
1586 let no_file = flags & AVFMT_NOFILE as i32 != 0;
1587 let global_header = flags & AVFMT_GLOBALHEADER as i32 != 0;
1588
1589 log::debug!(
1590 "Output format '{format_name}' - No file: {}, Global header: {}",
1591 if no_file { "True" } else { "False" },
1592 if global_header { "True" } else { "False" }
1593 );
1594
1595 let format_names: Vec<&str> = format_name.split(',').collect();
1597 if format_names
1598 .iter()
1599 .any(|&f| matches!(f, "mp4" | "mov" | "mkv" | "avi" | "flac" | "ogg" | "webm"))
1600 {
1601 log::debug!("Output format '{format_name}' typically requires seeking.");
1602 return true;
1603 }
1604
1605 if format_names.iter().any(|&f| {
1607 matches!(
1608 f,
1609 "mpegts" | "hls" | "m3u8" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
1610 )
1611 }) {
1612 log::debug!("Output format '{format_name}' does not typically require seeking.");
1613 return false;
1614 }
1615
1616 if format_name == "flv" {
1618 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'.");
1619 return false;
1620 }
1621
1622 if no_file {
1624 log::debug!(
1625 "Output format '{format_name}' uses AVFMT_NOFILE. Seeking is likely unnecessary."
1626 );
1627 return false;
1628 }
1629
1630 if global_header {
1632 log::debug!(
1633 "Output format '{format_name}' uses AVFMT_GLOBALHEADER. Seeking may be required."
1634 );
1635 return true;
1636 }
1637 } else {
1638 log::debug!("Output format is null. Cannot determine if seeking is required.");
1639 }
1640
1641 log::debug!("Output format '{format_name}' does not match any known rules. Assuming seeking is not required.");
1643 false
1644}
1645
1646struct InputOpaque {
1647 read: Box<dyn FnMut(&mut [u8]) -> i32>,
1648 seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1649}
1650
1651#[allow(dead_code)]
1652struct OutputOpaque {
1653 write: Box<dyn FnMut(&[u8]) -> i32>,
1654 seek: Option<Box<dyn FnMut(i64, i32) -> i64>>,
1655}
1656
1657unsafe extern "C" fn write_packet_wrapper(
1658 opaque: *mut libc::c_void,
1659 buf: *const u8,
1660 buf_size: libc::c_int,
1661) -> libc::c_int {
1662 if buf.is_null() {
1663 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1664 }
1665 let closure = &mut *(opaque as *mut Box<dyn FnMut(&[u8]) -> i32>);
1666
1667 let slice = std::slice::from_raw_parts(buf, buf_size as usize);
1668
1669 (*closure)(slice)
1670}
1671
1672unsafe extern "C" fn read_packet_wrapper(
1673 opaque: *mut libc::c_void,
1674 buf: *mut u8,
1675 buf_size: libc::c_int,
1676) -> libc::c_int {
1677 if buf.is_null() {
1678 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO);
1679 }
1680
1681 let context = &mut *(opaque as *mut InputOpaque);
1682
1683 let slice = std::slice::from_raw_parts_mut(buf, buf_size as usize);
1684
1685 (context.read)(slice)
1686}
1687
1688unsafe extern "C" fn seek_packet_wrapper(
1689 opaque: *mut libc::c_void,
1690 offset: i64,
1691 whence: libc::c_int,
1692) -> i64 {
1693 let context = &mut *(opaque as *mut InputOpaque);
1694
1695 if let Some(seek_func) = &mut context.seek {
1696 (*seek_func)(offset, whence)
1697 } else {
1698 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64
1699 }
1700}
1701
1702fn fg_bind_inputs(filter_graphs: &mut Vec<FilterGraph>, demuxs: &mut Vec<Demuxer>) -> Result<()> {
1703 if filter_graphs.is_empty() {
1704 return Ok(());
1705 }
1706 bind_fg_inputs_by_fg(filter_graphs)?;
1707
1708 for filter_graph in filter_graphs.iter_mut() {
1709 for i in 0..filter_graph.inputs.len() {
1710 fg_complex_bind_input(
1711 filter_graph,
1712 i,
1713 demuxs,
1714 )?;
1715 }
1716 }
1717
1718 Ok(())
1719}
1720
1721struct FilterLabel {
1722 linklabel: String,
1723 media_type: AVMediaType,
1724}
1725
1726fn bind_fg_inputs_by_fg(filter_graphs: &mut Vec<FilterGraph>) -> Result<()> {
1727 let fg_labels = filter_graphs
1728 .iter()
1729 .map(|filter_graph| {
1730 let inputs = filter_graph
1731 .inputs
1732 .iter()
1733 .map(|input| FilterLabel {
1734 linklabel: input.linklabel.clone(),
1735 media_type: input.media_type,
1736 })
1737 .collect::<Vec<_>>();
1738 let outputs = filter_graph
1739 .outputs
1740 .iter()
1741 .map(|output| FilterLabel {
1742 linklabel: output.linklabel.clone(),
1743 media_type: output.media_type,
1744 })
1745 .collect::<Vec<_>>();
1746 (inputs, outputs)
1747 })
1748 .collect::<Vec<_>>();
1749
1750 for (i, (inputs, _outputs)) in fg_labels.iter().enumerate() {
1751 for input_filter_label in inputs.iter() {
1752 if input_filter_label.linklabel.is_empty() {
1753 continue;
1754 }
1755
1756 'outer: for (j, (_inputs, outputs)) in fg_labels.iter().enumerate() {
1757 if i == j {
1758 continue;
1759 }
1760
1761 for (output_idx, output_filter_label) in outputs.iter().enumerate() {
1762 if output_filter_label.linklabel != input_filter_label.linklabel {
1763 continue;
1764 }
1765 if output_filter_label.media_type != input_filter_label.media_type {
1766 warn!(
1767 "Tried to connect {:?} output to {:?} input",
1768 output_filter_label.media_type, input_filter_label.media_type
1769 );
1770 return Err(FilterGraphParseError::InvalidArgument.into());
1771 }
1772
1773 {
1774 let filter_graph = &filter_graphs[j];
1775 let output_filter = &filter_graph.outputs[output_idx];
1776 if output_filter.has_dst() {
1777 continue;
1778 }
1779
1780 }
1781
1782 let (sender, finished_flag_list) = {
1783 let filter_graph = &mut filter_graphs[i];
1784 filter_graph.get_src_sender()
1785 };
1786
1787 {
1788 let filter_graph = &mut filter_graphs[j];
1789 filter_graph.outputs[output_idx].set_dst(sender);
1790 filter_graph.outputs[output_idx].fg_input_index = i;
1791 filter_graph.outputs[output_idx].finished_flag_list = finished_flag_list;
1792 }
1793
1794 break 'outer;
1795 }
1796 }
1797 }
1798 }
1799 Ok(())
1800}
1801
1802fn fg_complex_bind_input(
1803 filter_graph: &mut FilterGraph,
1804 input_filter_index: usize,
1805 demuxs: &mut Vec<Demuxer>,
1806) -> Result<()> {
1807 let graph_desc = &filter_graph.graph_desc;
1808 let input_filter = &mut filter_graph.inputs[input_filter_index];
1809 let (demux_idx, stream_idx) =
1810 if !input_filter.linklabel.is_empty() && input_filter.linklabel != "in" {
1811 let (demux_idx, stream_idx) = fg_find_input_idx_by_linklabel(
1812 &input_filter.linklabel,
1813 input_filter.media_type,
1814 demuxs,
1815 graph_desc,
1816 )?;
1817
1818 info!(
1819 "Binding filter input with label '{}' to input stream {stream_idx}:{demux_idx}",
1820 input_filter.linklabel
1821 );
1822 (demux_idx, stream_idx)
1823 } else {
1824 let mut demux_idx = -1i32;
1825 let mut stream_idx = 0;
1826 for (d_idx, demux) in demuxs.iter().enumerate() {
1827 for (st_idx, intput_stream) in demux.get_streams().iter().enumerate() {
1828 if intput_stream.is_used() {
1829 continue;
1830 }
1831 if intput_stream.codec_type == input_filter.media_type {
1832 demux_idx = d_idx as i32;
1833 stream_idx = st_idx;
1834 break;
1835 }
1836 }
1837 if demux_idx >= 0 {
1838 break;
1839 }
1840 }
1841
1842 if demux_idx < 0 {
1843 warn!("Cannot find a matching stream for unlabeled input pad {}", input_filter.name);
1844 return Err(FilterGraphParseError::InvalidArgument.into());
1845 }
1846
1847 debug!("FilterGraph binding unlabeled input {input_filter_index} to input stream {stream_idx}:{demux_idx}");
1848
1849 (demux_idx as usize, stream_idx)
1850 };
1851
1852 let demux = &mut demuxs[demux_idx];
1853
1854 ifilter_bind_ist(filter_graph, input_filter_index, stream_idx, demux)
1855}
1856
1857#[cfg(feature = "docs-rs")]
1858fn ifilter_bind_ist(
1859 filter_graph: &mut FilterGraph,
1860 input_index: usize,
1861 stream_idx: usize,
1862 demux: &mut Demuxer,
1863) -> Result<()> {
1864 Ok(())
1865}
1866
1867#[cfg(not(feature = "docs-rs"))]
1868fn ifilter_bind_ist(
1869 filter_graph: &mut FilterGraph,
1870 input_index: usize,
1871 stream_idx: usize,
1872 demux: &mut Demuxer,
1873) -> Result<()> {
1874 unsafe {
1875 let input_filter = &mut filter_graph.inputs[input_index];
1876 let ist = *(*demux.in_fmt_ctx).streams.add(stream_idx);
1877 let par = (*ist).codecpar;
1878 if (*par).codec_type == AVMEDIA_TYPE_VIDEO {
1879 let framerate = av_guess_frame_rate(demux.in_fmt_ctx, ist, null_mut());
1880 input_filter.opts.framerate = framerate;
1881 } else if (*par).codec_type == AVMEDIA_TYPE_SUBTITLE {
1882 input_filter.opts.sub2video_width = (*par).width;
1883 input_filter.opts.sub2video_height = (*par).height;
1884
1885 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1886 let nb_streams = (*demux.in_fmt_ctx).nb_streams;
1887 for j in 0..nb_streams {
1888 let par1 = (**(*demux.in_fmt_ctx).streams.add(j as usize)).codecpar;
1889 if (*par1).codec_type == AVMEDIA_TYPE_VIDEO {
1890 input_filter.opts.sub2video_width =
1891 std::cmp::max(input_filter.opts.sub2video_width, (*par1).width);
1892 input_filter.opts.sub2video_height =
1893 std::cmp::max(input_filter.opts.sub2video_height, (*par1).height);
1894 }
1895 }
1896 }
1897
1898 if input_filter.opts.sub2video_width <= 0 || input_filter.opts.sub2video_height <= 0 {
1899 input_filter.opts.sub2video_width =
1900 std::cmp::max(input_filter.opts.sub2video_width, 720);
1901 input_filter.opts.sub2video_height =
1902 std::cmp::max(input_filter.opts.sub2video_height, 576);
1903 }
1904
1905 demux.get_stream_mut(stream_idx).have_sub2video = true;
1906 }
1907
1908 let dec_ctx = {
1909 let input_stream = demux.get_stream_mut(stream_idx);
1910 avcodec_alloc_context3(input_stream.codec.as_ptr())
1911 };
1912 if dec_ctx.is_null() {
1913 return Err(FilterGraphParseError::OutOfMemory.into());
1914 }
1915 let _codec_ctx = CodecContext::new(dec_ctx);
1916
1917 let fallback = input_filter.opts.fallback.as_mut_ptr();
1918 if (*dec_ctx).codec_type == AVMEDIA_TYPE_AUDIO {
1919 (*fallback).format = (*dec_ctx).sample_fmt as i32;
1920 (*fallback).sample_rate = (*dec_ctx).sample_rate;
1921
1922 let ret = av_channel_layout_copy(&mut (*fallback).ch_layout, &(*dec_ctx).ch_layout);
1923 if ret < 0 {
1924 return Err(FilterGraphParseError::from(ret).into());
1925 }
1926 } else if (*dec_ctx).codec_type == AVMEDIA_TYPE_VIDEO {
1927 (*fallback).format = (*dec_ctx).pix_fmt as i32;
1928 (*fallback).width = (*dec_ctx).width;
1929 (*fallback).height = (*dec_ctx).height;
1930 (*fallback).sample_aspect_ratio = (*dec_ctx).sample_aspect_ratio;
1931 (*fallback).colorspace = (*dec_ctx).colorspace;
1932 (*fallback).color_range = (*dec_ctx).color_range;
1933 }
1934 (*fallback).time_base = (*dec_ctx).pkt_timebase;
1935
1936 input_filter.opts.flags |= IFILTER_FLAG_AUTOROTATE;
1938
1939 let tsoffset = if demux.copy_ts {
1940 let mut tsoffset = if demux.start_time_us.is_some() {
1941 demux.start_time_us.unwrap()
1942 } else {
1943 0
1944 };
1945 if (*demux.in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
1946 tsoffset += (*demux.in_fmt_ctx).start_time
1947 }
1948 tsoffset
1949 } else {
1950 0
1951 };
1952 if demux.start_time_us.is_some() {
1953 input_filter.opts.trim_start_us = Some(tsoffset);
1954 }
1955 input_filter.opts.trim_end_us = demux.recording_time_us;
1956
1957 let (sender, finished_flag_list) = filter_graph.get_src_sender();
1958 {
1959 let input_stream = demux.get_stream_mut(stream_idx);
1960 input_stream.add_fg_dst(sender, input_index, finished_flag_list);
1961 };
1962
1963 let node = Arc::make_mut(&mut filter_graph.node);
1964 let SchNode::Filter { inputs, .. } = node else {
1965 unreachable!()
1966 };
1967 inputs.insert(input_index, demux.node.clone());
1968
1969
1970 demux.connect_stream(stream_idx);
1971 Ok(())
1972 }
1973}
1974
1975fn fg_find_input_idx_by_linklabel(
1976 linklabel: &str,
1977 filter_media_type: AVMediaType,
1978 demuxs: &mut Vec<Demuxer>,
1979 desc: &str,
1980) -> Result<(usize, usize)> {
1981 let new_linklabel = if linklabel.starts_with("[") && linklabel.ends_with("]") {
1982 if linklabel.len() <= 2 {
1983 warn!("Filter linklabel is empty");
1984 return Err(InvalidFilterSpecifier(desc.to_string()).into());
1985 } else {
1986 &linklabel[1..linklabel.len() - 1]
1987 }
1988 } else {
1989 linklabel
1990 };
1991
1992 let (file_idx, remainder) = strtol(new_linklabel)?;
1993 if file_idx < 0 || file_idx as usize >= demuxs.len() {
1994 return Err(InvalidFileIndexInFg(file_idx as usize, desc.to_string()).into());
1995 }
1996
1997 let (media_type, _allow_unused) = stream_specifier_parse(remainder)?;
1998
1999 if media_type != filter_media_type {
2000 warn!("Invalid stream label: {linklabel}");
2001 return Err(FilterGraphParseError::InvalidArgument.into());
2002 }
2003
2004 let demux = &demuxs[file_idx as usize];
2005
2006 let mut stream_idx = -1i32;
2007
2008 for (idx, dec_stream) in demux.get_streams().iter().enumerate() {
2009 if (*dec_stream).codec_type == media_type {
2010 stream_idx = idx as i32;
2011 break;
2012 }
2013 }
2014
2015 if stream_idx < 0 {
2016 warn!(
2017 "Stream specifier '{remainder}' in filtergraph description {desc} matches no streams."
2018 );
2019 return Err(FilterGraphParseError::InvalidArgument.into());
2020 }
2021 Ok((file_idx as usize, stream_idx as usize))
2022}
2023
2024fn stream_specifier_parse(specifier: &str) -> Result<(AVMediaType, bool)> {
2025 let specifier = if specifier.starts_with(':') {
2026 &specifier[1..]
2027 } else {
2028 specifier
2029 };
2030
2031 match specifier {
2032 "v" => Ok((AVMEDIA_TYPE_VIDEO, false)),
2033 "a" => Ok((AVMEDIA_TYPE_AUDIO, false)),
2034 "s" => Ok((AVMEDIA_TYPE_SUBTITLE, false)),
2035 "d" => Ok((AVMEDIA_TYPE_DATA, false)),
2036 "t" => Ok((AVMEDIA_TYPE_ATTACHMENT, false)),
2037 "v?" => Ok((AVMEDIA_TYPE_VIDEO, true)),
2038 "a?" => Ok((AVMEDIA_TYPE_AUDIO, true)),
2039 "s?" => Ok((AVMEDIA_TYPE_SUBTITLE, true)),
2040 "d?" => Ok((AVMEDIA_TYPE_DATA, true)),
2041 "t?" => Ok((AVMEDIA_TYPE_ATTACHMENT, true)),
2042 _ => Err(InvalidFilterSpecifier(specifier.to_string()).into()),
2044 }
2045}
2046
2047fn strtol(input: &str) -> Result<(i64, &str)> {
2049 let mut chars = input.chars().peekable();
2050 let mut negative = false;
2051
2052 if let Some(&ch) = chars.peek() {
2053 if ch == '-' {
2054 negative = true;
2055 chars.next();
2056 } else if !ch.is_digit(10) {
2057 return Err(ParseInteger);
2058 }
2059 }
2060
2061 let number_start = input.len() - chars.clone().collect::<String>().len();
2062
2063 let number_str: String = chars.by_ref().take_while(|ch| ch.is_digit(10)).collect();
2064
2065 if number_str.is_empty() {
2066 return Err(ParseInteger);
2067 }
2068
2069 let number: i64 = number_str.parse().map_err(|_| ParseInteger)?;
2070
2071 let remainder_index = number_start + number_str.len();
2072 let remainder = &input[remainder_index..];
2073
2074 if negative {
2075 Ok((-number, remainder))
2076 } else {
2077 Ok((number, remainder))
2078 }
2079}
2080
2081fn init_filter_graphs(filter_complexs: Vec<FilterComplex>) -> Result<Vec<FilterGraph>> {
2082 let mut filter_graphs = Vec::with_capacity(filter_complexs.len());
2083 for (i, filter) in filter_complexs.iter().enumerate() {
2084 let filter_graph = init_filter_graph(i, &filter.filter_descs, filter.hw_device.clone())?;
2085 filter_graphs.push(filter_graph);
2086 }
2087 Ok(filter_graphs)
2088}
2089
2090
2091#[cfg(feature = "docs-rs")]
2092fn init_filter_graph(
2093 fg_index: usize,
2094 filter_desc: &str,
2095 hw_device: Option<String>,
2096) -> Result<FilterGraph> {
2097 Err(Bug)
2098}
2099
2100#[cfg(not(feature = "docs-rs"))]
2101fn init_filter_graph(
2102 fg_index: usize,
2103 filter_desc: &str,
2104 hw_device: Option<String>,
2105) -> Result<FilterGraph> {
2106 let desc_cstr = CString::new(filter_desc)?;
2107
2108 unsafe {
2109 let mut graph = avfilter_graph_alloc();
2112 (*graph).nb_threads = 1;
2113
2114 let mut seg = null_mut();
2115 let mut ret = avfilter_graph_segment_parse(graph, desc_cstr.as_ptr(), 0, &mut seg);
2116 if ret < 0 {
2117 avfilter_graph_free(&mut graph);
2118 return Err(FilterGraphParseError::from(ret).into());
2119 }
2120
2121 ret = avfilter_graph_segment_create_filters(seg, 0);
2122 if ret < 0 {
2123 avfilter_graph_free(&mut graph);
2124 avfilter_graph_segment_free(&mut seg);
2125 return Err(FilterGraphParseError::from(ret).into());
2126 }
2127
2128 #[cfg(not(feature = "docs-rs"))]
2129 {
2130 ret = graph_opts_apply(seg);
2131 }
2132 if ret < 0 {
2133 avfilter_graph_segment_free(&mut seg);
2134 avfilter_graph_free(&mut graph);
2135 return Err(FilterGraphParseError::from(ret).into());
2136 }
2137
2138 let mut inputs = null_mut();
2139 let mut outputs = null_mut();
2140 ret = avfilter_graph_segment_apply(seg, 0, &mut inputs, &mut outputs);
2141 avfilter_graph_segment_free(&mut seg);
2142
2143 if ret < 0 {
2144 avfilter_inout_free(&mut inputs);
2145 avfilter_inout_free(&mut outputs);
2146 avfilter_graph_free(&mut graph);
2147 return Err(FilterGraphParseError::from(ret).into());
2148 }
2149
2150 let input_filters = inouts_to_input_filters(fg_index, inputs)?;
2151 let output_filters = inouts_to_output_filters(outputs)?;
2152
2153 if output_filters.len() == 0 {
2154 avfilter_inout_free(&mut inputs);
2155 avfilter_inout_free(&mut outputs);
2156 avfilter_graph_free(&mut graph);
2157 return Err(FilterZeroOutputs);
2158 }
2159
2160 let filter_graph = FilterGraph::new(
2161 filter_desc.to_string(),
2162 hw_device,
2163 input_filters,
2164 output_filters,
2165 );
2166
2167 avfilter_inout_free(&mut inputs);
2168 avfilter_inout_free(&mut outputs);
2169 avfilter_graph_free(&mut graph);
2170
2171 Ok(filter_graph)
2172 }
2173}
2174
2175unsafe fn inouts_to_input_filters(
2176 fg_index: usize,
2177 inouts: *mut AVFilterInOut,
2178) -> Result<Vec<InputFilter>> {
2179 let mut cur = inouts;
2180 let mut filterinouts = Vec::new();
2181 let mut filter_index = 0;
2182 while !cur.is_null() {
2183 let linklabel = if (*cur).name.is_null() {
2184 ""
2185 } else {
2186 let linklabel = CStr::from_ptr((*cur).name);
2187 let result = linklabel.to_str();
2188 if let Err(_) = result {
2189 return Err(FilterDescUtf8);
2190 }
2191 result.unwrap()
2192 };
2193
2194 let filter_ctx = (*cur).filter_ctx;
2195 let media_type = avfilter_pad_get_type((*filter_ctx).input_pads, (*cur).pad_idx);
2196
2197 let pads = (*filter_ctx).input_pads;
2198 let nb_pads = (*filter_ctx).nb_inputs;
2199
2200 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
2201
2202 let fallback = frame_alloc()?;
2203
2204 let mut filter = InputFilter::new(linklabel.to_string(), media_type, name, fallback);
2205 filter.opts.name = format!("fg:{fg_index}:{filter_index}");
2206 filterinouts.push(filter);
2207
2208 cur = (*cur).next;
2209 filter_index += 1;
2210 }
2211 Ok(filterinouts)
2212}
2213
2214unsafe fn inouts_to_output_filters(inouts: *mut AVFilterInOut) -> Result<Vec<OutputFilter>> {
2215 let mut cur = inouts;
2216 let mut output_filters = Vec::new();
2217 while !cur.is_null() {
2218 let linklabel = if (*cur).name.is_null() {
2219 ""
2220 } else {
2221 let linklabel = CStr::from_ptr((*cur).name);
2222 let result = linklabel.to_str();
2223 if let Err(_) = result {
2224 return Err(FilterDescUtf8);
2225 }
2226 result.unwrap()
2227 };
2228
2229 let filter_ctx = (*cur).filter_ctx;
2230 let media_type = avfilter_pad_get_type((*filter_ctx).output_pads, (*cur).pad_idx);
2231
2232 let pads = (*filter_ctx).output_pads;
2233 let nb_pads = (*filter_ctx).nb_outputs;
2234
2235 let name = describe_filter_link(cur, filter_ctx, pads, nb_pads)?;
2236
2237 let filter = OutputFilter::new(linklabel.to_string(), media_type, name);
2238 output_filters.push(filter);
2239
2240 cur = (*cur).next;
2241 }
2242 Ok(output_filters)
2243}
2244
2245unsafe fn describe_filter_link(
2246 cur: *mut AVFilterInOut,
2247 filter_ctx: *mut AVFilterContext,
2248 pads: *mut AVFilterPad,
2249 nb_pads: c_uint,
2250) -> Result<String> {
2251 let filter = (*filter_ctx).filter;
2252 let name = (*filter).name;
2253 let name = CStr::from_ptr(name);
2254 let result = name.to_str();
2255 if let Err(_) = result {
2256 return Err(FilterNameUtf8);
2257 }
2258 let name = result.unwrap();
2259
2260 let name = if nb_pads > 1 {
2261 name.to_string()
2262 } else {
2263 let pad_name = avfilter_pad_get_name(pads, (*cur).pad_idx);
2264 let pad_name = CStr::from_ptr(pad_name);
2265 let result = pad_name.to_str();
2266 if let Err(_) = result {
2267 return Err(FilterNameUtf8);
2268 }
2269 let pad_name = result.unwrap();
2270 format!("{name}:{pad_name}")
2271 };
2272 Ok(name)
2273}
2274
2275fn open_input_files(inputs: &mut Vec<Input>, copy_ts: bool) -> Result<Vec<Demuxer>> {
2276 let mut demuxs = Vec::new();
2277 for (i, input) in inputs.iter_mut().enumerate() {
2278 unsafe {
2279 let result = open_input_file(i, input, copy_ts);
2280 if let Err(e) = result {
2281 free_input_av_format_context(demuxs);
2282 return Err(e);
2283 }
2284 let demux = result.unwrap();
2285 demuxs.push(demux)
2286 }
2287 }
2288 Ok(demuxs)
2289}
2290
2291unsafe fn free_input_av_format_context(demuxs: Vec<Demuxer>) {
2292 for mut demux in demuxs {
2293 avformat_close_input(&mut demux.in_fmt_ctx);
2294 }
2295}
2296
2297#[cfg(feature = "docs-rs")]
2298unsafe fn open_input_file(
2299 index: usize,
2300 input: &mut Input,
2301) -> Result<Demuxer> {
2302 Err(Bug)
2303}
2304
2305#[cfg(not(feature = "docs-rs"))]
2306unsafe fn open_input_file(
2307 index: usize,
2308 input: &mut Input,
2309 copy_ts: bool
2310) -> Result<Demuxer> {
2311 let mut in_fmt_ctx = avformat_alloc_context();
2312 if in_fmt_ctx.is_null() {
2313 return Err(OpenInputError::OutOfMemory.into());
2314 }
2315
2316 let recording_time_us = match input.stop_time_us {
2317 None => input.recording_time_us,
2318 Some(stop_time_us) => {
2319 let start_time_us = input.start_time_us.unwrap_or_else(|| 0);
2320 if stop_time_us <= start_time_us {
2321 error!("stop_time_us value smaller than start_time_us; aborting.");
2322 return Err(OpenOutputError::InvalidArgument.into());
2323 } else {
2324 Some(stop_time_us - start_time_us)
2325 }
2326 }
2327 };
2328
2329 match &input.url {
2330 None => {
2331 if input.read_callback.is_none() {
2332 error!("input url and read_callback is none.");
2333 return Err(OpenInputError::InvalidSource.into());
2334 }
2335
2336 let avio_ctx_buffer_size = 1024 * 64;
2337 let mut avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
2338 if avio_ctx_buffer.is_null() {
2339 avformat_close_input(&mut in_fmt_ctx);
2340 return Err(OpenInputError::OutOfMemory.into());
2341 }
2342
2343 let have_seek_callback = input.seek_callback.is_some();
2344 let input_opaque = Box::new(InputOpaque {
2345 read: input.read_callback.take().unwrap(),
2346 seek: input.seek_callback.take(),
2347 });
2348 let opaque = Box::into_raw(input_opaque) as *mut libc::c_void;
2349
2350 let mut avio_ctx = avio_alloc_context(
2351 avio_ctx_buffer as *mut libc::c_uchar,
2352 avio_ctx_buffer_size as i32,
2353 0,
2354 opaque,
2355 Some(read_packet_wrapper),
2356 None,
2357 if have_seek_callback {
2358 Some(seek_packet_wrapper)
2359 } else {
2360 None
2361 },
2362 );
2363 if avio_ctx.is_null() {
2364 av_freep(&mut avio_ctx_buffer as *mut _ as *mut c_void);
2365 avformat_close_input(&mut in_fmt_ctx);
2366 return Err(OpenInputError::OutOfMemory.into());
2367 }
2368
2369 (*in_fmt_ctx).pb = avio_ctx;
2370 (*in_fmt_ctx).flags = AVFMT_FLAG_CUSTOM_IO;
2371
2372 let ret = avformat_open_input(&mut in_fmt_ctx, null(), null(), null_mut());
2373 if ret < 0 {
2374 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2375 avio_context_free(&mut avio_ctx);
2376 avformat_close_input(&mut in_fmt_ctx);
2377 return Err(OpenInputError::from(ret).into());
2378 }
2379
2380 let ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2381 if ret < 0 {
2382 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2383 avio_context_free(&mut avio_ctx);
2384 avformat_close_input(&mut in_fmt_ctx);
2385 return Err(FindStreamError::from(ret).into());
2386 }
2387
2388 if !have_seek_callback && input_requires_seek(in_fmt_ctx) {
2389 av_freep(&mut (*avio_ctx).buffer as *mut _ as *mut c_void);
2390 avio_context_free(&mut avio_ctx);
2391 avformat_close_input(&mut in_fmt_ctx);
2392 warn!("The input format supports seeking, but no seek callback is provided. This may cause issues.");
2393 return Err(OpenInputError::SeekFunctionMissing.into());
2394 }
2395 }
2396 Some(url) => {
2397 let file_iformat = if let Some(format) = &input.format {
2398 let format_cstr = CString::new(format.clone())?;
2399
2400 let file_iformat = ffmpeg_sys_next::av_find_input_format(format_cstr.as_ptr());
2401 if file_iformat.is_null() {
2402 error!("Unknown input format: '{format}'");
2403 return Err(OpenInputError::InvalidFormat(format.clone()).into());
2404 }
2405 file_iformat
2406 } else {
2407 null()
2408 };
2409
2410 let url_cstr = CString::new(url.as_str())?;
2411
2412 let input_opts = convert_options(input.input_opts.clone())?;
2413 let mut input_opts = hashmap_to_avdictionary(&input_opts);
2414 let scan_all_pmts_key = CString::new("scan_all_pmts")?;
2415 if ffmpeg_sys_next::av_dict_get(input_opts, scan_all_pmts_key.as_ptr(), null(), ffmpeg_sys_next::AV_DICT_MATCH_CASE).is_null() {
2416 let scan_all_pmts_value = CString::new("1")?;
2417 ffmpeg_sys_next::av_dict_set(&mut input_opts, scan_all_pmts_key.as_ptr(), scan_all_pmts_value.as_ptr(), ffmpeg_sys_next::AV_DICT_DONT_OVERWRITE);
2418 };
2419 (*in_fmt_ctx).flags |= ffmpeg_sys_next::AVFMT_FLAG_NONBLOCK;
2420
2421 let mut ret = avformat_open_input(&mut in_fmt_ctx, url_cstr.as_ptr(), file_iformat, &mut input_opts);
2422 av_dict_free(&mut input_opts);
2423 if ret < 0 {
2424 avformat_close_input(&mut in_fmt_ctx);
2425 return Err(OpenInputError::from(ret).into());
2426 }
2427
2428 ret = avformat_find_stream_info(in_fmt_ctx, null_mut());
2429 if ret < 0 {
2430 avformat_close_input(&mut in_fmt_ctx);
2431 return Err(FindStreamError::from(ret).into());
2432 }
2433 }
2434 }
2435
2436 let mut timestamp = input.start_time_us.unwrap_or(0);
2437 if (*in_fmt_ctx).start_time != ffmpeg_sys_next::AV_NOPTS_VALUE {
2439 timestamp += (*in_fmt_ctx).start_time;
2440 }
2441
2442 if let Some(start_time_us) = input.start_time_us {
2444 let mut seek_timestamp = timestamp;
2445
2446 if (*(*in_fmt_ctx).iformat).flags & ffmpeg_sys_next::AVFMT_SEEK_TO_PTS == 0 {
2447 let mut dts_heuristic = false;
2448 let stream_count = (*in_fmt_ctx).nb_streams;
2449
2450 for i in 0..stream_count {
2451 let stream = *(*in_fmt_ctx).streams.add(i as usize);
2452 let par = (*stream).codecpar;
2453 if (*par).video_delay != 0 {
2454 dts_heuristic = true;
2455 break;
2456 }
2457 }
2458 if dts_heuristic {
2459 seek_timestamp -= 3 * AV_TIME_BASE as i64 / 23;
2460 }
2461 }
2462 let ret = ffmpeg_sys_next::avformat_seek_file(in_fmt_ctx, -1, i64::MIN, seek_timestamp, seek_timestamp, 0);
2463 if ret < 0 {
2464 warn!("could not seek to position {:.3}", start_time_us as f64 / AV_TIME_BASE as f64);
2465 }
2466 }
2467
2468 let url = input
2469 .url
2470 .clone()
2471 .unwrap_or_else(|| format!("read_callback[{index}]"));
2472
2473
2474 let demux = Demuxer::new(
2475 url,
2476 input.url.is_none(),
2477 in_fmt_ctx,
2478 0 - if copy_ts { 0 } else { timestamp },
2479 input.frame_pipelines.take(),
2480 input.video_codec.clone(),
2481 input.audio_codec.clone(),
2482 input.subtitle_codec.clone(),
2483 input.readrate,
2484 input.start_time_us,
2485 recording_time_us,
2486 input.exit_on_error,
2487 input.stream_loop,
2488 input.hwaccel.clone(),
2489 input.hwaccel_device.clone(),
2490 input.hwaccel_output_format.clone(),
2491 copy_ts
2492 )?;
2493
2494 Ok(demux)
2495}
2496
2497fn convert_options(
2498 opts: Option<HashMap<String, String>>,
2499) -> Result<Option<HashMap<CString, CString>>> {
2500 if opts.is_none() {
2501 return Ok(None);
2502 }
2503
2504 let converted = opts.map(|map| {
2505 map.into_iter()
2506 .map(|(k, v)| Ok((CString::new(k)?, CString::new(v)?)))
2507 .collect::<Result<HashMap<CString, CString>, _>>() });
2509
2510 converted.transpose() }
2512
2513unsafe fn input_requires_seek(fmt_ctx: *mut AVFormatContext) -> bool {
2514 if fmt_ctx.is_null() {
2515 return false;
2516 }
2517
2518 let mut format_name = "unknown".to_string();
2519 let mut format_names: Vec<&str> = Vec::with_capacity(0);
2520
2521 if !(*fmt_ctx).iformat.is_null() {
2522 let iformat = (*fmt_ctx).iformat;
2523 format_name = CStr::from_ptr((*iformat).name)
2524 .to_string_lossy()
2525 .into_owned();
2526 let flags = (*iformat).flags;
2527 let no_binsearch = flags & AVFMT_NOBINSEARCH as i32 != 0;
2528 let no_gensearch = flags & AVFMT_NOGENSEARCH as i32 != 0;
2529
2530 log::debug!(
2531 "Input format '{format_name}' - Binary search: {}, Generic search: {}",
2532 if no_binsearch { "Disabled" } else { "Enabled" },
2533 if no_gensearch { "Disabled" } else { "Enabled" }
2534 );
2535
2536 format_names = format_name.split(',').collect();
2537
2538 if format_names.iter().any(|&f| {
2539 matches!(
2540 f,
2541 "mp4" | "mkv" | "avi" | "mov" | "flac" | "wav" | "aac" | "ogg" | "mp3" | "webm"
2542 )
2543 }) {
2544 if !no_binsearch && !no_gensearch {
2545 return true;
2546 }
2547 }
2548
2549 if format_names.iter().any(|&f| {
2550 matches!(
2551 f,
2552 "hls" | "m3u8" | "mpegts" | "mms" | "udp" | "rtp" | "rtp_mpegts" | "http" | "srt"
2553 )
2554 }) {
2555 log::debug!("Live stream detected ({format_name}). Seeking is not possible.");
2556 return false;
2557 }
2558
2559 if no_binsearch && no_gensearch {
2560 log::debug!("Input format '{format_name}' has both NOBINSEARCH and NOGENSEARCH set. Seeking is likely restricted.");
2561 }
2562 }
2563
2564 let format_duration = (*fmt_ctx).duration;
2565
2566 if format_names.iter().any(|&f| f == "flv") {
2567 if format_duration <= 0 {
2568 log::debug!(
2569 "Input format 'flv' detected with no valid duration. Seeking is not possible."
2570 );
2571 } else {
2572 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.");
2573 }
2574 return false;
2575 }
2576
2577 if format_duration > 0 {
2578 log::debug!("Format '{format_name}' has a duration of {format_duration}. Seeking is likely possible.");
2579 return true;
2580 }
2581
2582 let mut video_stream_index = -1;
2583 for i in 0..(*fmt_ctx).nb_streams {
2584 let stream = *(*fmt_ctx).streams.offset(i as isize);
2585 if (*stream).codecpar.is_null() {
2586 continue;
2587 }
2588 if (*(*stream).codecpar).codec_type == AVMEDIA_TYPE_VIDEO {
2589 video_stream_index = i as i32;
2590 break;
2591 }
2592 }
2593
2594 let stream_index = if video_stream_index >= 0 {
2595 video_stream_index
2596 } else {
2597 -1
2598 };
2599
2600 let original_pos = if !(*fmt_ctx).pb.is_null() {
2601 (*(*fmt_ctx).pb).pos
2602 } else {
2603 -1
2604 };
2605
2606 if original_pos >= 0 {
2607 let seek_target = 1 * AV_TIME_BASE as i64;
2608 let seek_result = av_seek_frame(fmt_ctx, stream_index, seek_target, AVSEEK_FLAG_BACKWARD);
2609
2610 if seek_result >= 0 {
2611 log::debug!("Seek test successful.");
2612
2613 (*(*fmt_ctx).pb).pos = original_pos;
2614 avformat_flush(fmt_ctx);
2615 log::debug!("Restored fmt_ctx.pb.pos to {original_pos} and flushed format context.",);
2616 return true;
2617 } else {
2618 log::debug!("Seek test failed (return code {seek_result}). This format likely does not support seeking.");
2619 }
2620 }
2621
2622 false
2623}
2624
2625#[cfg(test)]
2626mod tests {
2627 use std::ffi::{CStr, CString};
2628 use std::ptr::null_mut;
2629
2630 use crate::core::context::ffmpeg_context::{strtol, FfmpegContext, Output};
2631 use ffmpeg_sys_next::{
2632 avfilter_graph_alloc, avfilter_graph_free, avfilter_graph_parse_ptr, avfilter_inout_free,
2633 };
2634
2635 #[test]
2636 fn test_filter() {
2637 let desc_cstr = CString::new("[1:v][2:v]concat=n=2:v=1:a=0[vout]").unwrap();
2638 unsafe {
2641 let mut graph = avfilter_graph_alloc();
2642 let mut inputs = null_mut();
2643 let mut outputs = null_mut();
2644
2645 let ret = avfilter_graph_parse_ptr(
2646 graph,
2647 desc_cstr.as_ptr(),
2648 &mut inputs,
2649 &mut outputs,
2650 null_mut(),
2651 );
2652 if ret < 0 {
2653 avfilter_inout_free(&mut inputs);
2654 avfilter_inout_free(&mut outputs);
2655 avfilter_graph_free(&mut graph);
2656 println!("err ret:{}", crate::util::ffmpeg_utils::av_err2str(ret));
2657 return;
2658 }
2659
2660 println!("inputs.is_null:{}", inputs.is_null());
2661 println!("outputs.is_null:{}", outputs.is_null());
2662
2663 let mut cur = inputs;
2664 while !cur.is_null() {
2665 let input_name = CStr::from_ptr((*cur).name);
2666 println!("Input name: {}", input_name.to_str().unwrap());
2667 cur = (*cur).next;
2668 }
2669
2670 let output_name = CStr::from_ptr((*outputs).name);
2671 println!("Output name: {}", output_name.to_str().unwrap());
2672
2673 let filter_ctx = (*outputs).filter_ctx;
2674 avfilter_inout_free(&mut outputs);
2675 println!("filter_ctx.is_null:{}", filter_ctx.is_null());
2676 }
2677 }
2678
2679 #[test]
2680 fn test_new() {
2681 let _ = env_logger::builder()
2682 .filter_level(log::LevelFilter::Debug)
2683 .is_test(true)
2684 .try_init();
2685 let _ffmpeg_context = FfmpegContext::new(
2686 vec!["test.mp4".to_string().into()],
2687 vec!["hue=s=0".to_string().into()],
2688 vec!["output.mp4".to_string().into()],
2689 )
2690 .unwrap();
2691 let _ffmpeg_context = FfmpegContext::new(
2692 vec!["test.mp4".into()],
2693 vec!["[0:v]hue=s=0".into()],
2694 vec!["output.mp4".to_string().into()],
2695 )
2696 .unwrap();
2697 let _ffmpeg_context = FfmpegContext::new(
2698 vec!["test.mp4".into()],
2699 vec!["hue=s=0[my-out]".into()],
2700 vec![Output::from("output.mp4").add_stream_map("my-out")],
2701 )
2702 .unwrap();
2703 let _ffmpeg_context = FfmpegContext::new(
2704 vec!["test.mp4".into()],
2705 vec!["hue=s=0".into()],
2706 vec![Output::from("output.mp4").add_stream_map_with_copy("0:v?")],
2707 )
2708 .unwrap();
2709 let result = FfmpegContext::new(
2710 vec!["test.mp4".into()],
2711 vec!["hue=s=0".into()],
2712 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v?")],
2713 );
2714 assert!(result.is_err());
2715 let result = FfmpegContext::new(
2716 vec!["test.mp4".into()],
2717 vec!["hue=s=0[fg-out]".into()],
2718 vec![
2719 Output::from("output.mp4").add_stream_map("my-out?"),
2720 Output::from("output.mp4").add_stream_map("fg-out"),
2721 ],
2722 );
2723 assert!(result.is_err());
2724 let result = FfmpegContext::new(
2726 vec!["test.mp4".into()],
2727 vec!["hue=s=0".into()],
2728 vec![Output::from("output.mp4").add_stream_map_with_copy("1:v")],
2729 );
2730 assert!(result.is_err());
2731 let result = FfmpegContext::new(
2732 vec!["test.mp4".into()],
2733 vec!["hue=s=0[fg-out]".into()],
2734 vec![Output::from("output.mp4").add_stream_map("fg-out?")],
2735 );
2736 assert!(result.is_err());
2737 }
2738
2739 #[test]
2740 fn test_builder() {
2741 let _ = env_logger::builder()
2742 .filter_level(log::LevelFilter::Debug)
2743 .is_test(true)
2744 .try_init();
2745
2746 let context1 = FfmpegContext::builder()
2747 .input("test.mp4")
2748 .filter_desc("hue=s=0")
2749 .output("output.mp4")
2750 .build()
2751 .unwrap();
2752
2753 let context2 = FfmpegContext::builder()
2754 .inputs(vec!["test.mp4"])
2755 .filter_descs(vec!["hue=s=0"])
2756 .outputs(vec!["output.mp4"])
2757 .build()
2758 .unwrap();
2759 }
2760
2761 #[test]
2762 fn test_strtol() {
2763 let input = "-123---abc";
2764 let result = strtol(input);
2765 assert_eq!(result.unwrap(), (-123, "---abc"));
2766
2767 let input = "123---abc";
2768 let result = strtol(input);
2769 assert_eq!(result.unwrap(), (123, "---abc"));
2770
2771 let input = "-123aa";
2772 let result = strtol(input);
2773 assert_eq!(result.unwrap(), (-123, "aa"));
2774
2775 let input = "-aa";
2776 let result = strtol(input);
2777 assert!(result.is_err());
2778
2779 let input = "abc";
2780 let result = strtol(input);
2781 assert!(result.is_err())
2782 }
2783}