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.

Diagram

📄 Transcript

Analyze Topics

Identify Segments

Format Chapters

📋 Chapters JSON

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

bash
export OPENAI_API_KEY=your_key_here
pip install praisonaiagents

Step-by-Step Build

  1. Create Recipe Directory
    bash
    mkdir -p ~/.praisonai/templates/youtube-chapter-generator
    cd ~/.praisonai/templates/youtube-chapter-generator
  2. Create TEMPLATE.yaml
    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
    python
    # 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
    # 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
# 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

Model 1: Embedded SDK

python
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"])

Model 2: CLI Invocation

bash
# Pipe transcript from file
cat transcript.txt | praison recipes run youtube-chapter-generator \
  --input '{"transcript_text": "'"$(cat)"'"}'

Model 3: Plugin Mode

python
class YouTubeChapterPlugin:
    def generate_chapters(self, transcript):
        from praisonai import recipe
        return recipe.run(
            "youtube-chapter-generator",
            input={"transcript_text": transcript}
        )

Model 4: Local HTTP Sidecar

javascript
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();

Model 5: Remote Managed Runner

python
response = requests.post(
    "https://api.video-tools.com/chapters",
    headers={"Authorization": f"Bearer {api_key}"},
    json={"transcript_text": transcript}
)

Model 6: Event-Driven

python
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

Too few chapters generated

Try using style: "detailed" or provide a longer transcript with more topic changes.

Timestamps don't match video

Ensure your transcript includes accurate timestamps. The recipe uses the timestamps from your input.

Next Steps