Contributing to showtime
Thanks for helping. showtime is a Claude Code skill that makes videos entirely on the user's machine, on macOS (Apple Silicon and Intel), Windows 10/11 and Linux. Read CONTEXT.md for the words we use and .out-of-scope/ for what we have decided not to do. Everyone here follows the code of conduct. Bugs, feature ideas, example requests and docs problems each have an issue form; questions and videos you made go to Discussions.
Set up a development checkout#
git clone https://github.com/FavioVazquez/showtime
cd showtime
skills/showtime/bin/showtime setup # Windows: skills\showtime\bin\showtime.cmd setup
skills/showtime/bin/showtime doctor
To try your working copy inside Claude Code without installing the plugin, link it as a personal skill (development only; the published install is the plugin):
skills/showtime/bin/showtime setup --link # ~/.claude/skills/showtime -> this checkout
The stable command ~/.showtime/bin/showtime follows a newer version, but not a tie: running a checkout of
the version you have installed (or a second checkout, or a scratch copy) leaves it on the installed plugin,
or on the folder it already runs. setup --link points it at the linked checkout. Run a checkout's own
skills/showtime/bin/showtime to use that checkout without touching the command.
Ground rules#
- Everything runs locally. No cloud AI services, API keys, accounts, uploads or telemetry. The network is for downloads (pinned by URL, size and SHA-256 wherever the source allows) and for public web sources the user asks for (archive media search, site capture), which fetch and never send the user's files.
- Cross-platform is not optional. Python (stdlib-only for the launcher and installer) and Node only;
no shell-only logic. Use
pathlib, pass subprocess arguments as lists (never a shell string), handle.exe, and put file paths into ffmpeg filter arguments withst.ff.filter_path(). Never call a bareffmpeg: go throughst.ff(orscripts/lib/ff.mjsin Node). Fonts are files fetched by setup, never system font names. - Original work, clean licenses. Write your own code and docs. Libraries are installed by setup and pinned; assets and models must allow commercial use (CC0, public domain, OFL, MIT, Apache; CC-BY only with automatic credits).
- Friendly by default. Every command has
--helpwith examples and sensible defaults. Errors say what went wrong, why, and the exact fix (ShowtimeError(message, why=..., hint=...)); tracebacks appear only with--debug. Anything slower than 30 s announces an estimate and shows progress (st.common.estimate()andst.common.Progress). Optional extras are never installed silently: callst.common.require_extra(name, feature). - Never overwrite earlier work. Outputs go to a fresh
showtime-out/<slug>-<timestamp>/folder unless the user names a file.
Adding a command#
- Python: add
skills/showtime/lib/st/cli_<module>.pywith a literalCOMMANDS = {"name": "one line"}andregister(subparsers); the CLI discovers it. Node: addskills/showtime/scripts/<name>.mjs; the launcher routesshowtime <name>to it. - Put the command in a group in
skills/showtime/lib/st/launcher.py(GROUPS) soshowtime --helpshows it with an example. - Mention it in SKILL.md or a reference only as it really exists:
scripts/check_release.pyfails when the docs name a command or sub-command the CLI does not have. - A new workflow guide needs a
route-<topic>case inbenchmarks/routing-eval/and a line in its family inreferences/index.md, not a row in SKILL.md;python3 scripts/check_release.py --only routingchecks it.
Add an X-ray mechanism#
The X-ray mechanism library (skills/showtime/library/xray/) holds one folder per kind of object, and
showtime new xray <dir> --mechanism <object> copies one into a project. It grows with every verified X-ray. To
show a film you made without adding a mechanism, use the "X-ray for the gallery" issue form instead; the
X-ray page shows both lanes.
A pull request adds one folder, skills/showtime/library/xray/<object>/, started as a copy of the closest entry:
mechanism.js:XRAY.mechanism({...})with the parts, the design box, four named anchors a clip places on its object, the outline, anddraw(k)(the kit is described in the library'sREADME.md).diagram.js(optional): the same object as an isometric living diagram, for--layout diagram.README.md: the parts; what one event moves; what is measured and what is drawn; the anchors; and aVerified on:line with the clip, its credit and license, and the receipt lineshowtime xray verifyprinted.
Checklist for the pull request:
- [ ] Made on a real clip and verified: the
Verified on:line quotes the receipt as it printed ("N of N events on their frame · audio X ms from the original"), and qa had no FAIL. - [ ] The clip is yours, or CC0, CC BY or CC BY-SA, with its credit line in the README. No license found, no entry.
- [ ] Objects only: the see-through layout is never drawn over a person.
- [ ] A strike peaks on its event's frame; anything that advances (a wheel, a carriage) steps on that frame.
- [ ] Which key, note or part is named only from evidence (the paper, a display,
notes.json); otherwise the drawing says "a keystroke", "a pluck". - [ ] Six to ten labelled parts of the real kind of mechanism, simplified, not a symbol; labels readable at 1080x1920 (28 px and up).
- [ ]
showtime new xray /tmp/x --mechanism <object>thenshowtime check /tmp/xpass with the sample clip. - [ ] No third-party drawings or code: the mechanism is your own work (see "Original work, clean licenses").
Tests#
python skills/showtime/tests/run_all.py --fast # what CI runs on every pull request (11 min on a clean Ubuntu machine, 18 on Windows and a macOS VM)
python skills/showtime/tests/run_all.py # everything (longer; renders, voice, ASR)
python skills/showtime/tests/run_all.py -k audio # one module
python skills/showtime/tests/run_all.py --fast --changed --explain # only what your changes can affect
python scripts/check_release.py --check # versions, SKILL.md, links, commands, paths, names
Each module has tests/test_<module>.py: stdlib + subprocess, real runs, short and small renders. Every
validator needs a known-bad sample that must fail next to a known-good one that passes, so a green run
proves the check still bites.
run_all.py runs several test files at once: -j N files in parallel, each in its own process with its
own working folder and TMPDIR (default auto = min(files, cores / 2, 8), and up to cores / 2 on machines
with more than 16 cores, fewer when memory is short); -j 1 runs them one after another with live output.
From 9 processes at once up, long files run in parts (tests/_split.py says which files and which tests
must share a process; tests/_part.py runs a part, and any test the plan does not know runs in the first
part). A test that leaves something for a later test (a result on the class, a file in the class's
folder) is kept with it when the scan can see it; otherwise add the pair to KEEP_TOGETHER there.
--changed [REF] runs only the test files that the changes since REF (default: the merge-base with the
integration branch, plus uncommitted and untracked files) can affect: what each test used in earlier runs
(recorded while tests run, in ~/.showtime/cache/test-impact.json), a static scan of imports, paths and
commands, and OVERRIDES in tests/_impact.py; a file it cannot place runs everything and says which.
Longest files start first (timings of the previous run, kept in ~/.showtime/cache/test-times.json); a
failing file's output is printed in full at the end. A new test
must use temp folders, free ports (port 0) and atomic writes into ~/.showtime/cache
(common.part_path + os.replace, common.cache_lock); a file that truly cannot share the machine goes
in SERIAL in run_all.py, with the reason. A test that serves something (its own HTTP server, or
showtime's: render, check, snap, export, preview) uses tests/_listen.py, so it skips in a sandbox that
refuses listen() (SHOWTIME_TEST_NO_LISTEN=1 simulates one). --shard I/N runs one of N weight-balanced parts of the
files (CI splits the fast suite into three jobs per OS this way); the split uses only the file names and
SHARD_WEIGHTS in run_all.py, so give a new heavy test file a weight there.
Example media#
The examples live in their own repository,
showtime-examples: the 22 example folders, the
launch film, examples/MEDIA.json and scripts/publish_media.py. Example renders stay small in git
there: posters, share.txt, project sources and videos up to 10 MB. Any single file over 10 MB and every
.mov (ProRes masters) is published as a release asset of that repository instead; MEDIA.json lists
them (path, bytes, sha256, asset name) and a managed block at the end of its .gitignore keeps them out
of commits. This repository keeps only the README art (assets/readme/). Run these in a clone of
showtime-examples:
python scripts/publish_media.py # verify (check_release runs this too)
python scripts/publish_media.py --refresh # after adding or re-rendering example media
python scripts/publish_media.py --links --example 20 # markdown links for that example's README
python scripts/publish_media.py --upload --dry-run # the gh release upload command
python scripts/publish_media.py --upload # upload (needs gh and the release tag)
A README links an asset as https://github.com/<owner>/<repo>/releases/download/<tag>/<asset>, where
<asset> is the path under examples/ with -- for / (20-curtain-call-pack--pack--lower-thirds--lt-bar.mov);
--links prints them from the manifest.
Releases#
- Update CHANGELOG.md (what changed and why).
python scripts/check_release.py --set-version X.Y.Z(updatesst.__version__, both plugin manifests,setup/package.json,server.jsonandpackages/npm/package.json, and in the docs the site footer version, the README status line, the.mcpbdownload links and the Action pins). A new minor version also needs a person: the README's "What's new in X.Y.Z" summary and the status badge are reported, not rewritten.python scripts/check_release.py --check --mirrorand the full test run must be clean; work through the "Before publishing" list in CHANGELOG.md.--mirrorgoes online: it checks that every model and audio file the mirrors must hold is on its release (a new entry inmirror.jsonneedsscripts/stage_model_mirror.pyand its upload first).- When examples changed: in showtime-examples,
python scripts/publish_media.py --refresh, then--uploadto the release tag named inexamples/MEDIA.json(see "Example media"). - MCP packages (optional):
python scripts/build_packages.py all --out <folder outside the repo> --packbuilds the npm package (packages/npm/, named inserver.jsonfor the MCP Registry) and the.mcpbbundle (local stdio server for desktop clients and Smithery). It publishes nothing.