#![allow(unsafe_code)]
#![allow(unsafe_op_in_unsafe_fn)]
#![allow(clippy::cast_precision_loss)]
use std::path::Path;
use crate::error::EncodeError;
const AV_TIME_BASE: i64 = 1_000_000;
pub(crate) fn run_trim(
input: &Path,
output: &Path,
start_sec: f64,
end_sec: f64,
) -> Result<(), EncodeError> {
unsafe { run_trim_unsafe(input, output, start_sec, end_sec) }
}
unsafe fn run_trim_unsafe(
input: &Path,
output: &Path,
start_sec: f64,
end_sec: f64,
) -> Result<(), EncodeError> {
let in_ctx = ff_sys::avformat::open_input(input).map_err(EncodeError::from_ffmpeg_error)?;
if let Err(e) = ff_sys::avformat::find_stream_info(in_ctx) {
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
return Err(EncodeError::from_ffmpeg_error(e));
}
let Some(output_str) = output.to_str() else {
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
return Err(EncodeError::Ffmpeg {
code: 0,
message: "output path is not valid UTF-8".to_string(),
});
};
let Ok(c_output) = std::ffi::CString::new(output_str) else {
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
return Err(EncodeError::Ffmpeg {
code: 0,
message: "output path contains null bytes".to_string(),
});
};
let mut out_ctx: *mut ff_sys::AVFormatContext = std::ptr::null_mut();
let ret = ff_sys::avformat_alloc_output_context2(
&mut out_ctx,
std::ptr::null_mut(),
std::ptr::null(),
c_output.as_ptr(),
);
if ret < 0 || out_ctx.is_null() {
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
return Err(EncodeError::from_ffmpeg_error(ret));
}
let nb_streams = (*in_ctx).nb_streams as usize;
for i in 0..nb_streams {
let in_stream = *(*in_ctx).streams.add(i);
let out_stream = ff_sys::avformat_new_stream(out_ctx, std::ptr::null());
if out_stream.is_null() {
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
ff_sys::avformat_free_context(out_ctx);
return Err(EncodeError::Ffmpeg {
code: 0,
message: "avformat_new_stream failed".to_string(),
});
}
let ret = ff_sys::avcodec_parameters_copy((*out_stream).codecpar, (*in_stream).codecpar);
if ret < 0 {
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
ff_sys::avformat_free_context(out_ctx);
return Err(EncodeError::from_ffmpeg_error(ret));
}
(*(*out_stream).codecpar).codec_tag = 0;
}
let start_ts = (start_sec * AV_TIME_BASE as f64) as i64;
if let Err(e) = ff_sys::avformat::seek_file(in_ctx, -1, i64::MIN, start_ts, start_ts, 0) {
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
ff_sys::avformat_free_context(out_ctx);
return Err(EncodeError::from_ffmpeg_error(e));
}
let pb = match ff_sys::avformat::open_output(output, ff_sys::avformat::avio_flags::WRITE) {
Ok(pb) => pb,
Err(e) => {
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
ff_sys::avformat_free_context(out_ctx);
return Err(EncodeError::from_ffmpeg_error(e));
}
};
(*out_ctx).pb = pb;
let ret = ff_sys::avformat_write_header(out_ctx, std::ptr::null_mut());
if ret < 0 {
ff_sys::avformat::close_output(std::ptr::addr_of_mut!((*out_ctx).pb));
ff_sys::avformat_free_context(out_ctx);
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
return Err(EncodeError::from_ffmpeg_error(ret));
}
log::debug!("stream copy trim header written nb_streams={nb_streams}");
let pkt = ff_sys::av_packet_alloc();
if pkt.is_null() {
ff_sys::av_write_trailer(out_ctx);
ff_sys::avformat::close_output(std::ptr::addr_of_mut!((*out_ctx).pb));
ff_sys::avformat_free_context(out_ctx);
let mut p = in_ctx;
ff_sys::avformat::close_input(&mut p);
return Err(EncodeError::Ffmpeg {
code: 0,
message: "av_packet_alloc failed".to_string(),
});
}
let mut loop_err: Option<EncodeError> = None;
'read: loop {
match ff_sys::avformat::read_frame(in_ctx, pkt) {
Err(e) if e == ff_sys::error_codes::EOF => break 'read,
Err(e) => {
loop_err = Some(EncodeError::from_ffmpeg_error(e));
break 'read;
}
Ok(()) => {}
}
let stream_idx = (*pkt).stream_index as usize;
if stream_idx >= nb_streams {
ff_sys::av_packet_unref(pkt);
continue;
}
let in_stream = *(*in_ctx).streams.add(stream_idx);
let in_tb = (*in_stream).time_base;
let ts = if (*pkt).pts != ff_sys::AV_NOPTS_VALUE {
(*pkt).pts
} else {
(*pkt).dts
};
if ts != ff_sys::AV_NOPTS_VALUE && in_tb.den != 0 {
let ts_sec = ts as f64 * f64::from(in_tb.num) / f64::from(in_tb.den);
if ts_sec >= end_sec {
ff_sys::av_packet_unref(pkt);
break 'read;
}
}
let out_stream = *(*out_ctx).streams.add(stream_idx);
let out_tb = (*out_stream).time_base;
ff_sys::av_packet_rescale_ts(pkt, in_tb, out_tb);
(*pkt).stream_index = stream_idx as i32;
let ret = ff_sys::av_interleaved_write_frame(out_ctx, pkt);
ff_sys::av_packet_unref(pkt);
if ret < 0 {
loop_err = Some(EncodeError::from_ffmpeg_error(ret));
break 'read;
}
}
let mut pkt_ptr = pkt;
ff_sys::av_packet_free(&mut pkt_ptr);
ff_sys::av_write_trailer(out_ctx);
ff_sys::avformat::close_output(std::ptr::addr_of_mut!((*out_ctx).pb));
ff_sys::avformat_free_context(out_ctx);
let mut in_ctx_ptr = in_ctx;
ff_sys::avformat::close_input(&mut in_ctx_ptr);
log::debug!("stream copy trim complete");
match loop_err {
Some(e) => Err(e),
None => Ok(()),
}
}