Skip to main content

CLI toolkit to convert, generate, and verify videos and images to exact specs (fps, duration, resolution, format) — test fixtures, CI checks, and everyday conversions.

Project description

vidfix

Make, convert, fix, and verify media — to exact spec, in one command.

PyPI CI Python License

videos, pictures & audio from nothing · any format to any format · multi-track audio · captions & timecode burn-in · pass/fail checks for CI

vidfix is a CLI-first media toolkit. It creates any video, picture, or audio file from nothing, converts anything to any format, muxes audio and subtitles together, and forces existing files to exact specs — fps, duration, resolution, codec, audio channels. Then it proves it: vidfix info reads any file back in plain words, vidfix verify turns specs into pass/fail exit codes for CI. Built for anyone — developers, testers, creators, or someone who just wants a file converted.

No commands to learn. It talks to you.

uvx vidfix

That's it — nothing to install, no setup, no syntax to memorize. vidfix asks what you want step by step in plain words, checks every answer the moment you type it (a bad answer re-prompts right there — it never runs a job it knows will fail), runs the job, and shows you the equivalent one-liner for next time. When it's done it prints the full path where the file was saved, so you never have to hunt for the output.

Install

uvx vidfix                # try instantly — no install
pip install vidfix        # or install for keeps (or: uv tool install vidfix)

Everything vidfix needs is built in — install it and it just works.

Quick start

No syntax needed — just run vidfix and answer the prompts:

$ vidfix
What do you want to do? (generate/convert/caption/attach/format/verify/info/variants) [generate]:
Generate (video/audio-only/picture): video
formats: 3gp, avi, flv, m4v, mkv, mov, mp4, mpeg, mpg, mxf, ogv, ts, webm, wmv — pick any
Output file [test.mp4]: fixture.mp4
Pattern (smpte/color-bars/testsrc/gradient/...): smpte
Duration (e.g. 30s, 1min, 1:30) [5s]: 10 seconds
Resolution (e.g. 720p, 1080p, 1280x720) [720p]:
Audio (tone/silence/none) [tone]: tone
Audio channels (mono/stereo/5.1/7.1/left/right) [stereo]: 5.1
...
equivalent command: vidfix generate --pattern smpte --duration '10 seconds' --audio-layout 5.1 -o fixture.mp4
✓ wrote fixture.mp4
  location: /Users/you/clips/fixture.mp4

Every answer is validated on the spot and the equivalent one-liner is printed so you can script it next time. Answer the output prompt with just a format name (mxf) and vidfix names the file for you (fixture.mxf). Check the result any time with vidfix info fixture.mp4 or assert it with vidfix verify. Or go straight to the flags:

# A 60fps, 30s, 720p SMPTE-bars clip with a burned-in timecode
vidfix generate -o fixture.mp4 --fps 60 --duration 30s --res 720p --timecode

# Force real footage to exact specs
vidfix convert raw.mov --fps 29.97 --duration 10s --res 1280x720 -o fixture.mp4

# Assert specs in CI (exit 0 pass / 1 fail)
vidfix verify fixture.mp4 --fps 29.97 --duration 10s --res 1280x720

It refuses to write a broken file

Every output format has real limits — codecs it can hold, channel counts, frame rates, how many audio tracks fit. vidfix knows them. Impossible combinations stop before anything runs, with one plain sentence instead of a wall of log:

$ vidfix generate -o clip.webm --codec h264
error: .webm files can't hold h264; use one of: vp9.

$ vidfix attach clip.mp4 --audio en.wav --audio hi.mp3 -o out.flv
error: .flv holds at most 1 audio track(s); you attached 2. Use .mkv/.mp4/.mov.

$ vidfix generate -o surround.mp3 --audio-layout 5.1
error: .mp3 audio holds at most 2 channels; you asked for 6.

The wizard goes one step further: it simply never offers a choice the chosen format can't make.

Commands

Everything vidfix can do, at a glance (details in the sections below):

Command What it does
vidfix (no args) Interactive wizard — answer plain-word prompts, it runs the job and shows the equivalent one-liner
vidfix generate Create synthetic test media to exact specs — videos, audio-only files, or still pictures; patterns, tones, channel layouts, timecode burn-in, no source file needed
vidfix convert Force an existing video to exact fps / duration / resolution / codec (stream-copies when possible)
vidfix caption Burn a text caption into a video — position, size, color, optional start/end window
vidfix attach Mux an existing audio file and/or subtitle (.srt/.vtt) onto a video — soft track or --burn-ed in
vidfix format Convert any picture or video to any other format by output extension (incl. palette-optimized GIF, thumbnails, audio extraction)
vidfix verify Assert a file matches specs; exit 0 pass / 1 fail — wire straight into CI
vidfix info Show a file's details in plain words: type, duration, fps, resolution, codecs (--json for scripts)
vidfix variants Generate the fps × resolution cartesian product of variants, in parallel
vidfix preset list / preset show Inspect built-in + user presets (broadcast rate family, see Presets)

info and variants were previously named probe and matrix; the old names still work as hidden aliases, so existing scripts keep running.

vidfix convert — exact-spec transforms

vidfix convert in.mp4 --fps 60 --res 1080p -o out.mp4
vidfix convert in.mp4 --fps 60 --smooth -o out.mp4          # motion-interpolated 30→60
vidfix convert in.mp4 --duration 10s -o out.mp4             # trim: instant stream copy
vidfix convert in.mp4 --duration 10s --precise -o out.mp4   # frame-accurate re-encode
vidfix convert in.mp4 --duration 60s --extend-mode loop -o out.mp4
vidfix convert in.mp4 --preset df60 -o out.mp4              # 59.94 = exact 60000/1001
vidfix convert in.mp4 --audio-layout 5.1 -o out.mp4         # remix audio to 5.1

Trims with an unchanged codec are stream-copied (instant, no quality loss). Resolution changes pad with black bars to preserve aspect ratio (--stretch to disable). Drop-frame rates are handled as exact rationals (30000/1001), never lossy floats.

Option Meaning Default
-o, --output Output file path required
--fps Target frame rate: 30, 59.94, 30000/1001 keep source
--duration Target duration: 30s, 1min, 1:30, 90 — trims or extends keep source
--res Target resolution: 1280x720, 720p, 4k keep source
--codec Video codec: h264, h265, prores, vp9, mpeg2, theora fits the output container
--preset Preset name for defaults (vidfix preset list)
--smooth Motion-interpolate fps changes (minterpolate) off
--extend-mode When target duration > source: freeze last frame or loop freeze
--stretch Stretch to target resolution (no pad bars) off
--audio Audio track: keep source, tone (440Hz), silence, none (drop) — matches generate (--no-audio/--audio-tone still work as aliases) keep
--audio-layout Reshape audio channels: mono, stereo, 5.1, 7.1, left, right keep source
--precise Force re-encode for frame-accurate trims off
--text + --position/--size/--color/--start/--end Burn a caption while converting (same options as caption)
--timecode Burn in a running timecode off

vidfix generate — synthetic fixtures from nothing

vidfix generate -o bars.mp4 --fps 59.94 --duration 30s --res 1080p
vidfix generate -o count.mp4 --pattern testsrc --timecode    # running timecode overlay
vidfix generate -o df.mp4 --preset df30 --timecode           # drop-frame HH:MM:SS;FF burn-in
vidfix generate -o red.mp4 --pattern solid:red --audio none
vidfix generate -o surround.mp4 --audio-layout 5.1           # 6-channel audio
vidfix generate -o tone.wav --duration 3s --audio-layout left  # audio-only file
vidfix generate -o card.png --res 1080p --text "SCENE 1"     # still test card

Patterns: smpte, color-bars, testsrc, gradient, solid:COLOR. Audio: 440Hz tone (default), silence, none — any layout from mono to 7.1, or left/right for channel-identification checks.

The output extension picks the media kind: video (.mp4, .mov, …), audio-only (.wav, .mp3, .m4a, .flac), or a still picture (.png, .jpg, .jpeg, .webp, .bmp, .tiff).

Option Meaning Default
-o, --output Output file path; extension picks video / audio-only / picture required
--fps Frame rate: 30, 59.94, 30000/1001 30
--duration Duration: 30s, 30 seconds, 1min, 1:30, 90 5s
--res Resolution: 1280x720, 720p, 4k 1280x720
--pattern Test pattern: smpte, color-bars, testsrc, gradient, solid:COLOR smpte
--codec Video codec: h264, h265, prores, vp9, mpeg2, theora fits the output container
--audio Audio track: tone (440Hz), silence, none tone
--audio-layout Audio channels: mono, stereo, 5.1, 7.1, left, right mono tone / stereo silence
--timecode Burn in running timecode (HH:MM:SS:FF; drop-frame ;FF for NTSC) off
--text Burn a caption into the video or picture
--position Caption placement: top/center/bottom, optionally -left/-right (9-point grid) bottom
--size / --color Caption font size (h/12, pixels) and color (white, yellow, #ff0000) h/12 / white
--start / --end Show the caption only within this second window full clip
--preset Preset name for defaults (vidfix preset list)

vidfix verify — spec assertions for CI

vidfix verify out.mp4 --fps 60 --duration 30s --res 1280x720 --codec h264
vidfix verify out.mp4 --fps 29.97 --json | jq .passed

Prints a pass/fail table per property; exit code 0/1.

Option Meaning Default
--fps Expected frame rate not checked
--duration Expected duration not checked
--res Expected resolution not checked
--codec Expected video codec not checked
--fps-tolerance Allowed fps deviation 0.01
--duration-tolerance Allowed duration deviation in seconds 0.1
--json Machine-readable JSON instead of a table off

vidfix info — metadata at a glance

vidfix info clip.mp4             # rich table
vidfix info clip.mp4 --json      # jq-friendly
$ vidfix info clip.mp4
 type          mp4 video
 duration      30.03 seconds
 resolution    1080p (1920x1080)
 fps           29.970 (df30, 30000/1001)
 video codec   h264
 color format  standard (yuv420p)
 bitrate       1200 kb/s
 audio         aac · stereo (left+right) · normal quality (44100 Hz)

Everything reads in plain words, with the technical value kept in brackets: the type says whether the file is video or audio, durations are in seconds (1m 30s (90.00 seconds) for longer files), common resolutions get their everyday name (1080p, 4k), color formats a plain label (standard, 10-bit), audio channels show as mono/stereo/5.1/7.1 instead of a channel count (stereo (left+right), 5.1 surround), and sound quality in everyday words (normal quality (44100 Hz), high quality, low quality). The file type is resolved from the file's major_brand tag (mp4/mov/m4a/3gp) or the extension — not the raw demuxer list (mov,mp4,m4a,3gp,3g2,mj2); --json keeps the raw string as demuxer and the tag as major_brand alongside the friendly container. Broadcast rates get their preset-family label in the fps line (df30, df60, pal25, pal50, film24, film23976) so you can read them at a glance.

vidfix caption — burn text into a video

vidfix caption in.mp4 --text "Take 42" -o out.mp4
vidfix caption in.mp4 --text "INTRO" --position top --color yellow -o out.mp4
vidfix caption in.mp4 --text "3..2..1" --start 0 --end 3 -o out.mp4
vidfix caption in.mp4 --text "corner" --position top-right -o out.mp4
vidfix generate -o clip.mp4 --text "TEST CLIP"      # caption a generated video too
vidfix convert in.mp4 --text "SUBTITLE" --position bottom -o out.mp4   # or while converting

Positions form a 3×3 grid: top-left, top, top-right, left, center, right, bottom-left, bottom (default), bottom-right. The same caption options are available on caption, generate, and convert. Audio is stream-copied untouched (in caption).

Option Meaning Default
-o, --output Output file path required
--text Caption text to burn in required
--position Placement (9-point grid, see above) bottom
--size Font size (pixels or expression like h/12) h/12
--color Font color: white, yellow, #ff0000 white
--start Show caption from this second onward whole video
--end Hide caption after this second whole video

vidfix attach — mux audio and/or subtitles onto a video

vidfix attach clip.mp4 --audio track.m4a -o out.mp4                 # add an audio track
vidfix attach clip.mp4 --audio en.wav --audio hi.mp3 -o out.mkv     # two audio tracks
vidfix attach clip.mp4 --subs subs.srt -o out.mkv                   # soft subtitle track
vidfix attach clip.mp4 --subs subs.srt --burn -o out.mp4           # burn subtitles into the picture
vidfix attach clip.mp4 --audio track.m4a --subs subs.srt -o out.mkv # all at once
Option Meaning Default
-o, --output Output file path required
--audio Audio file to add as a track (repeat for multiple tracks)
--subs Subtitle file (.srt/.vtt) to add
--burn Burn subtitles into the picture (else a soft, toggle-able track) off

The video is stream-copied (no re-encode, no quality loss) unless subtitles are burned in. Repeat --audio to add several tracks (one per file, in order) — it works on any video, including a vidfix generate clip. .flv holds only one audio track; use .mkv/.mp4/.mov for more. Soft subtitles need a .mp4/.mov/.mkv/.webm output — for other containers use --burn.

vidfix format — any picture or video to any format

vidfix format clip.mov -o clip.mp4      # container conversion
vidfix format clip.mp4 -o clip.gif      # palette-optimized gif
vidfix format photo.png -o photo.webp   # image conversion
vidfix format clip.mp4 -o thumb.jpg     # first-frame thumbnail
vidfix format clip.mp4 -o audio.mp3     # extract the audio track

The output extension picks the format — the only option is -o, --output (required). Video targets: mp4, mov, mkv, webm, avi, m4v, ts, mxf, mpg, ogv, flv, wmv, 3gp, gif. Picture targets: png, jpg, webp, bmp, tiff. Audio targets (extract from a video): wav, mp3, m4a, flac. generate and convert write those containers too — each picks a codec that plays in it.

vidfix variants — variant grids in parallel

vidfix variants in.mp4 --fps 30,60 --res 720p,1080p -o out/
# → variants/in_30fps_720p.mp4, in_30fps_1080p.mp4, in_60fps_720p.mp4, ...
Option Meaning Default
-o, --output Output directory required
--fps Comma-separated frame rates, e.g. 30,60 required
--res Comma-separated resolutions, e.g. 720p,1080p required
--codec Video codec for all variants h264
--jobs Parallel worker processes min(4, cpus)

Exit code is 1 if any variant fails. vidfix info takes just the file and --json; vidfix preset list / preset show NAME take no options.

Presets

vidfix preset list
vidfix preset show df30
vidfix generate -o pal.mp4 --preset pal25 --timecode

Built-in broadcast rate family:

Preset fps Timecode counting
df30 29.97 (30000/1001) drop-frame (HH:MM:SS;FF)
df60 59.94 (60000/1001) drop-frame (HH:MM:SS;FF)
ndf30 29.97 (30000/1001) non-drop (HH:MM:SS:FF)
ndf60 59.94 (60000/1001) non-drop (HH:MM:SS:FF)
pal25 (alias ndf25) exact 25 non-drop (HH:MM:SS:FF)
pal50 exact 50 non-drop (HH:MM:SS:FF)
film24 exact 24 non-drop (HH:MM:SS:FF)
film23976 23.976 (24000/1001) non-drop (HH:MM:SS:FF)

Plus broadcast-1080i (25 fps, 1920x1080) and web-720p (30 fps, 720p, h264). With --timecode, DF presets burn semicolon drop-frame notation and NDF/PAL/film presets colon notation, matching broadcast convention. Aliases (alias: pal25) work in user presets too. Add your own in ~/.config/vidfix/presets.yaml; explicit flags always override.

Python API

from vidfix import convert, generate, verify, probe

generate("fixture.mp4", fps="59.94", duration="30s", res="720p")
info = probe("fixture.mp4")           # MediaInfo (pydantic)
result = verify("fixture.mp4", duration=30.0)
assert result.passed

vidfix vs moviepy vs raw FFmpeg

vidfix moviepy raw ffmpeg
Exact-spec test fixtures ✅ one command ⚠️ manual ⚠️ long filter incantations
Spec verification + exit codes ✅ built in ⚠️ ffprobe + shell glue
Install without system FFmpeg ✅ bundled ✅ bundled
Editing/compositing/effects ❌ not the goal
Programmatic frame access ✅ numpy frames ⚠️
Speed ✅ direct filters, stream copy ⚠️ python frame loop

Use moviepy to edit videos, raw ffmpeg for full control, vidfix for exact-spec media, quick conversions, and CI checks with zero setup.

CI usage

- name: Verify rendered output specs
  run: |
    pip install vidfix
    vidfix verify build/output.mp4 --fps 60 --duration 30s --res 1280x720

Development

uv sync                                   # install with dev deps
uv run pytest                             # unit + integration tests
uv run pytest -m "not integration"        # fast tests only
uv run ruff check . && uv run mypy        # lint + strict types

License

PolyForm Internal Use 1.0.0 — you may use vidfix freely for personal and internal business purposes (commercial included). Copying, redistributing, or building products from this source code is not permitted. Contributions are welcome via pull request.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vidfix-0.3.0.tar.gz (123.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vidfix-0.3.0-py3-none-any.whl (48.3 kB view details)

Uploaded Python 3

File details

Details for the file vidfix-0.3.0.tar.gz.

File metadata

  • Download URL: vidfix-0.3.0.tar.gz
  • Upload date:
  • Size: 123.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for vidfix-0.3.0.tar.gz
Algorithm Hash digest
SHA256 6517a5eb79346f7d40486cfba48aabecee155b74c2ff04a6a242bd9bd63093fa
MD5 2cdf20c72a4d88c0004462e92bc42eec
BLAKE2b-256 0f1b8e6f46fa151ec2cf0e20077612eda493c018dbbc47204d3b35ed10f09ce7

See more details on using hashes here.

File details

Details for the file vidfix-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: vidfix-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 48.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.30 {"installer":{"name":"uv","version":"0.11.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for vidfix-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 316886a3a0d4ea21a52f0f682d0fd45512deb15b8761e0a030c162c11a525ef3
MD5 032284880e301d3903d0ac56b28f49ff
BLAKE2b-256 a4fe1596415918ce43d20ffb58d62a5210dca2b147e322490c85f48603135168

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page