> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# YouTube Chapter Generator

> Generate YouTube chapters from transcripts with timestamps and descriptions

# 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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Input[📄 Transcript] --> Analyze[Analyze Topics]
    Analyze --> Segment[Identify Segments]
    Segment --> Format[Format Chapters]
    Format --> Output[📋 Chapters JSON]

    classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff

    class Input,Output input
    class Analyze,Segment,Format process
```

### Input/Output Contract

| Input | Type | Required | Description |
| - | - | - | - |
| `transcript_text` | string | Yes\* | Transcript text with timestamps |
| `video_path` | string | Yes\* | Path to video (alternative to transcript) |
| `style` | string | No | `concise` or `detailed` (default: `concise`) |

\*One of `transcript_text` or `video_path` is required.

| Output | Type | Description |
| - | - | - |
| `chapters_json` | array | Chapter markers with timestamps |
| `description_text` | string | YouTube-ready description with chapters |
| `ok` | boolean | Success indicator |

## Prerequisites

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
export OPENAI_API_KEY=your_key_here
pip install praisonaiagents
```

## Step-by-Step Build

**1. Create Recipe Directory**

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
mkdir -p ~/.praisonai/templates/youtube-chapter-generator
cd ~/.praisonai/templates/youtube-chapter-generator
```

**2. Create TEMPLATE.yaml**

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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**

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# recipe.py
import os
import re
import json
from praisonaiagents import Agent, Task, AgentTeam

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 = AgentTeam(
            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**

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# 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

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# 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

> [tab] Model 1: Embedded SDK
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> from praisonai import recipe
>
> # Load or define transcript
> transcript = "00:00 Introduction to the topic..."
>
> 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"])
> ```

> [tab] Model 2: CLI Invocation
>
> ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> # Pipe transcript from file
> cat transcript.txt | praison recipes run youtube-chapter-generator \
>   --input '{"transcript_text": "'"$(cat)"'"}'
> ```

> [tab] Model 3: Plugin Mode
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> class YouTubeChapterPlugin:
>     def generate_chapters(self, transcript):
>         from praisonai import recipe
>         return recipe.run(
>             "youtube-chapter-generator",
>             input={"transcript_text": transcript}
>         )
> ```

> [tab] Model 4: Local HTTP Sidecar
>
> ```javascript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> const response = await fetch('http://localhost:8765/recipes/youtube-chapter-generator/run', {
>   method: 'POST',
>   body: JSON.stringify({ transcript_text: transcript })
> });
> const { chapters_json, description_text } = await response.json();
> ```

> [tab] Model 5: Remote Managed Runner
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> response = requests.post(
>     "https://api.video-tools.com/chapters",
>     headers={"Authorization": f"Bearer {api_key}"},
>     json={"transcript_text": transcript}
> )
> ```

> [tab] Model 6: Event-Driven
>
> ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> def on_video_transcribed(event):
>     import queue as q
>     job_queue = q.Queue()  # Replace with SQS/RabbitMQ in production
>     job_queue.put({
>         "recipe": "youtube-chapter-generator",
>         "input": {"transcript_text": event['transcript']},
>         "callback_url": f"https://api.example.com/videos/{event['video_id']}/chapters"
>     })
> ```

## Troubleshooting

> [accordion] Too few chapters generated
>
> Try using `style: "detailed"` or provide a longer transcript with more topic changes.

> [accordion] Timestamps don't match video
>
> Ensure your transcript includes accurate timestamps. The recipe uses the timestamps from your input.

## Next Steps

* **[Video Caption Generator](https://praison.ai/docs/examples/recipe-examples/video-caption-generator.md)** - Generate transcripts first
* **[Video Highlights Reel Planner](https://praison.ai/docs/examples/recipe-examples/simple-video-highlights-reel-planner.md)** - Plan highlight clips


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.