use std::{path::Path, process};
use avio::{
BitrateMode, H264Options, H264Profile, H265Options, H265Profile, HardwareEncoder, VideoCodec,
VideoCodecEncodeExt, VideoCodecOptions, VideoDecoder, VideoEncoder,
};
fn main() {
let mut args = std::env::args().skip(1);
let mut input = None::<String>;
let mut output = None::<String>;
let mut codec_str = "h264".to_string();
let mut crf: u32 = 23;
while let Some(flag) = args.next() {
match flag.as_str() {
"--input" | "-i" => input = Some(args.next().unwrap_or_default()),
"--output" | "-o" => output = Some(args.next().unwrap_or_default()),
"--codec" | "-c" => codec_str = args.next().unwrap_or_else(|| "h264".to_string()),
"--crf" => {
let v = args.next().unwrap_or_default();
crf = v.parse().unwrap_or(23);
}
other => {
eprintln!("Unknown flag: {other}");
process::exit(1);
}
}
}
let input = input.unwrap_or_else(|| {
eprintln!(
"Usage: gpl_encode --input <file> --output <file> \
[--codec h264|h265] [--crf N]"
);
process::exit(1);
});
let output = output.unwrap_or_else(|| {
eprintln!("--output is required");
process::exit(1);
});
#[cfg(feature = "gpl")]
println!("gpl feature: ENABLED — libx264 / libx265 are candidates");
#[cfg(not(feature = "gpl"))]
println!(
"gpl feature: DISABLED — H.264 falls back to VP9 (LGPL); \
H.265 falls back to AV1 (LGPL)"
);
println!();
let (video_codec, codec_options, description) = match codec_str.to_lowercase().as_str() {
"h264" | "avc" => {
let opts = VideoCodecOptions::H264(H264Options {
profile: H264Profile::High,
level: Some(41),
bframes: 2,
gop_size: 250,
refs: 3,
preset: None, tune: None,
});
(VideoCodec::H264, opts, "H.264 High@4.1")
}
"h265" | "hevc" => {
let opts = VideoCodecOptions::H265(H265Options {
profile: H265Profile::Main,
preset: None, ..H265Options::default()
});
(VideoCodec::H265, opts, "H.265 Main")
}
other => {
eprintln!("Unknown codec '{other}' (try h264, h265)");
process::exit(1);
}
};
println!(
"Codec: {:?} is_lgpl_compatible (static, codec family) = {}",
video_codec,
video_codec.is_lgpl_compatible(),
);
let probe = match VideoDecoder::open(&input).build() {
Ok(d) => d,
Err(e) => {
eprintln!("Error opening input: {e}");
process::exit(1);
}
};
let width = probe.width();
let height = probe.height();
let fps = probe.frame_rate();
drop(probe);
let in_name = Path::new(&input)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&input);
let out_name = Path::new(&output)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&output);
println!("Input: {in_name} {width}×{height} {fps:.2} fps");
println!("Output: {out_name} {description} crf={crf}");
println!();
let mut encoder = match VideoEncoder::create(&output)
.video(width, height, fps)
.video_codec(video_codec)
.bitrate_mode(BitrateMode::Crf(crf))
.codec_options(codec_options)
.hardware_encoder(HardwareEncoder::None)
.build()
{
Ok(e) => e,
Err(e) => {
eprintln!("Error building encoder: {e}");
process::exit(1);
}
};
let actual = encoder.actual_video_codec().to_string();
let lgpl_ok = encoder.is_lgpl_compliant();
println!("Encoder selected: {actual}");
println!("is_lgpl_compliant (runtime) = {lgpl_ok}");
if lgpl_ok {
println!(" → LGPL-compatible: safe to distribute under LGPL terms");
} else {
println!(
" → GPL encoder: distributing this binary requires GPL compliance \
or a commercial MPEG LA licence"
);
}
println!();
let mut decoder = match VideoDecoder::open(&input).build() {
Ok(d) => d,
Err(e) => {
eprintln!("Error opening decoder: {e}");
process::exit(1);
}
};
let mut frames: u64 = 0;
loop {
let frame = match decoder.decode_one() {
Ok(Some(f)) => f,
Ok(None) => break,
Err(e) => {
eprintln!("Decode error: {e}");
process::exit(1);
}
};
if let Err(e) = encoder.push_video(&frame) {
eprintln!("Encode error: {e}");
process::exit(1);
}
frames += 1;
}
if let Err(e) = encoder.finish() {
eprintln!("Error finalising output: {e}");
process::exit(1);
}
#[allow(clippy::cast_precision_loss)]
let kb = std::fs::metadata(&output).map_or(0, |m| m.len()) as f64 / 1024.0;
let size_str = if kb < 1024.0 {
format!("{kb:.0} KB")
} else {
format!("{:.1} MB", kb / 1024.0)
};
println!("Done. {out_name} {size_str} {frames} frames encoder={actual}");
}