← back to Goodquestion Ai

scripts/make-avatar-video.sh

221 lines

#!/bin/bash
# =============================================================================
# Make Avatar + Graphics Video — Julian Goldie Style
# Generates narration → graphic slides → composites with avatar PIP
# =============================================================================
set -e

BASEDIR="/root/Projects/goodquestion-ai/dashboard-captures/video-01"
AVATARDIR="/root/Projects/goodquestion-ai/dashboard-captures/avatar"
VOICE="en-US-AndrewNeural"
RATE="-10%"
FPS=30
WIDTH=1920
HEIGHT=1080
AVATAR_SIZE=220
AVATAR_MARGIN=40

mkdir -p "$BASEDIR/narration" "$BASEDIR/slides" "$BASEDIR/segments"

echo "═══════════════════════════════════════════════"
echo "  STEP 1: Generate Narration (edge-tts)"
echo "═══════════════════════════════════════════════"

# Segment 1: Hook
edge-tts --voice "$VOICE" --rate="$RATE" --text \
  "I replaced an entire dev team with AI. No employees. No contractors. Just me and Claude Code. Here's what I built in one weekend." \
  --write-media "$BASEDIR/narration/01-hook.mp3" 2>/dev/null
echo "  01-hook.mp3"

# Segment 2: Trading
edge-tts --voice "$VOICE" --rate="$RATE" --text \
  "First up — a real-time trading analytics dashboard. Live charts. Portfolio performance graphs. Win rate radials. Strategy breakdowns. All from actual market data. Built in six hours." \
  --write-media "$BASEDIR/narration/02-trading.mp3" 2>/dev/null
echo "  02-trading.mp3"

# Segment 3: Civic
edge-tts --voice "$VOICE" --rate="$RATE" --text \
  "Then — a civic engagement platform. AI-powered content generation, network analysis with force-directed graphs, Three.js visualizations, and a full API layer. Twelve tabs. Hundreds of components." \
  --write-media "$BASEDIR/narration/03-civic.mp3" 2>/dev/null
echo "  03-civic.mp3"

# Segment 4: Scale
edge-tts --voice "$VOICE" --rate="$RATE" --text \
  "The secret? Parallel AI agents. I dispatch six at once, each building a different feature. They read the codebase, understand the patterns, and ship working code. Autonomously." \
  --write-media "$BASEDIR/narration/04-scale.mp3" 2>/dev/null
echo "  04-scale.mp3"

# Segment 5: Results
edge-tts --voice "$VOICE" --rate="$RATE" --text \
  "The numbers don't lie. One hundred and five production processes running on one server. Ninety-five vendor integrations. Over a hundred thousand data records. All managed by one person." \
  --write-media "$BASEDIR/narration/05-results.mp3" 2>/dev/null
echo "  05-results.mp3"

# Segment 6: CTA
edge-tts --voice "$VOICE" --rate="$RATE" --text \
  "This isn't science fiction. This is Tuesday. Follow Agent Abrams for more. Link in bio." \
  --write-media "$BASEDIR/narration/06-cta.mp3" 2>/dev/null
echo "  06-cta.mp3"

echo ""
echo "═══════════════════════════════════════════════"
echo "  STEP 2: Create Graphic Slides (ffmpeg)"
echo "═══════════════════════════════════════════════"

# Color scheme matching CrypTrade neon theme
BG="#0E0E10"
CYAN="#00F0FF"
ORANGE="#F7931A"
GREEN="#00C389"
WHITE="#E0E0E0"
DIM="#6B7280"

# Slide 1: Hook — Dark with just "AGENT ABRAMS" glowing
ffmpeg -y -f lavfi -i "color=c=${BG}:s=${WIDTH}x${HEIGHT}:d=1" \
  -vf "drawtext=text='AGENT ABRAMS':fontsize=80:fontcolor=${CYAN}:x=(w-text_w)/2:y=(h-text_h)/2-60:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='vs. An Entire Dev Team':fontsize=36:fontcolor=${DIM}:x=(w-text_w)/2:y=(h-text_h)/2+40:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" \
  -frames:v 1 "$BASEDIR/slides/01-hook.png" 2>/dev/null
echo "  01-hook.png"

# Slide 2: Trading Analytics
ffmpeg -y -f lavfi -i "color=c=${BG}:s=${WIDTH}x${HEIGHT}:d=1" \
  -vf "drawtext=text='TRADING ANALYTICS':fontsize=64:fontcolor=${CYAN}:x=(w-text_w)/2:y=120:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━':fontsize=20:fontcolor=${CYAN}@0.3:x=(w-text_w)/2:y=210:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ 6 Interactive Charts':fontsize=40:fontcolor=${WHITE}:x=300:y=300:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ Live Portfolio Tracking':fontsize=40:fontcolor=${WHITE}:x=300:y=380:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ Strategy Breakdown':fontsize=40:fontcolor=${WHITE}:x=300:y=460:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='⏱ Built in 6 Hours':fontsize=48:fontcolor=${GREEN}:x=(w-text_w)/2:y=600:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" \
  -frames:v 1 "$BASEDIR/slides/02-trading.png" 2>/dev/null
echo "  02-trading.png"

# Slide 3: Civic Platform
ffmpeg -y -f lavfi -i "color=c=${BG}:s=${WIDTH}x${HEIGHT}:d=1" \
  -vf "drawtext=text='CIVIC ENGAGEMENT':fontsize=64:fontcolor=${CYAN}:x=(w-text_w)/2:y=120:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━':fontsize=20:fontcolor=${CYAN}@0.3:x=(w-text_w)/2:y=210:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ 12 Feature Tabs':fontsize=40:fontcolor=${WHITE}:x=300:y=300:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ AI Content Engine':fontsize=40:fontcolor=${WHITE}:x=300:y=380:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ 3D Network Graphs':fontsize=40:fontcolor=${WHITE}:x=300:y=460:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ Force-Directed Viz':fontsize=40:fontcolor=${WHITE}:x=300:y=540:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" \
  -frames:v 1 "$BASEDIR/slides/03-civic.png" 2>/dev/null
echo "  03-civic.png"

# Slide 4: Parallel Agents
ffmpeg -y -f lavfi -i "color=c=${BG}:s=${WIDTH}x${HEIGHT}:d=1" \
  -vf "drawtext=text='PARALLEL AGENTS':fontsize=64:fontcolor=${ORANGE}:x=(w-text_w)/2:y=120:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━':fontsize=20:fontcolor=${ORANGE}@0.3:x=(w-text_w)/2:y=210:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ 6 Agents at Once':fontsize=40:fontcolor=${WHITE}:x=300:y=300:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ Autonomous Coding':fontsize=40:fontcolor=${WHITE}:x=300:y=380:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ Pattern Recognition':fontsize=40:fontcolor=${WHITE}:x=300:y=460:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='▸ Zero Hand-Holding':fontsize=40:fontcolor=${WHITE}:x=300:y=540:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" \
  -frames:v 1 "$BASEDIR/slides/04-scale.png" 2>/dev/null
echo "  04-scale.png"

# Slide 5: Results
ffmpeg -y -f lavfi -i "color=c=${BG}:s=${WIDTH}x${HEIGHT}:d=1" \
  -vf "drawtext=text='THE RESULTS':fontsize=64:fontcolor=${GREEN}:x=(w-text_w)/2:y=120:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━':fontsize=20:fontcolor=${GREEN}@0.3:x=(w-text_w)/2:y=210:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='105':fontsize=120:fontcolor=${CYAN}:x=200:y=320:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='PM2 Processes':fontsize=28:fontcolor=${DIM}:x=200:y=460:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='95':fontsize=120:fontcolor=${ORANGE}:x=650:y=320:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='Integrations':fontsize=28:fontcolor=${DIM}:x=650:y=460:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='100K+':fontsize=100:fontcolor=${GREEN}:x=1050:y=330:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='Records':fontsize=28:fontcolor=${DIM}:x=1100:y=460:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='1 PERSON':fontsize=60:fontcolor=${WHITE}:x=(w-text_w)/2:y=580:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" \
  -frames:v 1 "$BASEDIR/slides/05-results.png" 2>/dev/null
echo "  05-results.png"

# Slide 6: CTA
ffmpeg -y -f lavfi -i "color=c=${BG}:s=${WIDTH}x${HEIGHT}:d=1" \
  -vf "drawtext=text='goodquestion.ai':fontsize=72:fontcolor=${CYAN}:x=(w-text_w)/2:y=(h-text_h)/2-80:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf,\
drawtext=text='@AgentAbrams':fontsize=48:fontcolor=${ORANGE}:x=(w-text_w)/2:y=(h-text_h)/2+20:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,\
drawtext=text='This is Tuesday.':fontsize=32:fontcolor=${DIM}:x=(w-text_w)/2:y=(h-text_h)/2+120:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" \
  -frames:v 1 "$BASEDIR/slides/06-cta.png" 2>/dev/null
echo "  06-cta.png"

echo ""
echo "═══════════════════════════════════════════════"
echo "  STEP 3: Prepare circular avatar"
echo "═══════════════════════════════════════════════"

# Create circular mask for avatar (used for PIP overlay)
# Crop avatar to circle with cyan glow border
for pose in front talking-1 talking-2; do
  ffmpeg -y -i "$AVATARDIR/avatar-${pose}.png" \
    -vf "scale=${AVATAR_SIZE}:${AVATAR_SIZE}:force_original_aspect_ratio=increase,crop=${AVATAR_SIZE}:${AVATAR_SIZE},format=rgba,geq=\
'r=r(X,Y)':g='g(X,Y)':b='b(X,Y)':a='if(lte(pow(X-${AVATAR_SIZE}/2\,2)+pow(Y-${AVATAR_SIZE}/2\,2)\,pow(${AVATAR_SIZE}/2-4\,2))\,255\,0)'" \
    "$BASEDIR/avatar-circle-${pose}.png" 2>/dev/null
  echo "  avatar-circle-${pose}.png"
done

echo ""
echo "═══════════════════════════════════════════════"
echo "  STEP 4: Build per-segment videos"
echo "═══════════════════════════════════════════════"

SEGMENTS=("01-hook" "02-trading" "03-civic" "04-scale" "05-results" "06-cta")
AVATARS=("talking-1" "talking-1" "talking-1" "talking-2" "talking-1" "front")

for i in "${!SEGMENTS[@]}"; do
  seg="${SEGMENTS[$i]}"
  avatar="${AVATARS[$i]}"
  narr="$BASEDIR/narration/${seg}.mp3"
  slide="$BASEDIR/slides/${seg}.png"
  out="$BASEDIR/segments/${seg}.mp4"

  # Get narration duration
  dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$narr" 2>/dev/null)
  dur_ceil=$(python3 -c "import math; print(math.ceil(float('${dur}')))")

  # Alternate between talking and front avatar every 0.4s to simulate mouth movement
  # Use overlay to place circular avatar in bottom-right
  AVATAR_X=$((WIDTH - AVATAR_SIZE - AVATAR_MARGIN))
  AVATAR_Y=$((HEIGHT - AVATAR_SIZE - AVATAR_MARGIN))

  ffmpeg -y \
    -loop 1 -i "$slide" \
    -loop 1 -i "$BASEDIR/avatar-circle-${avatar}.png" \
    -i "$narr" \
    -filter_complex "\
      [0:v]scale=${WIDTH}:${HEIGHT},format=yuv420p[bg];\
      [1:v]format=rgba[av];\
      [bg][av]overlay=${AVATAR_X}:${AVATAR_Y}:format=auto[out]" \
    -map "[out]" -map 2:a \
    -c:v libx264 -preset fast -crf 23 -pix_fmt yuv420p \
    -c:a aac -b:a 128k \
    -t "$dur" \
    -r $FPS \
    "$out" 2>/dev/null

  echo "  ${seg}.mp4 (${dur}s)"
done

echo ""
echo "═══════════════════════════════════════════════"
echo "  STEP 5: Concatenate all segments"
echo "═══════════════════════════════════════════════"

# Create concat list
rm -f "$BASEDIR/concat.txt"
for seg in "${SEGMENTS[@]}"; do
  echo "file 'segments/${seg}.mp4'" >> "$BASEDIR/concat.txt"
done

FINAL="$BASEDIR/agent-abrams-v1.mp4"
ffmpeg -y -f concat -safe 0 -i "$BASEDIR/concat.txt" \
  -c:v libx264 -preset medium -crf 22 -pix_fmt yuv420p \
  -c:a aac -b:a 192k \
  -movflags +faststart \
  "$FINAL" 2>/dev/null

FILESIZE=$(du -h "$FINAL" | cut -f1)
DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$FINAL" 2>/dev/null)

echo ""
echo "═══════════════════════════════════════════════"
echo "  DONE!"
echo "═══════════════════════════════════════════════"
echo "  Output: $FINAL"
echo "  Size:   $FILESIZE"
echo "  Duration: ${DURATION}s"
echo ""