Skip to main content

YouTube Chapter Generator

Generate YouTube-compatible chapter markers from video transcripts.

Problem Statement

Who: YouTubers, video editors, content managers
Why: Chapters improve viewer navigation and SEO. Manual chapter creation is tedious for long videos.

What You’ll Build

A recipe that analyzes transcripts, identifies topic changes, and generates timestamped chapters.

Input/Output Contract

InputTypeRequiredDescription
transcript_textstringYes*Transcript text with timestamps
video_pathstringYes*Path to video (alternative to transcript)
stylestringNoconcise or detailed (default: concise)
*One of transcript_text or video_path is required.
OutputTypeDescription
chapters_jsonarrayChapter markers with timestamps
description_textstringYouTube-ready description with chapters
okbooleanSuccess indicator

Prerequisites

export OPENAI_API_KEY=your_key_here
pip install praisonaiagents

Step-by-Step Build

1

Create Recipe Directory

mkdir -p ~/.praison/templates/youtube-chapter-generator
cd ~/.praison/templates/youtube-chapter-generator
2

Create TEMPLATE.yaml

name: youtube-chapter-generator
version: "1.0.0"
description: "Generate YouTube chapters from transcripts"
author: "PraisonAI"
license: "MIT"

tags:
  - youtube
  - video
  - chapters
  - content

requires:
  env:
    - OPENAI_API_KEY
  packages:
    - praisonaiagents

inputs:
  transcript_text:
    type: string
    description: "Transcript text with timestamps"
    required: false
  video_path:
    type: string
    description: "Path to video file (alternative input)"
    required: false
  style:
    type: string
    description: "Chapter style"
    required: false
    default: "concise"
    enum:
      - concise
      - detailed

outputs:
  chapters_json:
    type: array
    description: "Chapter markers with timestamps and titles"
  description_text:
    type: string
    description: "YouTube-ready description with chapters"
  ok:
    type: boolean
    description: "Success indicator"

cli:
  command: "praison recipes run youtube-chapter-generator"
  examples:
    - 'praison recipes run youtube-chapter-generator --input ''{"transcript_text": "00:00 Introduction..."}'''

safety:
  dry_run_default: false
  requires_consent: false
  overwrites_files: false
  network_access: true
  pii_handling: false
3

Create recipe.py

# recipe.py
import os
import re
import json
from praisonaiagents import Agent, Task, PraisonAIAgents

def run(input_data: dict, config: dict = None) -> dict:
    """Generate YouTube chapters from transcript."""
    transcript_text = input_data.get("transcript_text")
    video_path = input_data.get("video_path")
    style = input_data.get("style", "concise")
    
    if not transcript_text and not video_path:
        return {
            "ok": False,
            "error": {"code": "MISSING_INPUT", "message": "Either transcript_text or video_path is required"}
        }
    
    # If video_path provided, extract transcript first
    if video_path and not transcript_text:
        if not os.path.exists(video_path):
            return {"ok": False, "error": {"code": "FILE_NOT_FOUND", "message": f"Video not found: {video_path}"}}
        # Would integrate with transcription here
        return {"ok": False, "error": {"code": "NOT_IMPLEMENTED", "message": "Video transcription not implemented. Provide transcript_text."}}
    
    try:
        style_instructions = {
            "concise": "Create short, punchy chapter titles (3-5 words). Focus on key topics only.",
            "detailed": "Create descriptive chapter titles with context. Include subtopics."
        }
        
        # Create chapter analyzer agent
        analyzer = Agent(
            name="Content Analyzer",
            role="Video Content Specialist",
            goal="Identify logical chapter breaks in video content",
            instructions=f"""
            You are a YouTube content specialist.
            - Identify major topic transitions
            - Find natural break points
            - {style_instructions[style]}
            - Ensure first chapter starts at 00:00
            - Aim for 5-15 chapters for typical videos
            """,
        )
        
        # Create formatter agent
        formatter = Agent(
            name="Chapter Formatter",
            role="YouTube SEO Expert",
            goal="Format chapters for YouTube compatibility",
            instructions="""
            You are a YouTube SEO expert.
            - Format timestamps as HH:MM:SS or MM:SS
            - Keep titles under 100 characters
            - Make titles searchable and descriptive
            - Ensure proper YouTube chapter format
            """,
        )
        
        # Define tasks
        analyze_task = Task(
            name="analyze_content",
            description=f"""
            Analyze this transcript and identify chapter breaks:
            
            {transcript_text[:5000]}  # Truncate for context
            
            Identify 5-15 logical chapter points with timestamps.
            """,
            expected_output="List of chapter points with timestamps and topics",
            agent=analyzer,
        )
        
        format_task = Task(
            name="format_chapters",
            description="""
            Format the chapters for YouTube:
            - Start with 00:00
            - Use consistent timestamp format
            - Create engaging titles
            
            Output as JSON array: [{"timestamp": "00:00", "title": "Introduction"}, ...]
            """,
            expected_output="JSON array of formatted chapters",
            agent=formatter,
            context=[analyze_task],
        )
        
        # Execute
        agents = PraisonAIAgents(
            agents=[analyzer, formatter],
            tasks=[analyze_task, format_task],
        )
        
        result = agents.start()
        
        # Parse chapters
        chapters_text = result.get("format_chapters", "[]")
        chapters = parse_chapters(chapters_text)
        
        # Generate YouTube description
        description = generate_description(chapters)
        
        return {
            "ok": True,
            "chapters_json": chapters,
            "description_text": description,
            "artifacts": [],
            "warnings": [],
        }
        
    except Exception as e:
        return {"ok": False, "error": {"code": "PROCESSING_ERROR", "message": str(e)}}


def parse_chapters(text: str) -> list:
    """Parse chapters from agent output."""
    try:
        # Try JSON parse first
        match = re.search(r'\[.*\]', text, re.DOTALL)
        if match:
            return json.loads(match.group())
    except json.JSONDecodeError:
        pass
    
    # Fallback: parse timestamp lines
    chapters = []
    for line in text.split('\n'):
        match = re.match(r'(\d{1,2}:\d{2}(?::\d{2})?)\s*[-–:]\s*(.+)', line.strip())
        if match:
            chapters.append({
                "timestamp": match.group(1),
                "title": match.group(2).strip()
            })
    
    return chapters


def generate_description(chapters: list) -> str:
    """Generate YouTube-compatible description."""
    lines = ["📚 Chapters:", ""]
    for ch in chapters:
        lines.append(f"{ch['timestamp']} {ch['title']}")
    return '\n'.join(lines)
4

Create test_recipe.py

# test_recipe.py
import pytest
from recipe import run, parse_chapters, generate_description

def test_missing_input():
    result = run({})
    assert result["ok"] is False
    assert result["error"]["code"] == "MISSING_INPUT"

def test_parse_chapters_json():
    text = '[{"timestamp": "00:00", "title": "Intro"}]'
    chapters = parse_chapters(text)
    assert len(chapters) == 1
    assert chapters[0]["timestamp"] == "00:00"

def test_parse_chapters_text():
    text = "00:00 - Introduction\n01:30 - Main Topic"
    chapters = parse_chapters(text)
    assert len(chapters) == 2

def test_generate_description():
    chapters = [
        {"timestamp": "00:00", "title": "Introduction"},
        {"timestamp": "05:00", "title": "Main Content"}
    ]
    desc = generate_description(chapters)
    assert "00:00 Introduction" in desc
    assert "05:00 Main Content" in desc

@pytest.mark.integration
def test_end_to_end():
    transcript = """
    00:00 Hello everyone, welcome to today's video about AI.
    02:30 Let's start with the basics of machine learning.
    10:00 Now let's look at some practical applications.
    20:00 To summarize what we've learned today.
    """
    
    result = run({"transcript_text": transcript, "style": "concise"})
    assert result["ok"] is True
    assert len(result["chapters_json"]) > 0

Run Locally

# From transcript text
praison recipes run youtube-chapter-generator \
  --input '{"transcript_text": "00:00 Welcome... 05:00 Topic 1..."}'

# Detailed style
praison recipes run youtube-chapter-generator \
  --input '{"transcript_text": "...", "style": "detailed"}'

Deploy & Integrate: 6 Integration Models

from praisonai import recipe

result = recipe.run(
    "youtube-chapter-generator",
    input={
        "transcript_text": transcript,
        "style": "concise"
    }
)

if result.ok:
    # Copy to clipboard or update video
    print(result.output["description_text"])

Troubleshooting

Try using style: "detailed" or provide a longer transcript with more topic changes.
Ensure your transcript includes accurate timestamps. The recipe uses the timestamps from your input.

Next Steps