Objective
What you're building
A script writer that turns any topic from Lesson 2's queue into a consistent, structured 60-90 second script — tested against 5 real topics until the output is reliably good, not just good once.
The short-form structure: hook, body, CTA
SCRIPT_SYSTEM_PROMPT = """You write scripts for a short-form video channel about {niche}.
Structure, every time:
1. HOOK (first 3 seconds, spoken): a surprising fact, a bold claim, or a question -- must work with zero context
2. BODY (45-70 seconds): deliver the actual value, 2-3 concrete points, plain language
3. CTA (last 5-10 seconds): a specific call to action -- follow for more, comment your take, etc.
Constraints:
- Total spoken length: 60-90 seconds (roughly 150-220 words at natural speaking pace)
- No jargon without immediately explaining it
- Write for the EAR, not the eye -- short sentences, no complex clauses
- Tone: {tone_description}
"""
"Write for the ear, not the eye" is the single most important instruction here -- a script that reads perfectly well on a page can sound convoluted spoken aloud, because written and spoken language have genuinely different rhythms. This is the same principle Course 09's voice receptionist prompt used for a different reason (avoiding lists/jargon in a live phone conversation) — different context, same underlying truth about text meant to be heard rather than read.
Validating length before it breaks lesson 4
A script's word count, not its "feel," is what determines if it fits your target duration
Estimate speech duration as word_count / 150 words-per-minute (a reasonable average speaking pace) and reject or flag any script that estimates outside your 60-90 second target BEFORE sending it to ElevenLabs. Catching this here costs nothing; discovering a 3-minute voiceover after already generating it (a paid API call) wastes real money and time.
def estimate_duration_seconds(script_text: str, wpm: int = 150) -> float:
word_count = len(script_text.split())
return (word_count / wpm) * 60
def validate_script(script_text: str, target_min: int = 60, target_max: int = 90) -> bool:
duration = estimate_duration_seconds(script_text)
return target_min <= duration <= target_max
This is a cheap, deterministic check -- no API call needed, just word counting -- that catches the single most common script-writer failure (running long or short) before it costs anything downstream.
You test the script writer on 5 different topics. 4 come back well within 60-90 seconds. The 5th, about a genuinely complex topic, comes back at an estimated 130 seconds even though you asked for 60-90. What's the right response?
Forcing complex content into a fixed length by mechanically truncating usually produces something that ends mid-thought or loses the actual value. The better fix is upstream: ask the model to CHOOSE a narrower angle on the topic that fits the format, or accept that not every topic (even a good Reddit topic) suits a 90-second video and route it back to the queue for a future, longer-format video instead.
What you're building today
script_writer.py: the system prompt + generate_script + validate_script, tested against 5 real topics from Lesson 2's queue, with the prompt iterated until scripts are consistently well-structured and within the target duration.