YouTube Shorts rewards volume. Channels that post 5–10 times a day grow faster than channels that post once a week.
Manual production at that volume is impossible. But with an LLM writing the script and a TTS API doing the voiceover, you can generate 50 Shorts a week with a Python script that runs every morning.
Here's how to build it.
The architecture
A fully automated Shorts pipeline has four stages:
- Script generation — Claude or GPT writes a 30–60 second script on a topic
- Voiceover generation — LeanVox converts the script to audio
- Video assembly — ffmpeg combines voiceover with stock footage or generated visuals
- Upload — YouTube Data API v3 uploads and schedules the video
This tutorial covers stages 1–3. YouTube upload uses OAuth so it's best done from a web interface the first time to authenticate, then automated after.
Stage 1: Script generation
import anthropic
claude = anthropic.Anthropic()
def write_short_script(topic: str, style: str = "educational") -> str:
prompt = f"""Write a YouTube Shorts script about: {topic}
Style: {style}
Length: 45-60 seconds when spoken aloud (about 120-150 words)
Format: No stage directions, no "[pause]" markers, no intro fluff.
Start immediately with a hook that creates curiosity in the first sentence.
End with a clear call to action.
Return only the script text, nothing else."""
response = claude.messages.create(
model="claude-opus-4-5",
max_tokens=300,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
topics = [
"Why Python's GIL is finally going away in 3.13",
"The one git command most developers never use",
"Why your API is slow (and it's not your code)",
"The difference between async and parallel in Python",
"Why senior devs write less code, not more"
]
scripts = [(topic, write_short_script(topic)) for topic in topics]
Stage 2: Voiceover generation
from leanvox import Leanvox
import requests
import os
client = Leanvox(api_key="lv_live_...")
# Pick a consistent voice for your channel's brand
CHANNEL_VOICE = "podcast_conversational_female" # or your cloned voice
def generate_voiceover(script: str, output_path: str) -> str:
"""Generate voiceover MP3 from script text."""
result = client.generate(
text=script,
model="pro",
voice=CHANNEL_VOICE,
speed=1.1, # Slightly faster — works better for Shorts
)
audio = requests.get(result.audio_url).content
with open(output_path, "wb") as f:
f.write(audio)
return output_path
os.makedirs("shorts/audio", exist_ok=True)
for i, (topic, script) in enumerate(scripts):
audio_path = f"shorts/audio/short_{i:03d}.mp3"
generate_voiceover(script, audio_path)
print(f"✅ {topic[:50]}")
Stage 3: Video assembly with ffmpeg
Combine the voiceover with a looping background video or image. A simple approach uses a static background with animated text (handled by a template video):
import subprocess
import json
def get_audio_duration(audio_path: str) -> float:
"""Get duration of audio file in seconds."""
result = subprocess.run(
["ffprobe", "-v", "quiet", "-print_format", "json",
"-show_streams", audio_path],
capture_output=True, text=True
)
streams = json.loads(result.stdout)["streams"]
return float(streams[0]["duration"])
def assemble_short(
audio_path: str,
background_video: str,
output_path: str,
title_text: str = ""
) -> str:
"""Combine voiceover with background video."""
duration = get_audio_duration(audio_path)
# Loop background video to match audio length, overlay voiceover
cmd = [
"ffmpeg", "-y",
"-stream_loop", "-1", # loop background video
"-i", background_video, # background video
"-i", audio_path, # voiceover audio
"-t", str(duration), # trim to audio length
"-c:v", "libx264",
"-c:a", "aac",
"-b:a", "192k",
"-shortest",
"-vf", "scale=1080:1920", # vertical 9:16 format
output_path
]
subprocess.run(cmd, check=True, capture_output=True)
return output_path
os.makedirs("shorts/video", exist_ok=True)
for i, (topic, _) in enumerate(scripts):
audio_path = f"shorts/audio/short_{i:03d}.mp3"
video_path = f"shorts/video/short_{i:03d}.mp4"
assemble_short(
audio_path=audio_path,
background_video="templates/tech_background.mp4", # your looping background
output_path=video_path,
title_text=topic
)
print(f"✅ Video: {video_path}")
Making it production-ready
For a channel that posts daily, run this as a scheduled job. Use the CLI for bulk generation — it handles file input directly and supports async jobs for longer content:
# Generate voiceover from a script file with one command
lvox generate --model pro --voice podcast_conversational_female --speed 1.1 --file script.txt --output voiceover.mp3
For a fully automated weekly batch via Python:
import schedule
import time
from datetime import datetime
def daily_batch():
print(f"Running batch at {datetime.now()}")
weekly_topics = get_this_weeks_topics() # from your topic queue
for topic in weekly_topics:
script = write_short_script(topic)
# Submit all jobs in parallel, wait for results
job = client.generate_async(
text=script,
model="pro",
voice=CHANNEL_VOICE,
speed=1.1,
)
result = job.wait()
assemble_short(result.audio_url, "templates/bg.mp4", f"queue/{hash(topic)}.mp4")
schedule_youtube_upload(f"queue/{hash(topic)}.mp4", topic)
schedule.every().monday.at("06:00").do(daily_batch)
while True:
schedule.run_pending()
time.sleep(60)
Picking the right voice for your channel
Your voice is part of your brand. LeanVox has several approaches:
Use a curated voice — consistent across every video, no variation, builds recognition over time. Browse the podcast and narrator categories for voices that work in short clips.
Clone your own voice — if you already have a channel with your real voice, clone it. Your subscribers recognize you; the AI version sounds like you even when you're not recording.
# One-time: clone your voice from a 30-second sample
with open("my_voice.wav", "rb") as f:
voice = client.voices.clone(name="My Channel Voice", audio=f)
client.voices.unlock(voice.voice_id)
# Use your cloned voice for every Short
result = client.generate(
text=script,
model="pro",
voice=voice.voice_id,
)
Use Max tier for custom persona — describe the exact voice your channel persona should have:
result = client.generate(
text=script,
model="max",
instructions="Energetic tech educator, male, early 30s. Enthusiastic but not annoying. Clear and direct. Sounds like someone explaining something cool to a friend."
)
What it costs
A typical 45-second Short = ~130 words = ~750 characters.
| Volume | Pro tier cost | Monthly |
|---|---|---|
| 1 Short/day | $0.0075/video | $0.23 |
| 5 Shorts/day | $0.0075/video | $1.13 |
| 50 Shorts/day | $0.0075/video | $11.25 |
The voiceover cost is essentially rounding error. Your $1.00 signup credit covers 130+ Shorts.
Try it
Browse voices — find the one that fits your channel's personality. Preview before committing to a style.