interface TokenDefinitionBase { label: string; description: string; required?: boolean; /** * Whether an optional token starts switched on. Required tokens are always * on; optional ones default to off. */ defaultEnabled?: boolean; multivalue?: boolean; delimiter?: string; defaultValue?: string; helpLink?: string; } export interface TokenDefText extends TokenDefinitionBase { type: "text"; placeholder?: string; } export interface TokenDefNumber extends TokenDefinitionBase { type: "number"; min?: number; max?: number; step?: number; } export interface FileFilter { name: string; extensions: string[]; } export interface TokenDefOpen extends TokenDefinitionBase { type: "open"; filters: FileFilter[]; } export interface TokenDefSave extends TokenDefinitionBase { type: "save"; filters: FileFilter[]; defaultPath?: string; } /** A folder picker. The chosen folder already exists (the dialog ensures it). */ export interface TokenDefDirectory extends TokenDefinitionBase { type: "directory"; } export interface TokenDefUrl extends TokenDefinitionBase { type: "url"; } export interface SelectOption { label: string; value: string; } export interface TokenDefSelect extends TokenDefinitionBase { type: "select"; options: SelectOption[]; } /** * An on/off switch with no value of its own. It is "filled" while switched on, * so it's used from `when` conditions, e.g. * `{ "arg": "-an", "when": { "remove-audio": true } }`. */ export interface TokenDefFlag extends TokenDefinitionBase { type: "flag"; } /** A sub-field of a composite token. Composites don't nest. */ export type CompositeFieldDef = TokenDefText | TokenDefNumber | TokenDefSelect; export interface TokenDefComposite extends TokenDefinitionBase { type: "composite"; /** Named sub-fields, keyed by the name used in `template`. */ fields: Record; /** Mini-template assembling the fields, e.g. "{{w}}x{{h}}{{flag}}". */ template: string; } export type TokenDef = | TokenDefText | TokenDefNumber | TokenDefOpen | TokenDefSave | TokenDefDirectory | TokenDefUrl | TokenDefSelect | TokenDefFlag | TokenDefComposite; export type TokenDefType = TokenDef["type"]; /** Token types whose value is a filesystem path. */ export const PATH_TOKEN_TYPES: readonly TokenDefType[] = [ "open", "save", "directory", ]; /** * Include an arg only when every listed token matches: * - `true` / `false`: the token is filled (switched on and non-empty) or not; * - a string or list of strings: the token is filled with one of these values. */ export type Condition = Record; /** * One entry in a recipe's `args`. * * A plain string is split on whitespace into shell words; each word is * interpolated and then shell-quoted as a whole, so authors write the literal * argv (no hand-quoting) and a word like `{{dir}}/frame_%04d.png` stays one * argument. * * The object form adds a `when` condition, and/or builds one word out of * optional `parts` joined by `join` (e.g. an ffmpeg filter chain joined by * ","). With `parts`, `arg` holds the words that precede the joined word * (e.g. "-vf"). Parts follow the same inclusion rules as args. */ export type ArgSpec = | string | { arg?: string; when?: Condition; parts?: ArgSpec[]; join?: string; }; export type ArgObject = Exclude; export interface Recipe { name: string; description: string; hidden?: boolean; /** * Only list the recipe on this platform, matched like a doc's `platform` * frontmatter: "windows", "macos", "linux" (Linux and the BSDs), or "unix" * (macOS plus the Linux family). Omit it to list the recipe everywhere. */ platform?: string; base: string; args: ArgSpec[]; tokens: Record; }