Citas: fuentes → transcripciones → citas → cues y clips
Herramienta: agent-citations/citekit.py (Python estándar más mlx_whisper y ffmpeg). Nunca sobrescribe entradas y se niega a reemplazar sus propias salidas sin --force.
Estructura de carpetas
Una carpeta por tema bajo active_edits/<tema>/:
sources/video_sources.txt lista de URLs del usuario
sources/audio/<id>.m4a .info.json de yt-dlp (paso 1)
sources/transcripts/<id>.json .md paso 2
podcast/<ep>.json .md subtítulos de nuestro propio episodio, importados (paso 2b)
sources/quotes.json paso 3, escrito por ti
sources/citations/ salidas del paso 4
1. Descarga (la ejecuta el usuario)
YouTube bloquea yt-dlp anónimo aquí. Dale al usuario este comando en lugar de ejecutarlo tú. Requiere cookies de Firefox y los clientes tv/safari, y mantiene el audio pequeño (Whisper solo necesita 16 kHz mono):
cd active_edits/<tema>/sources && for id in ID1 ID2; do yt-dlp --cookies-from-browser firefox --extractor-args "youtube:player_client=tv,web_safari,mweb" -f "wa/w" -x --audio-format m4a --postprocessor-args "ExtractAudio:-ac 1 -ar 16000" --write-info-json --write-auto-subs --sub-langs en --sub-format json3 --no-overwrites -o "audio/%(id)s.%(ext)s" "https://youtu.be/$id"; done
Para el historial de un canal, listalo primero, luego elige: yt-dlp --flat-playlist --print "%(id)s %(duration_string)s %(title)s" https://www.youtube.com/@CANAL/videos.
Clasifica los títulos con Jev (vibe-classification) si la lista es larga.
2. Transcribir
citekit.py transcribe sources/audio/<id>.m4a: mlx-whisper large-v3-turbo, marcas de tiempo por palabra. El prompt inicial es el título y la descripción del video, lo que corrige la mayoría de las ortografías de nombres. Funciona aproximadamente 85× en tiempo real en este Mac (un tramo de 15 min tarda ~35 s; el modelo se descarga una vez).citekit.py captions sources/audio/<id>.en.json3: subtítulos automáticos de YouTube. Instantáneo, pero sin puntuación. El cliente tv a menudo no los guarda.- 2b. Nuestros propios episodios:
citekit.py itt EP.itt --id podcastN --media EP.mp4 --out active_edits/<tema>/podcast --fix 'REGEX=>Correcto'. Los subtítulos de FCP distorsionan los nombres. Para el episodio 1:--fix '\bver ?m+[iu]l+e?\b=>Vermeule' --fix '\bvermil\b=>Vermeule' --fix '\bAsian Vermeule=>Adrian Vermeule'. El episodio 1 también tiene correcciones a nivel de palabra registradas enpodcast/podcast1.fixes.json(Schmidt→Schmitt, etc.), que se reaplican al reimportar.
Corrige las malas transcripciones sobre la marcha (no preguntes)
Cuando notes una clara mala interpretación (un nombre, una cita distorsionada, un tartamudeo que Whisper duplicó), corrígela:
citekit.py fix TRANSCRIPT 'palabras viejas=>palabras nuevas' [...] [--near SEGUNDOS]. Edita <id>.json/.md in situ (las palabras nuevas comparten la temporización del segmento antiguo, las coincidencias pueden cruzar segmentos, la puntuación final se conserva), mantiene la salida sin modificar del motor como <id>.raw.json, y registra cada corrección en <id>.fixes.json. transcribe e itt reaplican ese registro, por lo que las correcciones sobreviven a una re-ejecución. ¿No estás seguro? Vuelve a escuchar el punto en lugar de adivinar: ffmpeg -ss T -to T2 -i audio.m4a x.wav && mlx_whisper x.wav --output-format txt. Corrige primero, luego cita las palabras corregidas: locate busca en la transcripción.
3. Extraer citas (a tu criterio)
- Lee la transcripción de nuestro episodio, si existe, y enumera las afirmaciones que hacen los presentadores sobre el tema, cada una con su tiempo en el episodio. Esos son los momentos que un clip puede respaldar o refutar.
- Para cada afirmación,
citekit.py search 'regex' sources/transcripts/para encontrar candidatos, luego lee el.mdalrededor del resultado. Lee también las conferencias completas: las mejores líneas no siempre son las que mencionan los presentadores. - Una buena cita es corta (5–25 s hablados), autocontenida, inequívocamente la posición del propio orador (no resumiendo a un oponente), y citable en un gráfico de cita (lower third). Mantén las palabras exactas de la transcripción. No las limpies, o
locateno podrá encontrarlas. - Escribe
sources/quotes.json:
{"project": "Ep1 Citas de Vermeule", "fps": 29.97, "episode": "../podcast/podcast1.json",
"quotes": [{"id": "publius-ejecutivo-debil", "source": "Bw9M01S8MFI", "approx": 1234,
"text": "palabras exactas de la transcripción",
"episode_at": 2575, "episode_context": "presentadores sobre Hamilton y el dictador romano",
"why": "su propia declaración de la tesis", "tags": ["ejecutivo", "Federalista"],
"citation": {"author": "~Adrian Vermeule", "title": "La Paradoja de Publio", "date": "5 jun 2018",
"publication": "Charla Chorley, LSE"}}]}
episode (relativo a quotes.json) es la transcripción de nuestro episodio. Con él, el episode_at de cada cita (aproximadamente cuando los presentadores plantean el punto) se convierte en un punto de inserción: el final de la oración del presentador que se está reproduciendo en ese momento. Cita pensamientos completos. Si la siguiente oración completa la imagen o el argumento, inclúyela (un clip de 5–25 s es el objetivo, no un límite estricto).
citation por defecto usa el título del video, la fecha de subida y el canal. Pon el lugar y la fecha reales (de la descripción) una vez por conferencia en un "sources": {"<id>": {"citation": {...}}} de nivel superior. La citation propia de una cita lo anula. Los autores reciben el prefijo ~ del programa, y \n divide un título largo en dos líneas.
Verifica cualquier cosa que esté citando (Hamilton, Kafka, el Digesto) contra el texto original. Whisper escucha palabras incorrectas plausibles: en la conferencia de Publio, dio los "cántaros de sacrificio" de Kafka como "cuadros".
4. Cues y clips
citekit.py cues sources/quotes.json ajusta cada cita a las marcas de tiempo de las palabras (difuso; advierte si la puntuación de coincidencia es < 0.8) y escribe en sources/citations/:
| Archivo | Para |
|---|---|
citations.md | hoja de revisión en orden de episodio: tiempo de inserción (tal cual y después de inserciones anteriores), las palabras del presentador justo antes del corte, la cita, tiempos de fuente/clip, enlace de YouTube ?t= |
clips.json | edición por agente: entrada/salida de fuente, entrada/salida con relleno (por defecto 5 s por cada lado, --pad-before/--pad-after, pad_before/pad_after por cita), insert_at, insert_at_after_earlier_inserts, insert_after |
citation_cues.json | agent-motion/titles_fcpxml.py: gráficos de cita (lower thirds) de la Versión 2 temporizados para el episodio con los clips insertados, que aparecen cuando empieza a hablar. Las citas no colocadas se colocan una tras otra |
download_clips.sh | lo ejecuta el usuario: video de calidad completa solo para esos rangos con relleno (--download-sections) |
Luego citekit.py audio-clips sources/citations/clips.json corta previsualizaciones de audio con relleno desde el audio local, para verificar al oído. Para colocar los gráficos de cita en FCP, sigue la skill de motion-graphics:
python3 agent-motion/titles_fcpxml.py sources/citations/citation_cues.json -o citations.fcpxml.
Notas
-
transcribedecodifica a WAV de 16 kHz por sí mismo y rechaza audio que se decodifica mucho más corto que el video. Una descarga de yt-dlp reanudada con un-fdiferente empalma dos códecs en un solo .m4a (visto 2026-10-18: AAC-LC luego HE-AAC), y ffmpeg suelta silenciosamente todo después del cambio. Vuelve a descargar con--no-continue. Nunca reanudes entre cambios de formato. -
Verifica un clip al oído, o económicamente:
mlx_whisper citations/audio_clips/<id>.m4a --output-format txtdebería mostrar la cita con unos segundos de introducción y final. -
Las marcas de tiempo de Whisper son precisas en aproximadamente ±0.3 s a nivel de palabra. Por eso existen los rellenos de clip. Los subtítulos iTT de FCP solo temporizan líneas completas, por lo que
ittdistribuye las palabras uniformemente en cada línea. -
El conector Wispr Flow solo lee transcripciones del Meeting Recorder. No puede transcribir un archivo.