Extraer transcripciones de un canal de YouTube (yt-dlp)
Canal objetivo: TheDataPubChannel (53 streams). Ubicación: ~/Downloads/datapub
Pipeline (reutilizable)
- Listar:
yt-dlp --flat-playlist --print "%(id)s|%(title)s|%(duration_string)s|%(upload_date)s" "<url>/streams" > _raw/_list.tsv- Ojo: flat-playlist NO trae upload_date. Hay que obtenerla por-video:
cut -d'|' -f1 _raw/_list.tsv | xargs -I{} -P6 yt-dlp --skip-download --print "%(id)s|%(upload_date)s" "https://www.youtube.com/watch?v={}"
- Ojo: flat-playlist NO trae upload_date. Hay que obtenerla por-video:
- Subtítulos auto:
yt-dlp --skip-download --write-auto-subs --sub-langs LANG --sub-format vtt -o "_raw/%(id)s.%(ext)s" URL- Template
%(id)s.%(ext)sauto-agrega el idioma →ID.es.vtt/ID.en.vtt
- Template
- Convertir VTT → txt plano:
python3 _raw/_convert.py(quita timestamps, tags, música, deduplica) - Script de descarga masiva:
_raw/_download_subs.sh(idempotente, salta lo existente)
Gotcha crítico: rate-limit 429
YouTube throttlea el endpoint de timedtext. Reglas:
- Las traducciones auto (
ensobre video enes) se throttlean MUCHO más que el original ASR (es). - Reintentos agresivos CALIENTAN el límite y termina afectando hasta el original. Solución: cooldown de 10-15 min SIN requests, luego ritmo suave (1 sub cada ~30s, backoff 90s en 429).
- Correr en background:
nohup ./_raw/_download_subs.sh es en > _raw/_download.log 2>&1 &
Calidad del resultado
es(ASR original): fiel pero con ruido de relleno (“ah ah ah”, letras sueltas).en(auto-traducido): más limpio porque YouTube limpia al traducir, pero deriva de la transcripción ruidosa.- Para base de conocimiento consultable: transcripts/es/*.txt + index.csv
403 Forbidden en descarga de audio (video data) — FIX
Los streams de googlevideo dan 403 aunque las captions (timedtext) funcionen. Causa: bot-detection de YouTube. Receta que FUNCIONA (yt-dlp 2026.07):
yt-dlp -f 140 \
--cookies-from-browser firefox \
--extractor-args "youtube:player_client=web_embedded,default" \
--js-runtimes node \
--remote-components ejs:github \
-o "audio/%(id)s.%(ext)s" URL
- Cookies de Chrome NO sirven en macOS (v10 cookies: “no key found”, app-bound encryption).
--remote-components ejs:githubbaja el solver del JS challenge (“n challenge”).web_embedded,defaultdestapa los formatos audio-only (140 m4a, 251 opus).
faster-whisper (global)
- Instalado global en python 3.12.2:
~/.asdf/installs/python/3.12.2/bin/python3.12(NO en 3.14: PyAV no compila ahí). - Modelo large-v3 ya descargado en ~/.cache/huggingface/hub/[CREDENCIAL]
- Benchmark M1 Pro (10c, 16GB), large-v3 int8, idioma es:
- cpu_threads=8 → 2.29x cpu_threads=4 → 2.92x (4 gana por contención)
- beam=5 → 3.18x beam=1 → 4.36x
- 71.1 h de audio total → beam5≈22h, beam1≈16h de cómputo
Export a second-brain (nodo.ws)
Script reusable: ~/Downloads/datapub/_raw/_to_secondbrain.py
- Copia transcripciones →
data/cursos/datapub-<charla>/<charla>.srt(texto limpio, sin timestamps; elsrtToTextde import-cursos lo maneja). - Genera
_stubs/projects/datapub-<charla>.md(projectstatus: shipped,uses/grantssolo a nodos EXISTENTES — la convención de import-cursos.mjs no pone nodos nuevos en uses para no dejar edges colgantes). - Genera
_stubs/cursos-nodos/<slug>.mdpara conceptos faltantes (rust, pyo3, bm25, topic-modeling, NER, MDP, CCA, portfolio-opt, kelly, causal-inference, explainable-ai). - Escribe
reports/cursos-analisis.md. Adopción: revisar stubs →node scripts/adoptar-cursos.mjs→npm run build. Contrato clave: la integridad referencial (lib/graph.mjs) exige que todouses/grants.node/edge.toapunte a slug existente; falla el build si no. Projects y nodes viven en dirs separados y no comparten namespace, pero se prefijandatapub-por claridad.