vidqa
Local, deterministic video QA — for humans, CI gates, and AI agents.
Every command prints one compact JSON object (sorted keys, ≤4KB) and exits 0 (pass) / 1 (gate failed) / 2 (error — a crash never masquerades as a gate failure). Deterministic checks are byte-identical run-to-run, so you can diff them and gate on them. Nothing is uploaded anywhere: analysis is ffmpeg + OpenCV, and the optional semantic layer talks to a local Ollama model.
Built for game/build QA — "is this capture smooth?", "did rendering break vs the golden frame?", "what does this clip actually show?" — at zero API cost, but nothing about it is game-specific.
Demo
A checkout flow recorded by a test runner, analyzed by vidqa:
$ vidqa strip run.mp4 --out sheet.png --every 1.5 # the image above
$ vidqa when run.mp4 "Payment failed" # when did the error show?
{"first_s":8.0,"found":true,"intervals":[{"end_s":10.5,"start_s":8.0}],"mode":"text","query":"Payment failed","sampled":31,"step_s":0.5}
$ vidqa shot run.mp4 --out evidence.png --at-text "Payment failed"
{"at_s":8.25,"found":true,"height":800,"out":"evidence.png","query":"Payment failed","width":1280}
$ vidqa ci run.mp4 --rules checkout.rules.json # gate it in CI
{"pass":true,"rules":[{"detail":{"first_s":10.5},"pass":true,"rule":{"by_s":15,"text":"Order confirmed","type":"expect_text"}},{"detail":{},"pass":true,"rule":{"type":"no_blank_frames"}}],"step_s":0.5}
Requirements
- Python 3.11+
- ffmpeg/ffprobe on
PATH(installed separately; not bundled) - Optional, for
ask: Ollama with a vision model (defaultqwen3-vl:8b) - Optional, for
live(Windows only): Intel PresentMon — a copy is bundled intools/ - Optional, for
speech: faster-whisper (pip install vidqa-cli[speech]; runs on CPU, model downloads on first use)
Install
pip install vidqa-cli
(The PyPI name is vidqa-cli; the module and the command are both vidqa.)
From source, for development:
git clone https://github.com/DSmereski/vidqa
cd vidqa
python -m venv .venv
.venv/Scripts/pip install -e .[dev] # Windows; use .venv/bin/pip elsewhere
Commands
| Command | What it answers |
|---|---|
vidqa report <video> [--golden ref --at t] |
One-call verdict: probe + timing + scenes (+ audio, + golden diff). Start here. |
vidqa probe <video> |
Streams, resolution, duration, CFR/VFR. |
vidqa timing <video> |
Stutter, duplicate frames, freezes; frame-time p50/p95/p99. |
vidqa diff <cand> --golden ref [--at t] [--mask-out m.png] [--ignore x,y,w,h] |
Golden-frame gate: SSIM floor + 8×8-grid worst-cell color error + pHash scene check; --ignore excludes dynamic regions (clock, fps counter). |
vidqa scenes <video> |
Scene-cut timestamps. |
vidqa audio <video> |
Silences, clipping, volume levels. |
vidqa speech <video> [--expect "phrase"] [--find "phrase"] |
Transcribe spoken audio (faster-whisper, local CPU); --expect gates on a substring, --find returns the timestamps where a phrase was spoken. |
vidqa ocr <video|image> [--at t] |
Read on-screen text (RapidOCR, CPU, offline after first model download). |
vidqa find <video|image> --template t.png [--at t] |
Locate a known UI element; exit 1 if absent. |
vidqa ask <video> "question" [--enum a,b,c] [--expect a] |
Local-VLM Q&A; --expect turns it into a gate. |
vidqa live <process.exe> [--seconds 10] |
Real frametimes of a running app via PresentMon ETW (Windows). |
vidqa when <video> "text" [--template el.png] |
When text or an element is visible: intervals in seconds; exit 1 if never. |
vidqa shot <video> --out f.png --at 12.5 | --at-text "Error" | --around 12.5 [--zoom] [--crop x,y,w,h] [--annotate x,y,w,h,label] |
Precise frame evidence, by timestamp or by visible text; --around writes a before/after pair, --zoom crops it to what actually changed; bake labeled boxes in. |
vidqa moments <video> [--text q] |
Auto-index a recording into chapter markers — freezes, stutter, cuts, blanks, silences, and where a text first/last appears — so nobody scrubs blind. |
vidqa contrast <video|image> [--at t] [--min-ratio 4.5] |
Flag low-contrast on-screen text (WCAG-approx accessibility probe); exit 1 when anything falls below the floor. |
vidqa locate <video> "fail text" [--shots dir] |
Failure auto-locate: feed it the assertion/error text → last-good and first-bad timestamps + the exact frames. |
vidqa judge <video> --rubric rubric.json |
Visual smoke review: a UX checklist judged by the local VLM, one enum verdict per item; exit 1 if any fail. Judges persistent qualities (theme, layout, end state) — a briefly flashed message belongs to when/locate. |
vidqa text <video> [--contains q] |
Everything the screen ever said: every text line with visibility intervals; short-lived toasts/snackbars flagged. |
vidqa redact <video> --out safe.mp4 --region x,y,w,h |
Black out regions (PII, tokens) — solid fill, not reversible blur — so a recording can be shared. |
vidqa strip <video> --out sheet.png [--every 1] |
Filmstrip contact sheet: the whole run as one timestamped thumbnail grid. |
vidqa clip <video> --out cut.mp4 --from 10 --to 14 |
Small mp4/gif excerpt for bug tickets. |
vidqa ci <video> --rules rules.json [--md report.md] |
CI gate: an expectations file → one exit code; --md also writes a PR-comment-ready report. |
vidqa trace <trace.zip> [--video v --at-step "click" --out f.png] |
Playwright trace → step timeline; grab the frame where a step completed. |
vidqa record-android --while "cmd" --out rec.mp4 |
Record the Android device screen (adb) while a test command runs; exits 1 if the command fails. |
vidqa load <video> [--content-by S] [--settled-by S] |
Perceived loading from video alone: time to first content + visual settle; deadlines gate. |
vidqa bugpack <video> --at T --out dir |
Ticket-ready evidence folder: frame, ±3s clip, event track, report JSON, summary.md with OCR. |
vidqa srt <video> --out events.srt |
Detected events (freezes, stutter, cuts, silences) as a subtitle track — any player shows the analysis on the scrubber. |
vidqa rundiff <a> <b> [--shots dir] [--ignore x,y,w,h] [--trace-a a.zip --trace-b b.zip] [--md report.md] |
Where two runs of the same test diverge: by clock, or aligned by Playwright steps when traces are given. Compares structure (pHash) and color, so a wrong-colored panel can't hide; exit 1 on divergence. |
Example:
$ vidqa timing capture.mp4
{"dup_ratio":0.0,"frame_count":3600,"frame_time_ms":{"p50":16.6667,"p95":16.6667,"p99":16.6833},"freeze_count":0,...}
$ vidqa ask capture.mp4 "What color is the health bar?" --enum red,yellow,green --expect green
{"answer":"green","confidence":"high","evidence":"...","frames_used":6,"model":"qwen3-vl:8b"}
Test recordings (web / Android / iOS)
Point vidqa at whatever your runner saved — a Playwright video, an adb screenrecord, an XCUITest attachment, an OBS capture:
vidqa when run.webm "Payment failed" # when did it appear?
vidqa locate run.webm "Payment failed" --shots evidence/ # last-good + first-bad frames
vidqa shot run.webm --out evidence.png --at-text "Payment failed"
vidqa strip run.webm --out sheet.png # whole run at a glance
vidqa ci run.webm --rules checkout.rules.json # gate it in CI
Automated runners move fast: a state a human would watch for seconds can
be on screen for under half a second, because the next click fires the
moment the assertion resolves (measured on real Playwright runs: an
error panel visible for 0.36 s). When gating on such flashes, tighten
the sampling with --step 0.25. Also, the recording starts at the first
painted frame — a form the test fills instantly may barely appear in the
video at all. The video is evidence of what happened after the page
rendered; VLM rubrics (judge) should ask about the states that
dominate it, not about flashes.
A rules file turns a recording into a pass/fail check:
{"rules": [
{"type": "expect_text", "text": "Order confirmed", "by_s": 20},
{"type": "forbid_text", "builtin": "error_pages"},
{"type": "max_freeze_s", "seconds": 3},
{"type": "no_blank_frames", "after_s": 5}
]}
Design contract
- One JSON object on stdout. Sorted keys, 4-decimal floats, no timestamps or hostnames — reruns of deterministic commands are byte-identical.
- Exit codes mean one thing each.
1always means "the check genuinely failed"; internal errors are always2. - Gate on determinism. VLM answers are schema-constrained (Ollama
structured outputs at temperature 0). Enum answers are argmax-stable;
free-prose
evidenceis best-effort context, never something to compare byte-wise. Automated gates should use deterministic fields or--enum/--expectonly. - Cheap first. For agents: deterministic metrics → OCR/template → local VLM. Most questions never need a model at all.
timingfreezes measure static content. Menus, loading screens, and an idle player all count — that is all recorded footage can show (verified against real game captures). For true performance hitches of a running build, usevidqa live(ETW ground truth).
MCP server
vidqa-mcp exposes every command as an MCP tool over stdio, for clients
that speak Model Context Protocol:
{"mcpServers": {"vidqa": {"command": "/path/to/.venv/Scripts/vidqa-mcp"}}}
Live frametimes (Windows)
vidqa live wraps Intel PresentMon
(bundled, MIT — see tools/PresentMon-LICENSE.txt) to measure the real
present-to-present frametimes of a running process via ETW. ETW capture
needs elevation once: run from an admin terminal, or add your user to
the "Performance Log Users" group and sign back in.
Testing
.venv/Scripts/python -m pytest -q
196 tests; fixtures are synthesized on the fly with ffmpeg (injected
freezes, dropped frames, seeded corruption, silence, clipping, drawn
text) plus Windows text-to-speech for the speech tests, so no test
media is checked in. eval/run_eval.py --runs 3 exercises the VLM lane
end-to-end against a local Ollama model.
License
MIT (see LICENSE). The repository bundles the Intel PresentMon binary
under its own MIT license (tools/PresentMon-LICENSE.txt). ffmpeg is a
separate install and is not distributed here.
Metadata
Release files for vidqa-cli 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vidqa_cli-0.2.0.tar.gz | 67.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vidqa_cli-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 121.2 kB
Release files / vidqa_cli-0.2.0.tar.gz
| Download URL | vidqa_cli-0.2.0.tar.gz |
|---|---|
| Size | 67.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
21919f027e1ae61e6ebe1e3bda6e4f6762cf579b34c749ee88b7f86b5b7927e3
|
|
BLAKE2b-256 checksum How to use checksums |
f3cb1190deed64d363aa53c7cdcba9a903014886feec80611cd241c80c390b37
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.
Transparency logRelease files / vidqa_cli-0.2.0-py3-none-any.whl
| Download URL | vidqa_cli-0.2.0-py3-none-any.whl |
|---|---|
| Size | 53.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2e77b5ff63dd1a2fbda36d6824471bbd771674a94f2c3b56e777435b84a4cf71
|
|
BLAKE2b-256 checksum How to use checksums |
fdab0cea50c7f51c115db5d2d82761ee610dd4d28a6eabe9f79baed943c78225
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.
Transparency log