use super::fg_bind::{ifilter_bind_ist, init_filter_graph};
use super::*;
unsafe fn process_metadata(mux: &Muxer, demuxs: &Vec<Demuxer>) -> Result<()> {
use crate::core::metadata::MetadataType;
use crate::core::metadata::{
copy_chapters_from_input, copy_metadata, copy_metadata_default, of_add_metadata,
};
let input_ctxs: Vec<*const AVFormatContext> = demuxs
.iter()
.map(|d| d.in_fmt_ctx_ptr() as *const AVFormatContext)
.collect();
let mut metadata_global_manual = false;
let mut metadata_streams_manual = false;
let mut metadata_chapters_manual = false;
let mut mark_manual = |meta_type: &MetadataType| -> () {
match meta_type {
MetadataType::Global => metadata_global_manual = true,
MetadataType::Stream(_) => metadata_streams_manual = true,
MetadataType::Chapter(_) => metadata_chapters_manual = true,
MetadataType::Program(_) => {}
}
};
for mapping in &mux.metadata_map {
mark_manual(&mapping.src_type);
mark_manual(&mapping.dst_type);
if mapping.input_index >= input_ctxs.len() {
log::warn!(target: LOG_TARGET,
"Metadata mapping references non-existent input file index {}",
mapping.input_index
);
continue;
}
let input_ctx = input_ctxs[mapping.input_index];
if let Err(e) = copy_metadata(
input_ctx,
mux.out_fmt_ctx_ptr(),
&mapping.src_type,
&mapping.dst_type,
) {
log::warn!(target: LOG_TARGET, "Failed to copy metadata from mapping: {}", e);
}
}
if mux.auto_copy_metadata && !metadata_chapters_manual {
if let Some(source_demux) = demuxs.iter().find(|d| unsafe {
!d.in_fmt_ctx_ptr().is_null() && (*d.in_fmt_ctx_ptr()).nb_chapters > 0
}) {
if let Err(e) = copy_chapters_from_input(
source_demux.in_fmt_ctx_ptr(),
source_demux.ts_offset,
mux.out_fmt_ctx_ptr(),
mux.start_time_us,
mux.recording_time_us,
true,
) {
log::warn!(target: LOG_TARGET, "Failed to copy chapters: {}", e);
}
}
}
let stream_input_mapping = mux.stream_input_mapping();
let encoding_streams = mux.encoding_streams();
if mux.auto_copy_metadata {
if let Err(e) = copy_metadata_default(
&input_ctxs,
mux.out_fmt_ctx_ptr(),
mux.nb_streams,
&stream_input_mapping,
&encoding_streams,
mux.recording_time_us.is_some(),
mux.auto_copy_metadata,
metadata_global_manual,
metadata_streams_manual,
) {
log::warn!(target: LOG_TARGET, "Failed to apply default metadata behavior: {}", e);
}
}
if let Err(e) = of_add_metadata(
mux.out_fmt_ctx_ptr(),
&mux.global_metadata,
&mux.stream_metadata,
&mux.chapter_metadata,
&mux.program_metadata,
) {
log::warn!(target: LOG_TARGET, "Failed to add user metadata: {}", e);
}
Ok(())
}
fn is_filter_output_linklabel(linklabel: &str) -> bool {
linklabel.starts_with('[')
}
unsafe fn expand_stream_maps(mux: &mut Muxer, demuxs: &[Demuxer]) -> Result<()> {
let stream_map_specs = std::mem::take(&mut mux.stream_map_specs);
for spec in stream_map_specs {
let (linklabel, is_negative) = if spec.linklabel.starts_with('-') {
(&spec.linklabel[1..], true)
} else {
(spec.linklabel.as_str(), false)
};
if is_filter_output_linklabel(linklabel) {
let pure_linklabel = if linklabel.starts_with('[') {
if let Some(end_pos) = linklabel.find(']') {
&linklabel[1..end_pos]
} else {
warn!(target: LOG_TARGET, "Invalid output link label: {}.", linklabel);
return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
}
} else {
linklabel
};
if pure_linklabel.is_empty() {
warn!(target: LOG_TARGET, "Invalid output link label: {}.", linklabel);
return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
}
mux.stream_maps.push(StreamMap {
file_index: 0, stream_index: 0, linklabel: Some(pure_linklabel.to_string()),
copy: spec.copy,
disabled: false,
});
continue;
}
let parse_result = strtol(linklabel);
if parse_result.is_err() {
mux.stream_maps.push(StreamMap {
file_index: 0,
stream_index: 0,
linklabel: Some(linklabel.to_string()),
copy: spec.copy,
disabled: false,
});
continue;
}
let (file_idx, remainder) = parse_result.unwrap();
if file_idx < 0 || file_idx as usize >= demuxs.len() {
warn!(target: LOG_TARGET, "Invalid input file index: {}.", file_idx);
return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
}
let file_idx = file_idx as usize;
let (spec_str, allow_unused) = if remainder.ends_with('?') {
(&remainder[..remainder.len() - 1], true)
} else {
(remainder, false)
};
let stream_spec = if spec_str.is_empty() {
StreamSpecifier::default()
} else {
let spec_str = if spec_str.starts_with(':') {
&spec_str[1..]
} else {
spec_str
};
StreamSpecifier::parse(spec_str).map_err(|e| {
warn!(target: LOG_TARGET, "Invalid stream specifier in '{}': {}", linklabel, e);
Error::OpenOutput(OpenOutputError::InvalidArgument)
})?
};
if is_negative {
for existing_map in &mut mux.stream_maps {
if existing_map.linklabel.is_none() && existing_map.file_index == file_idx {
let demux = &demuxs[file_idx];
let fmt_ctx = demux.in_fmt_ctx_ptr();
let avstream = *(*fmt_ctx).streams.add(existing_map.stream_index);
if stream_spec.matches(fmt_ctx, avstream) {
existing_map.disabled = true;
}
}
}
continue;
}
let demux = &demuxs[file_idx];
let fmt_ctx = demux.in_fmt_ctx_ptr();
let mut matched_count = 0;
for (stream_idx, _) in demux.get_streams().iter().enumerate() {
let avstream = *(*fmt_ctx).streams.add(stream_idx);
if stream_spec.matches(fmt_ctx, avstream) {
mux.stream_maps.push(StreamMap {
file_index: file_idx,
stream_index: stream_idx,
linklabel: None,
copy: spec.copy,
disabled: false,
});
matched_count += 1;
}
}
if matched_count == 0 {
if allow_unused {
info!(target: LOG_TARGET, "Stream map '{}' matches no streams; ignoring.", linklabel);
} else {
warn!(target: LOG_TARGET,
"Stream map '{}' matches no streams.\n\
To ignore this, add a trailing '?' to the map.",
linklabel
);
return Err(Error::OpenOutput(OpenOutputError::MatchesNoStreams(
linklabel.to_string(),
)));
}
}
}
Ok(())
}
pub(super) fn outputs_bind(
muxs: &mut Vec<Muxer>,
filter_graphs: &mut Vec<FilterGraph>,
demuxs: &mut Vec<Demuxer>,
) -> Result<()> {
unsafe {
for mux in muxs.iter_mut() {
if !mux.stream_map_specs.is_empty() {
expand_stream_maps(mux, demuxs)?;
}
}
}
for (i, mux) in muxs.iter_mut().enumerate() {
if mux.stream_maps.is_empty() {
let mut auto_disable = 0i32;
if mux.video_disable {
auto_disable |= 1 << (AVMEDIA_TYPE_VIDEO as i32);
}
if mux.audio_disable {
auto_disable |= 1 << (AVMEDIA_TYPE_AUDIO as i32);
}
if mux.subtitle_disable {
auto_disable |= 1 << (AVMEDIA_TYPE_SUBTITLE as i32);
}
if mux.data_disable {
auto_disable |= 1 << (AVMEDIA_TYPE_DATA as i32);
}
output_bind_by_unlabeled_filter(i, mux, filter_graphs, &mut auto_disable)?;
map_auto_streams(i, mux, demuxs, filter_graphs, auto_disable)?;
} else {
for stream_map in mux.stream_maps.clone() {
map_manual(i, mux, &stream_map, filter_graphs, demuxs)?;
}
}
unsafe {
crate::core::context::attachment::create_attachment_streams(mux)?;
}
unsafe {
process_metadata(mux, demuxs)?;
}
}
Ok(())
}
fn map_manual(
index: usize,
mux: &mut Muxer,
stream_map: &StreamMap,
filter_graphs: &mut Vec<FilterGraph>,
demuxs: &mut Vec<Demuxer>,
) -> Result<()> {
if stream_map.disabled {
return Ok(());
}
if let Some(linklabel) = &stream_map.linklabel {
for filter_graph in filter_graphs.iter_mut() {
for i in 0..filter_graph.outputs.len() {
let option = {
let output_filter = &filter_graph.outputs[i];
if output_filter.has_dst()
|| output_filter.linklabel.is_empty()
|| &output_filter.linklabel != linklabel
{
continue;
}
choose_encoder(mux, output_filter.media_type)?
};
match option {
None => {
error!(target: LOG_TARGET,
"Filtering and streamcopy cannot be used together. \
No encoder available for filter output type {:?}.",
filter_graph.outputs[i].media_type
);
return Err(OpenOutputError::InvalidArgument.into());
}
Some((codec_id, enc)) => {
return ofilter_bind_ost(
index,
mux,
filter_graph,
i,
codec_id,
enc,
None,
false,
)
.map(|_| ());
}
}
}
}
warn!(target: LOG_TARGET,
"Output with label '{}' does not exist in any defined filter graph, \
or was already used elsewhere.",
linklabel
);
return Err(OpenOutputError::InvalidArgument.into());
}
let demux_idx = stream_map.file_index;
let stream_index = stream_map.stream_index;
let demux = &mut demuxs[demux_idx];
let demux_node = demux.node.clone();
let (media_type, input_stream_duration, input_stream_time_base) = {
let input_stream = demux.get_stream_mut(stream_index);
(
input_stream.codec_type,
input_stream.duration,
input_stream.time_base,
)
};
match media_type {
AVMEDIA_TYPE_VIDEO if mux.video_disable => {
info!(target: LOG_TARGET, "Skipping video stream mapping (video_disable=true)");
return Ok(());
}
AVMEDIA_TYPE_AUDIO if mux.audio_disable => {
info!(target: LOG_TARGET, "Skipping audio stream mapping (audio_disable=true)");
return Ok(());
}
AVMEDIA_TYPE_SUBTITLE if mux.subtitle_disable => {
info!(target: LOG_TARGET, "Skipping subtitle stream mapping (subtitle_disable=true)");
return Ok(());
}
AVMEDIA_TYPE_DATA if mux.data_disable => {
info!(target: LOG_TARGET, "Skipping data stream mapping (data_disable=true)");
return Ok(());
}
_ => {}
}
info!(target: LOG_TARGET,
"Binding output stream to input {}:{} ({})",
demux_idx,
stream_index,
match media_type {
AVMEDIA_TYPE_VIDEO => "video",
AVMEDIA_TYPE_AUDIO => "audio",
AVMEDIA_TYPE_SUBTITLE => "subtitle",
AVMEDIA_TYPE_DATA => "data",
AVMEDIA_TYPE_ATTACHMENT => "attachment",
_ => "unknown",
}
);
let option = choose_encoder(mux, media_type)?;
match option {
None => {
let (packet_sender, pre_sender, gate, _st, output_stream_index) =
mux.new_copy_stream(demux_node)?;
demux.add_packet_dst(
packet_sender,
stream_index,
output_stream_index,
if mux.shortest {
mux.stream_source_finished(output_stream_index)
} else {
None
},
CopyMuxHandle { pre_sender, gate },
);
mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
unsafe {
streamcopy_init(
mux,
*(*demux.in_fmt_ctx_ptr()).streams.add(stream_index),
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
demux.framerate,
)?;
rescale_duration(
input_stream_duration,
input_stream_time_base,
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
);
mux.stream_ready()
}
}
Some((codec_id, enc)) => {
if stream_map.copy {
let (packet_sender, pre_sender, gate, _st, output_stream_index) =
mux.new_copy_stream(demux_node)?;
demux.add_packet_dst(
packet_sender,
stream_index,
output_stream_index,
if mux.shortest {
mux.stream_source_finished(output_stream_index)
} else {
None
},
CopyMuxHandle { pre_sender, gate },
);
mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
unsafe {
streamcopy_init(
mux,
*(*demux.in_fmt_ctx_ptr()).streams.add(stream_index),
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
demux.framerate,
)?;
rescale_duration(
input_stream_duration,
input_stream_time_base,
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
);
mux.stream_ready()
}
} else if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
init_simple_filtergraph(
demux,
stream_index,
codec_id,
enc,
index,
mux,
filter_graphs,
demux_idx,
)?;
} else {
let (frame_sender, output_stream_index) =
mux.add_enc_stream(media_type, enc, demux_node, false)?;
let input_stream = demux.get_stream_mut(stream_index);
input_stream.add_dst(frame_sender);
demux.connect_stream(stream_index);
mux.register_stream_source(output_stream_index, demux_idx, stream_index, true);
unsafe {
rescale_duration(
input_stream_duration,
input_stream_time_base,
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
);
}
}
}
}
Ok(())
}
#[cfg(not(docsrs))]
fn set_channel_layout(
ch_layout: &mut AVChannelLayout,
ch_layouts: &Option<Vec<AVChannelLayout>>,
layout_requested: &AVChannelLayout,
) -> Result<()> {
unsafe {
if layout_requested.order != AV_CHANNEL_ORDER_UNSPEC {
let ret = av_channel_layout_copy(ch_layout, layout_requested);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
return Ok(());
}
if ch_layouts.is_none() {
av_channel_layout_default(ch_layout, layout_requested.nb_channels);
return Ok(());
}
if let Some(layouts) = ch_layouts {
for layout in layouts {
if layout.nb_channels == layout_requested.nb_channels {
let ret = av_channel_layout_copy(ch_layout, layout);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
return Ok(());
}
}
}
av_channel_layout_default(ch_layout, layout_requested.nb_channels);
Ok(())
}
}
#[cfg(docsrs)]
fn configure_output_filter_opts(
index: usize,
mux: &mut Muxer,
output_filter: &mut OutputFilter,
codec_id: AVCodecID,
enc: *const AVCodec,
output_stream_index: usize,
) -> Result<()> {
Ok(())
}
#[cfg(not(docsrs))]
fn configure_output_filter_opts(
index: usize,
mux: &mut Muxer,
output_filter: &mut OutputFilter,
codec_id: AVCodecID,
enc: *const AVCodec,
output_stream_index: usize,
) -> Result<()> {
unsafe {
output_filter.opts.name = format!("#{index}:{output_stream_index}");
output_filter.opts.enc = enc;
output_filter.opts.trim_start_us = mux.start_time_us;
output_filter.opts.trim_duration_us = mux.recording_time_us;
output_filter.opts.ts_offset = mux.start_time_us;
output_filter.opts.sws_opts = mux.sws_opts.clone();
output_filter.opts.swr_opts = mux.swr_opts.clone();
output_filter.opts.flags = OFILTER_FLAG_DISABLE_CONVERT
| OFILTER_FLAG_AUTOSCALE
| if av_get_exact_bits_per_sample(codec_id) == 24 {
OFILTER_FLAG_AUDIO_24BIT
} else {
0
};
let enc_ctx = avcodec_alloc_context3(enc);
if enc_ctx.is_null() {
return Err(OpenOutputError::OutOfMemory.into());
}
let _codec_ctx = CodecContext::new(enc_ctx);
(*enc_ctx).thread_count = 0;
if output_filter.media_type == AVMEDIA_TYPE_VIDEO {
let mut formats: *const AVPixelFormat = null();
let mut ret = avcodec_get_supported_config(
enc_ctx,
null(),
AV_CODEC_CONFIG_PIX_FORMAT,
0,
&mut formats as *mut _ as *mut *const libc::c_void,
null_mut(),
);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
let mut current = formats;
let mut format_list = Vec::new();
let mut count = 0;
const MAX_FORMATS: usize = 512;
while !current.is_null() && *current != AV_PIX_FMT_NONE && count < MAX_FORMATS {
format_list.push(*current);
current = current.add(1);
count += 1;
}
if count >= MAX_FORMATS {
warn!(target: LOG_TARGET, "Reached maximum format limit");
}
output_filter.opts.formats = Some(format_list);
let mut framerates: *const AVRational = null();
ret = avcodec_get_supported_config(
enc_ctx,
null(),
AV_CODEC_CONFIG_FRAME_RATE,
0,
&mut framerates as *mut _ as *mut *const libc::c_void,
null_mut(),
);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
let mut framerate_list = Vec::new();
let mut current = framerates;
let mut count = 0;
const MAX_FRAMERATES: usize = 64;
while !current.is_null()
&& (*current).num != 0
&& (*current).den != 0
&& count < MAX_FRAMERATES
{
framerate_list.push(*current);
current = current.add(1);
count += 1;
}
if count >= MAX_FRAMERATES {
warn!(target: LOG_TARGET, "Reached maximum framerate limit");
}
output_filter.opts.framerates = Some(framerate_list);
if let Some(framerate) = mux.framerate {
output_filter.opts.framerate = framerate;
}
if let Some(framerate_max) = mux.framerate_max {
if mux.framerate.is_some() {
error!(target: LOG_TARGET, "Only one of framerate and framerate_max can be set for an output");
return Err(Error::OpenOutput(OpenOutputError::InvalidArgument));
}
output_filter.opts.framerate_max = framerate_max;
}
if let Some(pix_fmt) = mux.pix_fmt {
output_filter.opts.format = pix_fmt;
}
let mut color_spaces: *const AVColorSpace = null();
ret = avcodec_get_supported_config(
enc_ctx,
null(),
AV_CODEC_CONFIG_COLOR_SPACE,
0,
&mut color_spaces as *mut _ as *mut *const libc::c_void,
null_mut(),
);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
let mut color_space_list = Vec::new();
let mut current = color_spaces;
let mut count = 0;
const MAX_COLOR_SPACES: usize = 128;
while !current.is_null()
&& *current != AVCOL_SPC_UNSPECIFIED
&& count < MAX_COLOR_SPACES
{
color_space_list.push(*current);
current = current.add(1);
count += 1;
}
if count >= MAX_COLOR_SPACES {
warn!(target: LOG_TARGET, "Reached maximum color space limit");
}
output_filter.opts.color_spaces = Some(color_space_list);
let mut color_ranges: *const AVColorRange = null();
ret = avcodec_get_supported_config(
enc_ctx,
null(),
AV_CODEC_CONFIG_COLOR_RANGE,
0,
&mut color_ranges as *mut _ as *mut *const libc::c_void,
null_mut(),
);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
let mut color_range_list = Vec::new();
let mut current = color_ranges;
let mut count = 0;
const MAX_COLOR_RANGES: usize = 64;
while !current.is_null()
&& *current != AVCOL_RANGE_UNSPECIFIED
&& count < MAX_COLOR_RANGES
{
color_range_list.push(*current);
current = current.add(1);
count += 1;
}
if count >= MAX_COLOR_RANGES {
warn!(target: LOG_TARGET, "Reached maximum color range limit");
}
output_filter.opts.color_ranges = Some(color_range_list);
let stream = &mux.get_streams()[output_stream_index];
output_filter.opts.vsync_method = stream.vsync_method;
} else {
if let Some(sample_fmt) = &mux.audio_sample_fmt {
output_filter.opts.audio_format = *sample_fmt;
}
let mut audio_formats: *const AVSampleFormat = null();
let mut ret = avcodec_get_supported_config(
enc_ctx,
null(),
AV_CODEC_CONFIG_SAMPLE_FORMAT,
0,
&mut audio_formats as *mut _ as *mut _,
null_mut(),
);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
let mut current = audio_formats;
let mut audio_format_list = Vec::new();
let mut count = 0;
const MAX_AUDIO_FORMATS: usize = 32;
while !current.is_null() && *current != AV_SAMPLE_FMT_NONE && count < MAX_AUDIO_FORMATS
{
audio_format_list.push(*current);
current = current.add(1);
count += 1;
}
if count >= MAX_AUDIO_FORMATS {
warn!(target: LOG_TARGET, "Reached maximum audio format limit");
}
output_filter.opts.audio_formats = Some(audio_format_list);
if let Some(audio_sample_rate) = &mux.audio_sample_rate {
output_filter.opts.sample_rate = *audio_sample_rate;
}
let mut rates: *const i32 = null();
ret = avcodec_get_supported_config(
enc_ctx,
null(),
AV_CODEC_CONFIG_SAMPLE_RATE,
0,
&mut rates as *mut _ as *mut _,
null_mut(),
);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
let mut rate_list = Vec::new();
let mut current = rates;
let mut count = 0;
const MAX_SAMPLE_RATES: usize = 64;
while !current.is_null() && *current != 0 && count < MAX_SAMPLE_RATES {
rate_list.push(*current);
current = current.add(1);
count += 1;
}
if count >= MAX_SAMPLE_RATES {
warn!(target: LOG_TARGET, "Reached maximum sample rate limit");
}
output_filter.opts.sample_rates = Some(rate_list);
if let Some(channels) = &mux.audio_channels {
output_filter.opts.ch_layout.nb_channels = *channels;
}
let mut layouts: *const AVChannelLayout = null();
ret = avcodec_get_supported_config(
enc_ctx,
null(),
AV_CODEC_CONFIG_CHANNEL_LAYOUT,
0,
&mut layouts as *mut _ as *mut _,
null_mut(),
);
if ret < 0 {
return Err(OpenOutputError::from(ret).into());
}
let mut layout_list = Vec::new();
let mut current = layouts;
let mut count = 0;
const MAX_CHANNEL_LAYOUTS: usize = 128;
while !current.is_null()
&& (*current).order != AV_CHANNEL_ORDER_UNSPEC
&& count < MAX_CHANNEL_LAYOUTS
{
layout_list.push(*current);
current = current.add(1);
count += 1;
}
if count >= MAX_CHANNEL_LAYOUTS {
warn!(target: LOG_TARGET, "Reached maximum channel layout limit");
}
output_filter.opts.ch_layouts = Some(layout_list);
if output_filter.opts.ch_layout.nb_channels > 0 {
let layout_requested = output_filter.opts.ch_layout;
set_channel_layout(
&mut output_filter.opts.ch_layout,
&output_filter.opts.ch_layouts,
&layout_requested,
)?;
}
}
};
Ok(())
}
fn map_auto_streams(
mux_index: usize,
mux: &mut Muxer,
demuxs: &mut Vec<Demuxer>,
filter_graphs: &mut Vec<FilterGraph>,
auto_disable: i32,
) -> Result<()> {
unsafe {
let oformat = (*mux.out_fmt_ctx_ptr()).oformat;
map_auto_stream(
mux_index,
mux,
demuxs,
oformat,
AVMEDIA_TYPE_VIDEO,
filter_graphs,
auto_disable,
)?;
map_auto_stream(
mux_index,
mux,
demuxs,
oformat,
AVMEDIA_TYPE_AUDIO,
filter_graphs,
auto_disable,
)?;
map_auto_subtitle(mux, demuxs, oformat, auto_disable)?;
map_auto_data(mux, demuxs, oformat, auto_disable)?;
}
Ok(())
}
#[cfg(docsrs)]
unsafe fn map_auto_subtitle(
mux: &mut Muxer,
demuxs: &mut Vec<Demuxer>,
oformat: *const AVOutputFormat,
auto_disable: i32,
) -> Result<()> {
Ok(())
}
#[cfg(not(docsrs))]
unsafe fn map_auto_subtitle(
mux: &mut Muxer,
demuxs: &mut Vec<Demuxer>,
oformat: *const AVOutputFormat,
auto_disable: i32,
) -> Result<()> {
if auto_disable & (1 << AVMEDIA_TYPE_SUBTITLE as i32) != 0 {
return Ok(());
}
let output_codec = avcodec_find_encoder((*oformat).subtitle_codec);
if output_codec.is_null() && mux.subtitle_codec.is_none() {
return Ok(());
}
let output_descriptor = if output_codec.is_null() {
null()
} else {
avcodec_descriptor_get((*output_codec).id)
};
for (demux_idx, demux) in demuxs.iter_mut().enumerate() {
for stream_index in 0..demux.get_streams().len() {
if demux.get_stream(stream_index).codec_type != AVMEDIA_TYPE_SUBTITLE {
continue;
}
let input_descriptor =
avcodec_descriptor_get((*demux.get_stream(stream_index).codec_parameters).codec_id);
let mut input_props = 0;
if !input_descriptor.is_null() {
input_props =
(*input_descriptor).props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
}
let mut output_props = 0;
if !output_descriptor.is_null() {
output_props = (*output_descriptor).props
& (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB);
}
let compatible = mux.subtitle_codec.is_some()
|| input_props & output_props != 0
|| !input_descriptor.is_null() && !output_descriptor.is_null()
&& ((*input_descriptor).props == 0 || (*output_descriptor).props == 0);
if !compatible {
continue;
}
let option = choose_encoder(mux, AVMEDIA_TYPE_SUBTITLE)?;
if let Some((_codec_id, enc)) = option {
let (frame_sender, output_stream_index) =
mux.add_enc_stream(AVMEDIA_TYPE_SUBTITLE, enc, demux.node.clone(), false)?;
demux.get_stream_mut(stream_index).add_dst(frame_sender);
demux.connect_stream(stream_index);
mux.register_stream_source(output_stream_index, demux_idx, stream_index, true);
let input_stream = demux.get_stream(stream_index);
unsafe {
rescale_duration(
input_stream.duration,
input_stream.time_base,
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
);
}
} else {
let input_stream = demux.get_stream(stream_index);
let input_stream_duration = input_stream.duration;
let input_stream_time_base = input_stream.time_base;
let (packet_sender, pre_sender, gate, _st, output_stream_index) =
mux.new_copy_stream(demux.node.clone())?;
demux.add_packet_dst(
packet_sender,
stream_index,
output_stream_index,
if mux.shortest {
mux.stream_source_finished(output_stream_index)
} else {
None
},
CopyMuxHandle { pre_sender, gate },
);
mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
unsafe {
streamcopy_init(
mux,
*(*demux.in_fmt_ctx_ptr()).streams.add(stream_index),
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
demux.framerate,
)?;
rescale_duration(
input_stream_duration,
input_stream_time_base,
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
);
mux.stream_ready()
}
}
return Ok(());
}
}
Ok(())
}
#[cfg(docsrs)]
unsafe fn map_auto_data(
mux: &mut Muxer,
demuxs: &mut Vec<Demuxer>,
oformat: *const AVOutputFormat,
auto_disable: i32,
) -> Result<()> {
Ok(())
}
#[cfg(not(docsrs))]
unsafe fn map_auto_data(
mux: &mut Muxer,
demuxs: &mut Vec<Demuxer>,
oformat: *const AVOutputFormat,
auto_disable: i32,
) -> Result<()> {
if auto_disable & (1 << AVMEDIA_TYPE_DATA as i32) != 0 {
return Ok(());
}
let codec_id = av_guess_codec(
oformat,
null(),
(*mux.out_fmt_ctx_ptr()).url,
null(),
AVMEDIA_TYPE_DATA,
);
if codec_id == AV_CODEC_ID_NONE {
return Ok(());
}
for (demux_idx, demux) in demuxs.iter_mut().enumerate() {
let option = demux
.get_streams()
.iter()
.enumerate()
.find_map(|(index, input_stream)| {
if input_stream.codec_type == AVMEDIA_TYPE_DATA
&& (*input_stream.codec_parameters).codec_id == codec_id
{
Some(index)
} else {
None
}
});
if option.is_none() {
continue;
}
let stream_index = option.unwrap();
let option = choose_encoder(mux, AVMEDIA_TYPE_DATA)?;
if option.is_some() {
unreachable!("DATA streams do not have encoders in FFmpeg");
} else {
let input_stream = demux.get_stream(stream_index);
let input_stream_duration = input_stream.duration;
let input_stream_time_base = input_stream.time_base;
let (packet_sender, pre_sender, gate, _st, output_stream_index) =
mux.new_copy_stream(demux.node.clone())?;
demux.add_packet_dst(
packet_sender,
stream_index,
output_stream_index,
if mux.shortest {
mux.stream_source_finished(output_stream_index)
} else {
None
},
CopyMuxHandle { pre_sender, gate },
);
mux.register_stream_source(output_stream_index, demux_idx, stream_index, false);
streamcopy_init(
mux,
*(*demux.in_fmt_ctx_ptr()).streams.add(stream_index),
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
demux.framerate,
)?;
rescale_duration(
input_stream_duration,
input_stream_time_base,
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
);
mux.stream_ready()
}
break;
}
Ok(())
}
#[cfg(docsrs)]
unsafe fn map_auto_stream(
mux_index: usize,
mux: &mut Muxer,
demuxs: &mut Vec<Demuxer>,
oformat: *const AVOutputFormat,
media_type: AVMediaType,
filter_graphs: &mut Vec<FilterGraph>,
auto_disable: i32,
) -> Result<()> {
Ok(())
}
#[cfg(not(docsrs))]
unsafe fn map_auto_stream(
mux_index: usize,
mux: &mut Muxer,
demuxs: &mut Vec<Demuxer>,
oformat: *const AVOutputFormat,
media_type: AVMediaType,
filter_graphs: &mut Vec<FilterGraph>,
auto_disable: i32,
) -> Result<()> {
if auto_disable & (1 << media_type as i32) != 0 {
return Ok(());
}
if (media_type == AVMEDIA_TYPE_VIDEO
|| media_type == AVMEDIA_TYPE_AUDIO
|| media_type == AVMEDIA_TYPE_DATA)
&& av_guess_codec(
oformat,
null(),
(*mux.out_fmt_ctx_ptr()).url,
null(),
media_type,
) == AV_CODEC_ID_NONE
{
return Ok(());
}
let selected = if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
unsafe { select_best_stream(demuxs, oformat, media_type) }
} else {
demuxs.iter().enumerate().find_map(|(demux_idx, demux)| {
demux
.get_streams()
.iter()
.position(|input_stream| input_stream.codec_type == media_type)
.map(|stream_index| (demux_idx, stream_index))
})
};
let Some((demux_idx, stream_index)) = selected else {
return Ok(());
};
let demux = &mut demuxs[demux_idx];
let input_file_idx = demux_idx;
let option = choose_encoder(mux, media_type)?;
if let Some((codec_id, enc)) = option {
if media_type == AVMEDIA_TYPE_VIDEO || media_type == AVMEDIA_TYPE_AUDIO {
init_simple_filtergraph(
demux,
stream_index,
codec_id,
enc,
mux_index,
mux,
filter_graphs,
input_file_idx,
)?;
} else {
let (frame_sender, output_stream_index) =
mux.add_enc_stream(media_type, enc, demux.node.clone(), false)?;
demux.get_stream_mut(stream_index).add_dst(frame_sender);
demux.connect_stream(stream_index);
mux.register_stream_source(output_stream_index, input_file_idx, stream_index, true);
let input_stream = demux.get_stream(stream_index);
unsafe {
rescale_duration(
input_stream.duration,
input_stream.time_base,
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
);
}
}
return Ok(());
}
let input_stream = demux.get_stream(stream_index);
let input_stream_duration = input_stream.duration;
let input_stream_time_base = input_stream.time_base;
let (packet_sender, pre_sender, gate, _st, output_stream_index) =
mux.new_copy_stream(demux.node.clone())?;
demux.add_packet_dst(
packet_sender,
stream_index,
output_stream_index,
if mux.shortest {
mux.stream_source_finished(output_stream_index)
} else {
None
},
CopyMuxHandle { pre_sender, gate },
);
mux.register_stream_source(output_stream_index, input_file_idx, stream_index, false);
unsafe {
streamcopy_init(
mux,
*(*demux.in_fmt_ctx_ptr()).streams.add(stream_index),
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
demux.framerate,
)?;
rescale_duration(
input_stream_duration,
input_stream_time_base,
*(*mux.out_fmt_ctx_ptr()).streams.add(output_stream_index),
);
mux.stream_ready()
}
Ok(())
}
#[cfg(not(docsrs))]
unsafe fn select_best_stream(
demuxs: &[Demuxer],
oformat: *const AVOutputFormat,
media_type: AVMediaType,
) -> Option<(usize, usize)> {
const APIC_TAG: i32 =
(b'A' as i32) | ((b'P' as i32) << 8) | ((b'I' as i32) << 16) | ((b'C' as i32) << 24);
let qcr = if media_type == AVMEDIA_TYPE_VIDEO {
avformat_query_codec(oformat, (*oformat).video_codec, 0)
} else {
0
};
let mut best: Option<(usize, usize)> = None;
let mut best_score: i64 = 0;
for (demux_idx, demux) in demuxs.iter().enumerate() {
let mut file_best: Option<usize> = None;
let mut file_best_score: i64 = 0;
for (stream_index, input_stream) in demux.get_streams().iter().enumerate() {
if input_stream.codec_type != media_type {
continue;
}
let par = input_stream.codec_parameters;
let st = input_stream.stream.inner;
let attached_pic = (*st).disposition & AV_DISPOSITION_ATTACHED_PIC != 0;
let mut score: i64 = if media_type == AVMEDIA_TYPE_VIDEO {
(*par).width as i64 * (*par).height as i64
} else {
(*par).ch_layout.nb_channels as i64
};
if (*st).event_flags & AVSTREAM_EVENT_FLAG_NEW_PACKETS != 0 {
score += 100_000_000;
}
if (*st).disposition & AV_DISPOSITION_DEFAULT != 0 {
score += 5_000_000;
}
if media_type == AVMEDIA_TYPE_VIDEO && qcr != APIC_TAG && attached_pic {
score = 1;
}
if score > file_best_score {
if media_type == AVMEDIA_TYPE_VIDEO && qcr == APIC_TAG && !attached_pic {
continue;
}
file_best_score = score;
file_best = Some(stream_index);
}
}
if let Some(stream_index) = file_best {
let st = demux.get_stream(stream_index).stream.inner;
let attached_pic = (*st).disposition & AV_DISPOSITION_ATTACHED_PIC != 0;
if (media_type != AVMEDIA_TYPE_VIDEO || qcr == APIC_TAG || !attached_pic)
&& (*st).disposition & AV_DISPOSITION_DEFAULT != 0
{
file_best_score -= 5_000_000;
}
if file_best_score > best_score {
best_score = file_best_score;
best = Some((demux_idx, stream_index));
}
}
}
best
}
fn init_simple_filtergraph(
demux: &mut Demuxer,
stream_index: usize,
codec_id: AVCodecID,
enc: *const AVCodec,
mux_index: usize,
mux: &mut Muxer,
filter_graphs: &mut Vec<FilterGraph>,
input_file_idx: usize,
) -> Result<()> {
let codec_type = demux.get_stream(stream_index).codec_type;
let filter_desc = if codec_type == AVMEDIA_TYPE_VIDEO {
"null"
} else {
"anull"
};
let mut filter_graph = init_filter_graph(filter_graphs.len(), filter_desc, None, None, None)?;
ifilter_bind_ist(&mut filter_graph, 0, stream_index, demux)?;
let single_stream_direct_input = demux.get_streams().len() == 1;
ofilter_bind_ost(
mux_index,
mux,
&mut filter_graph,
0,
codec_id,
enc,
Some((input_file_idx, stream_index)),
single_stream_direct_input,
)?;
filter_graphs.push(filter_graph);
Ok(())
}
unsafe fn rescale_duration(src_duration: i64, src_time_base: AVRational, stream: *mut AVStream) {
(*stream).duration = av_rescale_q(src_duration, src_time_base, (*stream).time_base);
}
#[cfg(docsrs)]
fn streamcopy_init(
mux: &mut Muxer,
input_stream: *mut AVStream,
output_stream: *mut AVStream,
input_framerate: AVRational,
) -> Result<()> {
Ok(())
}
#[cfg(not(docsrs))]
fn streamcopy_init(
mux: &mut Muxer,
input_stream: *mut AVStream,
output_stream: *mut AVStream,
input_framerate: AVRational,
) -> Result<()> {
unsafe {
let codec_ctx = avcodec_alloc_context3(null_mut());
if codec_ctx.is_null() {
return Err(OpenOutputError::OutOfMemory.into());
}
let _codec_context = CodecContext::new(codec_ctx);
let mut ret = avcodec_parameters_to_context(codec_ctx, (*input_stream).codecpar);
if ret < 0 {
error!(target: LOG_TARGET, "Error setting up codec context options.");
return Err(OpenOutputError::from(ret).into());
}
ret = avcodec_parameters_from_context((*output_stream).codecpar, codec_ctx);
if ret < 0 {
error!(target: LOG_TARGET, "Error getting reference codec parameters.");
return Err(OpenOutputError::from(ret).into());
}
let mut codec_tag = 0;
if codec_tag == 0 {
let ct = (*(*mux.out_fmt_ctx_ptr()).oformat).codec_tag;
let mut codec_tag_tmp = 0;
if ct.is_null()
|| av_codec_get_id(ct, (*(*output_stream).codecpar).codec_tag)
== (*(*output_stream).codecpar).codec_id
|| av_codec_get_tag2(
ct,
(*(*output_stream).codecpar).codec_id,
&mut codec_tag_tmp,
) == 0
{
codec_tag = (*(*output_stream).codecpar).codec_tag;
}
}
(*(*output_stream).codecpar).codec_tag = codec_tag;
let codec_type = (*(*output_stream).codecpar).codec_type;
let mut fr = AVRational { num: 0, den: 0 };
if codec_type == AVMEDIA_TYPE_VIDEO {
fr = mux.framerate.unwrap_or(AVRational { num: 0, den: 0 });
if fr.num == 0 {
fr = input_framerate;
}
}
if fr.num != 0 {
(*output_stream).avg_frame_rate = fr;
} else {
(*output_stream).avg_frame_rate = (*input_stream).avg_frame_rate;
}
if (*output_stream).time_base.num <= 0 || (*output_stream).time_base.den <= 0 {
if fr.num != 0 {
(*output_stream).time_base = av_inv_q(fr);
} else {
(*output_stream).time_base =
av_add_q((*input_stream).time_base, AVRational { num: 0, den: 1 });
}
}
for i in 0..(*(*input_stream).codecpar).nb_coded_side_data {
let sd_src = (*(*input_stream).codecpar)
.coded_side_data
.offset(i as isize);
let sd_dst = av_packet_side_data_new(
&mut (*(*output_stream).codecpar).coded_side_data,
&mut (*(*output_stream).codecpar).nb_coded_side_data,
(*sd_src).type_,
(*sd_src).size,
0,
);
if sd_dst.is_null() {
return Err(OpenOutputError::OutOfMemory.into());
}
std::ptr::copy_nonoverlapping(
(*sd_src).data as *const u8,
(*sd_dst).data,
(*sd_src).size,
);
}
match (*(*output_stream).codecpar).codec_type {
AVMEDIA_TYPE_AUDIO => {
if ((*(*output_stream).codecpar).block_align == 1
|| (*(*output_stream).codecpar).block_align == 1152
|| (*(*output_stream).codecpar).block_align == 576)
&& (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_MP3
{
(*(*output_stream).codecpar).block_align = 0;
}
if (*(*output_stream).codecpar).codec_id == AV_CODEC_ID_AC3 {
(*(*output_stream).codecpar).block_align = 0;
}
}
AVMEDIA_TYPE_VIDEO => {
let sar = if (*input_stream).sample_aspect_ratio.num != 0 {
(*input_stream).sample_aspect_ratio
} else {
(*(*output_stream).codecpar).sample_aspect_ratio
};
(*output_stream).sample_aspect_ratio = sar;
(*(*output_stream).codecpar).sample_aspect_ratio = sar;
(*output_stream).r_frame_rate = (*input_stream).r_frame_rate;
}
_ => {}
}
};
Ok(())
}
fn output_bind_by_unlabeled_filter(
index: usize,
mux: &mut Muxer,
filter_graphs: &mut Vec<FilterGraph>,
auto_disable: &mut i32,
) -> Result<()> {
let fg_len = filter_graphs.len();
for i in 0..fg_len {
let filter_graph = &mut filter_graphs[i];
for i in 0..filter_graph.outputs.len() {
let media_type = filter_graph.outputs[i].media_type;
if *auto_disable & (1 << media_type as i32) != 0 {
continue;
}
let option = {
let output_filter = &filter_graph.outputs[i];
if (!output_filter.linklabel.is_empty() && output_filter.linklabel != "out")
|| output_filter.has_dst()
{
continue;
}
choose_encoder(mux, output_filter.media_type)?
};
match option {
None => {
warn!(target: LOG_TARGET,
"An unexpected media_type {:?} appears in output_filter",
media_type
);
}
Some((codec_id, enc)) => {
*auto_disable |= 1 << media_type as i32;
ofilter_bind_ost(index, mux, filter_graph, i, codec_id, enc, None, false)?;
}
}
}
}
Ok(())
}
fn ofilter_bind_ost(
index: usize,
mux: &mut Muxer,
filter_graph: &mut FilterGraph,
output_filter_index: usize,
codec_id: AVCodecID,
enc: *const AVCodec,
stream_source: Option<(usize, usize)>,
single_stream_direct_input: bool,
) -> Result<usize> {
let output_filter = &mut filter_graph.outputs[output_filter_index];
let (frame_sender, output_stream_index) = mux.add_enc_stream(
output_filter.media_type,
enc,
filter_graph.node.clone(),
single_stream_direct_input,
)?;
output_filter.set_dst(frame_sender);
if let Some((file_idx, stream_idx)) = stream_source {
mux.register_stream_source(output_stream_index, file_idx, stream_idx, true);
}
configure_output_filter_opts(
index,
mux,
output_filter,
codec_id,
enc,
output_stream_index,
)?;
Ok(output_stream_index)
}
fn choose_encoder(
mux: &Muxer,
media_type: AVMediaType,
) -> Result<Option<(AVCodecID, *const AVCodec)>> {
let media_codec = match media_type {
AVMEDIA_TYPE_VIDEO => mux.video_codec.clone(),
AVMEDIA_TYPE_AUDIO => mux.audio_codec.clone(),
AVMEDIA_TYPE_SUBTITLE => mux.subtitle_codec.clone(),
_ => return Ok(None),
};
match media_codec {
None => {
let url = CString::new(&*mux.url)?;
unsafe {
let codec_id = av_guess_codec(
(*mux.out_fmt_ctx_ptr()).oformat,
null(),
url.as_ptr(),
null(),
media_type,
);
let enc = avcodec_find_encoder(codec_id);
if enc.is_null() {
let format_name = (*(*mux.out_fmt_ctx_ptr()).oformat).name;
let format_name = CStr::from_ptr(format_name).to_str();
let codec_name = avcodec_get_name(codec_id);
let codec_name = CStr::from_ptr(codec_name).to_str();
if let (Ok(format_name), Ok(codec_name)) = (format_name, codec_name) {
error!(target: LOG_TARGET, "Automatic encoder selection failed Default encoder for format {format_name} (codec {codec_name}) is probably disabled. Please choose an encoder manually.");
}
return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
}
return Ok(Some((codec_id, enc)));
}
}
Some(media_codec) if media_codec != "copy" => unsafe {
let media_codec_cstr = CString::new(media_codec.clone())?;
let mut enc = avcodec_find_encoder_by_name(media_codec_cstr.as_ptr());
let desc = avcodec_descriptor_get_by_name(media_codec_cstr.as_ptr());
if enc.is_null() && !desc.is_null() {
enc = avcodec_find_encoder((*desc).id);
if !enc.is_null() {
let codec_name = (*enc).name;
let codec_name = CStr::from_ptr(codec_name).to_str();
let desc_name = (*desc).name;
let desc_name = CStr::from_ptr(desc_name).to_str();
if let (Ok(codec_name), Ok(desc_name)) = (codec_name, desc_name) {
debug!(target: LOG_TARGET, "Matched encoder '{codec_name}' for codec '{desc_name}'.");
}
}
}
if enc.is_null() {
error!(target: LOG_TARGET, "Unknown encoder '{media_codec}'");
return Err(OpenOutputError::from(AVERROR_ENCODER_NOT_FOUND).into());
}
if (*enc).type_ != media_type {
error!(target: LOG_TARGET, "Invalid encoder type '{media_codec}'");
return Err(OpenOutputError::InvalidArgument.into());
}
let codec_id = (*enc).id;
return Ok(Some((codec_id, enc)));
},
_ => {}
};
Ok(None)
}