1use std::process::ExitCode;
5
6use rff::Engine;
7
8use crate::args::{self, Action};
9
10pub 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 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
69pub 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
79pub 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
86fn 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
108fn print_usage() {
110 eprintln!("Usage: ffmpeg [options] [-i input]... output (try `ffmpeg -h`)");
111}
112
113fn 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
133fn 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}