Cómo convertí mi podcast en vídeo para YouTube sin grabar nada

Cómo convertí mi podcast en vídeo para YouTube sin grabar nada
ContenidoContents

Mi podcast es 100% sintético: el guión lo escribe una IA, una voz sintética lo locuta y un script lo monta con música. Yo no enciendo ningún micro. Así que cuando pensé en llevarlo a YouTube, la pregunta no era “¿cómo grabo vídeo?”, sino "¿cómo convierto el audio que ya tengo en algo que se pueda ver?". Spoiler: se puede, es gratis y no hace falta ninguna cámara.

Esto es la crónica de cómo lo monté, con sus obstáculos y sus trucos. Lo he escrito para que puedas replicarlo paso a paso si te apetece, pero sin que estorbe la lectura: el hilo es la historia, y el detalle técnico va en cajas que puedes saltarte.

Primera decisión: ¿dónde vive el vídeo?

Mi web sale a internet por un túnel desde un Mac mini que tengo en casa. Servir MP4 pesados desde ahí me saturaría la subida doméstica: un episodio de audio son ~6 MB; en vídeo se va a decenas o cientos. Conclusión rápida: el vídeo lo aloja YouTube (descubrimiento, subtítulos indexados y cero coste de ancho de banda para mí) y mi web, como mucho, lo embebe.

El formato

Quería algo limpio y reconocible: la portada del episodio a lo grande con una onda que reacciona a la voz; y cuando termina la música de entrada, que la portada se encoja a una esquina y aparezcan los subtítulos en el centro. Todo eso se puede hacer con ffmpeg y un par de trucos.

Obstáculo 1: necesito los tiempos de cada palabra

Para subtitular hace falta saber cuándo se dice cada cosa. Como el audio ya existe, lo transcribo en local con whisper.cpp (rápido y con la GPU del Mac):

1brew install whisper-cpp
2# modelo (una vez):
3curl -L -o ggml-large-v3-turbo.bin \
4  https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin
5# audio -> wav 16 kHz mono
6ffmpeg -i ep.mp3 -ar 16000 -ac 1 ep.wav
7# transcripción con tiempos por palabra
8whisper-cli -m ggml-large-v3-turbo.bin -f ep.wav -l es \
9  -ml 1 --split-on-word -osrt -of ep

Obstáculo 2: Whisper se inventa la intro

Whisper colocaba la primera frase empezando en el segundo 0… pero ahí solo hay música. Estiraba “Bienvenido a un nuevo episodio” a lo largo de 10 segundos de sintonía. La solución elegante: detectar dónde entra la voz midiendo la energía del audio, y transcribir desde ahí, sumando luego ese desfase. Sin la música delante, Whisper clava los tiempos.

En corto: recorro el audio en ventanas de 50 ms calculando su volumen (RMS); el primer punto que se mantiene por encima de un umbral es el inicio de la voz. Recorto desde ahí, transcribo, y sumo el desfase a cada marca de tiempo.

Obstáculo 3: subtítulos fieles (no “lo que la IA oyó”)

Whisper acierta el ~95%, pero ya tengo el guión exacto del episodio. Sería absurdo publicar erratas de transcripción teniendo el texto bueno. Así que alineo el guión real con los tiempos de Whisper, emparejando palabra a palabra, y me quedo con lo mejor de cada uno: el texto del guión, los tiempos de Whisper. Cero erratas.

En corto: un difflib.SequenceMatcher entre las palabras de Whisper (normalizadas) y las del guión transfiere cada marca de tiempo a la palabra correcta del guión. Donde difieren, se interpola.

Obstáculo 4: mi ffmpeg no sabía “quemar” subtítulos

Resulta que la versión de ffmpeg que tenía está compilada sin libass, así que el filtro de subtítulos ni existía (Unknown filter 'subtitles'). En vez de pelearme reinstalando medio sistema, lo resolví al revés: genero cada subtítulo como una imagen PNG —con mi tipografía y mis colores— usando Python y Pillow, y la superpongo. Bonus: control tipográfico total.

Obstáculo 5: la animación de la portada

Que la portada encoja y viaje a la esquina en el momento justo se hace con expresiones de tiempo en ffmpeg: el tamaño y la posición se recalculan en cada fotograma.

# p = progreso de la transición (0 a 1); T = instante en que entra la voz
[1:v]scale=w='820+(300-820)*clip((t-T)/D,0,1)':h='...':eval=frame[cov];
[bg][cov]overlay=x='(W-w)/2*(1-p)+(W-w-45)*p':y='...':eval=frame

Obstáculo 6: que escale a episodios largos

Meter 80 imágenes de subtítulos como entradas en ffmpeg es frágil. La solución limpia: montar una sola pista de subtítulos con transparencia —concatenando los PNG con sus tiempos— y superponerla de una vez. Da igual que el episodio tenga 80 subtítulos o 200.

Subir a YouTube, y publicar con el episodio

La subida la hace la API de YouTube (OAuth). Sube primero como privado —para que nada salga a medio hacer— pero en mi flujo el visto bueno del episodio vale para todo: cuando apruebo el audio, se publica el episodio y el vídeo a la vez, ambos públicos, y el vídeo entra en su lista de reproducción. Un solo “ok” para los dos formatos.

Este es el resultado, el episodio 10 ya en vídeo:

Y todo en un comando

Al final lo encerré en un único comando, así cada episodio nuevo sale en vídeo sin trabajo manual:

1make-podcast-video ep011 --upload

Detecta la voz, transcribe, alinea el guión, genera los subtítulos, monta la animación y sube a YouTube. Lo que antes era una tarde de pruebas, ahora es una línea.

Lo que viene: un presentador con cara

El siguiente paso, que ya tengo en el tintero, es probar un avatar de IA que ponga cara y mueva la boca con el audio. ¿Mejora la conexión con quien mira, o cruza el “valle inquietante”? Lo veremos. Próximamente. 🎭

La lección

No hacía falta cambiar mi forma de producir el podcast: solo reutilizar lo que ya tenía —audio, guión, portada— y encadenar herramientas que ya estaban ahí. A veces “hacer vídeo” no es grabar vídeo, es mirar lo que tienes desde otro ángulo.

CompartirShare