Metadata-Version: 2.5
Name: shortsmith
Version: 0.1.0
Summary: A topic in, a captioned vertical short out. Runs on your machine, needs no API key, and tells you what it cannot do.
Project-URL: Homepage, https://github.com/mohitagw15856/shortsmith
Project-URL: Issues, https://github.com/mohitagw15856/shortsmith/issues
Author: Mohit (mohitagw15856)
License: MIT
License-File: LICENSE
Keywords: captions,cli,content-creation,ffmpeg,local-first,ollama,reels,shorts,subtitles,text-to-speech,tiktok,video,youtube-shorts,zero-dependency
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

<h1 align="center">shortsmith</h1>

<p align="center"><b>Give it a topic, get back a captioned vertical short. On your machine, with no API key.</b></p>

<p align="center">
  <code>pipx install shortsmith</code> · <a href="#try-it-with-nothing-installed">try it with nothing installed</a> · <code>docker compose run --rm shortsmith make "your topic"</code>
</p>

<p align="center">
  <img src="assets/demo.gif" alt="shortsmith rendering a short in a terminal" width="880">
</p>

<p align="center">
  <img src="assets/frame.png" alt="A frame from a rendered short" width="240">
  <img src="assets/frame2.png" alt="Another frame from a rendered short" width="240">
</p>

<p align="center"><sub>Real frames, from <code>examples/why-bread-goes-stale.json</code>. Word-level caption highlighting is the karaoke effect — each word lights up as it is spoken.</sub></p>

---

```sh
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](https://github.com/FujiwaraChoki/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

```sh
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

```sh
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:

```sh
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. |

```sh
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.

```sh
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:

```sh
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

```sh
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:

1. **A background style.** One function in `visuals.py` returning a filtergraph.
2. **A speech engine.** One branch in `tts.py`.
3. **An ffmpeg build it breaks on.** Those are the real bugs — see the two above.

MIT. Fork it, rename it, ship it.
