pub(crate) const CTA: &str =
"want this supported? open an issue: https://github.com/YeautyYE/ez-ffmpeg/issues";
fn fmt_at(index: &Option<usize>) -> String {
match index {
Some(index) => format!(" (token #{index})"),
None => String::new(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CliScope {
Global,
Input,
Output,
AfterOutput,
}
impl std::fmt::Display for CliScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CliScope::Global => write!(f, "global scope"),
CliScope::Input => write!(f, "input #0"),
CliScope::Output => write!(f, "output #0"),
CliScope::AfterOutput => {
write!(f, "after output #0 (would apply to a following output)")
}
}
}
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum CliError {
#[error("tokenize error at byte {offset}: {message}\n the string form implements POSIX word splitting only — no variables, globs, tilde, pipes, redirects, comments or command lists; pass an argv slice to from_cli_args to sidestep shell quoting entirely\n {CTA}")]
Tokenize {
message: String,
offset: usize,
},
#[error("unsupported option `{option}` (token #{index}, {scope})\n {reason}{}\n {CTA}", .hint.as_deref().map(|h| format!("\n {h}")).unwrap_or_default())]
UnsupportedOption {
option: String,
index: usize,
scope: CliScope,
reason: String,
hint: Option<String>,
},
#[error("unsupported value `{value}` for `{option}` (token #{index})\n {reason}\n {CTA}")]
UnsupportedValue {
option: String,
value: String,
index: usize,
reason: String,
},
#[error("unsupported command layout at token #{index} (`{token}`)\n {reason}\n {CTA}")]
UnsupportedLayout {
token: String,
index: usize,
reason: String,
},
#[error("conflicting options `{first}`{} and `{second}`{}\n {reason}\n {CTA}", fmt_at(.first_index), fmt_at(.second_index))]
ConflictingOptions {
first: String,
second: String,
first_index: Option<usize>,
second_index: Option<usize>,
reason: String,
},
#[error("missing mandatory `-y`\n without -y the ffmpeg CLI prompts before overwriting an existing output; this library always creates/truncates the output and cannot reproduce that prompt, so the subset requires an explicit -y\n {CTA}")]
MissingOverwriteFlag,
#[error("command shape is not verified for execution\n parsed options: [{}]\n only shapes backed by a semantic golden may run; use emit_rust_code / emit_rust_code_from_args to generate unverified scaffolding code instead\n {CTA}", .parsed_options.join(", "))]
NotVerified {
parsed_options: Vec<String>,
},
#[error("command shape is not in the compatibility manifest\n parsed options: [{}]\n neither a verified shape nor a documented emit-only entry; nothing is generated for unenumerated shapes\n {CTA}", .parsed_options.join(", "))]
UnmatchedShape {
parsed_options: Vec<String>,
},
#[error("-vf requires an input with exactly one video stream; this input has {video_streams}\n the ffmpeg CLI would score-select one stream to filter; the subset only executes filters over a structurally unique source\n {CTA}")]
AmbiguousFilterSource {
video_streams: usize,
},
#[error("linked FFmpeg is not a verified runtime profile\n linked: libavcodec {linked_avcodec}, libavformat {linked_avformat}; verified profiles: {verified}\n emit_rust_code still works — only in-process execution is gated\n {CTA}")]
UnverifiedRuntimeProfile {
linked_avcodec: String,
linked_avformat: String,
verified: String,
},
#[error("building the pipeline failed: {0}")]
Build(
#[from]
crate::error::Error,
),
}