vidfix 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. vidfix-0.1.0/.github/workflows/ci.yml +34 -0
  2. vidfix-0.1.0/.github/workflows/publish.yml +22 -0
  3. vidfix-0.1.0/.gitignore +37 -0
  4. vidfix-0.1.0/.python-version +1 -0
  5. vidfix-0.1.0/CHANGELOG.md +38 -0
  6. vidfix-0.1.0/LICENSE +57 -0
  7. vidfix-0.1.0/PKG-INFO +331 -0
  8. vidfix-0.1.0/README.md +299 -0
  9. vidfix-0.1.0/pyproject.toml +102 -0
  10. vidfix-0.1.0/src/vidfix/__init__.py +38 -0
  11. vidfix-0.1.0/src/vidfix/cli.py +513 -0
  12. vidfix-0.1.0/src/vidfix/core/__init__.py +0 -0
  13. vidfix-0.1.0/src/vidfix/core/caption.py +96 -0
  14. vidfix-0.1.0/src/vidfix/core/convert.py +170 -0
  15. vidfix-0.1.0/src/vidfix/core/duration.py +190 -0
  16. vidfix-0.1.0/src/vidfix/core/ffmpeg.py +238 -0
  17. vidfix-0.1.0/src/vidfix/core/formats.py +71 -0
  18. vidfix-0.1.0/src/vidfix/core/generate.py +224 -0
  19. vidfix-0.1.0/src/vidfix/core/matrix.py +90 -0
  20. vidfix-0.1.0/src/vidfix/core/presets.py +100 -0
  21. vidfix-0.1.0/src/vidfix/core/probe.py +268 -0
  22. vidfix-0.1.0/src/vidfix/core/verify.py +110 -0
  23. vidfix-0.1.0/src/vidfix/data/presets.yaml +45 -0
  24. vidfix-0.1.0/src/vidfix/exceptions.py +44 -0
  25. vidfix-0.1.0/src/vidfix/interactive.py +169 -0
  26. vidfix-0.1.0/src/vidfix/py.typed +0 -0
  27. vidfix-0.1.0/tests/test_caption.py +58 -0
  28. vidfix-0.1.0/tests/test_cli.py +255 -0
  29. vidfix-0.1.0/tests/test_convert.py +111 -0
  30. vidfix-0.1.0/tests/test_duration.py +125 -0
  31. vidfix-0.1.0/tests/test_ffmpeg.py +111 -0
  32. vidfix-0.1.0/tests/test_formats.py +52 -0
  33. vidfix-0.1.0/tests/test_generate.py +115 -0
  34. vidfix-0.1.0/tests/test_integration.py +217 -0
  35. vidfix-0.1.0/tests/test_interactive.py +109 -0
  36. vidfix-0.1.0/tests/test_matrix.py +40 -0
  37. vidfix-0.1.0/tests/test_presets.py +133 -0
  38. vidfix-0.1.0/tests/test_probe.py +192 -0
  39. vidfix-0.1.0/tests/test_verify.py +69 -0
  40. vidfix-0.1.0/uv.lock +838 -0
@@ -0,0 +1,34 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v5
14
+ with:
15
+ python-version: "3.12"
16
+ - run: uv sync --locked
17
+ - run: uv run ruff check .
18
+ - run: uv run ruff format --check .
19
+ - run: uv run mypy
20
+
21
+ test:
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ os: [ubuntu-latest, macos-latest, windows-latest]
26
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
27
+ runs-on: ${{ matrix.os }}
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: astral-sh/setup-uv@v5
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ - run: uv sync --locked
34
+ - run: uv run pytest --cov --cov-report=term
@@ -0,0 +1,22 @@
1
+ # PyPI release via trusted publishing. Activates when the repo goes public at
2
+ # v0.1.0 — configure the "vidfix" project on PyPI with this repo + workflow as
3
+ # a trusted publisher first.
4
+ name: Publish
5
+
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+ permissions:
15
+ id-token: write # required for PyPI trusted publishing
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: astral-sh/setup-uv@v5
19
+ with:
20
+ python-version: "3.12"
21
+ - run: uv build
22
+ - run: uv publish
@@ -0,0 +1,37 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+
7
+ # Virtual environments
8
+ .venv/
9
+ venv/
10
+
11
+ # Build artifacts
12
+ dist/
13
+ build/
14
+
15
+ # Tooling caches
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ .coverage
20
+ coverage.xml
21
+ htmlcov/
22
+
23
+ # Local-only session notes (never publish)
24
+ CLAUDE.md
25
+ plan.md
26
+
27
+ # Editors / OS
28
+ .idea/
29
+ .vscode/
30
+ .DS_Store
31
+
32
+ # Test media output
33
+ *.mp4
34
+ *.mov
35
+ *.webm
36
+ *.mkv
37
+ !tests/fixtures/*.mp4
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-07-18
9
+
10
+ ### Added
11
+
12
+ - `vidfix convert` — transform videos to exact fps/duration/resolution/codec with
13
+ stream-copy fast path for trims, `--smooth` motion interpolation, `--extend-mode
14
+ freeze|loop`, aspect-preserving pad (or `--stretch`), `--no-audio`, `--audio-tone`,
15
+ `--precise`.
16
+ - `vidfix generate` — synthetic test videos from FFmpeg lavfi sources: smpte,
17
+ color-bars, testsrc, gradient, solid:COLOR patterns; tone/silence/no audio;
18
+ `--timecode` real SMPTE timecode burn-in (drop-frame `HH:MM:SS;FF` with
19
+ semicolon, non-drop with colon); `--text` caption burn-in.
20
+ - `vidfix caption` — burn text onto existing videos: positions top/center/bottom,
21
+ `--start`/`--end` time window.
22
+ - `vidfix verify` — assert fps/duration/resolution/codec with tolerances; rich
23
+ pass/fail table or `--json`; exit 0/1 for CI.
24
+ - `vidfix info` — metadata via system ffprobe (JSON) or `ffmpeg -i` banner
25
+ fallback; friendly container names ("mp4 (major_brand: isom)").
26
+ - `vidfix format` — convert any picture or video to any format (image↔image,
27
+ video→any container, palette-optimized GIFs, first-frame thumbnails).
28
+ - `vidfix variants` — parallel cartesian product of fps x resolution variants.
29
+ - Interactive wizard — bare `vidfix` walks through action/file/preset/specs
30
+ step-by-step, prints the equivalent command, then runs it.
31
+ - Presets: built-in broadcast rate family `df30`, `df60`, `ndf30`, `ndf60`,
32
+ `pal25` (alias `ndf25`), `pal50`, `film24`, `film23976`, plus
33
+ `broadcast-1080i`, `web-720p` (drop-frame rates as exact rationals); user
34
+ presets in `~/.config/vidfix/presets.yaml`; `vidfix preset list|show`.
35
+ - Typed Python API: `convert`, `generate`, `verify`, `probe`, `to_format`.
36
+ - Bundled FFmpeg via imageio-ffmpeg — no system install needed.
37
+
38
+ [0.1.0]: https://github.com/gouravrana7/vidfix/releases/tag/v0.1.0
vidfix-0.1.0/LICENSE ADDED
@@ -0,0 +1,57 @@
1
+ Required Notice: Copyright (c) 2026 Gourav Rana (https://github.com/gouravrana7/vidfix)
2
+
3
+ # PolyForm Internal Use License 1.0.0
4
+
5
+ <https://polyformproject.org/licenses/internal-use/1.0.0>
6
+
7
+ ## Acceptance
8
+
9
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license), and you may not distribute the software.
14
+
15
+ ## Changes and New Works License
16
+
17
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
18
+
19
+ ## Patent License
20
+
21
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
22
+
23
+ ## Fair Use
24
+
25
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
26
+
27
+ ## Internal Business Use
28
+
29
+ Use of the software for the internal business operations of you and your company is use for a permitted purpose.
30
+
31
+ ## No Other Rights
32
+
33
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
34
+
35
+ ## Patent Defense
36
+
37
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
38
+
39
+ ## Violations
40
+
41
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
42
+
43
+ ## No Liability
44
+
45
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
46
+
47
+ ## Definitions
48
+
49
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
50
+
51
+ **You** refers to the individual or entity agreeing to these terms.
52
+
53
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
54
+
55
+ **Your licenses** are all the licenses granted to you for the software under these terms.
56
+
57
+ **Use** means anything you do with the software requiring one of your licenses.
vidfix-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,331 @@
1
+ Metadata-Version: 2.4
2
+ Name: vidfix
3
+ Version: 0.1.0
4
+ Summary: CLI toolkit to convert, generate, and verify videos and images to exact specs (fps, duration, resolution, format) — test fixtures, CI checks, and everyday conversions.
5
+ Project-URL: Homepage, https://github.com/gouravrana7/vidfix
6
+ Project-URL: Issues, https://github.com/gouravrana7/vidfix/issues
7
+ Project-URL: Changelog, https://github.com/gouravrana7/vidfix/blob/main/CHANGELOG.md
8
+ Author-email: Gourav Rana <gourav.rana70000@gmail.com>
9
+ License: PolyForm-Internal-Use-1.0.0
10
+ License-File: LICENSE
11
+ Keywords: ci,ffmpeg,qa,test-fixtures,testing,video
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: Other/Proprietary License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Multimedia :: Video :: Conversion
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: imageio-ffmpeg>=0.6.0
27
+ Requires-Dist: pydantic>=2
28
+ Requires-Dist: pyyaml>=6.0.3
29
+ Requires-Dist: rich>=13.0.0
30
+ Requires-Dist: typer>=0.12.0
31
+ Description-Content-Type: text/markdown
32
+
33
+ # vidfix
34
+
35
+ <!-- badges: PyPI badges activate once published -->
36
+ [![PyPI](https://img.shields.io/pypi/v/vidfix)](https://pypi.org/project/vidfix/)
37
+ [![CI](https://github.com/gouravrana7/vidfix/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/gouravrana7/vidfix/actions/workflows/ci.yml)
38
+ [![Python](https://img.shields.io/pypi/pyversions/vidfix)](https://pypi.org/project/vidfix/)
39
+ [![License](https://img.shields.io/badge/License-PolyForm_Internal_Use-blue.svg)](LICENSE)
40
+
41
+ **Videos and images, exactly the way you need them.** vidfix is a CLI-first
42
+ media toolkit for anyone — developers, QA engineers, creators, or someone who
43
+ just wants a file converted: force videos to exact specs (fps, duration,
44
+ resolution), generate test clips from nothing, convert any picture or video to
45
+ any format, grab thumbnails and GIFs, and verify media specs in CI with proper
46
+ exit codes.
47
+
48
+ FFmpeg is bundled (via imageio-ffmpeg), so `pip install vidfix` just works — no
49
+ system FFmpeg required.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install vidfix # or: uv tool install vidfix
55
+ ```
56
+
57
+ ## Quick start
58
+
59
+ **No syntax needed** — just run `vidfix` and answer the prompts:
60
+
61
+ ```
62
+ $ vidfix
63
+ What do you want to do? (convert/generate/caption/format/verify/info/variants): convert
64
+ Which file do you want to convert?: raw.mov
65
+ Preset (none/broadcast-1080i/df30/df60/film24/.../web-720p): df60
66
+ Target duration (e.g. 30s, 1:30) (enter to skip): 10s
67
+ ...
68
+ equivalent command: vidfix convert raw.mov --preset df60 --duration 10s --codec h264 -o raw_converted.mov
69
+ ```
70
+
71
+ Every answer is validated on the spot, and the equivalent one-liner is printed
72
+ so you can script it next time. Or go straight to the flags:
73
+
74
+ ```bash
75
+ # A 60fps, 30s, 720p SMPTE-bars clip with a burned-in timecode
76
+ vidfix generate -o fixture.mp4 --fps 60 --duration 30s --res 720p --timecode
77
+
78
+ # Force real footage to exact specs
79
+ vidfix convert raw.mov --fps 29.97 --duration 10s --res 1280x720 -o fixture.mp4
80
+
81
+ # Assert specs in CI (exit 0 pass / 1 fail)
82
+ vidfix verify fixture.mp4 --fps 29.97 --duration 10s --res 1280x720
83
+ ```
84
+
85
+ ## Commands
86
+
87
+ Everything vidfix can do, at a glance (details in the sections below):
88
+
89
+ | Command | What it does |
90
+ |---|---|
91
+ | `vidfix` (no args) | Interactive wizard — answer prompts, see the equivalent one-liner, run it |
92
+ | `vidfix generate` | Create a synthetic test video to exact specs — patterns, test tone, timecode burn-in, no source file needed |
93
+ | `vidfix convert` | Force an existing video to exact fps / duration / resolution / codec (stream-copies when possible) |
94
+ | `vidfix caption` | Burn a text caption into a video — position, size, color, optional start/end window |
95
+ | `vidfix format` | Convert any picture or video to any other format by output extension (incl. palette-optimized GIF, thumbnails) |
96
+ | `vidfix verify` | Assert a file matches specs; exit 0 pass / 1 fail — wire straight into CI |
97
+ | `vidfix info` | Pretty-print media metadata: container, duration, fps, resolution, codecs (`--json` for scripts) |
98
+ | `vidfix variants` | Generate the fps × resolution cartesian product of variants, in parallel |
99
+ | `vidfix preset list` / `preset show` | Inspect built-in + user presets (broadcast rate family, see [Presets](#presets)) |
100
+
101
+ `info` and `variants` were previously named `probe` and `matrix`; the old names
102
+ still work as hidden aliases, so existing scripts keep running.
103
+
104
+ ### `vidfix convert` — exact-spec transforms
105
+
106
+ ```bash
107
+ vidfix convert in.mp4 --fps 60 --res 1080p -o out.mp4
108
+ vidfix convert in.mp4 --fps 60 --smooth -o out.mp4 # motion-interpolated 30→60
109
+ vidfix convert in.mp4 --duration 10s -o out.mp4 # trim: instant stream copy
110
+ vidfix convert in.mp4 --duration 10s --precise -o out.mp4 # frame-accurate re-encode
111
+ vidfix convert in.mp4 --duration 60s --extend-mode loop -o out.mp4
112
+ vidfix convert in.mp4 --preset df60 -o out.mp4 # 59.94 = exact 60000/1001
113
+ ```
114
+
115
+ Trims with an unchanged codec use FFmpeg stream copy (instant, no quality loss).
116
+ Resolution changes pad with black bars to preserve aspect ratio (`--stretch` to
117
+ disable). Drop-frame rates are passed to FFmpeg as exact rationals
118
+ (`30000/1001`), never lossy floats.
119
+
120
+ | Option | Meaning | Default |
121
+ |---|---|---|
122
+ | `-o, --output` | Output file path | required |
123
+ | `--fps` | Target frame rate: `30`, `59.94`, `30000/1001` | keep source |
124
+ | `--duration` | Target duration: `30s`, `1:30`, `90` — trims or extends | keep source |
125
+ | `--res` | Target resolution: `1280x720`, `720p`, `4k` | keep source |
126
+ | `--codec` | Video codec: `h264`, `h265`, `prores`, `vp9` | `h264` |
127
+ | `--preset` | Preset name for defaults (`vidfix preset list`) | — |
128
+ | `--smooth` | Motion-interpolate fps changes (minterpolate) | off |
129
+ | `--extend-mode` | When target duration > source: `freeze` last frame or `loop` | `freeze` |
130
+ | `--stretch` | Stretch to target resolution (no pad bars) | off |
131
+ | `--no-audio` | Drop the audio track | off |
132
+ | `--audio-tone` | Replace audio with a 440Hz test tone | off |
133
+ | `--precise` | Force re-encode for frame-accurate trims | off |
134
+
135
+ ### `vidfix generate` — synthetic fixtures from nothing
136
+
137
+ ```bash
138
+ vidfix generate -o bars.mp4 --fps 59.94 --duration 30s --res 1080p
139
+ vidfix generate -o count.mp4 --pattern testsrc --timecode # running timecode overlay
140
+ vidfix generate -o df.mp4 --preset df30 --timecode # drop-frame HH:MM:SS;FF burn-in
141
+ vidfix generate -o red.mp4 --pattern solid:red --audio none
142
+ ```
143
+
144
+ Patterns: `smpte`, `color-bars`, `testsrc`, `gradient`, `solid:COLOR`.
145
+ Audio: 440Hz `tone` (default), `silence`, `none`.
146
+
147
+ | Option | Meaning | Default |
148
+ |---|---|---|
149
+ | `-o, --output` | Output file path | required |
150
+ | `--fps` | Frame rate: `30`, `59.94`, `30000/1001` | `30` |
151
+ | `--duration` | Duration: `30s`, `1:30`, `90` | `5s` |
152
+ | `--res` | Resolution: `1280x720`, `720p`, `4k` | `1280x720` |
153
+ | `--pattern` | Test pattern: `smpte`, `color-bars`, `testsrc`, `gradient`, `solid:COLOR` | `smpte` |
154
+ | `--codec` | Video codec: `h264`, `h265`, `prores`, `vp9` | `h264` |
155
+ | `--audio` | Audio track: `tone` (440Hz), `silence`, `none` | `tone` |
156
+ | `--timecode` | Burn in running timecode (`HH:MM:SS:FF`; drop-frame `;FF` for NTSC) | off |
157
+ | `--text` | Burn a caption into the video | — |
158
+ | `--preset` | Preset name for defaults (`vidfix preset list`) | — |
159
+
160
+ ### `vidfix verify` — spec assertions for CI
161
+
162
+ ```bash
163
+ vidfix verify out.mp4 --fps 60 --duration 30s --res 1280x720 --codec h264
164
+ vidfix verify out.mp4 --fps 29.97 --json | jq .passed
165
+ ```
166
+
167
+ Prints a pass/fail table per property; exit code 0/1.
168
+
169
+ | Option | Meaning | Default |
170
+ |---|---|---|
171
+ | `--fps` | Expected frame rate | not checked |
172
+ | `--duration` | Expected duration | not checked |
173
+ | `--res` | Expected resolution | not checked |
174
+ | `--codec` | Expected video codec | not checked |
175
+ | `--fps-tolerance` | Allowed fps deviation | `0.01` |
176
+ | `--duration-tolerance` | Allowed duration deviation in seconds | `0.1` |
177
+ | `--json` | Machine-readable JSON instead of a table | off |
178
+
179
+ ### `vidfix info` — metadata at a glance
180
+
181
+ ```bash
182
+ vidfix info clip.mp4 # rich table
183
+ vidfix info clip.mp4 --json # jq-friendly
184
+ ```
185
+
186
+ ```
187
+ $ vidfix info clip.mp4
188
+ container mp4
189
+ duration 0:00:30.030 (30.030s)
190
+ resolution 1280x720
191
+ fps 29.970 (df30, 30000/1001)
192
+ video codec h264
193
+ pixel format yuv420p
194
+ bitrate 1200 kb/s
195
+ audio aac 44100 Hz 2ch
196
+ ```
197
+
198
+ The container name is resolved from the file's `major_brand` tag (mp4/mov/m4a/3gp)
199
+ or the extension — not ffprobe's raw demuxer list (`mov,mp4,m4a,3gp,3g2,mj2`);
200
+ `--json` keeps the raw string as `demuxer` and the tag as `major_brand` alongside
201
+ the friendly `container`. Broadcast rates get their preset-family label in the fps
202
+ line (`df30`, `df60`, `pal25`, `pal50`, `film24`, `film23976`) so QA can read them
203
+ at a glance. Uses system `ffprobe` when available, otherwise parses FFmpeg output
204
+ directly.
205
+
206
+ ### `vidfix caption` — burn text into a video
207
+
208
+ ```bash
209
+ vidfix caption in.mp4 --text "Take 42" -o out.mp4
210
+ vidfix caption in.mp4 --text "INTRO" --position top --color yellow -o out.mp4
211
+ vidfix caption in.mp4 --text "3..2..1" --start 0 --end 3 -o out.mp4
212
+ vidfix generate -o clip.mp4 --text "TEST CLIP" # caption a generated video too
213
+ ```
214
+
215
+ Positions: `top`, `center`, `bottom` (default). Audio is stream-copied untouched.
216
+
217
+ | Option | Meaning | Default |
218
+ |---|---|---|
219
+ | `-o, --output` | Output file path | required |
220
+ | `--text` | Caption text to burn in | required |
221
+ | `--position` | Placement: `top`, `center`, `bottom` | `bottom` |
222
+ | `--size` | Font size (pixels or expression like `h/12`) | `h/12` |
223
+ | `--color` | Font color: `white`, `yellow`, `#ff0000` | `white` |
224
+ | `--start` | Show caption from this second onward | whole video |
225
+ | `--end` | Hide caption after this second | whole video |
226
+
227
+ ### `vidfix format` — any picture or video to any format
228
+
229
+ ```bash
230
+ vidfix format clip.mov -o clip.mp4 # container conversion
231
+ vidfix format clip.mp4 -o clip.gif # palette-optimized gif
232
+ vidfix format photo.png -o photo.webp # image conversion
233
+ vidfix format clip.mp4 -o thumb.jpg # first-frame thumbnail
234
+ ```
235
+
236
+ The output extension picks the format — the only option is `-o, --output` (required).
237
+
238
+ ### `vidfix variants` — variant grids in parallel
239
+
240
+ ```bash
241
+ vidfix variants in.mp4 --fps 30,60 --res 720p,1080p -o out/
242
+ # → variants/in_30fps_720p.mp4, in_30fps_1080p.mp4, in_60fps_720p.mp4, ...
243
+ ```
244
+
245
+ | Option | Meaning | Default |
246
+ |---|---|---|
247
+ | `-o, --output` | Output directory | required |
248
+ | `--fps` | Comma-separated frame rates, e.g. `30,60` | required |
249
+ | `--res` | Comma-separated resolutions, e.g. `720p,1080p` | required |
250
+ | `--codec` | Video codec for all variants | `h264` |
251
+ | `--jobs` | Parallel FFmpeg processes | `min(4, cpus)` |
252
+
253
+ Exit code is 1 if any variant fails. `vidfix info` takes just the file and
254
+ `--json`; `vidfix preset list` / `preset show NAME` take no options.
255
+
256
+ ### Presets
257
+
258
+ ```bash
259
+ vidfix preset list
260
+ vidfix preset show df30
261
+ vidfix generate -o pal.mp4 --preset pal25 --timecode
262
+ ```
263
+
264
+ Built-in broadcast rate family:
265
+
266
+ | Preset | fps | Timecode counting |
267
+ |---|---|---|
268
+ | `df30` | 29.97 (30000/1001) | drop-frame (`HH:MM:SS;FF`) |
269
+ | `df60` | 59.94 (60000/1001) | drop-frame (`HH:MM:SS;FF`) |
270
+ | `ndf30` | 29.97 (30000/1001) | non-drop (`HH:MM:SS:FF`) |
271
+ | `ndf60` | 59.94 (60000/1001) | non-drop (`HH:MM:SS:FF`) |
272
+ | `pal25` (alias `ndf25`) | exact 25 | non-drop (`HH:MM:SS:FF`) |
273
+ | `pal50` | exact 50 | non-drop (`HH:MM:SS:FF`) |
274
+ | `film24` | exact 24 | non-drop (`HH:MM:SS:FF`) |
275
+ | `film23976` | 23.976 (24000/1001) | non-drop (`HH:MM:SS:FF`) |
276
+
277
+ Plus `broadcast-1080i` (25 fps, 1920x1080) and `web-720p` (30 fps, 720p, h264).
278
+ With `--timecode`, DF presets burn semicolon drop-frame notation and NDF/PAL/film
279
+ presets colon notation, matching broadcast convention. Aliases (`alias: pal25`)
280
+ work in user presets too. Add your own in `~/.config/vidfix/presets.yaml`;
281
+ explicit flags always override.
282
+
283
+ ## Python API
284
+
285
+ ```python
286
+ from vidfix import convert, generate, verify, probe
287
+
288
+ generate("fixture.mp4", fps="59.94", duration="30s", res="720p")
289
+ info = probe("fixture.mp4") # MediaInfo (pydantic)
290
+ result = verify("fixture.mp4", duration=30.0)
291
+ assert result.passed
292
+ ```
293
+
294
+ ## vidfix vs moviepy vs raw FFmpeg
295
+
296
+ | | vidfix | moviepy | raw ffmpeg |
297
+ |---|---|---|---|
298
+ | Exact-spec test fixtures | ✅ one command | ⚠️ manual | ⚠️ long filter incantations |
299
+ | Spec verification + exit codes | ✅ built in | ❌ | ⚠️ ffprobe + shell glue |
300
+ | Install without system FFmpeg | ✅ bundled | ✅ bundled | ❌ |
301
+ | Editing/compositing/effects | ❌ not the goal | ✅ | ✅ |
302
+ | Programmatic frame access | ❌ | ✅ numpy frames | ⚠️ |
303
+ | Speed | ✅ direct filters, stream copy | ⚠️ python frame loop | ✅ |
304
+
305
+ Use **moviepy** to *edit* videos, **raw ffmpeg** for full control, **vidfix**
306
+ for exact-spec media, quick conversions, and CI checks with zero setup.
307
+
308
+ ## CI usage
309
+
310
+ ```yaml
311
+ - name: Verify rendered output specs
312
+ run: |
313
+ pip install vidfix
314
+ vidfix verify build/output.mp4 --fps 60 --duration 30s --res 1280x720
315
+ ```
316
+
317
+ ## Development
318
+
319
+ ```bash
320
+ uv sync # install with dev deps
321
+ uv run pytest # unit + integration tests
322
+ uv run pytest -m "not integration" # fast tests only (no FFmpeg)
323
+ uv run ruff check . && uv run mypy # lint + strict types
324
+ ```
325
+
326
+ ## License
327
+
328
+ [PolyForm Internal Use 1.0.0](LICENSE) — you may use vidfix freely for personal
329
+ and internal business purposes (commercial included). Copying, redistributing,
330
+ or building products from this source code is not permitted. Contributions are
331
+ welcome via pull request.