Turn one YouTube source video into several short (9:16) narrated videos through a resumable, command-driven pipeline with a human approval gate.
Run as python -m shorts <command> (activate the venv first, or use
.venv/bin/python -m shorts). X is the project name; omit it and the CLI
uses the sole / most-recent project.
python -m shorts fetch <url> --name X # yt-dlp -> source video; also seeds ideas/prompt.json
python -m shorts transcribe X # ffmpeg + OpenAI Whisper -> transcript
# optional: edit ideas/prompt.json to steer idea generation (pt-BR brief by default)
python -m shorts ideate X # prompt.json + transcript -> ideas/NN-slug.md (each with "- [ ] Approved")
# review ideas/*.md, tick "- [x] Approved", edit narration if needed
python -m shorts voice X # Gemini TTS -> voice/NN-slug.mp3 for approved ideas
python -m shorts plan X # ai-vedit plan -> renders/NN-slug.plan.json
# review / edit renders/*.plan.json before rendering
python -m shorts render X # ai-vedit render (from the plan) -> renders/NN-slug.mp4
python -m shorts youtube auth # one-time Google OAuth consent -> saves a token
python -m shorts publish X # upload approved+rendered shorts (private + scheduled)
python -m shorts status [X] # show stage + per-idea state
ideas/prompt.json is {"prompt": "..."} — the creative brief sent to the
ideation model, pre-filled in Brazilian Portuguese. Edit it any time; after the
first ideate run, changes need python -m shorts ideate X --force.
Narration voice is tuned globally in config.toml under [voice]: base_url
(VoiceStudio server, default http://localhost:3900), model (engine id,
e.g. omnivoice), voice (a voice profile id — your cloned voice), plus
optional language, speed, and instruct. Changing any of them
re-synthesizes affected clips on the next voice run and regenerates their
plan on the next plan run — no --force needed.
plan and render are separate so you can inspect the shot list before
spending render time. plan writes one renders/NN-slug.plan.json per approved
idea (JSON: audio_path + a beats array); it won't overwrite a plan you've
hand-edited unless the narration, voice settings, or [render] options changed
(or you pass --force). render turns each reviewed plan into
renders/NN-slug.mp4 and re-runs automatically when the plan file's contents
change.
[render] in config.toml: min_beat_duration tunes the plan; aspect
(top-level, 9:16 / 16:9) is passed to ai-vedit render. The
[render.subtitle] table controls burned-in captions — enabled (default
true) plus optional styling: font, font_size, primary_color (#RRGGBB),
bold, italic, uppercase, position (bottom/middle/top),
margin_vertical, max_chars_per_line, max_lines, max_duration. Each maps
to an ai-vedit plan --subtitle-* flag and is only sent when set; changing any
of them regenerates the affected plan on the next plan run. Requires
ai-vedit ≥ 0.2.0.
A local browser UI over the same pipeline:
python -m shorts serve # opens http://127.0.0.1:8765
python -m shorts serve --no-open # don't open a browser
python -m shorts serve --port 9000It lists projects, runs every stage with live-streamed output, and lets you
view/edit/approve ideas, edit ideas/prompt.json, and edit the
renders/NN-slug.plan.json shot lists. All files stay on disk exactly as the
CLI writes them — the server is only a front end, and runs each stage by
invoking python -m shorts <stage> as a subprocess.
Localhost only. There is no authentication. Do not pass --host 0.0.0.0 or
otherwise expose the port: the server can start processes and serves data
derived from your .env. There is also no CSRF protection, so any web page open
in the same browser can fire simple cross-origin POSTs at 127.0.0.1:8765
(e.g. POST /api/projects/<name>/run/<stage>, which takes no body) — an accepted
risk for this local-only, single-user tool.
Narration is synthesized by a local
VoiceStudio server over its
OpenAI-compatible POST /v1/audio/speech API — runs entirely on your
machine, no API key, and can use a cloned voice.
-
Install and open VoiceStudio; leave it running (default
http://localhost:3900) whenever you runvoice. -
Clone or pick a voice inside the app, then find its id:
curl http://localhost:3900/v1/audio/voices
Look for your profile's
voice_id(e.g."39f10351"). -
Point
config.tomlat it:[voice] model = "omnivoice" # or another engine id from GET /engines voice = "39f10351" # your cloned voice's id
python -m shorts voice X calls the running VoiceStudio server directly — no
API key needed. If the server isn't reachable it fails with a clear error
telling you to start VoiceStudio first. Tune model / voice / language /
speed / instruct under [voice] in config.toml (see above) and re-run
voice to hear changes; no --force needed since narration re-synthesizes
when those settings change.
Schedule rendered shorts to your own channel. Uploaded as private + scheduled so YouTube publishes them at the chosen time.
-
console.cloud.google.com → new project → enable YouTube Data API v3.
-
OAuth consent screen: User type External; add scope
.../auth/youtube.upload; add your Google account under Test users; leave the publishing status at Testing. -
Credentials → Create OAuth client ID → Desktop app → download the JSON.
-
Point
config.tomlat it:[youtube] client_secret = "client_secret.json" category_id = 22
python -m shorts youtube auth # one browser consent; re-run ~weekly (Testing mode)
python -m shorts youtube status # is a token present?
python -m shorts publish X # upload the whole approved+rendered queue
python -m shorts publish X --slug 01-foo # just oneor the Publish panel in the web UI (cadence + per-idea override + Upload).
The Testing-mode refresh token lapses after ~7 days — re-run youtube auth when
publish says the token expired. Videos already uploaded and scheduled publish
on time regardless. Quota is ~6 uploads/day. Publishing is always an explicit
action; nothing else in the pipeline touches YouTube.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp config.example.toml config.toml # then edit assets_dir etc.
cp .env.example .env # then fill in OPENAI_API_KEY and GEMINI_API_KEYRequires ffmpeg and ai-vedit on PATH. config.toml and .env are read
from the current working directory (the repo root).
Current: unit tests only.
python -m pytest- Stage-level tests for
fetch/transcribe/ideate/voice/renderwith the OpenAI/Gemini clients andsubprocess(yt-dlp, ffmpeg, ai-vedit) mocked — assert the files written and the manifest transitions. - End-to-end smoke test behind a
--runslowmarker: a ~20s clip through all five stages against the real OpenAI/Gemini APIs andai-vedit. - Browser/end-to-end test of the web UI (
shorts serve): drive a project through the stages against a stubbed job runner.