Scripts already referenced from CLAUDE.md but never committed: deploy_searxng.sh (which stands up the SearXNG instance Devon's web_search depends on), post_milestone.py, generate_milestone_images.py, make_x_launch_assets.py, schedule_x_launch.py, download_music.py, and the Reaper bleep-selection script. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
149 lines
5.8 KiB
Python
149 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate 1000 downloads milestone images using Nano Banana 2 (Gemini Flash Image)."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from google import genai
|
|
from google.genai import types
|
|
|
|
# Load .env manually
|
|
env_path = Path(__file__).parent / ".env"
|
|
if env_path.exists():
|
|
for line in env_path.read_text().splitlines():
|
|
line = line.strip()
|
|
if line and not line.startswith("#") and "=" in line:
|
|
key, _, value = line.partition("=")
|
|
os.environ.setdefault(key.strip(), value.strip())
|
|
|
|
client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
|
|
MODEL = "gemini-3.1-flash-image-preview"
|
|
OUTPUT_DIR = Path("social_posts/1000_milestone")
|
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def generate_image(prompt: str, filename: str, aspect_ratio: str = "1:1"):
|
|
print(f"Generating {filename}...")
|
|
response = client.models.generate_content(
|
|
model=MODEL,
|
|
contents=[prompt],
|
|
config=types.GenerateContentConfig(
|
|
response_modalities=["TEXT", "IMAGE"],
|
|
image_config=types.ImageConfig(
|
|
aspect_ratio=aspect_ratio,
|
|
image_size="2K",
|
|
),
|
|
),
|
|
)
|
|
for part in response.parts:
|
|
if part.inline_data is not None:
|
|
image = part.as_image()
|
|
path = OUTPUT_DIR / filename
|
|
image.save(str(path))
|
|
print(f" Saved: {path}")
|
|
return
|
|
print(f" WARNING: No image generated for {filename}")
|
|
|
|
|
|
STYLE_BASE = (
|
|
"Professional podcast promotional graphic. Dark navy/black background with subtle "
|
|
"warm amber and gold accent lighting, evoking a late-night radio studio atmosphere. "
|
|
"Clean modern typography. No photorealistic people. Subtle microphone and radio wave "
|
|
"design elements. Polished, minimal, high contrast."
|
|
)
|
|
|
|
images = [
|
|
{
|
|
"filename": "main_milestone_square.png",
|
|
"aspect_ratio": "1:1",
|
|
"prompt": (
|
|
f"{STYLE_BASE} "
|
|
"Large bold glowing text '1,000' as the hero element in the center, with "
|
|
"'DOWNLOADS' directly below it in a thinner font. Below that in smaller text: "
|
|
"'27 episodes · 200+ callers · 1 month'. "
|
|
"At the top: 'LUKE AT THE ROOST' in elegant lettering. "
|
|
"Subtle golden microphone icon above the title. "
|
|
"At the bottom: 'lukeattheroost.com' in small clean text. "
|
|
"The overall feel is celebratory but classy, like a late-night milestone announcement."
|
|
),
|
|
},
|
|
{
|
|
"filename": "main_milestone_twitter.png",
|
|
"aspect_ratio": "16:9",
|
|
"prompt": (
|
|
f"{STYLE_BASE} "
|
|
"Wide banner format. Left side has 'LUKE AT THE ROOST' title with a subtle "
|
|
"microphone graphic. Right side has large bold glowing '1,000 DOWNLOADS' text "
|
|
"with '27 episodes · 200+ callers · 1 month' below. "
|
|
"Warm amber glow connecting the two sides. "
|
|
"Bottom right corner: 'lukeattheroost.com'. "
|
|
"Designed as a Twitter/X post image."
|
|
),
|
|
},
|
|
{
|
|
"filename": "carousel_1_downloads.png",
|
|
"aspect_ratio": "1:1",
|
|
"prompt": (
|
|
f"{STYLE_BASE} "
|
|
"Instagram carousel slide 1. Giant bold text '1,000' taking up most of the frame, "
|
|
"with 'DOWNLOADS' below. Subtle radio wave ripples emanating from the numbers. "
|
|
"'LUKE AT THE ROOST' at the top in small elegant text. "
|
|
"Small '1/5' page indicator dots at the bottom."
|
|
),
|
|
},
|
|
{
|
|
"filename": "carousel_2_episodes.png",
|
|
"aspect_ratio": "1:1",
|
|
"prompt": (
|
|
f"{STYLE_BASE} "
|
|
"Instagram carousel slide 2. Large bold text '27' in the center with "
|
|
"'EPISODES' below. A subtle audio waveform timeline graphic running horizontally "
|
|
"behind the number. 'LUKE AT THE ROOST' at the top in small elegant text. "
|
|
"Small '2/5' page indicator dots at the bottom."
|
|
),
|
|
},
|
|
{
|
|
"filename": "carousel_3_callers.png",
|
|
"aspect_ratio": "1:1",
|
|
"prompt": (
|
|
f"{STYLE_BASE} "
|
|
"Instagram carousel slide 3. Large bold text '200+' in the center with "
|
|
"'CALLERS' below. Subtle vintage telephone handset icon above the number. "
|
|
"'LUKE AT THE ROOST' at the top in small elegant text. "
|
|
"Small '3/5' page indicator dots at the bottom."
|
|
),
|
|
},
|
|
{
|
|
"filename": "carousel_4_regulars.png",
|
|
"aspect_ratio": "1:1",
|
|
"prompt": (
|
|
f"{STYLE_BASE} "
|
|
"Instagram carousel slide 4. Large bold text '13' in the center with "
|
|
"'RETURNING REGULARS' below. Subtle connected dots/nodes graphic suggesting "
|
|
"a network of recurring characters. "
|
|
"'LUKE AT THE ROOST' at the top in small elegant text. "
|
|
"Small '4/5' page indicator dots at the bottom."
|
|
),
|
|
},
|
|
{
|
|
"filename": "carousel_5_thankyou.png",
|
|
"aspect_ratio": "1:1",
|
|
"prompt": (
|
|
f"{STYLE_BASE} "
|
|
"Instagram carousel slide 5. Warm, heartfelt tone. Large elegant text "
|
|
"'THANK YOU' in the center with a soft golden glow. Below: "
|
|
"'lukeattheroost.com' and '208-439-LUKE' in clean small text. "
|
|
"'LUKE AT THE ROOST' at the top in small elegant text. "
|
|
"Small '5/5' page indicator dots at the bottom. "
|
|
"Slightly warmer color temperature than the other slides to feel like a closing moment."
|
|
),
|
|
},
|
|
]
|
|
|
|
if __name__ == "__main__":
|
|
for img in images:
|
|
try:
|
|
generate_image(img["prompt"], img["filename"], img["aspect_ratio"])
|
|
except Exception as e:
|
|
print(f" ERROR generating {img['filename']}: {e}")
|
|
print(f"\nDone! Images saved to {OUTPUT_DIR}/")
|