vedit

vedit is a compact postfix language that compiles linear video edits into one FFmpeg -filter_complex graph.

Build and run

make
make test
./vedit -n example.edit output.mp4  # compile and print only
./vedit example.edit output.mp4     # print, then execute FFmpeg

Override the built-in -c:v libx264 -c:a aac output encoding arguments by repeating --ffmpeg-arg once for each exact FFmpeg argv element:

./vedit -n \
  --ffmpeg-arg -c:v \
  --ffmpeg-arg libx265 \
  --ffmpeg-arg -crf \
  --ffmpeg-arg 18 \
  --ffmpeg-arg -preset \
  --ffmpeg-arg slow \
  --ffmpeg-arg -c:a \
  --ffmpeg-arg libopus \
  example.edit output.mkv

Providing any --ffmpeg-arg replaces both defaults completely. Arguments are inserted as final output options before generated metadata and the output path. They are passed directly to FFmpeg without shell evaluation and are reproduced exactly by -n; managed subtitle codec arguments are still generated separately.

Quoted strings may contain positional substitutions. Given video "$1" in a programme, run:

./vedit edit.txt output.mp4 "some input file.mp4"

Arguments after the output path are exposed as $1, $2, and so on. Substitutions may occur inside larger strings, such as video "clips/$1.mp4". Values are never interpreted by a shell.

ffprobe and ffmpeg must be available on PATH for normal CLI use.

The command printed by -n is self-contained, including codec-aware subtitle processing, cue clipping, concat-list creation, chapter metadata generation, and embedded-font extraction. Those steps appear as private helper invocations using the absolute path of the current vedit executable, so keep that executable available when running a saved command later.

Language

Sources and literals push values:

video "picture.mov"
audio "dialogue.wav"
combined "clip.mp4"
subtitles "captions.srt"
duration 250ms
duration 01:23.500
scalar 0.7
index 0
burn-style "font=sans-serif; size=42; color=#FFFFFF"
string "Japanese"
attachment "font.ttf"

Paths are quoted and may contain spaces or #. Outside quotes, # begins a comment. There is one instruction per line.

Stack operations are dup, drop, swap, over, and rot. Media operations are:

get-video  get-audio  get-subtitles split length
slice      take       concat delay
mux        attach-subtitles video-to-combined burn-subtitles
set-language set-title set-default set-forced set-tag
chapter add-chapter drop-chapter move-chapter
add-attachment drop-attachment move-attachment
volume     mix        silence
speed      fade-in    fade-out

The language also provides compile-time Boolean logic, typed scalar/duration arithmetic and comparisons, structured if/else/end, and bounded begin/while/repeat loops. Loops are fully unrolled while constructing the FFmpeg graph and are limited to 10,000 iterations.

The separate typecheck pass records each instruction's consumed and produced types without modifying the parsed instruction array. COMBINED values retain ordered collections of every video, audio, and subtitle track in their source, as well as chapters, container tags, and attachments. Indexed operations select, take, drop, append, replace, and reorder tracks. Untouched compatible subtitle codecs are stream-copied; ASS/SSA styles, font attachments, metadata, dispositions, and chapters are preserved through Matroska workflows. Compilation requires exactly one final media value.

Source durations come from ffprobe JSON. Stream-specific durations are preferred, with format.duration as fallback. Results are cached by path. Known durations propagate through the graph and are used by length and to calculate fade-out start times.

See LANGUAGE.md for all value types, operations, signatures, and stack semantics.