use std::path::PathBuf;
use std::process::ExitCode;
use anyhow::Result;
use clap::{Parser, Subcommand, ValueEnum};
use tracing_subscriber::EnvFilter;
use rivet::spec::{AudioCodecPolicy, BitDepth, ChunkSeamMode, ColorPolicy, GpuFamily};
mod commands;
#[derive(Clone, Copy, ValueEnum)]
pub(crate) enum ModeArg {
Single,
Hls,
}
#[derive(Clone, Copy, ValueEnum)]
pub(crate) enum AudioArg {
Auto,
Opus,
Drop,
}
impl From<AudioArg> for AudioCodecPolicy {
fn from(a: AudioArg) -> Self {
match a {
AudioArg::Auto => AudioCodecPolicy::Auto,
AudioArg::Opus => AudioCodecPolicy::ForceOpus,
AudioArg::Drop => AudioCodecPolicy::Drop,
}
}
}
#[derive(Clone, Copy, ValueEnum)]
pub(crate) enum GpuFamilyArg {
Nvidia,
Amd,
Intel,
}
impl From<GpuFamilyArg> for GpuFamily {
fn from(a: GpuFamilyArg) -> Self {
match a {
GpuFamilyArg::Nvidia => GpuFamily::Nvidia,
GpuFamilyArg::Amd => GpuFamily::Amd,
GpuFamilyArg::Intel => GpuFamily::Intel,
}
}
}
#[derive(Clone, Copy, ValueEnum)]
pub(crate) enum ColorArg {
Sdr,
Hdr10,
Hlg,
Passthrough,
}
impl From<ColorArg> for ColorPolicy {
fn from(a: ColorArg) -> Self {
match a {
ColorArg::Sdr => ColorPolicy::TonemapToSdr,
ColorArg::Hdr10 => ColorPolicy::Hdr10,
ColorArg::Hlg => ColorPolicy::Hlg,
ColorArg::Passthrough => ColorPolicy::Passthrough,
}
}
}
#[derive(Clone, Copy, ValueEnum)]
pub(crate) enum PixelArg {
Auto,
#[value(name = "8bit")]
Eight,
#[value(name = "10bit")]
Ten,
}
impl From<PixelArg> for BitDepth {
fn from(a: PixelArg) -> Self {
match a {
PixelArg::Auto => BitDepth::Auto,
PixelArg::Eight => BitDepth::EightBit,
PixelArg::Ten => BitDepth::TenBit,
}
}
}
#[derive(Clone, Copy, ValueEnum)]
pub(crate) enum SeamArg {
Parallel,
Constqp,
Serial,
}
impl From<SeamArg> for ChunkSeamMode {
fn from(a: SeamArg) -> Self {
match a {
SeamArg::Parallel => ChunkSeamMode::Parallel,
SeamArg::Constqp => ChunkSeamMode::ParallelConstQp,
SeamArg::Serial => ChunkSeamMode::Serial,
}
}
}
#[derive(Parser)]
#[command(
name = "rivet",
version,
about = "Modular GPU-accelerated video transcoder (AV1 + Opus).",
long_about = None
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Transcode {
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, value_enum, default_value = "single")]
mode: ModeArg,
#[arg(long = "rung", value_name = "WxH")]
rungs: Vec<String>,
#[arg(long)]
ladder: bool,
#[arg(long)]
max_short_side: Option<u32>,
#[arg(long, default_value_t = 4.0)]
segment_seconds: f32,
#[arg(long)]
crf: Option<u8>,
#[arg(long)]
speed: Option<u8>,
#[arg(long, value_enum, default_value = "auto")]
audio: AudioArg,
#[arg(long)]
max_fps: Option<f64>,
#[arg(long)]
gpu: Option<u32>,
#[arg(long)]
single_gpu: bool,
#[arg(long, value_enum)]
gpu_family: Option<GpuFamilyArg>,
#[arg(long, default_value = "auto")]
decode_gpu: rivet::DecodePolicy,
#[arg(long, value_enum, default_value = "sdr")]
color: ColorArg,
#[arg(long, value_enum, default_value = "auto")]
pixel_format: PixelArg,
#[arg(long = "seam-mode", value_enum, default_value = "parallel")]
seam_mode: SeamArg,
#[arg(long)]
filter: Option<String>,
#[arg(long)]
codec: Option<String>,
#[arg(long)]
trim_start: Option<f64>,
#[arg(long)]
trim_end: Option<f64>,
},
Splice {
#[arg(short, long)]
output: PathBuf,
#[arg(required = true)]
clips: Vec<String>,
#[arg(long, value_enum, default_value = "single")]
mode: ModeArg,
#[arg(long, default_value_t = 4.0)]
segment_seconds: f32,
#[arg(long)]
codec: Option<String>,
#[arg(long)]
crf: Option<u8>,
#[arg(long, value_enum, default_value = "auto")]
audio: AudioArg,
#[arg(long, default_value = "auto")]
decode_gpu: rivet::DecodePolicy,
},
Probe {
input: PathBuf,
#[arg(long)]
json: bool,
},
Devices {
#[arg(long)]
json: bool,
},
#[command(visible_alias = "caps")]
Capabilities {
#[arg(long)]
json: bool,
},
Pipe {
#[arg(long)]
crf: Option<u8>,
#[arg(long)]
speed: Option<u8>,
#[arg(long, value_enum)]
audio: Option<AudioArg>,
#[arg(long, value_enum)]
color: Option<ColorArg>,
#[arg(long = "bit-depth", visible_alias = "pixel-format", value_enum)]
bit_depth: Option<PixelArg>,
#[arg(long = "max-fps")]
max_fps: Option<f64>,
#[arg(long)]
width: Option<u32>,
#[arg(long)]
height: Option<u32>,
#[arg(long)]
gpu: Option<u32>,
#[arg(long)]
filter: Option<String>,
},
#[cfg(feature = "ipc")]
Ipc {
#[arg(long)]
socket: PathBuf,
},
#[cfg(feature = "batch")]
Batch {
manifest: PathBuf,
#[arg(long)]
dry_run: bool,
#[arg(long)]
stop_on_error: bool,
},
#[cfg(feature = "server")]
Serve {
#[arg(long, default_value = "127.0.0.1:8080")]
addr: String,
},
}
fn main() -> ExitCode {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
.with_writer(std::io::stderr)
.init();
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("error: {e:#}");
ExitCode::FAILURE
}
}
}
fn run() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Command::Transcode {
input,
output,
mode,
rungs,
ladder,
max_short_side,
segment_seconds,
crf,
speed,
audio,
max_fps,
gpu,
single_gpu,
gpu_family,
decode_gpu,
color,
pixel_format,
seam_mode,
filter,
codec,
trim_start,
trim_end,
} => commands::transcode::run(commands::transcode::TranscodeArgs {
input,
output,
mode,
rungs,
ladder,
max_short_side,
segment_seconds,
crf,
speed,
audio,
max_fps,
gpu,
single_gpu,
gpu_family,
decode_gpu,
color,
pixel_format,
seam_mode,
filter,
codec,
trim_start,
trim_end,
}),
Command::Splice {
output,
clips,
mode,
segment_seconds,
codec,
crf,
audio,
decode_gpu,
} => commands::splice::run(output, clips, mode, segment_seconds, codec, crf, audio, decode_gpu),
Command::Probe { input, json } => commands::probe::run(input, json),
Command::Devices { json } => {
commands::devices::run(json);
Ok(())
}
Command::Capabilities { json } => {
commands::capabilities::run(json);
Ok(())
}
Command::Pipe {
crf,
speed,
audio,
color,
bit_depth,
max_fps,
width,
height,
gpu,
filter,
} => commands::pipe::run(commands::pipe::PipeArgs {
crf,
speed,
audio,
color,
bit_depth,
max_fps,
width,
height,
gpu,
filter,
}),
#[cfg(feature = "ipc")]
Command::Ipc { socket } => commands::ipc::run(&socket),
#[cfg(feature = "batch")]
Command::Batch {
manifest,
dry_run,
stop_on_error,
} => commands::batch::run(&manifest, dry_run, stop_on_error),
#[cfg(feature = "server")]
Command::Serve { addr } => commands::serve::run(addr),
}
}