← back to AgentAbrams

media/chapters.py

69 lines

#!/usr/bin/env python3
"""
chapters.py — Extract chapter markers from markdown H2 headers.

Generates timestamps compatible with YouTube and podcast chapter markers.

Usage:
    python3 media/chapters.py <post.md> [audio_duration_seconds]
"""

import json
import sys
from pathlib import Path


def generate_chapters(md_path: str, audio_duration: int = 180) -> list[dict]:
    text = Path(md_path).read_text(encoding="utf-8")
    lines = text.split("\n")

    headers = []
    for line in lines:
        if line.startswith("## "):
            title = line.replace("## ", "").strip()
            if title.lower() not in ("what i'm not sharing", "snippet and skill"):
                headers.append(title)

    if not headers:
        return [{"time": 0, "time_formatted": "0:00", "title": "Introduction"}]

    step = max(1, audio_duration // (len(headers) + 1))
    chapters = [{"time": 0, "time_formatted": "0:00", "title": "Introduction"}]

    for i, title in enumerate(headers):
        t = step * (i + 1)
        minutes = t // 60
        seconds = t % 60
        chapters.append({
            "time": t,
            "time_formatted": f"{minutes}:{seconds:02d}",
            "title": title,
        })

    return chapters


def main():
    if len(sys.argv) < 2:
        print("Usage: chapters.py <post.md> [duration_seconds]", file=sys.stderr)
        sys.exit(2)
    if not Path(sys.argv[1]).is_file():
        print(f"File not found: {sys.argv[1]}", file=sys.stderr)
        sys.exit(2)

    duration = int(sys.argv[2]) if len(sys.argv) > 2 else 180
    chapters = generate_chapters(sys.argv[1], duration)

    out_dir = Path("media/output")
    out_dir.mkdir(parents=True, exist_ok=True)
    out_file = out_dir / "chapters.json"
    out_file.write_text(json.dumps(chapters, indent=2), encoding="utf-8")

    print(f"Chapters generated: {out_file}")
    for ch in chapters:
        print(f"  {ch['time_formatted']}  {ch['title']}")


if __name__ == "__main__":
    main()