shortsmith
Give it a topic, get back a captioned vertical short. On your machine, with no API key.
pipx install shortsmith · try it with nothing installed · docker compose run --rm shortsmith make "your topic"
Real frames, from examples/why-bread-goes-stale.json. Word-level caption highlighting is the karaoke effect — each word lights up as it is spoken.
shortsmith make "why bread goes stale"
Script, narration, backgrounds, word-timed captions, one mp4. It needs ffmpeg and nothing else — no Python dependencies at all, no Pexels key, no cloud voice, no account.
Why another one of these
MoneyPrinter has 45k stars between its two versions and a lot of people who cannot get it to run. The reasons are worth naming, because avoiding them is most of what this is:
| Failure | What happens here |
|---|---|
| Needs a Pexels API key for footage | Backgrounds are generated by ffmpeg. Nothing to sign up for. |
| Needs a cloud TTS account | Uses whatever speech engine you already have; renders silent with captions if you have none. |
| Python dependency tree that breaks yearly | Zero runtime dependencies. Standard library plus ffmpeg. |
| One-shot pipeline; a failure at 90% loses everything | Every step is cached and resumable. Change one line of narration and only that scene re-renders. |
| Fails cryptically deep in a filtergraph | shortsmith doctor tells you what is missing and what it will cost you, before you spend five minutes. |
Try it with nothing installed
shortsmith make "why bread goes stale" --provider offline
offline writes the script from a template rather than a model — no key, no GPU,
no network. It is not pretending to write; it exists so you can watch the pipeline
work on a clean machine before deciding whether to install anything.
Then check what you have:
$ shortsmith doctor
ok ffmpeg /opt/homebrew/bin/ffmpeg
ok captions libass: burned-in, word-timed
ok say Built into macOS. Fine, and already there.
Script writing:
?? ollama Local. Nothing leaves the machine.
-- anthropic needs ANTHROPIC_API_KEY
ok offline No model at all: a template.
Writing the script
shortsmith make "why bread goes stale" --provider ollama # local, default
shortsmith make "..." --provider anthropic # needs a key
shortsmith make "..." --provider offline # no model at all
The prompt is short and opinionated, because a model asked for "a script about X" reliably writes an intro, and an intro is the one thing a short cannot afford. It asks for a hook under 12 words, no "in this video", numbers spoken as words, and nothing invented — if the model is unsure of a figure, leave it out.
Then it checks its own work:
hook Bread does not dry out. It recrystallises, which is why the freezer fixes it.
2 Starch molecules that were loose after baking slowly line back up.
...
Worth a look:
· scene 3 contains "in this video", which wastes the hook
· about 78s of narration; shorts are capped at 60
Split the two steps to edit in between — which is the point:
shortsmith script "why bread goes stale" -o bread.json
$EDITOR bread.json
shortsmith render bread.json
Narration
Speech engines are probed in order of quality and every one is optional:
| Engine | Notes |
|---|---|
piper |
Neural, local, genuinely good. The one to install. |
edge-tts |
Microsoft's voices. Free, but it is a network call. |
say |
Built into macOS. Already there. |
espeak-ng |
Robotic, works everywhere, needs nothing. |
shortsmith make "..." --tts piper --voice ~/voices/en_GB-alba-medium.onnx
shortsmith make "..." --tts none # silent cut, captions only
If none is installed the video still renders — silent, captioned, and the tool says so. That is more useful than refusing to run.
Every clip is loudness-normalised to −16 LUFS. A short that is quieter than the one before it in the feed gets scrolled past.
Backgrounds
Five styles, all generated by ffmpeg filtergraphs: gradient, drift, grain,
pulse, solid. Each scene gets a different one, deterministically, so a rerun
does not silently change the look.
The palettes are not decorative choices. Every one has an ink colour that clears
7:1 against both ends of its own gradient, and there is a test that fails if
one does not — because a caption you cannot read at 60% phone brightness is a
caption that did not exist.
shortsmith make "..." --style grain # force one
shortsmith make "..." --music bed.mp3 # ducked under the narration
Music is sidechain-compressed against the voice track, which is the difference between "has background music" and "you can hear the narrator".
Captions
ASS subtitles with per-word karaoke timing, burned in. Word timing is weighted by length rather than split evenly — "the" and "extraordinarily" do not take the same time to say, and an even split visibly drifts by the end of a sentence.
An .srt and the .ass are written next to the video either way. Every platform
accepts an .srt on upload.
Resumable
out/
bread.mp4
bread.ass
bread.srt
.bread/
manifest.json what has been built, and from what
audio/ one wav per scene
scenes/ one mp4 per scene
Each step records a fingerprint of its inputs. Rerun after editing scene 3 and scenes 1, 2, 4 and 5 are reused untouched — including their narration, which is the slow part.
Two things that will bite you, and what to do
Your ffmpeg may not be able to draw text. Plenty of builds ship without libass
and libfreetype, and captions are most of what a short is. doctor says so up
front, the render continues without burned-in text, and the sidecar files are
written so you can burn them in later:
ffmpeg -i out.mp4 -vf subtitles=out.ass final.mp4
Fonts are resolved to a file path, never a family name. drawtext=font=Arial
asks fontconfig to find it, and on a machine with no font cache — a static build, a
fresh container, a CI runner — that can take minutes or hang outright. It cost an
afternoon to find. shortsmith searches the standard directories itself and always
passes fontfile=.
Options
--provider ollama|anthropic|openai|offline
--model <name>
--scenes <n> how many beats (default 5)
--preset vertical|square|horizontal|vertical-fast
--fps <n>
--tts auto|none|piper|edge-tts|say|espeak
--voice <name or model path>
--rate <wpm>
--music <file> --music-gain <0-1>
--style gradient|drift|grain|pulse|solid
--font <name or path>
--no-captions --no-on-screen
--crf <n> quality; lower is bigger and better
--project <dir> where the working files live
What this will not do
- It will not upload for you. Automated posting is how accounts get banned, and an OAuth token in a config file is a liability. The mp4 and the caption files are yours to post.
- It will not scrape stock footage. No Pexels key required means no Pexels terms to violate.
- It will not make you money. It makes a video. What you put in it is the entire variable, and no tool can supply that.
Contributing
pip install -e ".[dev]"
pytest
54 tests. The ones needing ffmpeg skip cleanly without it, so pytest is still
useful on a machine that has none.
Most useful contributions, in order:
- A background style. One function in
visuals.pyreturning a filtergraph. - A speech engine. One branch in
tts.py. - An ffmpeg build it breaks on. Those are the real bugs — see the two above.
MIT. Fork it, rename it, ship it.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shortsmith-0.1.0.tar.gz.
File metadata
- Download URL: shortsmith-0.1.0.tar.gz
- Upload date:
- Size: 348.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52c917cedbbbe7aba81c264174609d660d6b2f2cf0a519d02eba476549c5633a
|
|
| MD5 |
140ca0f83a62e7abb1523e5c03653e40
|
|
| BLAKE2b-256 |
187af4838a2009635e45de843c804020429ac4a6928f38a79da61ce7625c671e
|
Provenance
The following attestation bundles were made for shortsmith-0.1.0.tar.gz:
Publisher:
release.yml on mohitagw15856/shortsmith
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shortsmith-0.1.0.tar.gz -
Subject digest:
52c917cedbbbe7aba81c264174609d660d6b2f2cf0a519d02eba476549c5633a - Sigstore transparency entry: 2587243675
- Sigstore integration time:
-
Permalink:
mohitagw15856/shortsmith@2a57e6fd54a09b7ecb19ab65b9b0bb9abaff67a1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mohitagw15856
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2a57e6fd54a09b7ecb19ab65b9b0bb9abaff67a1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file shortsmith-0.1.0-py3-none-any.whl.
File metadata
- Download URL: shortsmith-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4593e0dda31cb5274239c6663c11faf62bb86650ca52697f0df8e3bf3a17050f
|
|
| MD5 |
8c6e506da09d64de9a1c0c311ef2869c
|
|
| BLAKE2b-256 |
7856e2a1ee0d578411c83a625dbb61ff6e4743c813626418e817697809c400f7
|
Provenance
The following attestation bundles were made for shortsmith-0.1.0-py3-none-any.whl:
Publisher:
release.yml on mohitagw15856/shortsmith
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shortsmith-0.1.0-py3-none-any.whl -
Subject digest:
4593e0dda31cb5274239c6663c11faf62bb86650ca52697f0df8e3bf3a17050f - Sigstore transparency entry: 2587243996
- Sigstore integration time:
-
Permalink:
mohitagw15856/shortsmith@2a57e6fd54a09b7ecb19ab65b9b0bb9abaff67a1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mohitagw15856
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2a57e6fd54a09b7ecb19ab65b9b0bb9abaff67a1 -
Trigger Event:
push
-
Statement type: