# ⚙️ Daily Production Workflow

**Purpose:** How a single birthday video gets from name idea → uploaded YouTube video in <30 min, fully automated via cron.

**Cadence:** 1 video/day at **9:00 AM ET** (US East — captures both coasts' "morning birthday" search traffic).

**Stack:** MiniMax image_generate + MusicGen (local) / Suno (web) + ffmpeg + yt_batch_upload.py (existing).

---

## Pipeline Overview

```
[9:00 AM cron]
    │
    ▼
[1. Pick name] ─────────► birthday_queue.json (FIFO)
    │
    ▼
[2. Generate assets in parallel]
    ├─► Background scene (MiniMax image_generate, 30s)
    ├─► Thumbnail (MiniMax image_generate, 30s)
    ├─► Song (MusicGen local / Suno web, 5-15 min)
    │
    ▼
[3. Render video]
    └─► ffmpeg composite (Luna + scene + text + audio), 2 min
    │
    ▼
[4. Upload to YouTube]
    └─► yt_batch_upload.py (existing n8n flow)
    │
    ▼
[5. Log + track]
    └─► yt_uploads.json entry + view tracking
```

---

## Step 1 — Name Source (Priority Queue)

### `birthday_queue.json` structure

Lives at: `~/ai-server/projects/birthday/birthday_queue.json` (or `/Volumes/TommyStudio/sss_active/birthday/`)

```json
{
  "queue": [
    {
      "name": "Sarah",
      "slug": "sarah",
      "age": 7,
      "gender": "female",
      "color_palette": "pink-lavender",
      "scene": "meadow",
      "song_style": "pop",
      "lyric_variant": "B",
      "scheduled_date": "2026-09-15",
      "status": "pending"
    },
    {
      "name": "Marcus",
      "slug": "marcus",
      "age": 5,
      "gender": "male",
      "color_palette": "red-yellow-blue",
      "scene": "carnival",
      "song_style": "country",
      "lyric_variant": "C",
      "scheduled_date": "2026-09-16",
      "status": "pending"
    }
  ],
  "completed_count": 0,
  "next_index": 0
}
```

### Name list source (top of queue)

Pre-populate the first 365 entries before launch:

1. **Pull US SSA Top 1000 names by birth year (1920-2024).**
   - URL: https://www.ssa.gov/oact/babynames/state/namesbystate.zip
   - 5MB zip, parsed in 1 min
2. **Deduplicate** (keep one row per unique first name, all-time rank = sum of births across years).
3. **Sort by all-time frequency** (most-popular first → fills highest-traffic search slots early).
4. **Take top 365** as the launch queue.

### De-duplication vs variants

If we ever ship multiple variants per name (Year 2: pop + country + lullaby), each variant = one queue entry. Keep them grouped by name for playlist cohesion.

---

## Step 2 — Asset Generation (Parallel)

Three assets can generate simultaneously. Run them in parallel to halve wall-clock time.

### 2A. Background Scene (MiniMax image_generate)

```bash
# Prompt template in covers/cover_specs.md (same template as thumbnail)
# Generate scene image only (no text overlay, no Luna)
image_generate \
  --prompt "[scene-specific prompt from covers/<name>_thumbnail.md, MINUS the name text and Luna]" \
  --size 1920x1080 \
  --output /Volumes/TommyStudio/sss_active/birthday/scenes/<name>_bg.png
```

**Time:** 30s
**Cost:** ~$0.02

### 2B. Thumbnail (MiniMax image_generate)

```bash
# Full thumbnail with name + Luna
image_generate \
  --prompt "$(cat /Users/aihub01/sss_review_hub/birthday/covers/<name>_thumbnail.md | grep 'prompt:' -A 20)" \
  --size 1280x720 \
  --outputFormat jpeg \
  --quality high \
  --output /Volumes/TommyStudio/sss_active/birthday/thumbs/<name>_thumb.jpg
```

**Time:** 30s
**Cost:** ~$0.02

### 2C. Song (MusicGen local)

```bash
# MusicGen via audiocraft
python -m audiocraft \
  --model musicgen-large \
  --text "happy birthday song for $NAME, $STYLE, 30 seconds, kid-friendly, ukulele and claps" \
  --duration 30 \
  --output /Volumes/TommyStudio/sss_active/birthday/songs/${NAME}_instrumental.wav
```

**Time:** 5-15 min on M-series Mac (M2/M3 Pro), 30-60s if GPU available locally.
**Cost:** $0

**Then layer the name TTS (ElevenLabs Wiselady, per TOOLS.md):**

```bash
# Generate spoken name clip
elevenlabs_generate \
  --voice English_Wiselady \
  --text "$NAME" \
  --output /tmp/${NAME}_name.wav

# Layer name over instrumental at 11s and 18s (the "Happy birthday dear NAME" moments)
ffmpeg -i ${NAME}_instrumental.wav \
  -i /tmp/${NAME}_name.wav \
  -filter_complex "[1:a]adelay=11000|11000,volume=1.5[a1];[0:a][a1]amix=inputs=2:duration=first" \
  /Volumes/TommyStudio/sss_active/birthday/songs/${NAME}_song.mp3
```

**Alternative (Suno web, paid):**

Manual workflow (cannot fully automate, requires human in loop):
1. Open suno.com
2. Custom mode → paste lyrics:
   ```
   [Verse]
   Happy birthday to you
   Happy birthday to you
   Happy birthday dear <NAME>
   Happy birthday to you!
   
   [Bridge]
   Hip hip hooray for <NAME>
   It's your special day
   <PERSONALIZATION_LINE>
   Hip hip hooray for <NAME>
   ```
3. Style: `<STYLE>` (pop / country / rock / lullaby pop)
4. Generate → download → save to songs folder

---

## Step 3 — Render Video (ffmpeg)

```bash
#!/bin/bash
# render_birthday.sh — composite all assets into 30s MP4

NAME=$1
AGE=$2
SENDER=${3:-"Your Friends at Smart Stuff Studios"}

BG="/Volumes/TommyStudio/sss_active/birthday/scenes/${NAME}_bg.png"
LUNA="/Users/aihub01/sss_active/sprites/luna_base.png"  # pre-rendered
SONG="/Volumes/TommyStudio/sss_active/birthday/songs/${NAME}_song.mp3"
OUT="/Volumes/TommyStudio/sss_active/birthday/videos/${NAME}.mp4"

# Composite: background + Luna overlay + text overlay + audio
ffmpeg -loop 1 -i "$BG" \
  -loop 1 -i "$LUNA" \
  -i "$SONG" \
  -filter_complex "
    [0:v]scale=1920:1080[bg];
    [1:v]scale=400:400[lua];
    [bg][lua]overlay=x=80:y=600:enable='between(t,3,28)'[v1];
    [v1]drawtext=text='${NAME}':fontfile=/Library/Fonts/Baloo-Bold.ttf:fontcolor=#FF69B4:fontsize=200:x=(w-text_w)/2:y=300:enable='between(t,3,28)'[v2];
    [v2]drawtext=text='Happy Birthday ${NAME}!':fontcolor=white:fontsize=60:x=(w-text_w)/2:y=900[v3]
  " \
  -t 30 \
  -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 192k \
  -pix_fmt yuv420p \
  -shortest \
  "$OUT"
```

**Time:** 2-3 min
**Output:** 1920x1080, 30fps, ~10-15 MB MP4

---

## Step 4 — Upload via yt_batch_upload.py (existing)

The script at `~/ai-server/scripts/yt_upload.py` (referenced in TOOLS.md) handles YouTube uploads via n8n. Per the existing pipeline:

```bash
# Per existing yt_batch_upload.py usage
python ~/ai-server/scripts/yt_upload.py \
  --video "/Volumes/TommyStudio/sss_active/birthday/videos/${NAME}.mp4" \
  --title "Happy Birthday ${NAME}! 🎂 | Personalized Birthday Song from Luna" \
  --description "$(cat /Users/aihub01/sss_review_hub/birthday/youtube_description.md | \
    sed "s/\[NAME\]/${NAME}/g; s/\[AGE\]/${AGE}/g")" \
  --tags "happy birthday ${NAME},happy birthday ${NAME} song,${NAME} birthday,birthday song for ${NAME},personalized birthday song,luna birthday,smart stuff studios" \
  --thumbnail "/Volumes/TommyStudio/sss_active/birthday/thumbs/${NAME}_thumb.jpg" \
  --category "Entertainment" \
  --audience "not_made_for_kids" \
  --playlist "Personalized Birthday Songs"
```

**Time:** 1-2 min (network upload + YouTube processing)
**Logging:** writes entry to `~/.n8n/yt_uploads.json` (existing) + appends to `~/ai-server/logs/birthday_uploads.log`

---

## Step 5 — Cron Setup

### Crontab entry

```cron
# Birthday video — 9:00 AM ET every day
0 9 * * * /Users/aihub01/ai-server/scripts/birthday_daily.sh >> ~/ai-server/logs/birthday_cron.log 2>&1
```

### `birthday_daily.sh` wrapper

```bash
#!/bin/bash
# ~/ai-server/scripts/birthday_daily.sh
# Runs the daily birthday video pipeline

set -e

QUEUE=~/ai-server/projects/birthday/birthday_queue.json
LOG=~/ai-server/logs/birthday_cron.log

# Pull next entry
ENTRY=$(python ~/ai-server/scripts/birthday_pop_queue.py "$QUEUE")
NAME=$(echo "$ENTRY" | jq -r .name)
AGE=$(echo "$ENTRY" | jq -r .age)
SLUG=$(echo "$ENTRY" | jq -r .slug)

echo "[$(date)] Starting birthday video for $NAME (age $AGE)" >> "$LOG"

# Step 2A-2C in parallel (background tasks)
generate_assets "$NAME" "$AGE" &

# Step 3 — wait for assets, then render
wait
render_birthday "$NAME" "$AGE"

# Step 4 — upload
upload_birthday "$NAME" "$AGE"

# Step 5 — log
echo "[$(date)] ✓ $NAME uploaded. yt_video_id: $(cat /tmp/last_yt_id)" >> "$LOG"

# Update queue
python ~/ai-server/scripts/birthday_mark_done.py "$QUEUE" "$SLUG"
```

---

## Step 6 — Tracking

### Per-video metrics to log

For each completed video, append to `~/ai-server/logs/birthday_metrics.csv`:

```
date,name,yt_video_id,views_24h,views_7d,views_30d,ctr,avg_view_duration,subscribers_gained,revenue_usd
2026-09-15,Sarah,dQw4w9WgXcQ,234,892,3421,4.2%,28s,12,$0.84
2026-09-16,Marcus,x7YpJZ9kLmM,189,734,2891,3.8%,26s,8,$0.71
...
```

### Daily review cron (for Max)

```cron
# Daily 6 PM ET — pull metrics, summarize top performers
0 18 * * * python ~/ai-server/scripts/birthday_daily_report.py
```

Outputs to `~/ai-server/state/birthday_report.md` and posts to Tommy via ntfy if any video crosses 10K views in 24h.

### Weekly review (Monday 9 AM)

- Identify top-10 performers (by views per day since upload)
- Identify bottom-10 (potential algorithm issues or weak thumbnails)
- Adjust next week's name picks: boost search-volume names, deprioritize low-traffic names

---

## Pre-flight Checklist (every morning, before cron fires)

The cron script `birthday_daily.sh` should auto-check:

```bash
# 1. TommyStudio mounted?
mount | grep -q TommyStudio || { echo "FATAL: TommyStudio unmounted"; exit 1; }

# 2. Disk space > 5GB free on TommyStudio?
FREE=$(df -k /Volumes/TommyStudio | tail -1 | awk '{print $4}')
[ "$FREE" -gt 5242880 ] || { echo "FATAL: <5GB free on TommyStudio"; exit 1; }

# 3. Internal disk space > 2GB free? (logs may write locally)
FREE_INT=$(df -k ~ | tail -1 | awk '{print $4}')
[ "$FREE_INT" -gt 2097152 ] || { echo "WARN: <2GB free internal"; }

# 4. MiniMax image_generate API reachable?
curl -s --max-time 5 -o /dev/null -w "%{http_code}" "$IMAGE_GEN_API" | grep -q 200 || { echo "FATAL: image gen API down"; exit 1; }

# 5. yt_batch_upload.py creds valid?
[ -f ~/.n8n/yt_credentials.json ] || { echo "FATAL: YT creds missing"; exit 1; }

# 6. MusicGen path valid? (or Suno cookies fresh?)
[ -d ~/audiocraft ] || [ -f ~/.config/suno/cookies.json ] || { echo "WARN: No music source ready"; }

echo "✓ All pre-flight checks passed"
```

If any FATAL fails, send ntfy alert (`tommy-studio-alerts` topic) and abort the run. The video skips to the next day.

---

## Cost Roll-up (per video)

| Asset | Tool | Cost |
|---|---|---|
| Scene image | MiniMax image_generate | $0.02 |
| Thumbnail | MiniMax image_generate | $0.02 |
| Song (Year 1) | Suno Pro (paid plan, $10/mo amortized over 30 videos/mo) | $0.33 |
| Song (Year 2) | MusicGen local | $0.00 |
| Render | ffmpeg local | $0.00 |
| Upload | yt_batch_upload.py (existing infra) | $0.00 |
| **Total per video (Y1)** | | **$0.37** |
| **Total per video (Y2+)** | | **$0.04** |

**Year-1 total cost (365 videos):** $135
**Year-2+ total cost (365 videos):** $15

Versus revenue (per channel_plan.md § 4):
- Year-1 floor: $730 (if 1K views/video average)
- Year-3 floor: $4,400/year passive
- Plus $10K-30K upside from upsells/sponsorships

**ROI is overwhelmingly positive.** Even Year-1 conservative case is 5x return on cost.

---

## Token Efficiency Notes (per AGENTS.md)

This pipeline respects Max v2 rules:
- **No M3 cron:** The 9 AM cron fires a Python/bash pipeline, NOT a Max session. Max is involved only for name picks and weekly reviews (not in the hot path).
- **No re-reading big files:** The cron pulls YAML/JSON configs, not Markdown bibles.
- **Batched:** All 3 assets (scene, thumb, song) generate in parallel, not sequentially.
- **Disk-safe:** Every heavy output goes to TommyStudio, not internal disk.

---

## When To Ask For Help (Stop-Loss Rules)

If any of these hit, STOP and surface to Tommy:

1. **Cron fails 3 days in a row** — something systemic, don't loop.
2. **MiniMax image_generate cost spikes** — daily cost > $5 (likely a bad prompt loop).
3. **Music licensing strike** — pause all uploads immediately.
4. **YT flags channel as "Made for Kids"** — override per-video, document dispute.
5. **Suno/MusicGen both offline** — fall back to stock music for that day, note in queue.

---

## Launch Checklist (Tommy-approved gating)

Before flipping cron from "test" to "production":

- [ ] 30 sample videos uploaded + first-week CTR data reviewed
- [ ] Channel profile picture (Luna logo), banner (365-name mosaic?), about text complete
- [ ] Channel custom URL set (`@LunasBirthdaySongs`)
- [ ] Channel sections configured: "Top 10 Names", "Recent", "By Style", "Lullaby", "Rock"
- [ ] End-screen template applied to all 30 sample videos
- [ ] Playlist created: "All Birthday Songs" → auto-add via YT Studio
- [ ] Watermark enabled (subscribe button overlay)
- [ ] Sponsor outreach email drafted (Year 2)
- [ ] Custom-song landing page live (lunasbirthdaysongs.com/custom)
- [ ] TikTok channel `@lunasbirthdaysongs` linked in YouTube about
