use ffmpeg_sys_next::{
av_dict_copy, av_dict_set, av_mallocz, av_realloc_f, av_rescale_q, AVChapter, AVFormatContext,
AV_DICT_DONT_OVERWRITE, AV_TIME_BASE_Q,
};
use std::cmp::{max, min};
use std::ffi::c_void;
use std::mem;
fn clamp_i128_to_i64(value: i128) -> i64 {
if value > i64::MAX as i128 {
i64::MAX
} else if value < i64::MIN as i128 {
i64::MIN
} else {
value as i64
}
}
pub unsafe fn copy_metadata_default(
input_ctxs: &[*const AVFormatContext],
output_ctx: *mut AVFormatContext,
output_stream_count: usize,
stream_input_mapping: &[(usize, (usize, usize))], encoding_streams: &[usize], recording_time_set: bool,
auto_copy_enabled: bool, skip_global_copy: bool,
skip_stream_copy: bool,
) -> Result<(), String> {
if output_ctx.is_null() {
return Err("Output AVFormatContext is null".to_string());
}
if !auto_copy_enabled {
return Ok(());
}
let output_ref = &mut *output_ctx;
if !skip_global_copy && !input_ctxs.is_empty() && !input_ctxs[0].is_null() {
let first_input = &*input_ctxs[0];
av_dict_copy(
&mut output_ref.metadata,
first_input.metadata,
AV_DICT_DONT_OVERWRITE,
);
av_dict_set(
&mut output_ref.metadata,
b"creation_time\0".as_ptr() as *const i8,
std::ptr::null(),
0,
);
av_dict_set(
&mut output_ref.metadata,
b"company_name\0".as_ptr() as *const i8,
std::ptr::null(),
0,
);
av_dict_set(
&mut output_ref.metadata,
b"product_name\0".as_ptr() as *const i8,
std::ptr::null(),
0,
);
av_dict_set(
&mut output_ref.metadata,
b"product_version\0".as_ptr() as *const i8,
std::ptr::null(),
0,
);
if recording_time_set {
av_dict_set(
&mut output_ref.metadata,
b"duration\0".as_ptr() as *const i8,
std::ptr::null(),
0,
);
}
}
if !skip_stream_copy && output_stream_count > 0 && !output_ref.streams.is_null() {
let output_streams =
std::slice::from_raw_parts_mut(output_ref.streams, output_stream_count);
for (output_idx, (input_file_idx, input_stream_idx)) in stream_input_mapping {
let output_idx = *output_idx;
let input_file_idx = *input_file_idx;
let input_stream_idx = *input_stream_idx;
if output_idx >= output_stream_count {
log::warn!("Invalid output stream index {}", output_idx);
continue;
}
if input_file_idx >= input_ctxs.len() {
log::warn!("Invalid input file index {}", input_file_idx);
continue;
}
let input_ctx = input_ctxs[input_file_idx];
if input_ctx.is_null() {
continue;
}
let input_ref = &*input_ctx;
if input_stream_idx >= input_ref.nb_streams as usize {
log::warn!("Invalid input stream index {}", input_stream_idx);
continue;
}
let output_stream_ptr = output_streams[output_idx];
if output_stream_ptr.is_null() {
continue;
}
let output_stream = &mut *output_stream_ptr;
let input_streams =
std::slice::from_raw_parts(input_ref.streams, input_ref.nb_streams as usize);
let input_stream_ptr = input_streams[input_stream_idx];
if input_stream_ptr.is_null() {
continue;
}
let input_stream = &*input_stream_ptr;
av_dict_copy(
&mut output_stream.metadata,
input_stream.metadata,
AV_DICT_DONT_OVERWRITE,
);
if encoding_streams.contains(&output_idx) {
av_dict_set(
&mut output_stream.metadata,
b"encoder\0".as_ptr() as *const i8,
std::ptr::null(),
0,
);
}
}
}
Ok(())
}
pub unsafe fn copy_chapters_from_input(
input_ctx: *const AVFormatContext,
input_ts_offset_us: i64,
output_ctx: *mut AVFormatContext,
mux_start_time_us: Option<i64>,
mux_recording_time_us: Option<i64>,
copy_metadata: bool,
) -> Result<(), String> {
if input_ctx.is_null() || output_ctx.is_null() {
return Err("AVFormatContext is null".to_string());
}
let input_ref = &*input_ctx;
if input_ref.nb_chapters == 0 {
return Ok(());
}
let output_ref = &mut *output_ctx;
let existing = output_ref.nb_chapters as usize;
let additional = input_ref.nb_chapters as usize;
let total_capacity = existing + additional;
let tmp = av_realloc_f(
output_ref.chapters as *mut c_void,
total_capacity.max(1),
mem::size_of::<*mut AVChapter>(),
) as *mut *mut AVChapter;
if tmp.is_null() {
return Err("Failed to grow chapter table".to_string());
}
output_ref.chapters = tmp;
let start_time = mux_start_time_us.unwrap_or(0);
let recording_time = mux_recording_time_us.unwrap_or(i64::MAX);
let chapters = std::slice::from_raw_parts(input_ref.chapters, input_ref.nb_chapters as usize);
let mut out_count = output_ref.nb_chapters;
for &chapter_ptr in chapters {
if chapter_ptr.is_null() {
continue;
}
let in_ch: &AVChapter = &*chapter_ptr;
let delta = clamp_i128_to_i64(start_time as i128 - input_ts_offset_us as i128);
let ts_off = av_rescale_q(delta, AV_TIME_BASE_Q, in_ch.time_base);
let rt = if recording_time == i64::MAX {
i64::MAX
} else {
av_rescale_q(recording_time, AV_TIME_BASE_Q, in_ch.time_base)
};
if in_ch.end < ts_off {
continue;
}
if rt != i64::MAX {
let limit = ts_off.saturating_add(rt);
if in_ch.start > limit {
break;
}
}
let mut start = in_ch.start.saturating_sub(ts_off);
start = max(0, start);
let mut end = in_ch.end.saturating_sub(ts_off);
if rt != i64::MAX {
end = min(end, rt);
}
end = max(end, start);
let out_ptr = av_mallocz(mem::size_of::<AVChapter>()) as *mut AVChapter;
if out_ptr.is_null() {
return Err("Failed to allocate chapter".to_string());
}
(*out_ptr).id = in_ch.id;
(*out_ptr).time_base = in_ch.time_base;
(*out_ptr).start = start;
(*out_ptr).end = end;
*output_ref.chapters.add(out_count as usize) = out_ptr;
out_count += 1;
if copy_metadata {
av_dict_copy(&mut (*out_ptr).metadata, in_ch.metadata, 0);
}
}
output_ref.nb_chapters = out_count;
Ok(())
}