Extraer subtítulos de YouTube automáticamente con n8n (YouTube Data API)

16 minutos
Cómo obtener la transcripción de un vídeo con YouTube Data API y n8n

¿Qué hace este workflow?

Este workflow de n8n extrae transcripciones limpias y estructuradas de vídeos de YouTube usando la YouTube Data API oficial de Google. Está diseñado específicamente para trabajar con vídeos de tu propio canal, proporcionando acceso nativo a los subtítulos mediante autenticación OAuth2.

El workflow ejecuta automáticamente estas acciones:

  • Recibe parámetros de entrada: youtubeVideoId y preferredLanguage (ej: es, en, fr)
  • Lista todas las pistas de subtítulos disponibles usando el endpoint captions.list de YouTube API
  • Selecciona inteligentemente el idioma: prioriza tu idioma preferido o hace fallback a la primera pista disponible
  • Descarga el archivo VTT (WebVTT format) con los subtítulos completos
  • Limpia y procesa el texto: elimina timestamps, etiquetas WEBVTT, marcas como [Música], palabras duplicadas y espacios innecesarios
  • Devuelve JSON estructurado: texto plano listo para usar con metadatos (idioma, conteo de palabras, caracteres, status)

Casos de uso y beneficios

Este workflow está optimizado para creadores de contenido, equipos de marketing y desarrolladores que gestionan su propio canal de YouTube y necesitan automatizar la extracción de transcripciones con máxima calidad y control.

Casos de uso principales:

  • Generación automática de contenido: Crea resúmenes, posts de blog, threads de Twitter o newsletters a partir de tus vídeos de YouTube. Ideal para reutilizar contenido de video en múltiples formatos sin transcribir manualmente.
  • Base de datos de contenido indexable: Almacena transcripciones en Supabase, Notion o Google Sheets para crear un sistema de búsqueda semántica de todo tu catálogo de vídeos. Perfecto para equipos que necesitan encontrar referencias específicas en cientos de horas de contenido.
  • Pipelines de IA y análisis: Alimenta modelos de lenguaje (ChatGPT, Claude) con transcripciones limpias para clasificación automática, extracción de keywords, generación de embeddings o análisis de sentimiento de tu contenido.
  • Accesibilidad y SEO: Genera subtítulos corregidos o mejora los automáticos de YouTube. Crea páginas de transcripciones completas para mejorar el SEO de tu sitio web con contenido de video transcrito.
  • Documentación de cursos y webinars: Convierte automáticamente tus vídeos educativos en documentación de texto estructurada, ideal para crear wikis internas o recursos de aprendizaje complementarios.

Beneficio clave vs. soluciones externas: Al usar la YouTube Data API oficial, tienes acceso garantizado a los subtítulos de tu canal sin depender de servicios de terceros, límites de rate o costes adicionales por API. La calidad es óptima porque proviene directamente de YouTube.

⚠️ Limitación importante: Este workflow solo funciona con vídeos de tu propio canal (el canal autenticado con OAuth2). Para vídeos externos o de terceros, obtendrás errores 403 (Forbidden) porque la API de Google no permite acceso a captions de canales ajenos. Si necesitas transcribir vídeos públicos de otros canales, consulta el workflow con YouTube Transcript API que está diseñado específicamente para ese caso de uso.

Requisitos previos

  • Canal de YouTube propio: El vídeo debe pertenecer al canal autenticado con OAuth2. No funciona con vídeos de terceros debido a restricciones de permisos de la YouTube Data API.
  • Credenciales OAuth2 de Google: Configuradas en n8n con tipo youTubeOAuth2Api. Requiere los scopes:
    • youtube.force-ssl
    • youtube.readonly o youtube
    Guía oficial de autenticación Google en n8n
  • Proyecto en Google Cloud Console: Debes tener la YouTube Data API v3 habilitada en tu proyecto de Google Cloud. Habilitar YouTube Data API v3
  • Vídeos con subtítulos: El vídeo debe tener al menos una pista de subtítulos (automáticos o manuales). Si no hay captions, el workflow devuelve un error controlado.
  • Nodos n8n requeridos:
    • Execute Workflow Trigger (para usar como sub-workflow)
    • Set (variables)
    • HTTP Request (con autenticación YouTube OAuth2)
    • IF (condicional)
    • Code (JavaScript para procesamiento)
    • Extract from File
    • Stop and Error
  • Versión mínima de n8n: 1.0.0 (todos los nodos son nativos)
  • Conocimientos básicos: Entender el flujo de autenticación OAuth2, uso de expresiones n8n ({{ $json.field }}) y llamadas entre workflows

Cómo configurar el workflow paso a paso

Paso 1: Configura el trigger de sub-workflow

Añade el nodo When Executed by Another Workflow como punto de entrada:

  • Workflow Inputs: Define dos parámetros de entrada:
    • youtubeVideoId (String) – El ID del vídeo de YouTube (ej: «nxub8Bmia68»)
    • preferredLanguage (String) – Código de idioma preferido (ej: «es», «en», «fr»)

Ejemplo de payload para ejecutar este workflow desde otro:

{
  "youtubeVideoId": "nxub8Bmia68",
  "preferredLanguage": "es"
}

Este enfoque permite usar el workflow como módulo reutilizable desde cualquier otro flujo de n8n.

Paso 2: Establece variables con Set node

Añade un nodo Set para normalizar las variables de entrada:

  • Assignment 1:
    • Name: youtubeVideoId
    • Value: ={{ $json.youtubeVideoId }}
  • Assignment 2:
    • Name: preferredLanguage
    • Value: ={{ $json.preferredLanguage }}

Esto facilita referenciar estos valores en nodos posteriores usando $('Set Variables').item.json.youtubeVideoId.

Paso 3: Lista pistas de subtítulos con YouTube API

Añade un nodo HTTP Request para consultar las pistas disponibles:

  • Method: GET
  • URL: https://www.googleapis.com/youtube/v3/captions?part=snippet&videoId={{ $('Set Variables').item.json.youtubeVideoId }}
  • Authentication: Predefined Credential TypeyouTubeOAuth2Api
  • Credential: Selecciona tu credencial de YouTube OAuth2 configurada previamente

Respuesta esperada: La API devuelve un objeto con array items[] conteniendo todas las pistas de subtítulos disponibles, cada una con metadata de idioma, tipo (automática/manual), y caption ID único.

⚠️ Nota crítica: Que obtengas una lista de captions no garantiza que puedas descargarlas. Si el vídeo no es de tu canal autenticado, el siguiente paso (descarga) devolverá 403 Forbidden.

Paso 4: Valida existencia de subtítulos con IF node

Añade un nodo IF para verificar si hay captions disponibles:

  • Condition: Number → Greater Than
  • Value 1: ={{ $json.items.length }}
  • Value 2: 0

Flujo TRUE: Continúa al selector de idioma si hay captions
Flujo FALSE: Redirige al nodo de error controlado

Paso 5: Selecciona idioma con lógica de fallback

Añade un nodo Code (JavaScript) que implementa la selección inteligente de idioma:

// Extract caption ID with preferred language priority or first available
const captionList = $input.first().json;
const captions = captionList.items || [];
const preferredLanguage = $('Set Variables').item.json.preferredLanguage || 'es';

let captionId = null;
let language = null;

// Search preferred language first
const preferredCaption = captions.find(caption => caption.snippet.language === preferredLanguage);
if (preferredCaption) {
  captionId = preferredCaption.id;
  language = preferredCaption.snippet.language;
} else {
  // Fallback: first available caption
  if (captions.length > 0) {
    captionId = captions[0].id;
    language = captions[0].snippet.language;
  }
}

return [{
  json: {
    captionId,
    language,
    videoId: captionList.items[0]?.snippet.videoId,
    totalCaptions: captions.length,
    status: captionId ? 'found' : 'no_captions',
    preferredLanguage: preferredLanguage
  }
}];

Lógica implementada:

  1. Busca coincidencia exacta con preferredLanguage
  2. Si no existe, toma la primera pista disponible (fallback)
  3. Devuelve captionId para descarga + metadata útil

Paso 6: Descarga el archivo VTT

Añade otro nodo HTTP Request para descargar la pista seleccionada:

  • Method: GET
  • URL: https://www.googleapis.com/youtube/v3/captions/{{ $('Caption Language Selector').item.json.captionId }}
  • Authentication: Predefined Credential TypeyouTubeOAuth2Api
  • Response Format: File (por defecto descarga como binario)

La respuesta es un archivo VTT (WebVTT format) que contiene timestamps y texto de los subtítulos.

Paso 7: Extrae texto del archivo con Extract from File

Añade un nodo Extract from File:

  • Operation: Extract text from file
  • Destination Key: content

Este nodo convierte el binario VTT en string de texto accesible desde $json.content.

Paso 8: Limpia y estructura el texto con Code node

Añade un nodo Code final que limpia el VTT y genera output estructurado:

// Get subtitle content from previous node
const subtitleContent = $('Caption File Conversion').first().json.content;

// Split into lines
const lines = subtitleContent.split('\n');

// Function to detect timestamp lines (VTT format)
const isTimestampLine = (line) => {
    const timestampRegex = /^(\d{1,2}:\d{2}:\d{2}\.\d{3},)+\d{1,2}:\d{2}:\d{2}\.\d{3}/;
    return timestampRegex.test(line.trim());
};

// Function to detect WEBVTT headers
const isHeaderLine = (line) => {
    const headerRegex = /^(WEBVTT|Kind:|Language:)/i;
    return headerRegex.test(line.trim());
};

// Filter: exclude timestamps, headers and empty lines
const textLines = lines.filter(line => {
    return !isTimestampLine(line) && !isHeaderLine(line) && line.trim() !== '';
});

// Clean text: remove [Música], normalize whitespace
const cleanText = (line) => {
    let cleaned = line;
    
    // Remove non-text elements like [Música]
    cleaned = cleaned.replace(/\[\w+\]/g, '');
    
    // Remove repeated words
    cleaned = cleaned.replace(/\b(\w+)\s+\1\b/g, '$1');
    
    return cleaned.trim();
};

const cleanedLines = textLines.map(cleanText);

// Join ALL text into single string, remove all \n and normalize spaces
const plainText = cleanedLines.join(' ').replace(/\s+/g, ' ').trim();

return [{
    json: {
        videoId: $('Set Variables').item.json.youtubeVideoId,
        language: $('Caption Language Selector').item.json.language,
        text: plainText,
        wordCount: plainText.split(' ').length,
        charCount: plainText.length,
        status: 'success',
        source: 'youtube_captions'
    }
}];

Procesamiento aplicado:

  • Elimina timestamps (00:01:23.456 → 00:01:25.789)
  • Remueve headers WEBVTT, Kind, Language
  • Filtra etiquetas de sonido [Música], [Aplausos]
  • Elimina palabras duplicadas consecutivas
  • Normaliza espacios en blanco
  • Genera estadísticas (word count, char count)

Paso 9: Gestiona errores con rama de fallback

En la rama FALSE del IF, añade:

Nodo Code (No Captions Fallback):

// No captions available - return structured error
return [{
  json: {
    videoId: $('Set Variables').item.json.youtubeVideoId,
    language: $('Set Variables').item.json.preferredLanguage,
    error: 'No captions available for this video',
    status: 'no_captions',
    suggestion: 'Use Whisper AI fallback',
    source: 'youtube_api'
  }
}];

Nodo Stop and Error:

  • Error Message: "There's no captions in YouTube Video"

Esto permite que el workflow padre capture el error y tome acciones alternativas (ej: usar Whisper AI para transcribir el audio).

Notas técnicas importantes

Limitaciones de la YouTube Data API:

  • Solo canal propio: La API captions.download solo funciona con vídeos de tu canal autenticado. Para vídeos externos obtendrás 403 Forbidden. Esta es una restricción de Google, no de n8n.
  • Quota de API: YouTube Data API tiene un límite de 10,000 unidades/día por proyecto de forma gratuita. Cada llamada a captions.list consume ~50 unidades, y captions.download otras ~200 unidades. Con este workflow, puedes procesar aproximadamente 40 vídeos/día sin costo adicional.
  • Formato VTT vs SRT: YouTube devuelve por defecto formato VTT (WebVTT). Si necesitas SRT, puedes añadir el parámetro tfmt=srt a la URL de descarga, aunque el código de limpieza actual está optimizado para VTT.
  • Subtítulos automáticos vs manuales: YouTube genera subtítulos automáticos para muchos idiomas. La calidad de estos es variable – los manuales son siempre más precisos. El workflow no diferencia entre ambos; simplemente toma la pista disponible según tu criterio de idioma.

Troubleshooting común:

  • Error 403 Forbidden al descargar: El vídeo no pertenece a tu canal autenticado. Verifica que el youtubeVideoId corresponde a un vídeo de tu propio canal. Alternativa: usa YouTube Transcript API para vídeos externos.
  • Error 401 Unauthorized: Las credenciales OAuth2 expiraron o no tienen los scopes correctos. Re-autentica la credencial en n8n asegurándote de incluir youtube.readonly o youtube scope.
  • Lista vacía de captions: El vídeo no tiene subtítulos (ni automáticos ni manuales). YouTube no genera automáticamente captions para todos los vídeos – depende del idioma y la calidad del audio. Solución: sube manualmente un archivo SRT en YouTube Studio o usa Whisper AI para transcribir.
  • Idioma preferido no encontrado: El workflow hace fallback a la primera pista disponible. Si esto no es deseado, modifica el código del nodo «Caption Language Selector» para devolver error en lugar de fallback.
  • Texto con caracteres raros: Algunos subtítulos automáticos incluyen entidades HTML ( , "). Añade en el nodo «Clean Transcript» una línea adicional: cleaned = cleaned.replace(/&#?\w+;/g, ''); para eliminarlas.

Consideraciones de rendimiento:

  • Tiempo de ejecución: Para un vídeo típico de 10 minutos, el workflow tarda 3-5 segundos en completarse (2 llamadas API + procesamiento de texto).
  • Tamaño de transcripciones: Un vídeo de 1 hora genera aproximadamente 8,000-12,000 palabras de texto limpio (~50-70KB). Asegúrate de que tu sistema downstream puede manejar estos volúmenes si procesas vídeos largos en batch.

Mejoras y personalizaciones

Optimizaciones del workflow:

  • Soporte multi-idioma simultáneo: Modifica el nodo «Caption Language Selector» para devolver múltiples captionIds en lugar de uno solo, y procesa cada idioma en paralelo con Split In Batches. Útil si necesitas transcripciones en español e inglés del mismo vídeo.
  • Cache de transcripciones: Añade un nodo que verifique en una base de datos (Supabase/PostgreSQL) si ya existe la transcripción del videoId antes de llamar a la API. Reduce consumo de quota y mejora velocidad en ejecuciones repetidas.
  • Formato con timestamps preservados: En lugar de eliminar timestamps en «Clean Transcript», conviértelos en marcadores de párrafo: cada bloque de 30 segundos se convierte en un párrafo separado. Útil para indexación temporal con embeddings.
  • Detección de speaker diarization: Si tus subtítulos manuales incluyen identificadores de speaker (ej: «John: …»), añade lógica en el Code node para extraer y estructurar por speaker: { speaker: "John", text: "..." }.

Integraciones adicionales:

  • Post-procesamiento con IA: Conecta la salida directamente a OpenAI o Claude para:
    • Generar resumen ejecutivo
    • Extraer bullet points clave
    • Clasificar por temática/categoría
    • Generar título SEO-optimizado
  • Embeddings para búsqueda semántica: Añade un nodo que envíe el texto a OpenAI Embeddings API y almacene los vectores en Pinecone o Supabase pgvector para búsqueda semántica avanzada en tu catálogo de vídeos.
  • Publicación automática: Crea una variante que publique automáticamente la transcripción como:
    • Post de WordPress (custom post type «Transcripción»)
    • Página de Notion
    • Documento de Google Docs
    • Thread de Twitter usando la API v2
  • Batch processing de canal completo: Combina este workflow con un nodo que liste todos los vídeos de tu canal (YouTube API channels.list + playlistItems.list) y procesa transcripciones en batch con Split In Batches + Wait entre lotes para respetar rate limits.

Alternativas para vídeos externos:

  • YouTube Transcript API: Para vídeos públicos de otros canales, usa este workflow alternativo que utiliza una API de terceros especializada en extracción de transcripciones de vídeos públicos.
  • Whisper AI fallback: Crea un flujo híbrido que intente primero YouTube Data API (para vídeos propios) y, si falla con 403, descargue el audio del vídeo con yt-dlp y lo transcriba con Whisper AI (OpenAI o local con n8n-nodes-whisper).

Descargar gratis este workflow

Importa este workflow directamente en tu instancia de n8n. Descarga el archivo JSON y luego ve a Workflows → Import from File.