AINode.jsAutomation

AI YouTube Video Summarizer

TT
TopicTrick Team
AI YouTube Video Summarizer

AI YouTube Video Summarizer

Paste a YouTube URL and receive a structured summary with chapters, key insights, and action items — in seconds. This tool fetches the video transcript automatically and uses GPT-4o to condense hours of content into a focused, readable brief.

This is Tool 29 of the Build 50 AI Automation Tools course.


What You'll Build

  • POST /summarize — YouTube URL in, structured summary out
  • Auto-fetch transcript (no API key needed for most videos)
  • Chapters, key insights, quotes, and action items
  • Map-reduce for long videos (>1 hour)

Setup

bash
mkdir yt-summarizer && cd yt-summarizer
npm init -y
npm install express youtube-transcript openai p-limit dotenv
bash
# .env
OPENAI_API_KEY=sk-your-key-here
PORT=3000

Transcript & Summary Service

js
// src/services/summaryService.js
import { YoutubeTranscript } from 'youtube-transcript';
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

function extractVideoId(url) {
  const patterns = [
    /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
    /youtube\.com\/shorts\/([^&\n?#]+)/,
  ];
  for (const pattern of patterns) {
    const match = url.match(pattern);
    if (match) return match[1];
  }
  throw new Error('Could not extract video ID from URL');
}

async function fetchTranscript(videoId) {
  const items = await YoutubeTranscript.fetchTranscript(videoId);
  return items.map(item => ({
    text: item.text,
    offset: item.offset,
    duration: item.duration,
  }));
}

function transcriptToText(items) {
  return items.map(i => i.text).join(' ').replace(/\s+/g, ' ').trim();
}

function chunkTranscript(items, chunkDurationMs = 10 * 60 * 1000) {
  const chunks = [];
  let current = [];
  let chunkStart = 0;

  for (const item of items) {
    if (item.offset - chunkStart > chunkDurationMs && current.length > 0) {
      chunks.push({ text: current.join(' '), startMs: chunkStart, endMs: item.offset });
      current = [];
      chunkStart = item.offset;
    }
    current.push(item.text);
  }

  if (current.length > 0) {
    chunks.push({ text: current.join(' '), startMs: chunkStart, endMs: Infinity });
  }

  return chunks;
}

async function summarizeTranscript(text, title = '') {
  const truncated = text.length > 60_000 ? text.slice(0, 60_000) + ' [truncated]' : text;

  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: `You are an expert content summarizer. Analyze this YouTube video transcript and provide a structured summary.

Return ONLY a JSON object:
{
  "title": "video title or inferred topic",
  "oneSentenceSummary": "one sentence that captures the entire video",
  "summary": "3-4 paragraph comprehensive summary",
  "chapters": [
    { "title": "chapter title", "summary": "2-3 sentence chapter summary", "timestamp": "estimated MM:SS" }
  ],
  "keyInsights": ["5-7 most important insights or takeaways"],
  "quotes": ["2-3 most quotable or memorable lines from the transcript"],
  "actionItems": ["concrete things a viewer can do after watching"],
  "targetAudience": "who this video is best suited for",
  "difficulty": "beginner | intermediate | advanced",
  "topics": ["main topics covered"]
}`,
      },
      {
        role: 'user',
        content: `Video title: ${title}\n\nTranscript:\n${truncated}`,
      },
    ],
    temperature: 0.3,
    response_format: { type: 'json_object' },
  });

  return JSON.parse(response.choices[0].message.content);
}

async function mapReduceSummarize(chunks, title) {
  // Map: summarize each chunk
  const chunkSummaries = await Promise.all(
    chunks.map(async (chunk, i) => {
      const resp = await openai.chat.completions.create({
        model: 'gpt-4o-mini',
        messages: [
          { role: 'system', content: 'Summarize this transcript segment in 3-5 sentences. Be concise.' },
          { role: 'user', content: chunk.text.slice(0, 8_000) },
        ],
        temperature: 0.2,
      });
      return `Segment ${i + 1}: ${resp.choices[0].message.content}`;
    })
  );

  // Reduce: synthesize chunk summaries
  const combined = chunkSummaries.join('\n\n');
  return summarizeTranscript(combined, title);
}

export async function summarizeVideo(url) {
  const videoId = extractVideoId(url);
  const transcriptItems = await fetchTranscript(videoId);
  const fullText = transcriptToText(transcriptItems);

  // Long video: use map-reduce
  const totalDurationMs = transcriptItems[transcriptItems.length - 1]?.offset || 0;
  const ONE_HOUR = 60 * 60 * 1000;

  let summary;
  if (totalDurationMs > ONE_HOUR) {
    const chunks = chunkTranscript(transcriptItems);
    summary = await mapReduceSummarize(chunks, '');
  } else {
    summary = await summarizeTranscript(fullText);
  }

  return {
    videoId,
    url,
    transcriptLength: transcriptItems.length,
    durationMinutes: Math.round(totalDurationMs / 60_000),
    ...summary,
  };
}

Server

js
// src/server.js
import 'dotenv/config';
import express from 'express';
import { summarizeVideo } from './services/summaryService.js';

const app = express();
app.use(express.json());

app.post('/summarize', async (req, res, next) => {
  try {
    const { url } = req.body;
    if (!url) return res.status(400).json({ error: 'YouTube URL required' });

    const summary = await summarizeVideo(url);
    res.json({ success: true, ...summary });
  } catch (err) { next(err); }
});

app.use((err, _req, res, _next) => res.status(500).json({ error: err.message }));
app.listen(process.env.PORT ?? 3000, () => console.log('YouTube summarizer running'));

Testing

bash
curl -X POST http://localhost:3000/summarize \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }'

Sample response:

json
{
  "videoId": "dQw4w9WgXcQ",
  "durationMinutes": 3,
  "oneSentenceSummary": "A timeless pop anthem about perseverance and emotional vulnerability in relationships.",
  "keyInsights": [
    "Persistence and emotional honesty are central themes",
    "The chorus emphasises commitment despite uncertainty"
  ],
  "actionItems": [
    "Reflect on how you communicate commitment in your own relationships"
  ],
  "difficulty": "beginner",
  "topics": ["music", "relationships", "pop culture"]
}

Build 50 AI Automation Tools — Tool 29 of 50

YouTube summarizer is live. Continue to Tool 30 to build an AI podcast transcript summarizer.


    Summary

    • youtube-transcript fetches captions without an API key — works for most English videos with auto-captions
    • Map-reduce pattern handles hour-long lectures and conference talks without truncation
    • gpt-4o-mini for chunk summaries cuts cost significantly — only the final synthesis uses GPT-4o
    • Chapters with timestamps make the summary navigable — readers can jump to specific sections
    • Extend with a /search endpoint that searches across a library of summarized videos by topic

    Continue to Tool 30: AI Podcast Transcript Summarizer →