← 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()