#![allow(unsafe_code)]
#![allow(unsafe_op_in_unsafe_fn)]
use std::ffi::CString;
use std::path::Path;
use crate::FilterError;
use crate::effects::stabilizer::{AnalyzeOptions, Interpolation, StabilizeOptions};
pub(super) unsafe fn analyze_vidstab_unsafe(
input: &Path,
output_trf: &Path,
opts: &AnalyzeOptions,
) -> Result<(), FilterError> {
macro_rules! bail {
($graph:ident, $code:expr, $msg:expr) => {{
let mut g = $graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: $code,
message: $msg.to_string(),
});
}};
}
let vidstab_filt = ff_sys::avfilter_get_by_name(c"vidstabdetect".as_ptr());
if vidstab_filt.is_null() {
return Err(FilterError::Ffmpeg {
code: 0,
message: "vidstabdetect filter not available in this FFmpeg build".to_string(),
});
}
let input_str = input.to_string_lossy();
let trf_str = output_trf.to_string_lossy();
let movie_args =
CString::new(format!("filename={input_str}")).map_err(|_| FilterError::Ffmpeg {
code: 0,
message: "input path contains null byte".to_string(),
})?;
let shakiness = opts.shakiness.clamp(1, 10);
let accuracy = opts.accuracy.clamp(1, 15);
let stepsize = opts.stepsize.clamp(1, 32);
let vidstab_args = CString::new(format!(
"shakiness={shakiness}:accuracy={accuracy}:stepsize={stepsize}:result={trf_str}"
))
.map_err(|_| FilterError::Ffmpeg {
code: 0,
message: "trf path contains null byte".to_string(),
})?;
let graph = ff_sys::avfilter_graph_alloc();
if graph.is_null() {
return Err(FilterError::Ffmpeg {
code: 0,
message: "avfilter_graph_alloc failed".to_string(),
});
}
let movie_filt = ff_sys::avfilter_get_by_name(c"movie".as_ptr());
if movie_filt.is_null() {
bail!(graph, 0, "filter not found: movie");
}
let mut src_ctx: *mut ff_sys::AVFilterContext = std::ptr::null_mut();
let ret = ff_sys::avfilter_graph_create_filter(
&raw mut src_ctx,
movie_filt,
c"vidstab_src".as_ptr(),
movie_args.as_ptr(),
std::ptr::null_mut(),
graph,
);
if ret < 0 {
bail!(graph, ret, format!("movie create_filter failed code={ret}"));
}
log::debug!(
"filter added name=vidstabdetect args={}",
vidstab_args.to_string_lossy()
);
let mut vstab_ctx: *mut ff_sys::AVFilterContext = std::ptr::null_mut();
let ret = ff_sys::avfilter_graph_create_filter(
&raw mut vstab_ctx,
vidstab_filt,
c"vidstab_detect".as_ptr(),
vidstab_args.as_ptr(),
std::ptr::null_mut(),
graph,
);
if ret < 0 {
bail!(
graph,
ret,
format!("vidstabdetect create_filter failed code={ret}")
);
}
let buffersink_filt = ff_sys::avfilter_get_by_name(c"buffersink".as_ptr());
if buffersink_filt.is_null() {
bail!(graph, 0, "filter not found: buffersink");
}
let mut sink_ctx: *mut ff_sys::AVFilterContext = std::ptr::null_mut();
let ret = ff_sys::avfilter_graph_create_filter(
&raw mut sink_ctx,
buffersink_filt,
c"vidstab_sink".as_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
graph,
);
if ret < 0 {
bail!(
graph,
ret,
format!("buffersink create_filter failed code={ret}")
);
}
let ret = ff_sys::avfilter_link(src_ctx, 0, vstab_ctx, 0);
if ret < 0 {
bail!(
graph,
ret,
format!("avfilter_link movie→vidstabdetect failed code={ret}")
);
}
let ret = ff_sys::avfilter_link(vstab_ctx, 0, sink_ctx, 0);
if ret < 0 {
bail!(
graph,
ret,
format!("avfilter_link vidstabdetect→buffersink failed code={ret}")
);
}
let ret = ff_sys::avfilter_graph_config(graph, std::ptr::null_mut());
if ret < 0 {
bail!(
graph,
ret,
format!(
"avfilter_graph_config failed code={ret} message={}",
ff_sys::av_error_string(ret)
)
);
}
loop {
let raw_frame = ff_sys::av_frame_alloc();
if raw_frame.is_null() {
break;
}
let ret = ff_sys::av_buffersink_get_frame(sink_ctx, raw_frame);
let mut ptr = raw_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(ptr));
if ret < 0 {
break;
}
}
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
log::info!(
"stabilization analysis complete trf={}",
output_trf.display()
);
Ok(())
}
unsafe fn drain_encoded_packets(
enc_ctx: *mut ff_sys::AVCodecContext,
pkt: *mut ff_sys::AVPacket,
out_ctx: *mut ff_sys::AVFormatContext,
stream_tb: ff_sys::AVRational,
) {
while ff_sys::avcodec::receive_packet(enc_ctx, pkt).is_ok() {
let enc_tb = (*enc_ctx).time_base;
ff_sys::av_packet_rescale_ts(pkt, enc_tb, stream_tb);
(*pkt).stream_index = 0;
let ret = ff_sys::av_interleaved_write_frame(out_ctx, pkt);
ff_sys::av_packet_unref(pkt);
if ret < 0 {
log::warn!(
"av_interleaved_write_frame failed error={}",
ff_sys::av_error_string(ret)
);
break;
}
}
}
pub(super) unsafe fn transform_vidstab_unsafe(
input: &Path,
trf_path: &Path,
output: &Path,
opts: &StabilizeOptions,
) -> Result<(), FilterError> {
macro_rules! bail {
($graph:ident, $code:expr, $msg:expr) => {{
let mut g = $graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: $code,
message: format!("{}", $msg),
});
}};
}
let vstab_filt = ff_sys::avfilter_get_by_name(c"vidstabtransform".as_ptr());
if vstab_filt.is_null() {
return Err(FilterError::Ffmpeg {
code: 0,
message: "vidstabtransform filter not available in this FFmpeg build".to_string(),
});
}
let input_str = input.to_string_lossy();
let trf_str = trf_path.to_string_lossy();
let movie_args =
CString::new(format!("filename={input_str}")).map_err(|_| FilterError::Ffmpeg {
code: 0,
message: "input path contains null byte".to_string(),
})?;
let c_output =
CString::new(output.to_string_lossy().as_ref()).map_err(|_| FilterError::Ffmpeg {
code: 0,
message: "output path contains null byte".to_string(),
})?;
let crop_str = if opts.crop_black { "black" } else { "keep" };
let interpol_str = match opts.interpol {
Interpolation::Bilinear => "bilinear",
Interpolation::Bicubic => "bicubic",
};
let smoothing = opts.smoothing;
let zoom = opts.zoom;
let optzoom = opts.optzoom.clamp(0, 2);
let vstab_args = CString::new(format!(
"input={trf_str}:smoothing={smoothing}:crop={crop_str}:zoom={zoom}:optzoom={optzoom}:interpol={interpol_str}"
))
.map_err(|_| FilterError::Ffmpeg {
code: 0,
message: "trf path contains null byte".to_string(),
})?;
let graph = ff_sys::avfilter_graph_alloc();
if graph.is_null() {
return Err(FilterError::Ffmpeg {
code: 0,
message: "avfilter_graph_alloc failed".to_string(),
});
}
let movie_filt = ff_sys::avfilter_get_by_name(c"movie".as_ptr());
if movie_filt.is_null() {
bail!(graph, 0, "filter not found: movie");
}
let mut src_ctx: *mut ff_sys::AVFilterContext = std::ptr::null_mut();
let ret = ff_sys::avfilter_graph_create_filter(
&raw mut src_ctx,
movie_filt,
c"vstab_t_src".as_ptr(),
movie_args.as_ptr(),
std::ptr::null_mut(),
graph,
);
if ret < 0 {
bail!(graph, ret, format!("movie create_filter failed code={ret}"));
}
log::debug!(
"filter added name=vidstabtransform args={}",
vstab_args.to_string_lossy()
);
let mut vstab_ctx: *mut ff_sys::AVFilterContext = std::ptr::null_mut();
let ret = ff_sys::avfilter_graph_create_filter(
&raw mut vstab_ctx,
vstab_filt,
c"vstab_t_filter".as_ptr(),
vstab_args.as_ptr(),
std::ptr::null_mut(),
graph,
);
if ret < 0 {
bail!(
graph,
ret,
format!("vidstabtransform create_filter failed code={ret}")
);
}
let format_filt = ff_sys::avfilter_get_by_name(c"format".as_ptr());
if format_filt.is_null() {
bail!(graph, 0, "filter not found: format");
}
let mut fmt_ctx: *mut ff_sys::AVFilterContext = std::ptr::null_mut();
let ret = ff_sys::avfilter_graph_create_filter(
&raw mut fmt_ctx,
format_filt,
c"vstab_t_fmt".as_ptr(),
c"pix_fmts=yuv420p".as_ptr(),
std::ptr::null_mut(),
graph,
);
if ret < 0 {
bail!(
graph,
ret,
format!("format create_filter failed code={ret}")
);
}
let buffersink_filt = ff_sys::avfilter_get_by_name(c"buffersink".as_ptr());
if buffersink_filt.is_null() {
bail!(graph, 0, "filter not found: buffersink");
}
let mut sink_ctx: *mut ff_sys::AVFilterContext = std::ptr::null_mut();
let ret = ff_sys::avfilter_graph_create_filter(
&raw mut sink_ctx,
buffersink_filt,
c"vstab_t_sink".as_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
graph,
);
if ret < 0 {
bail!(
graph,
ret,
format!("buffersink create_filter failed code={ret}")
);
}
let ret = ff_sys::avfilter_link(src_ctx, 0, vstab_ctx, 0);
if ret < 0 {
bail!(
graph,
ret,
format!("avfilter_link movie→vidstabtransform failed code={ret}")
);
}
let ret = ff_sys::avfilter_link(vstab_ctx, 0, fmt_ctx, 0);
if ret < 0 {
bail!(
graph,
ret,
format!("avfilter_link vidstabtransform→format failed code={ret}")
);
}
let ret = ff_sys::avfilter_link(fmt_ctx, 0, sink_ctx, 0);
if ret < 0 {
bail!(
graph,
ret,
format!("avfilter_link format→buffersink failed code={ret}")
);
}
let ret = ff_sys::avfilter_graph_config(graph, std::ptr::null_mut());
if ret < 0 {
bail!(
graph,
ret,
format!(
"avfilter_graph_config failed code={ret} message={}",
ff_sys::av_error_string(ret)
)
);
}
let filter_tb = ff_sys::av_buffersink_get_time_base(sink_ctx);
let first_frame = ff_sys::av_frame_alloc();
if first_frame.is_null() {
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: 0,
message: "av_frame_alloc failed".to_string(),
});
}
let ret = ff_sys::av_buffersink_get_frame(sink_ctx, first_frame);
if ret < 0 {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: ret,
message: format!(
"first frame pull failed code={ret} message={}",
ff_sys::av_error_string(ret)
),
});
}
let frame_width = (*first_frame).width;
let frame_height = (*first_frame).height;
let enc_codec = {
let candidates: &[&std::ffi::CStr] = &[
c"h264_nvenc",
c"h264_qsv",
c"h264_amf",
c"h264_videotoolbox",
c"libx264",
c"mpeg4",
];
let mut found: Option<*const ff_sys::AVCodec> = None;
for name in candidates {
if let Some(c) = ff_sys::avcodec::find_encoder_by_name(name.as_ptr()) {
log::info!(
"stabilization transform selected encoder encoder={}",
name.to_string_lossy()
);
found = Some(c);
break;
}
}
if let Some(c) = found {
c
} else {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: 0,
message: "no H.264 encoder available (tried h264_nvenc, libx264, mpeg4, etc.)"
.to_string(),
});
}
};
let mut enc_ctx = match ff_sys::avcodec::alloc_context3(enc_codec) {
Ok(ctx) => ctx,
Err(code) => {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code,
message: "avcodec_alloc_context3 failed".to_string(),
});
}
};
(*enc_ctx).width = frame_width;
(*enc_ctx).height = frame_height;
(*enc_ctx).pix_fmt = ff_sys::AVPixelFormat_AV_PIX_FMT_YUV420P;
(*enc_ctx).time_base = filter_tb;
if let Err(code) = ff_sys::avcodec::open2(enc_ctx, enc_codec, std::ptr::null_mut()) {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
ff_sys::avcodec::free_context(&raw mut enc_ctx);
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code,
message: format!("avcodec_open2 failed code={code}"),
});
}
let mut out_ctx: *mut ff_sys::AVFormatContext = std::ptr::null_mut();
let ret = ff_sys::avformat_alloc_output_context2(
&raw mut out_ctx,
std::ptr::null_mut(),
std::ptr::null(),
c_output.as_ptr(),
);
if ret < 0 || out_ctx.is_null() {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
ff_sys::avcodec::free_context(&raw mut enc_ctx);
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: ret,
message: format!("avformat_alloc_output_context2 failed code={ret}"),
});
}
let out_stream = ff_sys::avformat_new_stream(out_ctx, std::ptr::null());
if out_stream.is_null() {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
ff_sys::avcodec::free_context(&raw mut enc_ctx);
ff_sys::avformat_free_context(out_ctx);
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: 0,
message: "avformat_new_stream failed".to_string(),
});
}
if let Err(code) = ff_sys::avcodec::parameters_from_context((*out_stream).codecpar, enc_ctx) {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
ff_sys::avcodec::free_context(&raw mut enc_ctx);
ff_sys::avformat_free_context(out_ctx);
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code,
message: format!("avcodec_parameters_from_context failed code={code}"),
});
}
let pb = match ff_sys::avformat::open_output(output, ff_sys::avformat::avio_flags::WRITE) {
Ok(pb) => pb,
Err(code) => {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
ff_sys::avcodec::free_context(&raw mut enc_ctx);
ff_sys::avformat_free_context(out_ctx);
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code,
message: format!("avio_open failed code={code}"),
});
}
};
(*out_ctx).pb = pb;
let ret = ff_sys::avformat_write_header(out_ctx, std::ptr::null_mut());
if ret < 0 {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
ff_sys::avcodec::free_context(&raw mut enc_ctx);
ff_sys::avformat::close_output(std::ptr::addr_of_mut!((*out_ctx).pb));
ff_sys::avformat_free_context(out_ctx);
let mut g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: ret,
message: format!("avformat_write_header failed code={ret}"),
});
}
let stream_tb = (*out_stream).time_base;
let mut pkt = ff_sys::av_packet_alloc();
if pkt.is_null() {
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
ff_sys::avcodec::free_context(&raw mut enc_ctx);
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 g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
return Err(FilterError::Ffmpeg {
code: 0,
message: "av_packet_alloc failed".to_string(),
});
}
let _ = ff_sys::avcodec::send_frame(enc_ctx, first_frame);
drain_encoded_packets(enc_ctx, pkt, out_ctx, stream_tb);
let mut fp = first_frame;
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
loop {
let frame = ff_sys::av_frame_alloc();
if frame.is_null() {
break;
}
let ret = ff_sys::av_buffersink_get_frame(sink_ctx, frame);
let mut fp = frame;
if ret < 0 {
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
break;
}
let _ = ff_sys::avcodec::send_frame(enc_ctx, frame);
drain_encoded_packets(enc_ctx, pkt, out_ctx, stream_tb);
ff_sys::av_frame_free(std::ptr::addr_of_mut!(fp));
}
let _ = ff_sys::avcodec::send_frame(enc_ctx, std::ptr::null());
drain_encoded_packets(enc_ctx, pkt, out_ctx, stream_tb);
ff_sys::av_packet_free(&raw mut pkt);
ff_sys::avcodec::free_context(&raw mut enc_ctx);
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 g = graph;
ff_sys::avfilter_graph_free(std::ptr::addr_of_mut!(g));
log::info!(
"stabilization transform complete output={}",
output.display()
);
Ok(())
}