1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
//! CLI-compat layer (`cli` feature): run or translate a strict, documented
//! subset of ffmpeg command lines in-process.
//!
//! Two capabilities share one typed intermediate representation:
//!
//! - **Run**: [`from_cli_args`] / [`from_cli`] parse a command and build a
//! ready-to-start [`FfmpegContext`] on the crate's native pipeline.
//! - **Translate**: [`emit_rust_code_from_args`] / [`emit_rust_code`] turn
//! the same command into a complete, compile-ready builder program to copy
//! into your codebase.
//!
//! ```no_run
//! use ez_ffmpeg::cli::from_cli_args;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! from_cli_args(&["-i", "in.mkv", "-c:v", "libx264", "-crf", "23",
//! "-preset", "fast", "-c:a", "aac", "-y", "out.mp4"])?
//! .start()?
//! .wait()?;
//! # Ok(())
//! # }
//! ```
//!
//! The layer is optional; enable it in `Cargo.toml`:
//!
//! ```toml
//! [dependencies.ez-ffmpeg]
//! version = "*"
//! features = ["cli"]
//! ```
//!
//! # The contract: classify everything, approximate nothing
//!
//! **Broad CLI compatibility is a non-goal.** The ffmpeg CLI is ~14k lines
//! of option machinery whose semantics move with every release; chasing it
//! wholesale produces the "runs, but subtly different" failures that destroy
//! trust. This layer does the opposite:
//!
//! - every argv token must classify against the versioned compatibility
//! manifest, or the ENTIRE command is rejected with a token-anchored,
//! typed diagnostic ([`CliError`]) — nothing is dropped or guessed;
//! - commands whose exact shape is backed by a semantic golden test (stream
//! identity, codecs, dimensions, durations, playlist topology compared
//! against the real ffmpeg CLI) are **verified**: they may execute;
//! - commands that parse and match an ENUMERATED unverified manifest entry
//! are **emit-only**: the code generators label their output "unverified
//! scaffolding" and [`from_cli_args`] refuses to run them;
//! - commands that parse but match neither manifest table are **rejected
//! outright** ([`CliError::UnmatchedShape`]) — run and emit alike; no
//! silent scaffolding class exists;
//! - execution additionally requires a verified runtime profile of the
//! linked FFmpeg (currently 7.1 only; 8.1 joins once its version-matched
//! golden lane passes) — anything else fails with the typed
//! [`CliError::UnverifiedRuntimeProfile`] before any I/O.
//!
//! CLI-initiated pipelines also run with strict AVOption handling: an option
//! no component consumed fails the run (fftools `check_avoptions` parity)
//! instead of the default builder path's warning.
//!
//! # The manifest (generated; revision-pinned by an exact-equality test)
//!
//! <!-- manifest:begin -->
//! Manifest revision 4; dialect: ffmpeg 7.1 command line.
//!
//! | option | scope | selector | repeat | notes | maps to |
//! |---|---|---|---|---|---|
//! | `-y` | global | run | repeatable | flag | mandatory overwrite gate |
//! | `-hide_banner` | global | no-op | repeatable | accepted, no in-process effect | none (documented no-op) |
//! | `-nostdin` | global | no-op | repeatable | accepted, no in-process effect | none (documented no-op) |
//! | `-stats` | global | no-op | repeatable | accepted, no in-process effect | none (documented no-op) |
//! | `-nostats` | global | no-op | repeatable | accepted, no in-process effect | none (documented no-op) |
//! | `-loglevel` | global | no-op | repeatable | no in-process effect; value: `[repeat+][level+]LEVEL` | none (documented no-op) |
//! | `-v` | global | no-op | repeatable | no in-process effect; value: `[repeat+][level+]LEVEL` | none (documented no-op) |
//! | `-ss` | input or output (position-scoped) | container | once | decimal seconds only (`10`, `2.5`) | Input::set_start_time_us / Output::set_start_time_us |
//! | `-t` | input or output (position-scoped) | container | once | decimal seconds only (`10`, `2.5`) | Input::set_recording_time_us / Output::set_recording_time_us |
//! | `-to` | input or output (position-scoped) | container | once | decimal seconds only (`10`, `2.5`) | Input::set_stop_time_us / Output::set_stop_time_us |
//! | `-f` | input or output (position-scoped) | container | once | container/demuxer name (`[a-z0-9_]+`) | Input::set_format / Output::set_format |
//! | `-vn` | output | video | repeatable | flag | Output::disable_video |
//! | `-an` | output | audio | repeatable | flag | Output::disable_audio |
//! | `-c:v` | output | video | once | codec name or `copy` (`[A-Za-z0-9_-]+`) | Output::set_video_codec |
//! | `-c:a` | output | audio | once | codec name or `copy` (`[A-Za-z0-9_-]+`) | Output::set_audio_codec |
//! | `-b:v` | output | video | once | `NNN` with optional `k`/`K`/`m`/`M` suffix | Output::set_video_bitrate |
//! | `-b:a` | output | audio | once | `NNN` with optional `k`/`K`/`m`/`M` suffix | Output::set_audio_bitrate |
//! | `-crf` | output | video | once | integer 0..=51 | Output::set_video_codec_opt("crf", …), libx264 only |
//! | `-preset` | output | video | once | x264 preset name | Output::set_video_codec_opt("preset", …), libx264 only |
//! | `-pix_fmt` | output | video | once | pixel format name (`[a-z0-9_]+`) | Output::set_pix_fmt |
//! | `-ar` | output | audio | once | positive integer | Output::set_audio_sample_rate |
//! | `-ac` | output | audio | once | positive integer | Output::set_audio_channels |
//! | `-frames:v` | output | video | once | exactly `1` | Output::set_max_video_frames(1) |
//! | `-vf` | output | video | once | single `scale=…` chain only | Output::set_video_filter |
//! | `-map` | output | stream map | accumulates | basic index maps only (`0`, `0:v`, `0:a:1`, `0:1`, …) | Output::add_stream_map / add_stream_map_with_copy |
//! | `-movflags` | output | container | once | exactly `+faststart` | Output::set_format_opt("movflags", "+faststart") |
//! | `-hls_time` | output | container | once | decimal seconds > 0 | Output::set_format_opt("hls_time", …) |
//! | `-hls_playlist_type` | output | container | once | exactly `vod` | Output::set_format_opt("hls_playlist_type", "vod") |
//! | `-hls_list_size` | output | container | once | exactly `0` | Output::set_format_opt("hls_list_size", "0") |
//! | `-hls_segment_filename` | output | container | once | non-empty path; `-`-leading values rejected | Output::set_format_opt("hls_segment_filename", …) |
//!
//! Command layout is fixed: exactly one `-i` input and exactly one output
//! path, in the canonical `[global/input options] -i INPUT [output options]
//! OUTPUT [global options]` order — after the output path only GLOBAL options
//! are accepted (e.g. a trailing `-y`). The `-` stdin/stdout pseudo-paths are
//! excluded — pipe I/O is process wiring, not part of the in-process subset.
//!
//! Verified shapes (may execute; each is backed by a semantic golden and a
//! compile-pinned emitted example):
//!
//! | id | shape | container |
//! |---|---|---|
//! | V1 | H.264/AAC transcode (crf + preset) | `.mp4` |
//! | V2 | re-encoded clip (input -ss, output -t) | `.mp4` |
//! | V3 | audio extract (-vn, AAC) | `.m4a` |
//! | V4 | single-frame thumbnail (input -ss, -an, mjpeg) | `.jpg` |
//! | V5 | scaled H.264/AAC transcode (-vf scale) | `.mp4` |
//! | V6 | single-rendition VOD HLS | `.m3u8` |
//!
//! Unverified entries (emit-only: code generation with a scaffolding banner,
//! execution refused):
//!
//! | id | shape |
//! |---|---|
//! | U1 | input-side trim (-ss + -t before -i) |
//! | U2 | faststart remux (explicit per-media copy) |
//! | U3 | scale with audio copy |
//! | U4 | thumbnail recipe shape (-vf + -frames:v) |
//! | U5 | audio extract via stream copy |
//! | U6 | PCM / resampled audio extract |
//! | U7 | partial HLS option set |
//! | U8 | mapped transcode (basic index maps) |
//! | U9 | mapped audio copy |
//! | U10 | cross-scope trim pair (input -t, output -to) |
//! | U11 | output-side seek transcode |
//! | U12 | bitrate-driven transcode |
//! | U13 | pixel-format transcode |
//! | U14 | explicit input format (e.g. lavfi) |
//! | U15 | container-defaults remux |
//! | U16 | video-codec-only transcode |
//! | U17 | transcode without preset |
//! | U18 | transcode with audio bitrate |
//! | U19 | audio+video codec selection only |
//! | U20 | transcode shape with unpinned values/container |
//! | U21 | clip shape with unpinned values/container |
//! | U22 | audio-extract shape with unpinned values/container |
//! | U23 | thumbnail shape with unpinned values/container |
//! | U24 | scaled-transcode shape with unpinned values/container |
//! | U25 | VOD HLS shape with unpinned values/container |
//!
//! Everything else — including parseable option sets outside both tables — is
//! rejected with a typed diagnostic.
//! <!-- manifest:end -->
//!
//! # String form
//!
//! [`from_cli`] / [`emit_rust_code`] accept one string and apply POSIX word
//! splitting ONLY: single/double quotes, backslash escapes,
//! backslash-newline continuation, plus caret-newline continuation as an
//! explicitly named cmd.exe compatibility extension. No variables, globs,
//! tilde, pipes, redirects, comments or command lists — those tokens are
//! rejected, never emulated (unquoted `*`, `?` and `[` included: quote or
//! escape a literal). Windows `cmd.exe` quoting is NOT reproduced:
//! commands relying on `CommandLineToArgvW` backslash rules must use the
//! argv form.
pub use ;
use crateFfmpegContext;
use ;
/// Builds a ready-to-start [`FfmpegContext`] from ffmpeg-style argv tokens.
///
/// This is the primary form: argv has zero quoting ambiguity (the same shape
/// `ffmpeg.wasm` chose). A leading `ffmpeg` / `ffmpeg.exe` token is
/// tolerated and stripped.
///
/// The command must classify completely against the compatibility manifest
/// AND match a verified (golden-backed) shape, and the linked FFmpeg must be
/// a verified runtime profile — otherwise a typed [`CliError`] is returned
/// before any I/O. See the [module docs](self) for the exact surface.
/// Single-string convenience wrapper over [`from_cli_args`].
///
/// Applies the documented POSIX word-splitting contract (see the
/// [module docs](self)); everything after tokenization is identical to the
/// argv form.
/// Translates ffmpeg-style argv tokens into a complete Rust program using
/// the ez-ffmpeg builder API.
///
/// Emission works for verified shapes and for the manifest's enumerated
/// unverified entries — including shapes that are
/// not verified for execution, whose output is prominently labeled
/// "unverified scaffolding".
///
/// The generated program and [`from_cli_args`] consume the same lowered
/// plan — same builder calls, same values, same order — but they are not
/// policy-identical. The runtime path layers three checks on top that the
/// emitted program deliberately does not carry:
///
/// - **runtime-profile gate**: [`from_cli_args`] refuses to execute on a
/// non-verified linked FFmpeg ([`CliError::UnverifiedRuntimeProfile`]);
/// the emitted program runs against whatever FFmpeg it is built with;
/// - **strict AVOption handling**: an in-process run fails on an option no
/// component consumed (fftools `check_avoptions` parity, set through a
/// crate-private flag); the emitted builder program only logs the
/// default warning;
/// - **unique-video-source prerequisite**: an in-process `-vf` run fails
/// unless the opened input has exactly one video stream
/// ([`CliError::AmbiguousFilterSource`]); the emitted program uses the
/// builder default, which score-selects a stream like the ffmpeg CLI.
/// Single-string convenience wrapper over [`emit_rust_code_from_args`],
/// using the same POSIX word-splitting contract as [`from_cli`].
/// Major.minor of the linked libavcodec and libavformat (in that order),
/// extracted from FFmpeg's packed version words.
/// Whether the LINKED libavcodec/libavformat pair matches a verified runtime
/// profile. Tests use this to stay honest on non-verified lanes: on a linked
/// 8.x build the correct expectation is the typed `UnverifiedRuntimeProfile`
/// failure, not runtime success.
pub
/// Runtime-profile gate: in-process execution is allowed only on linked
/// FFmpeg builds whose libavcodec/libavformat major.minor pairs match a
/// verified profile. Purely a version check — it runs before any I/O.