← All posts
· 5 min read

Automate YouTube Shorts with AI Voice: Generate 50 Videos a Week with Python

How content creators and developers are building automated YouTube Shorts pipelines using LLMs for scripts and TTS for voiceover.

youtubeautomationpythonai

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:

  1. Script generation — Claude or GPT writes a 30–60 second script on a topic
  2. Voiceover generation — LeanVox converts the script to audio
  3. Video assembly — ffmpeg combines voiceover with stock footage or generated visuals
  4. 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.

VolumePro tier costMonthly
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.

Get your API key · Docs


Try LeanVox free

$0.50 in free credits. No credit card required. Start generating speech in 30 seconds.

Get started free →