use std::process;
use avio::{AudioDecoder, ExportPreset, SampleFormat, VideoDecoder, VideoEncoder};
fn preset_by_name(name: &str) -> Option<ExportPreset> {
match name {
"youtube_1080p" => Some(ExportPreset::youtube_1080p()),
"youtube_4k" => Some(ExportPreset::youtube_4k()),
"twitter" => Some(ExportPreset::twitter()),
"instagram_square" => Some(ExportPreset::instagram_square()),
"instagram_reels" => Some(ExportPreset::instagram_reels()),
"bluray_1080p" => Some(ExportPreset::bluray_1080p()),
"podcast_mono" => Some(ExportPreset::podcast_mono()),
"lossless_rgb" => Some(ExportPreset::lossless_rgb()),
"web_h264" => Some(ExportPreset::web_h264()),
_ => None,
}
}
fn main() {
let mut args = std::env::args().skip(1);
let mut input = None::<String>;
let mut output = None::<String>;
let mut preset_name = "youtube_1080p".to_string();
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()),
"--preset" | "-p" => {
preset_name = args.next().unwrap_or_else(|| "youtube_1080p".to_string());
}
other => {
eprintln!("Unknown flag: {other}");
process::exit(1);
}
}
}
let input = input.unwrap_or_else(|| {
eprintln!(
"Usage: export_preset --input <file> --output <file> \
[--preset youtube_1080p|youtube_4k|twitter|instagram_square|\
instagram_reels|bluray_1080p|podcast_mono|lossless_rgb|web_h264]"
);
process::exit(1);
});
let output = output.unwrap_or_else(|| {
eprintln!("--output is required");
process::exit(1);
});
let preset = preset_by_name(&preset_name).unwrap_or_else(|| {
eprintln!("Unknown preset: {preset_name}");
process::exit(1);
});
let expect_video = preset.video.is_some();
println!("Preset: {} (video={})", preset.name, expect_video);
println!("Input: {input}");
println!("Output: {output}");
println!();
let (width, height, fps) = if expect_video {
match VideoDecoder::open(&input).build() {
Ok(dec) => (dec.width(), dec.height(), dec.frame_rate()),
Err(e) => {
eprintln!("Error probing video: {e}");
process::exit(1);
}
}
} else {
(0, 0, 0.0)
};
let builder = VideoEncoder::create(&output);
let builder = preset.apply_video(builder);
let builder = if expect_video {
builder.video(width, height, fps)
} else {
builder
};
let builder = preset.apply_audio(builder);
let mut encoder = match builder.build() {
Ok(e) => e,
Err(e) => {
eprintln!("Error building encoder: {e}");
process::exit(1);
}
};
println!("Encoding…");
if expect_video {
let mut vdec = match VideoDecoder::open(&input).build() {
Ok(d) => d,
Err(e) => {
eprintln!("Error opening video decoder: {e}");
process::exit(1);
}
};
let mut frames: u64 = 0;
loop {
match vdec.decode_one() {
Ok(Some(frame)) => {
if let Err(e) = encoder.push_video(&frame) {
eprintln!("Video encode error: {e}");
process::exit(1);
}
frames += 1;
}
Ok(None) => break,
Err(e) => {
eprintln!("Video decode error: {e}");
process::exit(1);
}
}
}
println!(" Video: {frames} frame(s)");
}
let sr = preset.audio.sample_rate;
let ch = preset.audio.channels;
let mut adec = match AudioDecoder::open(&input)
.output_format(SampleFormat::F32)
.output_sample_rate(sr)
.build()
{
Ok(d) => d,
Err(e) => {
eprintln!("Error opening audio decoder: {e}");
process::exit(1);
}
};
let _ = ch; let mut audio_frames: u64 = 0;
loop {
match adec.decode_one() {
Ok(Some(frame)) => {
if let Err(e) = encoder.push_audio(&frame) {
eprintln!("Audio encode error: {e}");
process::exit(1);
}
audio_frames += 1;
}
Ok(None) => break,
Err(e) => {
eprintln!("Audio decode error: {e}");
process::exit(1);
}
}
}
println!(" Audio: {audio_frames} frame(s)");
if let Err(e) = encoder.finish() {
eprintln!("Error finalising output: {e}");
process::exit(1);
}
let size = match std::fs::metadata(&output) {
Ok(m) => {
#[allow(clippy::cast_precision_loss)]
let kb = m.len() as f64 / 1024.0;
if kb < 1024.0 {
format!("{kb:.0} KB")
} else {
format!("{:.1} MB", kb / 1024.0)
}
}
Err(_) => "(unknown size)".to_string(),
};
println!("Done. {output} {size}");
}