#!/usr/bin/env python3
"""Placeholder "narrated powerpoint" for a post's narrated slides version.

Until the real deck is recorded, this renders simple slides in the site's
clean 8-bit look (Geist Pixel titles, Plus Jakarta Sans text, the theme's
self-hosted fonts) and narrates each one with ffmpeg's flite voice, then
joins them into one MP4 (H.264 + AAC) and prints its duration in seconds.

Source format: slides separated by blank lines, each with
  # Slide title
  - bullet (any number)
  > narration (one or more lines, spoken while the slide shows)

Usage:
  python3 scripts/placeholder-slides.py SLIDES.txt OUT.mp4
Requires Pillow and ffmpeg built with libflite, libx264 and aac.
"""

import pathlib
import subprocess
import sys
import tempfile

from PIL import Image, ImageDraw, ImageFont

FONTS = pathlib.Path(__file__).resolve().parent.parent / "web/themes/custom/marcus_theme/fonts"
W, H = 1280, 720
BACKGROUND, INK, MUTED, PRIMARY, CARD = "#f1f3f9", "#0a0c10", "#4b5563", "#4f46e5", "#ffffff"


def run(*args):
    subprocess.run(args, check=True, capture_output=True)


def duration(path):
    out = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", path],
                         check=True, capture_output=True, text=True).stdout
    return float(out.strip())


def parse(text):
    slides = []
    for block in text.strip().split("\n\n"):
        slide = {"title": "", "bullets": [], "narration": []}
        for line in block.splitlines():
            line = line.strip()
            if line.startswith("# "):
                slide["title"] = line[2:]
            elif line.startswith("- "):
                slide["bullets"].append(line[2:])
            elif line.startswith("> "):
                slide["narration"].append(line[2:])
        if slide["title"] or slide["bullets"]:
            slides.append(slide)
    return slides


def render(slide, number, total, out):
    pixel = ImageFont.truetype(str(FONTS / "geist-pixel-400-latin.woff2"), 64)
    small_pixel = ImageFont.truetype(str(FONTS / "tiny5-400-latin.woff2"), 28)
    body = ImageFont.truetype(str(FONTS / "plus-jakarta-sans-500-latin.woff2"), 36)
    img = Image.new("RGB", (W, H), BACKGROUND)
    d = ImageDraw.Draw(img)
    # Dot grid backdrop, like the site.
    for x in range(20, W, 40):
        for y in range(20, H, 40):
            d.rectangle([x, y, x + 2, y + 2], fill="#d5d9e6")
    # The card with a hard 8-bit shadow.
    x0, y0, x1, y1 = 80, 70, W - 80, H - 110
    d.rectangle([x0 + 10, y0 + 10, x1 + 10, y1 + 10], fill=INK)
    d.rectangle([x0, y0, x1, y1], fill=CARD, outline=INK, width=4)
    d.rectangle([x0 + 50, y0 + 50, x0 + 74, y0 + 74], fill=PRIMARY)
    d.text((x0 + 100, y0 + 30), slide["title"], font=pixel, fill=INK)
    y = y0 + 150
    for bullet in slide["bullets"]:
        d.rectangle([x0 + 60, y + 16, x0 + 72, y + 28], fill=PRIMARY)
        d.text((x0 + 100, y), bullet, font=body, fill=MUTED)
        y += 70
    d.text((80, H - 70), "Workflows of AI", font=small_pixel, fill=INK)
    label = f"{number} / {total}"
    d.text((W - 80 - d.textlength(label, font=small_pixel), H - 70), label, font=small_pixel, fill=MUTED)
    img.save(out)


def main():
    source, out = sys.argv[1], sys.argv[2]
    slides = parse(pathlib.Path(source).read_text())
    with tempfile.TemporaryDirectory() as tmp:
        parts = []
        for i, slide in enumerate(slides, 1):
            png, wav, mp4 = f"{tmp}/s{i:02d}.png", f"{tmp}/s{i:02d}.wav", f"{tmp}/s{i:02d}.mp4"
            render(slide, i, len(slides), png)
            text = pathlib.Path(f"{tmp}/s{i:02d}.txt")
            text.write_text(" ".join(slide["narration"]) or slide["title"])
            run("ffmpeg", "-y", "-f", "lavfi", "-i", f"flite=textfile={text}:voice=slt", "-ar", "44100", "-ac", "1", wav)
            seconds = duration(wav) + 0.8
            run("ffmpeg", "-y", "-loop", "1", "-framerate", "10", "-i", png, "-i", wav,
                "-af", "apad", "-t", f"{seconds:.2f}", "-c:v", "libx264", "-tune", "stillimage",
                "-pix_fmt", "yuv420p", "-r", "10", "-c:a", "aac", "-b:a", "96k", "-ar", "44100", mp4)
            parts.append(mp4)
        listing = f"{tmp}/list.txt"
        pathlib.Path(listing).write_text("".join(f"file '{p}'\n" for p in parts))
        pathlib.Path(out).parent.mkdir(parents=True, exist_ok=True)
        run("ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", listing, "-c", "copy", "-movflags", "+faststart", out)
    print(round(duration(out)))


if __name__ == "__main__":
    main()
