Week 13 of 16

Build: Async Batch Generator

A tool that generates prompts for multiple shots across all platforms — all at once.

Day 63 75 minutes Build

Day 63 of 80

What You're Building

The batch generator takes a list of shots and generates prompts for all of them across all platforms simultaneously. 5 shots × 3 platforms = 15 API calls — all in parallel, completing in roughly the time of one call.

This is a real productivity tool. When you're planning a shoot, you can describe 10 shots and get 30 prompts back in under 5 seconds.

Create batch_generator.py

batch_generator.py Python
import asyncio
import json
import anthropic
from dotenv import load_dotenv
from logger import get_logger

load_dotenv()
logger = get_logger(__name__)

client = anthropic.AsyncAnthropic()

PLATFORMS = ["Kling", "Runway", "Veo"]


async def generate_one(shot, platform):
    """Generate a single prompt. Returns a dict with the result or error info."""
    try:
        message = await client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=150,
            messages=[{
                "role": "user",
                "content": f"Write a {platform} AI video prompt for: {shot}. "
                           f"Under 75 words. Only the prompt."
            }]
        )
        logger.info(f"Generated: [{platform}] {shot[:40]}...")
        return {
            "platform": platform,
            "shot": shot,
            "prompt": message.content[0].text,
            "status": "success",
        }
    except Exception as e:
        logger.error(f"Failed: [{platform}] {shot[:40]}... — {e}")
        return {
            "platform": platform,
            "shot": shot,
            "prompt": None,
            "status": f"error: {e}",
        }


async def batch_generate(shots, platforms=None):
    """Generate prompts for all shots × all platforms in parallel."""
    platforms = platforms or PLATFORMS

    # Build a flat list of all tasks — every combination of shot × platform.
    tasks = []
    for shot in shots:
        for platform in platforms:
            tasks.append(generate_one(shot, platform))

    print(f"Generating {len(tasks)} prompts ({len(shots)} shots × {len(platforms)} platforms)...")

    # asyncio.gather runs them ALL in parallel and returns results in order.
    results = await asyncio.gather(*tasks)
    return results


async def main():
    """Interactive mode — enter shots, generate in batch."""
    print("=== DVP Batch Prompt Generator ===\n")
    print("Enter shots one per line. Type 'done' when finished.\n")

    shots = []
    while True:
        shot = input(f"  Shot {len(shots) + 1} (or 'done'): ")
        if shot.strip().lower() == "done":
            break
        if shot.strip():
            shots.append(shot.strip())

    if not shots:
        print("No shots entered.")
        return

    # Run the async batch generation
    results = await batch_generate(shots)

    # Display results grouped by shot
    print(f"\n{'=' * 60}")
    current_shot = None
    for r in results:
        if r["shot"] != current_shot:
            current_shot = r["shot"]
            print(f"\n  SHOT: {current_shot}")
            print(f"  {'-' * 50}")

        if r["status"] == "success":
            print(f"\n  [{r['platform']}]")
            print(f"  {r['prompt']}\n")
        else:
            print(f"\n  [{r['platform']}] FAILED: {r['status']}\n")

    # Offer to save results
    successful = [r for r in results if r["status"] == "success"]
    print(f"\n{len(successful)} of {len(results)} prompts generated successfully.")

    if successful and input("Save all to database? (y/n): ").lower() == "y":
        from database import Database
        db = Database("prompt_vault.db")
        for r in successful:
            db.add_prompt(r["platform"], r["shot"], r["prompt"])
        db.close()
        print(f"Saved {len(successful)} prompts to database.")


# In a .py file, run async code with asyncio.run().
# This starts the event loop, runs main(), and cleans up.
if __name__ == "__main__":
    asyncio.run(main())

Error handling per task — the try/except inside generate_one() catches failures at the individual level. If one API call fails (rate limit, network blip), you still get all the successful results. The failed one returns a dict with status: "error" instead of crashing the whole batch.

Results stay in order. asyncio.gather() always returns results in the same order you passed the tasks. So results[0] corresponds to tasks[0], results[1] to tasks[1], etc. — even if task[5] finished before task[0].

asyncio.run(main()) is the standard entry point for async programs. It creates the event loop, runs your async main() function, and cleans up when it's done. Only call this once, at the very end of your script.

Run It

Run: python batch_generator.py

Enter 3–5 shots from a real project you're working on. Watch them all generate simultaneously — notice how the "Starting..." messages appear all at once, then results come in as they complete.

End of Day Checklist

Tomorrow

Day 64 finishes the batch generator with additional features, then Day 65 adds an async /generate endpoint to your FastAPI app.