1use std::process::ExitCode;
10
11use rff::probe::MediaInfo;
12use rff::Engine;
13
14pub fn run(args: Vec<String>) -> ExitCode {
16 let mut hide_banner = false;
17 let mut want_version = false;
18 let mut as_json = false;
19 let mut show_targets = false;
20 let mut input: Option<String> = None;
21
22 let mut i = 0;
23 while i < args.len() {
24 let arg = &args[i];
25 match arg.as_str() {
26 "-hide_banner" => hide_banner = true,
27 "-version" => want_version = true,
28 "-show_format" | "-show_streams" | "-show_entries" => {}
30 "-show_targets" => show_targets = true,
31 "-of" | "-print_format" => {
32 i += 1;
33 as_json = args.get(i).map(|v| v == "json").unwrap_or(false);
34 }
35 "-v" | "-loglevel" => {
36 i += 1; }
38 "-i" => {
39 i += 1;
40 input = args.get(i).cloned();
41 }
42 other if !other.starts_with('-') => input = Some(other.to_string()),
43 other => eprintln!("[warning] unrecognized option `{other}` (ignored)"),
44 }
45 i += 1;
46 }
47
48 if !hide_banner {
49 eprint!("{}", crate::ffmpeg::banner());
50 }
51 if want_version {
52 crate::ffmpeg::print_version();
53 return ExitCode::SUCCESS;
54 }
55
56 let Some(input) = input else {
57 eprintln!("[error] no input file specified. Usage: ffprobe [options] INPUT");
58 return ExitCode::FAILURE;
59 };
60
61 let engine = Engine::new();
62 if show_targets {
63 return match rff::targets::targets(&engine, &input) {
64 Ok(plan) => {
65 if as_json {
66 println!("{}", plan.to_json());
67 } else {
68 print_targets(&input, &plan);
69 }
70 ExitCode::SUCCESS
71 }
72 Err(err) => {
73 eprintln!("[error] {input}: {err}");
74 ExitCode::FAILURE
75 }
76 };
77 }
78 match rff::probe::probe(&engine, &input) {
79 Ok(info) => {
80 if as_json {
81 print_json(&input, &info);
82 } else {
83 print_human(&input, &info);
84 }
85 ExitCode::SUCCESS
86 }
87 Err(err) => {
88 eprintln!("[error] {input}: {err}");
89 ExitCode::FAILURE
90 }
91 }
92}
93
94fn print_targets(input: &str, plan: &rff::targets::Plan) {
100 let stem = std::path::Path::new(input)
101 .file_stem()
102 .and_then(|s| s.to_str())
103 .unwrap_or("out");
104
105 println!(
106 "Input #0, {}, from '{input}':",
107 plan.source_format.as_deref().unwrap_or("?")
108 );
109 for s in &plan.source {
110 println!(" Stream #0:{}: {}: {}", s.index, s.media_type, s.codec_id);
111 }
112
113 println!(
114 "
115Convert to ({} targets):",
116 plan.targets.len()
117 );
118 let mut kind = None;
119 for t in &plan.targets {
120 if kind != Some(t.kind) {
121 println!(
122 "
123 {}",
124 t.kind
125 );
126 kind = Some(t.kind);
127 }
128 println!(
129 " .{:<6} {:<9} {}",
130 t.extension,
131 t.fidelity,
132 t.stream_summary()
133 );
134 for note in &t.notes {
135 println!(" ! {note}");
136 }
137 println!(" $ {}", t.command(input, stem));
138 }
139}
140
141fn print_human(input: &str, info: &MediaInfo) {
143 println!("Input #0, {}, from '{}':", info.format_name, input);
144 for stream in &info.streams {
145 let detail = match stream.media_type {
146 rff_core::MediaType::Video => {
147 format!(
148 "Video: {}, {}x{}",
149 stream.codec_id, stream.width, stream.height
150 )
151 }
152 rff_core::MediaType::Audio => format!(
153 "Audio: {}, {} Hz, {} ch",
154 stream.codec_id, stream.sample_rate, stream.channels
155 ),
156 other => format!("{other}: {}", stream.codec_id),
157 };
158 println!(" Stream #0:{}: {detail}", stream.index);
159 }
160}
161
162fn print_json(input: &str, info: &MediaInfo) {
164 println!("{{");
165 println!(" \"format\": {{");
166 println!(" \"filename\": {},", json_str(input));
167 println!(" \"format_name\": {},", json_str(&info.format_name));
168 println!(" \"nb_streams\": {}", info.streams.len());
169 println!(" }},");
170 println!(" \"streams\": [");
171 for (idx, stream) in info.streams.iter().enumerate() {
172 let comma = if idx + 1 < info.streams.len() {
173 ","
174 } else {
175 ""
176 };
177 println!(" {{");
178 println!(" \"index\": {},", stream.index);
179 println!(
180 " \"codec_type\": {},",
181 json_str(&stream.media_type.to_string())
182 );
183 println!(
184 " \"codec_name\": {},",
185 json_str(stream.codec_id.name())
186 );
187 println!(" \"width\": {},", stream.width);
188 println!(" \"height\": {},", stream.height);
189 println!(" \"sample_rate\": {},", stream.sample_rate);
190 println!(" \"channels\": {}", stream.channels);
191 println!(" }}{comma}");
192 }
193 println!(" ]");
194 println!("}}");
195}
196
197fn json_str(s: &str) -> String {
199 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
200 format!("\"{escaped}\"")
201}