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!("
114Convert to ({} targets):", plan.targets.len());
115 let mut kind = None;
116 for t in &plan.targets {
117 if kind != Some(t.kind) {
118 println!("
119 {}", t.kind);
120 kind = Some(t.kind);
121 }
122 println!(
123 " .{:<6} {:<9} {}",
124 t.extension,
125 t.fidelity,
126 t.stream_summary()
127 );
128 for note in &t.notes {
129 println!(" ! {note}");
130 }
131 println!(" $ {}", t.command(input, stem));
132 }
133}
134
135fn print_human(input: &str, info: &MediaInfo) {
137 println!("Input #0, {}, from '{}':", info.format_name, input);
138 for stream in &info.streams {
139 let detail = match stream.media_type {
140 rff_core::MediaType::Video => {
141 format!(
142 "Video: {}, {}x{}",
143 stream.codec_id, stream.width, stream.height
144 )
145 }
146 rff_core::MediaType::Audio => format!(
147 "Audio: {}, {} Hz, {} ch",
148 stream.codec_id, stream.sample_rate, stream.channels
149 ),
150 other => format!("{other}: {}", stream.codec_id),
151 };
152 println!(" Stream #0:{}: {detail}", stream.index);
153 }
154}
155
156fn print_json(input: &str, info: &MediaInfo) {
158 println!("{{");
159 println!(" \"format\": {{");
160 println!(" \"filename\": {},", json_str(input));
161 println!(" \"format_name\": {},", json_str(&info.format_name));
162 println!(" \"nb_streams\": {}", info.streams.len());
163 println!(" }},");
164 println!(" \"streams\": [");
165 for (idx, stream) in info.streams.iter().enumerate() {
166 let comma = if idx + 1 < info.streams.len() {
167 ","
168 } else {
169 ""
170 };
171 println!(" {{");
172 println!(" \"index\": {},", stream.index);
173 println!(
174 " \"codec_type\": {},",
175 json_str(&stream.media_type.to_string())
176 );
177 println!(
178 " \"codec_name\": {},",
179 json_str(stream.codec_id.name())
180 );
181 println!(" \"width\": {},", stream.width);
182 println!(" \"height\": {},", stream.height);
183 println!(" \"sample_rate\": {},", stream.sample_rate);
184 println!(" \"channels\": {}", stream.channels);
185 println!(" }}{comma}");
186 }
187 println!(" ]");
188 println!("}}");
189}
190
191fn json_str(s: &str) -> String {
193 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
194 format!("\"{escaped}\"")
195}