use std::ffi::OsString;
use std::fs;
use std::path::Path;
use super::plan::*;
use super::{
CaptureMeta, EncodePlan, Engine, EngineError, Hdr, MediaInfo, Outcome, ShrinkOpts, VideoSpec,
};
use crate::budget;
use crate::detect::{detect_kind, MediaKind};
use crate::options::AudioCodec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PassKind {
Single,
First,
Second,
}
#[derive(Debug, Default, Clone)]
pub struct MediaEngine {
cancel: Option<deepshrink_ffmpeg::CancelToken>,
}
impl MediaEngine {
pub fn new() -> Self {
Self::default()
}
pub fn with_cancel(cancel: deepshrink_ffmpeg::CancelToken) -> Self {
Self {
cancel: Some(cancel),
}
}
fn tools(&self) -> Result<deepshrink_ffmpeg::Tools, EngineError> {
let tools = deepshrink_ffmpeg::locate()?;
Ok(match &self.cancel {
Some(c) => tools.with_cancel(c.clone()),
None => tools,
})
}
pub fn run_with_progress(
&self,
plan: &EncodePlan,
on_progress: &mut dyn FnMut(PassKind, f64),
) -> Result<Outcome, EngineError> {
let outcome = match self.run_inner(plan, on_progress) {
Ok(o) => o,
Err(e) => {
if e.is_cancelled() && plan.output != plan.input {
let _ = fs::remove_file(&plan.output);
cleanup_passlog(&passlog_base(plan));
}
return Err(e);
}
};
if plan.spec.keep_metadata {
copy_mtime(&plan.input, &outcome.output);
}
Ok(outcome)
}
fn run_inner(
&self,
plan: &EncodePlan,
on_progress: &mut dyn FnMut(PassKind, f64),
) -> Result<Outcome, EngineError> {
let tools = self.tools()?;
let encoder = resolve_encoder(&tools, plan)?;
let zscale = wants_zscale(&tools, plan);
if let Some(target_vmaf) = plan.target_vmaf {
if plan.spec.video.crf.is_some() && !plan.spec.audio_only && !plan.spec.passthrough {
return self.run_crf_search(
&tools,
plan,
encoder,
zscale,
target_vmaf,
on_progress,
);
}
}
let source = if plan.guard_larger && !plan.spec.passthrough {
fs::metadata(&plan.input).map(|m| m.len()).unwrap_or(0)
} else {
0
};
if source > 0
&& plan.target_vmaf.is_none()
&& !plan.spec.audio_only
&& plan.spec.video.crf.is_some()
{
if let Some(predicted) = predict_crf_bytes(&tools, plan, encoder, zscale) {
if super::not_worth_it(predicted, source) {
return self.keep_original(&tools, plan, on_progress);
}
}
}
let ceiling = match ceiling_fit(&tools, plan, encoder, zscale) {
Some((ceiling, _)) => {
let o = self.run_plain(&tools, &ceiling, encoder, zscale, on_progress)?;
plan.target_bytes
.is_some_and(|t| o.final_bytes <= t)
.then_some(o)
}
None => None,
};
let mut outcome = match ceiling {
Some(o) => o,
None => self.run_plain(&tools, plan, encoder, zscale, on_progress)?,
};
if source > 0 && super::not_worth_it(outcome.final_bytes, source) {
let _ = fs::remove_file(&outcome.output);
return self.keep_original(&tools, plan, on_progress);
}
if plan.target_vmaf.is_some() && !plan.spec.audio_only && !plan.spec.passthrough {
outcome.vmaf = self.measure_output(&tools, plan, &plan.output);
}
Ok(outcome)
}
fn run_plain(
&self,
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
encoder: &str,
zscale: bool,
on_progress: &mut dyn FnMut(PassKind, f64),
) -> Result<Outcome, EngineError> {
let passlog = passlog_base(plan);
let total = plan.source_duration_sec;
if plan.spec.passthrough {
return self.run_passthrough(tools, plan, on_progress);
}
if plan.spec.two_pass {
let args1 = build_pass_args(plan, PassKind::First, &passlog, encoder, zscale);
tools.run_pass(&args1, total, &mut |f| on_progress(PassKind::First, f))?;
let args2 = build_pass_args(plan, PassKind::Second, &passlog, encoder, zscale);
tools.run_pass(&args2, total, &mut |f| on_progress(PassKind::Second, f))?;
} else {
let args = build_pass_args(plan, PassKind::Single, &passlog, encoder, zscale);
tools.run_pass(&args, total, &mut |f| on_progress(PassKind::Single, f))?;
}
let mut size = fs::metadata(&plan.output)?.len();
if let (Some(target), Some(mut vbps)) = (plan.target_bytes, plan.spec.video.bitrate_bps) {
let (tries, pass) = if plan.spec.two_pass {
(1, PassKind::Second)
} else if plan.spec.video.hardware {
(3, PassKind::Single)
} else {
(0, PassKind::Single)
};
for _ in 0..tries {
if size <= target {
break;
}
let Some(corrected) = corrected_bitrate(vbps, target, size) else {
break;
};
vbps = corrected;
let mut retry = plan.clone();
retry.spec.video.bitrate_bps = Some(corrected);
let args = build_pass_args(&retry, pass, &passlog, encoder, zscale);
tools.run_pass(&args, total, &mut |f| on_progress(pass, f))?;
size = fs::metadata(&plan.output)?.len();
}
}
cleanup_passlog(&passlog);
Ok(Outcome {
output: plan.output.clone(),
final_bytes: size,
vmaf: None,
already_compact: false,
})
}
pub fn estimate(&self, plan: &EncodePlan) -> Result<Option<u64>, EngineError> {
if plan.spec.passthrough {
return Ok(fs::metadata(&plan.input).ok().map(|m| m.len()));
}
if ceiling_plan(plan).is_some() {
let tools = self.tools()?;
let encoder = resolve_encoder(&tools, plan)?;
let zscale = wants_zscale(&tools, plan);
let fit = ceiling_fit(&tools, plan, encoder, zscale).map(|(_, bytes)| bytes);
return Ok(fit.or(plan.expected_bytes));
}
if let Some(bytes) = plan.expected_bytes {
return Ok(Some(bytes));
}
if plan.spec.audio_only || plan.spec.video.crf.is_none() {
return Ok(None);
}
let tools = self.tools()?;
let encoder = resolve_encoder(&tools, plan)?;
let zscale = wants_zscale(&tools, plan);
Ok(predict_crf_bytes(&tools, plan, encoder, zscale))
}
fn keep_original(
&self,
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
on_progress: &mut dyn FnMut(PassKind, f64),
) -> Result<Outcome, EngineError> {
let _ = tools; let output = match plan.input.extension() {
Some(ext) => plan.output.with_extension(ext),
None => plan.output.clone(),
};
{
let mut src = fs::File::open(&plan.input)?;
let mut dst = fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&output)?;
std::io::copy(&mut src, &mut dst)?;
}
if let Ok(meta) = fs::metadata(&plan.input) {
let _ = fs::set_permissions(&output, meta.permissions());
}
on_progress(PassKind::Single, 1.0);
Ok(Outcome {
final_bytes: fs::metadata(&output)?.len(),
output,
vmaf: None,
already_compact: true,
})
}
fn run_passthrough(
&self,
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
on_progress: &mut dyn FnMut(PassKind, f64),
) -> Result<Outcome, EngineError> {
let args = build_pass_args(plan, PassKind::Single, "", "copy", false);
let remuxed = tools.run_pass(&args, plan.source_duration_sec, &mut |f| {
on_progress(PassKind::Single, f)
});
if remuxed.is_err() {
fs::copy(&plan.input, &plan.output)?;
on_progress(PassKind::Single, 1.0);
}
let size = fs::metadata(&plan.output)?.len();
Ok(Outcome {
output: plan.output.clone(),
final_bytes: size,
vmaf: None,
already_compact: true,
})
}
fn run_crf_search(
&self,
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
encoder: &str,
zscale: bool,
target_vmaf: f64,
on_progress: &mut dyn FnMut(PassKind, f64),
) -> Result<Outcome, EngineError> {
let (ref_w, ref_h) = match (plan.source_width, plan.source_height) {
(Some(w), Some(h)) => (w, h),
_ => return self.run_plain(tools, plan, encoder, zscale, on_progress),
};
let ref_fps = plan.source_fps.unwrap_or(0.0);
let total = plan.source_duration_sec;
let (lo, hi) = plan.spec.video.codec.crf_search_bounds();
let n_threads = thread_count();
let mut err: Option<EngineError> = None;
let mut last_crf: Option<u8> = None;
let (chosen_crf, chosen_vmaf) = budget::search_crf(target_vmaf, lo, hi, |crf| {
if err.is_some() {
return f64::NEG_INFINITY;
}
match encode_at_crf(tools, plan, encoder, zscale, crf, total, on_progress).and_then(
|()| {
last_crf = Some(crf);
deepshrink_ffmpeg::measure_vmaf(
&tools.ffmpeg,
&plan.output,
&plan.input,
ref_w,
ref_h,
ref_fps,
n_threads,
)
.map_err(EngineError::from)
},
) {
Ok(v) => v,
Err(e) => {
err = Some(e);
f64::NEG_INFINITY
}
}
});
if let Some(e) = err {
return Err(e);
}
if last_crf != Some(chosen_crf) {
encode_at_crf(tools, plan, encoder, zscale, chosen_crf, total, on_progress)?;
}
let size = fs::metadata(&plan.output)?.len();
Ok(Outcome {
output: plan.output.clone(),
final_bytes: size,
vmaf: Some(chosen_vmaf),
already_compact: false,
})
}
fn measure_output(
&self,
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
output: &Path,
) -> Option<f64> {
let (w, h) = (plan.source_width?, plan.source_height?);
let fps = plan.source_fps.unwrap_or(0.0);
deepshrink_ffmpeg::measure_vmaf(
&tools.ffmpeg,
output,
&plan.input,
w,
h,
fps,
thread_count(),
)
.ok()
}
}
impl Engine for MediaEngine {
fn supports(&self, input: &Path) -> bool {
matches!(detect_kind(input), MediaKind::Video | MediaKind::Audio)
}
fn probe(&self, input: &Path) -> Result<MediaInfo, EngineError> {
let tools = deepshrink_ffmpeg::locate()?;
let p = deepshrink_ffmpeg::probe(&tools.ffprobe, input)?;
let video = p.video_stream();
let audio = p.audio_stream();
let size_bytes = p
.size_bytes()
.or_else(|| fs::metadata(input).ok().map(|m| m.len()))
.unwrap_or(0);
Ok(MediaInfo {
path: input.to_path_buf(),
kind: detect_kind(input),
duration_sec: p.duration_sec().unwrap_or(0.0),
size_bytes,
width: video.and_then(|v| v.display_size().0),
height: video.and_then(|v| v.display_size().1),
fps: p.fps(),
video_codec: video.and_then(|v| v.codec_name.clone()),
audio_codec: audio.and_then(|a| a.codec_name.clone()),
audio_channels: audio.and_then(|a| a.channels),
audio_bitrate_bps: p.audio_bitrate_bps(),
capture: capture_meta(&p),
hdr: video
.and_then(|v| v.color_transfer.as_deref())
.and_then(Hdr::from_transfer),
})
}
fn plan(&self, info: &MediaInfo, opts: &ShrinkOpts) -> Result<EncodePlan, EngineError> {
let hw = opts.hardware && opts.target_vmaf.is_none() && hardware_encoding_available();
super::plan::plan(info, opts, hw)
}
fn run(&self, plan: &EncodePlan) -> Result<Outcome, EngineError> {
self.run_with_progress(plan, &mut |_, _| {})
}
}
pub fn hardware_encoding_available() -> bool {
static AVAILABLE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*AVAILABLE.get_or_init(|| {
cfg!(all(target_os = "macos", target_arch = "aarch64"))
&& deepshrink_ffmpeg::locate().is_ok_and(|t| {
deepshrink_ffmpeg::has_encoder(&t.ffmpeg, "h264_videotoolbox")
&& deepshrink_ffmpeg::has_encoder(&t.ffmpeg, "hevc_videotoolbox")
})
})
}
fn copy_mtime(input: &Path, output: &Path) {
let Ok(mtime) = fs::metadata(input).and_then(|m| m.modified()) else {
return;
};
if let Ok(f) = fs::File::options().write(true).open(output) {
let _ = f.set_modified(mtime);
}
}
fn capture_meta(p: &deepshrink_ffmpeg::Ffprobe) -> CaptureMeta {
let tag = |keys: &[&str]| {
keys.iter()
.find_map(|k| p.format_tag(k))
.map(str::to_string)
};
let created_local = tag(&["com.apple.quicktime.creationdate"]);
let created_utc = created_local
.as_deref()
.and_then(to_utc)
.or_else(|| tag(&["creation_time"]));
CaptureMeta {
created_utc,
created_local,
location: tag(&["com.apple.quicktime.location.ISO6709", "location"]),
make: tag(&["com.apple.quicktime.make", "make"]),
model: tag(&["com.apple.quicktime.model", "model"]),
}
}
fn metadata_args(plan: &EncodePlan) -> (Vec<OsString>, Option<&'static str>) {
if !plan.spec.keep_metadata {
return (vec!["-map_metadata".into(), "-1".into()], None);
}
let mut a: Vec<OsString> = vec!["-map_metadata".into(), "0".into()];
for (k, v) in &plan.spec.tags {
a.push("-metadata".into());
a.push(format!("{k}={v}").into());
}
(a, None)
}
fn movflags(faststart: bool, meta: Option<&'static str>) -> Option<String> {
let mut v = String::new();
if faststart {
v.push_str("+faststart");
}
if let Some(m) = meta {
v.push_str(m);
}
(!v.is_empty()).then_some(v)
}
fn ceiling_fit(
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
encoder: &str,
zscale: bool,
) -> Option<(EncodePlan, u64)> {
let target = plan.target_bytes?;
let ceiling = ceiling_plan(plan)?;
let predicted = predict_crf_bytes(tools, &ceiling, encoder, zscale)?;
((predicted as f64) < target as f64 * CEILING_MARGIN).then_some((ceiling, predicted))
}
fn wants_zscale(tools: &deepshrink_ffmpeg::Tools, plan: &EncodePlan) -> bool {
plan.spec.video.to_sdr == Some(Hdr::Pq)
&& deepshrink_ffmpeg::has_filter(&tools.ffmpeg, "zscale")
}
fn predict_crf_bytes(
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
encoder: &str,
zscale: bool,
) -> Option<u64> {
let duration = plan.source_duration_sec;
if !duration.is_finite() || duration <= 0.0 {
return None;
}
let (windows, bias) = sample_windows(plan);
let mut sample = plan.clone();
sample.spec.audio = None;
sample.spec.faststart = false;
let mut video_bytes = 0u64;
let mut sampled = 0.0;
for (i, &(start, len)) in windows.iter().enumerate() {
sample.output =
std::env::temp_dir().join(format!("deepshrink-sample-{}-{i}.mp4", std::process::id()));
let mut args = build_pass_args(&sample, PassKind::Single, "", encoder, zscale);
let at_input = args.iter().position(|a| a == "-i")?;
args.splice(
at_input..at_input,
[
OsString::from("-ss"),
OsString::from(format!("{start:.2}")),
OsString::from("-t"),
OsString::from(format!("{len:.2}")),
],
);
let ran = tools.run_pass(&args, len, &mut |_| {});
let bytes = fs::metadata(&sample.output).map(|m| m.len()).ok();
let _ = fs::remove_file(&sample.output);
video_bytes += bytes.filter(|_| ran.is_ok())?;
sampled += len;
}
Some(predicted_bytes(plan, video_bytes, sampled, bias))
}
fn video_filters(video: &VideoSpec, zscale: bool) -> Option<String> {
const COLORSPACE: &str = "colorspace=all=bt709:iall=bt2020:itrc=bt2020-10:format=yuv420p";
let mut chain = Vec::new();
if let Some(h) = video.height {
chain.push(if video.portrait {
format!("scale={h}:-2")
} else {
format!("scale=-2:{h}")
});
}
match video.to_sdr {
Some(Hdr::Pq) if zscale => chain.push(
"zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,\
tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p"
.to_string(),
),
Some(_) => chain.push(COLORSPACE.to_string()),
None => {}
}
(!chain.is_empty()).then(|| chain.join(","))
}
fn passlog_base(plan: &EncodePlan) -> String {
let stem = plan
.input
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "ds".to_string());
let dir = std::env::temp_dir();
dir.join(format!("deepshrink-{}-{}", std::process::id(), stem))
.to_string_lossy()
.into_owned()
}
fn cleanup_passlog(base: &str) {
for suffix in ["-0.log", "-0.log.mbtree"] {
let _ = fs::remove_file(format!("{base}{suffix}"));
}
}
fn encode_at_crf(
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
encoder: &str,
zscale: bool,
crf: u8,
total: f64,
on_progress: &mut dyn FnMut(PassKind, f64),
) -> Result<(), EngineError> {
let mut trial = plan.clone();
trial.spec.video.crf = Some(crf);
trial.spec.video.bitrate_bps = None;
trial.spec.two_pass = false;
let args = build_pass_args(&trial, PassKind::Single, "", encoder, zscale);
tools.run_pass(&args, total, &mut |f| on_progress(PassKind::Single, f))?;
Ok(())
}
fn thread_count() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
}
fn null_sink() -> &'static str {
if cfg!(windows) {
"NUL"
} else {
"/dev/null"
}
}
fn resolve_encoder(
tools: &deepshrink_ffmpeg::Tools,
plan: &EncodePlan,
) -> Result<&'static str, EngineError> {
let codec = plan.spec.video.codec;
if plan.spec.video.hardware && !plan.spec.passthrough && !plan.spec.audio_only {
if let Some(hw) = codec.hardware_encoder() {
return Ok(hw);
}
}
let primary = codec.encoder();
let Some(fallback) = codec.fallback_encoder() else {
return Ok(primary);
};
if plan.spec.passthrough || plan.spec.audio_only {
return Ok(primary);
}
if deepshrink_ffmpeg::has_encoder(&tools.ffmpeg, primary) {
return Ok(primary);
}
if deepshrink_ffmpeg::has_encoder(&tools.ffmpeg, fallback) {
return Ok(fallback);
}
Err(EngineError::Unsupported(format!(
"this ffmpeg build has no {} encoder (looked for {primary} and {fallback})",
codec.label()
)))
}
fn build_pass_args(
plan: &EncodePlan,
pass: PassKind,
passlog: &str,
encoder: &str,
zscale: bool,
) -> Vec<OsString> {
let s = &plan.spec;
let mut a: Vec<OsString> = Vec::new();
macro_rules! push {
($arg:expr) => {
a.push(OsString::from($arg))
};
}
push!("-hide_banner");
push!("-y");
push!("-loglevel");
push!("error");
push!("-progress");
push!("pipe:1");
push!("-nostats");
push!("-i");
a.push(plan.input.clone().into_os_string());
let (meta, meta_flag) = metadata_args(plan);
if s.passthrough {
push!("-c");
push!("copy");
a.extend(meta.iter().cloned());
if let Some(flags) = movflags(s.faststart, meta_flag) {
push!("-movflags");
push!(flags);
}
a.push(plan.output.clone().into_os_string());
return a;
}
if s.audio_only {
push!("-vn");
if let Some(au) = &s.audio {
push!("-c:a");
push!(au.codec.encoder());
if au.mono {
push!("-ac");
push!("1");
}
if let Some(sr) = au.sample_rate {
push!("-ar");
push!(sr.to_string());
}
push!("-b:a");
push!(au.bitrate_bps.to_string());
if matches!(au.codec, AudioCodec::Opus) {
push!("-vbr");
push!(if au.vbr { "on" } else { "constrained" });
}
}
a.extend(meta.iter().cloned());
if let Some(flags) = movflags(false, meta_flag) {
push!("-movflags");
push!(flags);
}
a.push(plan.output.clone().into_os_string());
return a;
}
push!("-c:v");
push!(encoder);
if let Some(vf) = video_filters(&s.video, zscale) {
push!("-vf");
push!(vf);
}
if let Some(f) = s.video.fps {
push!("-r");
push!(f.to_string());
}
let videotoolbox = encoder.ends_with("_videotoolbox");
if !videotoolbox {
let (speed_flag, speed_value) = s.video.preset.speed_flags(encoder);
push!(speed_flag);
push!(speed_value);
}
if let Some(tag) = s.video.codec.mp4_tag() {
push!("-tag:v");
push!(tag);
}
if s.video.to_sdr.is_some() {
for (flag, value) in [
("-pix_fmt", "yuv420p"),
("-color_primaries", "bt709"),
("-color_trc", "bt709"),
("-colorspace", "bt709"),
] {
push!(flag);
push!(value);
}
}
match (s.video.bitrate_bps, s.video.crf) {
(Some(bps), _) => {
push!("-b:v");
push!(bps.to_string());
if s.two_pass {
push!("-pass");
push!(match pass {
PassKind::First => "1",
_ => "2",
});
push!("-passlogfile");
push!(passlog);
}
}
(_, Some(crf)) => {
push!(if videotoolbox { "-q:v" } else { "-crf" });
push!(crf.to_string());
}
_ => {}
}
match pass {
PassKind::First => {
push!("-an");
push!("-f");
push!("null");
push!(null_sink());
}
PassKind::Second | PassKind::Single => {
match &s.audio {
Some(au) => {
push!("-c:a");
push!(au.codec.encoder());
if au.mono {
push!("-ac");
push!("1");
}
push!("-b:a");
push!(au.bitrate_bps.to_string());
}
None => push!("-an"),
}
a.extend(meta.iter().cloned());
if let Some(flags) = movflags(s.faststart, meta_flag) {
push!("-movflags");
push!(flags);
}
a.push(plan.output.clone().into_os_string());
}
}
a
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::SizeGoal;
use crate::options::{AudioChoice, AudioCodec, QualityPreset, ResolutionOpt, VideoCodec};
use crate::size::preset;
use std::path::PathBuf;
fn enc(plan: &EncodePlan) -> &'static str {
plan.spec.video.codec.encoder()
}
fn video_info(duration: f64, size: u64, w: u32, h: u32, audio: bool) -> MediaInfo {
MediaInfo {
path: PathBuf::from("/tmp/clip.mp4"),
kind: MediaKind::Video,
duration_sec: duration,
size_bytes: size,
width: Some(w),
height: Some(h),
fps: Some(30.0),
video_codec: Some("h264".into()),
audio_codec: if audio { Some("aac".into()) } else { None },
audio_channels: if audio { Some(2) } else { None },
audio_bitrate_bps: None,
capture: CaptureMeta::default(),
hdr: None,
}
}
fn opts_target(bytes: u64) -> ShrinkOpts {
ShrinkOpts {
goal: SizeGoal::Target(bytes),
..Default::default()
}
}
#[test]
fn supports_video_and_audio() {
let e = MediaEngine::new();
assert!(e.supports(&PathBuf::from("clip.mp4")));
assert!(e.supports(&PathBuf::from("lecture.wav")));
assert!(!e.supports(&PathBuf::from("photo.jpg")));
}
#[test]
fn plan_target_builds_two_pass_with_budget() {
let info = video_info(120.0, 300_000_000, 1920, 1080, true);
let plan = MediaEngine::new()
.plan(&info, &opts_target(8_000_000))
.unwrap();
assert!(plan.spec.two_pass);
assert_eq!(plan.target_bytes, Some(8_000_000));
assert_eq!(plan.output, PathBuf::from("/tmp/clip.shrink.mp4"));
let vbps = plan.spec.video.bitrate_bps.unwrap();
assert!(vbps >= budget::ABSOLUTE_MIN_VIDEO_BPS);
assert!(plan.spec.video.height.is_some());
assert!(plan.spec.audio.is_some());
assert!(plan.expected_bytes.unwrap() <= 8_000_000 + 8_000_000 / 20);
}
#[test]
fn plan_video_mono_downmixes_the_audio_track() {
let info = video_info(60.0, 100_000_000, 1280, 720, true);
let opts = ShrinkOpts {
mono: true,
..opts_target(8_000_000)
};
let plan = MediaEngine::new().plan(&info, &opts).unwrap();
let audio = plan.spec.audio.as_ref().expect("kept audio track");
assert!(audio.mono, "opts.mono downmixes the video's audio track");
let stereo = MediaEngine::new()
.plan(&info, &opts_target(8_000_000))
.unwrap();
assert!(!stereo.spec.audio.as_ref().unwrap().mono);
}
#[test]
fn video_mono_reaches_ffmpeg_as_ac_1() {
let info = video_info(60.0, 100_000_000, 1280, 720, true);
let plan = MediaEngine::new()
.plan(
&info,
&ShrinkOpts {
mono: true,
..opts_target(8_000_000)
},
)
.unwrap();
let args = build_pass_args(&plan, PassKind::Second, "/tmp/passlog", enc(&plan), false);
let joined: Vec<String> = args
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
let ac = joined.iter().position(|a| a == "-ac").expect("-ac emitted");
assert_eq!(joined[ac + 1], "1");
let stereo = MediaEngine::new()
.plan(&info, &opts_target(8_000_000))
.unwrap();
let stereo_args: Vec<String> = build_pass_args(
&stereo,
PassKind::Second,
"/tmp/passlog",
enc(&stereo),
false,
)
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(!stereo_args.iter().any(|a| a == "-ac"));
}
#[test]
fn plan_preset_discord_sets_target() {
let info = video_info(30.0, 50_000_000, 1280, 720, true);
let opts = ShrinkOpts {
goal: SizeGoal::Preset(preset("discord").unwrap()),
..Default::default()
};
let plan = MediaEngine::new().plan(&info, &opts).unwrap();
assert_eq!(plan.target_bytes, Some(10_000_000));
}
#[test]
fn plan_reduce_targets_complement_of_original() {
let info = video_info(60.0, 100_000_000, 1920, 1080, true);
let opts = ShrinkOpts {
goal: SizeGoal::Reduce(0.70),
..Default::default()
};
let plan = MediaEngine::new().plan(&info, &opts).unwrap();
assert_eq!(plan.target_bytes, Some(30_000_000));
}
#[test]
fn plan_passthrough_when_source_already_fits() {
let info = video_info(10.0, 200_000, 1280, 720, true);
let plan = MediaEngine::new()
.plan(&info, &opts_target(1_000_000))
.unwrap();
assert!(plan.spec.passthrough);
assert!(!plan.spec.two_pass);
assert_eq!(plan.expected_bytes, Some(200_000));
let args = build_pass_args(&plan, PassKind::Single, "/tmp/passlog", enc(&plan), false);
let joined: Vec<String> = args
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(joined.contains(&"copy".to_string()));
assert_eq!(plan.output, PathBuf::from("/tmp/clip.shrink.mp4"));
}
#[test]
fn plan_passthrough_keeps_the_source_container() {
let info = MediaInfo {
path: PathBuf::from("/tmp/voice.3gp"),
..video_info(10.0, 200_000, 320, 240, true)
};
let plan = MediaEngine::new()
.plan(&info, &opts_target(1_000_000))
.unwrap();
assert!(plan.spec.passthrough);
assert_eq!(plan.output, PathBuf::from("/tmp/voice.shrink.3gp"));
}
#[test]
fn plan_infeasible_when_target_too_small() {
let info = video_info(600.0, 500_000_000, 1920, 1080, true);
let err = MediaEngine::new().plan(&info, &opts_target(50_000));
assert!(matches!(err, Err(EngineError::Infeasible)));
}
#[test]
fn plan_quality_mode_uses_crf_single_pass() {
let info = video_info(60.0, 100_000_000, 1920, 1080, true);
let opts = ShrinkOpts {
goal: SizeGoal::Quality,
quality: QualityPreset::Balanced,
..Default::default()
};
let plan = MediaEngine::new().plan(&info, &opts).unwrap();
assert!(!plan.spec.two_pass);
assert_eq!(plan.spec.video.crf, Some(23));
assert!(plan.spec.video.bitrate_bps.is_none());
assert!(plan.expected_bytes.is_none());
}
#[test]
fn plan_drops_audio_when_requested() {
let info = video_info(30.0, 50_000_000, 1280, 720, true);
let opts = ShrinkOpts {
audio: AudioChoice::Drop,
..opts_target(8_000_000)
};
let plan = MediaEngine::new().plan(&info, &opts).unwrap();
assert!(plan.spec.audio.is_none());
}
fn audio_info(duration: f64, size: u64, channels: u32) -> MediaInfo {
MediaInfo {
path: PathBuf::from("/tmp/lecture.wav"),
kind: MediaKind::Audio,
duration_sec: duration,
size_bytes: size,
width: None,
height: None,
fps: None,
video_codec: None,
audio_codec: Some("pcm_s16le".into()),
audio_channels: Some(channels),
audio_bitrate_bps: None,
capture: CaptureMeta::default(),
hdr: None,
}
}
#[test]
fn quality_audio_bitrate_follows_tier_codec_and_channels() {
use QualityPreset::*;
assert_eq!(quality_audio_bps(Balanced, AudioCodec::Aac, false), 128_000);
assert_eq!(quality_audio_bps(Balanced, AudioCodec::Aac, true), 64_000);
assert_eq!(quality_audio_bps(Fast, AudioCodec::Opus, true), 32_000);
assert_eq!(quality_audio_bps(Max, AudioCodec::Mp3, false), 256_000);
for c in [AudioCodec::Aac, AudioCodec::Opus, AudioCodec::Mp3] {
let t: Vec<_> = [Fast, Balanced, Max]
.map(|q| quality_audio_bps(q, c, false))
.into();
assert!(t[0] < t[1] && t[1] < t[2], "{c:?}: {t:?}");
}
}
#[test]
fn a_compact_audiobook_is_kept_in_quality_mode() {
let mut info = audio_info(3600.0, 64_000 / 8 * 3600, 1);
info.path = PathBuf::from("/tmp/book.mp3");
let plan = MediaEngine::new()
.plan(&info, &ShrinkOpts::default())
.unwrap();
assert!(plan.spec.passthrough, "{}", plan.summary);
assert!(plan.output.to_string_lossy().ends_with(".mp3"));
assert!(plan.summary.contains("already compact"));
let smaller = ShrinkOpts {
audio_codec: AudioCodec::Opus,
quality: QualityPreset::Fast,
..ShrinkOpts::default()
};
let plan = MediaEngine::new().plan(&info, &smaller).unwrap();
assert!(!plan.spec.passthrough);
assert_eq!(plan.spec.audio.as_ref().unwrap().bitrate_bps, 32_000);
assert!(
plan.guard_larger,
"quality mode keeps the post-encode check"
);
let allow = ShrinkOpts {
allow_larger: true,
..ShrinkOpts::default()
};
let plan = MediaEngine::new().plan(&info, &allow).unwrap();
assert!(!plan.spec.passthrough && !plan.guard_larger);
}
#[test]
fn the_guard_is_for_quality_mode_only() {
let info = video_info(60.0, 50_000_000, 1920, 1080, true);
let quality = MediaEngine::new()
.plan(&info, &ShrinkOpts::default())
.unwrap();
assert!(quality.guard_larger);
let target = MediaEngine::new()
.plan(&info, &opts_target(10_000_000))
.unwrap();
assert!(!target.guard_larger, "a size target is its own guarantee");
}
#[test]
fn plan_audio_single_pass_with_fitted_bitrate() {
let info = audio_info(3480.0, 600_000_000, 2);
let plan = MediaEngine::new()
.plan(&info, &opts_target(10_000_000))
.unwrap();
assert!(plan.spec.audio_only);
assert!(!plan.spec.two_pass);
assert_eq!(plan.output, PathBuf::from("/tmp/lecture.shrink.m4a"));
let au = plan.spec.audio.as_ref().unwrap();
assert!(budget::AUDIO_STEPS.contains(&au.bitrate_bps));
assert!(plan.expected_bytes.unwrap() <= 10_000_000 + 10_000_000 / 20);
}
#[test]
fn plan_audio_mono_source_marked_speech() {
let info = audio_info(600.0, 100_000_000, 1);
let plan = MediaEngine::new()
.plan(&info, &opts_target(5_000_000))
.unwrap();
assert!(plan.spec.audio.as_ref().unwrap().mono);
}
#[test]
fn plan_audio_opus_extension_and_vbr_args() {
let info = audio_info(600.0, 100_000_000, 2);
let opts = ShrinkOpts {
audio_codec: AudioCodec::Opus,
mono: true,
..opts_target(3_000_000)
};
let plan = MediaEngine::new().plan(&info, &opts).unwrap();
assert_eq!(plan.output, PathBuf::from("/tmp/lecture.shrink.opus"));
let args = build_pass_args(&plan, PassKind::Single, "/tmp/passlog", enc(&plan), false);
let j: Vec<String> = args
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(j.contains(&"-vn".to_string()));
assert!(j.contains(&"libopus".to_string()));
assert!(j.contains(&"-ac".to_string())); assert!(j.contains(&"-vbr".to_string()));
}
#[test]
fn plan_audio_infeasible_when_target_tiny() {
let info = audio_info(3600.0, 500_000_000, 2);
assert!(matches!(
MediaEngine::new().plan(&info, &opts_target(1_000)),
Err(EngineError::Infeasible)
));
}
#[test]
fn plan_audio_passthrough_when_source_fits() {
let info = audio_info(600.0, 2_000_000, 2);
let plan = MediaEngine::new()
.plan(&info, &opts_target(10_000_000))
.unwrap();
assert!(plan.spec.passthrough);
assert_eq!(plan.output, PathBuf::from("/tmp/lecture.shrink.wav"));
}
#[test]
fn pass1_args_have_no_audio_and_null_sink() {
let info = video_info(120.0, 300_000_000, 1920, 1080, true);
let plan = MediaEngine::new()
.plan(&info, &opts_target(8_000_000))
.unwrap();
let args = build_pass_args(&plan, PassKind::First, "/tmp/passlog", enc(&plan), false);
let joined: Vec<String> = args
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(joined.contains(&"-an".to_string()));
assert!(joined.contains(&"null".to_string()));
assert!(joined.iter().any(|a| a == "1")); assert!(!joined.iter().any(|a| a.contains("shrink.mp4")));
}
#[test]
fn pass2_args_write_output_with_audio() {
let info = video_info(120.0, 300_000_000, 1920, 1080, true);
let plan = MediaEngine::new()
.plan(&info, &opts_target(8_000_000))
.unwrap();
let args = build_pass_args(&plan, PassKind::Second, "/tmp/passlog", enc(&plan), false);
let joined: Vec<String> = args
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(joined.iter().any(|a| a.contains("clip.shrink.mp4")));
assert!(joined.contains(&"-c:a".to_string()));
assert!(joined.iter().any(|a| a.contains("+faststart")));
assert!(joined.iter().any(|a| a == "2")); }
fn joined(plan: &EncodePlan, pass: PassKind) -> Vec<String> {
joined_with(plan, pass, false)
}
fn joined_with(plan: &EncodePlan, pass: PassKind, zscale: bool) -> Vec<String> {
build_pass_args(plan, pass, "/tmp/passlog", enc(plan), zscale)
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect()
}
#[test]
fn hdr_transfer_is_recognised() {
assert_eq!(Hdr::from_transfer("arib-std-b67"), Some(Hdr::Hlg));
assert_eq!(Hdr::from_transfer("smpte2084"), Some(Hdr::Pq));
assert_eq!(Hdr::from_transfer("bt709"), None);
}
#[test]
fn hdr_is_tone_mapped_to_sdr_for_size_targets_only() {
let mut info = iphone_info();
info.hdr = Some(Hdr::Hlg);
let engine = MediaEngine::new();
let target = engine.plan(&info, &opts_target(10_000_000)).unwrap();
assert_eq!(target.spec.video.to_sdr, Some(Hdr::Hlg));
assert!(target.summary.contains("HDR → SDR"));
let vf_of =
|args: &[String]| args[args.iter().position(|a| a == "-vf").unwrap() + 1].clone();
let hlg = joined_with(&target, PassKind::Second, true);
let vf = vf_of(&hlg);
assert!(
vf.contains("colorspace=all=bt709") && !vf.contains("zscale"),
"{vf}"
);
assert!(
vf.find("scale=").unwrap() < vf.find("colorspace").unwrap(),
"{vf}"
);
for pair in [
["-pix_fmt", "yuv420p"],
["-color_trc", "bt709"],
["-colorspace", "bt709"],
] {
assert!(hlg.windows(2).any(|w| w == pair), "{pair:?}");
}
let mut pq = target.clone();
pq.spec.video.to_sdr = Some(Hdr::Pq);
let vf = vf_of(&joined_with(&pq, PassKind::Second, true));
assert!(
vf.contains("tonemap=hable") && vf.contains("npl=100"),
"{vf}"
);
let vf = vf_of(&joined_with(&pq, PassKind::Second, false));
assert!(
vf.contains("colorspace=all=bt709") && !vf.contains("zscale"),
"{vf}"
);
let quality = engine.plan(&info, &ShrinkOpts::default()).unwrap();
assert_eq!(quality.spec.video.to_sdr, None);
let args = joined(&quality, PassKind::Single);
assert!(!args
.iter()
.any(|a| a == "-pix_fmt" || a.contains("colorspace")));
let sdr = engine
.plan(&iphone_info(), &opts_target(10_000_000))
.unwrap();
assert_eq!(sdr.spec.video.to_sdr, None);
assert!(!joined(&sdr, PassKind::Second)
.iter()
.any(|a| a == "-pix_fmt"));
}
#[test]
fn a_size_target_is_a_ceiling_at_the_quality_crf() {
let info = video_info(60.0, 200_000_000, 1920, 1080, true);
let engine = MediaEngine::new();
let opts = opts_target(50_000_000);
let plan = engine.plan(&info, &opts).unwrap();
let crf = opts.quality.default_crf(opts.video_codec);
assert_eq!(plan.ceiling_crf, Some(crf));
let ceiling = ceiling_plan(&plan).unwrap();
assert_eq!(ceiling.spec.video.crf, Some(crf));
assert_eq!(ceiling.spec.video.bitrate_bps, None);
assert!(!ceiling.spec.two_pass);
assert_eq!(ceiling.spec.video.height, plan.spec.video.height);
assert_eq!(ceiling.spec.audio, plan.spec.audio);
assert_eq!(ceiling.output, plan.output);
assert_eq!(ceiling.target_bytes, plan.target_bytes);
let quality = engine.plan(&info, &ShrinkOpts::default()).unwrap();
assert!(quality.ceiling_crf.is_none() && ceiling_plan(&quality).is_none());
let fits = engine.plan(&info, &opts_target(300_000_000)).unwrap();
assert!(fits.spec.passthrough && ceiling_plan(&fits).is_none());
}
#[test]
fn heavy_video_samples_shorter_windows() {
let engine = MediaEngine::new();
let mut info = video_info(120.0, 500_000_000, 1920, 1080, true);
info.fps = Some(30.0);
let plan = engine.plan(&info, &ShrinkOpts::default()).unwrap();
assert_eq!(sample_secs(&plan), 3.0);
info = video_info(120.0, 500_000_000, 3840, 2160, true);
info.fps = Some(60.0);
let plan = engine.plan(&info, &ShrinkOpts::default()).unwrap();
assert_eq!(sample_secs(&plan), MIN_SAMPLE_SECS);
}
#[test]
fn apple_hardware_uses_quality_one_pass_and_no_preset() {
let engine = MediaEngine::new();
let info = video_info(60.0, 200_000_000, 1920, 1080, true);
let mut plan = engine.plan(&info, &ShrinkOpts::default()).unwrap();
plan.spec.video.hardware = true;
plan.spec.video.crf = QualityPreset::Balanced.default_hw_quality(VideoCodec::H264);
let args: Vec<String> =
build_pass_args(&plan, PassKind::Single, "", "h264_videotoolbox", false)
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(args.windows(2).any(|w| w == ["-c:v", "h264_videotoolbox"]));
assert!(args.windows(2).any(|w| w == ["-q:v", "66"]), "{args:?}");
assert!(
!args.iter().any(|a| a == "-crf" || a == "-preset"),
"{args:?}"
);
let mut hw = plan.clone();
hw.spec.passthrough = false;
assert_eq!(
resolve_encoder(
&deepshrink_ffmpeg::Tools {
ffmpeg: "ffmpeg".into(),
ffprobe: "ffprobe".into(),
cancel: Default::default(),
},
&hw
)
.unwrap(),
"h264_videotoolbox"
);
assert_eq!(
QualityPreset::Balanced.default_hw_quality(VideoCodec::Av1),
None
);
}
#[test]
fn a_portrait_video_is_capped_on_its_short_side() {
let engine = MediaEngine::new();
let info = video_info(60.0, 200_000_000, 2160, 3840, true);
let opts = ShrinkOpts {
resolution: ResolutionOpt::Height(1080),
..ShrinkOpts::default()
};
let plan = engine.plan(&info, &opts).unwrap();
assert_eq!(plan.spec.video.height, Some(1080));
assert!(plan.spec.video.portrait);
let args = joined(&plan, PassKind::Single);
let vf = &args[args.iter().position(|a| a == "-vf").unwrap() + 1];
assert!(vf.starts_with("scale=1080:-2"), "{vf}");
let info = video_info(60.0, 200_000_000, 3840, 2160, true);
let plan = engine.plan(&info, &opts).unwrap();
assert!(!plan.spec.video.portrait);
let args = joined(&plan, PassKind::Single);
assert!(args.iter().any(|a| a.starts_with("scale=-2:1080")));
}
fn iphone_info() -> MediaInfo {
let mut info = video_info(60.0, 50_000_000, 2160, 3840, true);
info.path = PathBuf::from("/tmp/IMG_3325.MOV");
info.capture = CaptureMeta {
created_utc: to_utc("2026-09-26T20:01:54+0300"),
created_local: Some("2026-09-26T20:01:54+0300".into()),
location: Some("+50.4160+030.2796+155.635/".into()),
make: Some("Apple".into()),
model: Some("iPhone 12 Pro Max".into()),
};
info
}
#[test]
fn metadata_is_kept_by_default_and_strippable() {
let info = iphone_info();
let plan = MediaEngine::new()
.plan(&info, &ShrinkOpts::default())
.unwrap();
let a = joined(&plan, PassKind::Single);
let at = a.iter().position(|x| x == "-map_metadata").unwrap();
assert_eq!(a[at + 1], "0");
for tag in [
"creation_time=2026-09-26T17:01:54Z",
"location=+50.4160+030.2796+155.635/",
"make=Apple",
"model=iPhone 12 Pro Max",
"date=2026-09-26T20:01:54+0300",
] {
assert!(a.contains(&tag.to_string()), "{tag} in {a:?}");
}
assert!(a.contains(&"+faststart".to_string()));
let strip = ShrinkOpts {
keep_metadata: false,
..ShrinkOpts::default()
};
let plan = MediaEngine::new().plan(&info, &strip).unwrap();
let a = joined(&plan, PassKind::Single);
let at = a.iter().position(|x| x == "-map_metadata").unwrap();
assert_eq!(a[at + 1], "-1");
assert!(!a.iter().any(|x| x.starts_with("location=")));
assert!(a.contains(&"+faststart".to_string()));
}
#[test]
fn apple_local_time_converts_to_utc() {
let utc = |s: &str| to_utc(s);
assert_eq!(
utc("2026-09-26T20:01:54+0300").as_deref(),
Some("2026-09-26T17:01:54Z")
);
assert_eq!(
utc("2026-09-26T20:01:54+03:00").as_deref(),
Some("2026-09-26T17:01:54Z")
);
assert_eq!(
utc("2026-01-01T01:30:00+0300").as_deref(),
Some("2025-12-31T22:30:00Z")
);
assert_eq!(
utc("2026-03-01T23:00:00-0500").as_deref(),
Some("2026-03-02T04:00:00Z")
);
assert_eq!(
utc("2024-02-29T12:00:00.123Z").as_deref(),
Some("2024-02-29T12:00:00Z")
);
assert_eq!(utc("yesterday"), None);
}
#[test]
fn an_iphone_mov_stays_mov_in_quality_mode_only() {
let info = iphone_info();
let out = |opts: &ShrinkOpts| {
let plan = MediaEngine::new().plan(&info, opts).unwrap();
plan.output.to_string_lossy().into_owned()
};
assert!(out(&ShrinkOpts::default()).ends_with(".shrink.mov"));
assert!(out(&opts_target(8_000_000)).ends_with(".shrink.mp4"));
let av1 = ShrinkOpts {
video_codec: VideoCodec::Av1,
..ShrinkOpts::default()
};
assert!(out(&av1).ends_with(".shrink.mp4"));
let mp4 = video_info(60.0, 50_000_000, 1920, 1080, true);
let p = MediaEngine::new()
.plan(&mp4, &ShrinkOpts::default())
.unwrap();
assert!(p.output.to_string_lossy().ends_with(".shrink.mp4"));
}
#[test]
fn a_video_audio_track_is_never_upsampled() {
let mut info = video_info(60.0, 50_000_000, 1920, 1080, true);
info.audio_bitrate_bps = Some(64_000);
let bps_of = |info: &MediaInfo, opts: &ShrinkOpts| {
let plan = MediaEngine::new().plan(info, opts).unwrap();
plan.spec.audio.unwrap().bitrate_bps
};
assert_eq!(bps_of(&info, &ShrinkOpts::default()), 64_000);
assert!(bps_of(&info, &opts_target(20_000_000)) <= 64_000);
let explicit = ShrinkOpts {
audio: AudioChoice::Bitrate(128_000),
..ShrinkOpts::default()
};
assert_eq!(bps_of(&info, &explicit), 128_000);
info.audio_bitrate_bps = None;
assert_eq!(
bps_of(&info, &ShrinkOpts::default()),
budget::DEFAULT_AUDIO_BPS
);
}
#[test]
fn h265_adds_hvc1_tag() {
let info = video_info(60.0, 100_000_000, 1280, 720, false);
let opts = ShrinkOpts {
video_codec: VideoCodec::H265,
..opts_target(8_000_000)
};
let plan = MediaEngine::new().plan(&info, &opts).unwrap();
let args = build_pass_args(&plan, PassKind::Second, "/tmp/passlog", enc(&plan), false);
let joined: Vec<String> = args
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(joined.contains(&"hvc1".to_string()));
assert!(joined.contains(&"libx265".to_string()));
}
}