Skip to main content

rff_cli/
ffmpeg.rs

1//! The `ffmpeg` binary's logic: parse args, print the usual banners/listings,
2//! and dispatch transcode jobs into the engine.
3
4use std::process::ExitCode;
5
6use rff::Engine;
7
8use crate::args::{self, Action};
9
10/// Entry point used by `src/bin/ffmpeg.rs`. `args` is everything after argv[0].
11pub fn run(args: Vec<String>) -> ExitCode {
12    let cli = match args::parse(&args) {
13        Ok(cli) => cli,
14        Err(err) => {
15            eprintln!("Error parsing options: {err}");
16            return ExitCode::FAILURE;
17        }
18    };
19
20    // FFmpeg prints its banner to stderr unless -hide_banner.
21    if !cli.hide_banner {
22        eprint!("{}", banner());
23    }
24    for warning in &cli.warnings {
25        eprintln!("[warning] {warning}");
26    }
27
28    let engine = Engine::new();
29
30    match cli.action {
31        Action::Version => {
32            print_version();
33            ExitCode::SUCCESS
34        }
35        Action::Help => {
36            print_help();
37            ExitCode::SUCCESS
38        }
39        Action::ListCodecs => {
40            print_codecs(&engine);
41            ExitCode::SUCCESS
42        }
43        Action::ListFormats => {
44            print_formats(&engine);
45            ExitCode::SUCCESS
46        }
47        Action::Transcode(spec) => {
48            if spec.inputs.is_empty() && spec.outputs.is_empty() {
49                print_usage();
50                return ExitCode::FAILURE;
51            }
52            match rff::transcode::run(&engine, &spec) {
53                Ok(report) => {
54                    eprintln!(
55                        "done — {} packet(s) written, {} frame(s) decoded",
56                        report.packets_written, report.frames_decoded
57                    );
58                    ExitCode::SUCCESS
59                }
60                Err(err) => {
61                    eprintln!("[error] {err}");
62                    ExitCode::FAILURE
63                }
64            }
65        }
66    }
67}
68
69/// The startup banner. Deliberately NOT branded as "FFmpeg" — see the trademark
70/// note in the README.
71pub fn banner() -> String {
72    format!(
73        "remade_ffmpeg_rs {ver} (ffmpeg-compatible CLI) — Remade With Rust, by Mata Network\n\
74         Not affiliated with or endorsed by the FFmpeg project. \"FFmpeg\" is a trademark of Fabrice Bellard.\n",
75        ver = rff::VERSION,
76    )
77}
78
79/// `-version` output (to stdout).
80pub fn print_version() {
81    println!("remade_ffmpeg_rs version {}", rff::VERSION);
82    println!("A clean-room, permissively-licensed media toolkit written in Rust.");
83    println!("Apache-2.0. https://github.com/Remade-With-Rust/remade_ffmpeg_rs");
84}
85
86/// Minimal `-h` text.
87fn print_help() {
88    println!("{}", banner());
89    println!("Usage: ffmpeg [options] [-i input]... [output options] output\n");
90    println!("Common options:");
91    println!("  -i FILE            add an input file (repeatable)");
92    println!("  -f FMT             force container format for the next input/output");
93    println!("  -c:v CODEC         video codec for the output (e.g. h264, copy)");
94    println!("  -c:a CODEC         audio codec for the output (e.g. opus, copy)");
95    println!("  -b:v / -b:a RATE   target bitrate (e.g. 2M, 128k)");
96    println!("  -preset P          encoder speed/quality (h264: fast|medium|slow)");
97    println!("  -profile P         encoder profile (h264: baseline|main|high)");
98    println!("  -g N               keyframe interval in frames");
99    println!("  -vf FILTERS        video filter graph (e.g. scale=640:480, crop=W:H:X:Y)");
100    println!("  -map SPEC          select streams: INPUT[:v|:a|:N] (repeatable)");
101    println!("  -y / -n            overwrite / never overwrite the output");
102    println!("  -codecs            list supported codecs");
103    println!("  -formats           list supported container formats");
104    println!("  -hide_banner       suppress the startup banner");
105    println!("  -version           print version and exit");
106}
107
108/// One-line usage hint, printed when invoked with nothing to do.
109fn print_usage() {
110    eprintln!("Usage: ffmpeg [options] [-i input]... output   (try `ffmpeg -h`)");
111}
112
113/// `ffmpeg -codecs`: list every registered codec with its capabilities.
114fn print_codecs(engine: &Engine) {
115    println!("Codecs:");
116    println!(" D. = decoding supported");
117    println!(" .E = encoding supported");
118    println!(" -----");
119    let mut codecs: Vec<_> = engine.codecs.iter().collect();
120    codecs.sort_by_key(|c| c.name);
121    for codec in codecs {
122        let d = if codec.can_decode() { 'D' } else { '.' };
123        let e = if codec.can_encode() { 'E' } else { '.' };
124        println!(
125            " {d}{e} {:<9} {:<8} {}",
126            codec.media_type.to_string(),
127            codec.name,
128            codec.long_name
129        );
130    }
131}
132
133/// `ffmpeg -formats`: list every registered container format.
134fn print_formats(engine: &Engine) {
135    println!("File formats:");
136    println!(" D. = demuxing supported");
137    println!(" .E = muxing supported");
138    println!(" -----");
139    let mut formats: Vec<_> = engine.formats.iter().collect();
140    formats.sort_by_key(|f| f.name);
141    for format in formats {
142        let d = if format.can_demux() { 'D' } else { '.' };
143        let e = if format.can_mux() { 'E' } else { '.' };
144        println!(" {d}{e} {:<8} {}", format.name, format.long_name);
145    }
146}