Automatización de la producción audiovisual con software libre
En la OfiLibre producimos semanalmente los Cafés con OfiLibre, charlas informales de unos 15 minutos sobre software libre y conocimiento abierto. Cada episodio pasa por un proceso que va desde la grabación en directo hasta la publicación en múltiples plataformas. En guías anteriores ya explicamos cómo editamos los vídeos con FFmpeg y cuál es el proceso general de producción.
Esta guía se centra en la fase que va desde el vídeo editado hasta su publicación con subtítulos, un proceso que hemos automatizado casi por completo con herramientas libres. Veremos cómo usamos Whisper para transcribir, Pyannote para identificar quién habla en cada momento, FFmpeg para incrustar los subtítulos y la API de YouTube para subir el resultado final.
Visión general del flujo
El proceso completo se puede resumir así:
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Vídeo │ │ Whisper │ │ Pyannote │ │ FFmpeg │
│ editado │────▶│ Transcripción│────▶│ Diarización │────▶│ Subtítulos │
│ (.mp4) │ │ (.srt) │ │ (quién habla)│ │ incrustados │
└─────────────┘ └─────────────┘ └──────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ YouTube │
│ API │
│ (subida) │
└─────────────┘
Cada paso se ejecuta desde línea de comandos y puede encadenarse en un solo script. Veamos cada herramienta en detalle.
Whisper: transcripción automática de audio
Whisper es un modelo de reconocimiento de voz desarrollado por OpenAI y publicado bajo licencia MIT. Funciona en local, sin necesidad de conexión a internet ni de enviar datos a servidores externos, lo que lo hace ideal para entornos universitarios donde la privacidad de los contenidos es importante.
Instalación
Whisper se instala como un paquete de Python. Es recomendable usar un entorno virtual:
python3 -m venv whisper-env
source whisper-env/bin/activate
pip install openai-whisper
Para aprovechar la GPU (lo que acelera el proceso entre 5 y 10 veces), es necesario tener instalados los drivers de NVIDIA y CUDA. Si no se dispone de GPU, Whisper funciona igualmente en CPU, aunque más lento.
Uso básico
La forma más directa de transcribir un archivo es:
whisper cafe-2026-05-14.mp4 --language es --model medium --output_format srt
Esto genera un archivo .srt con los subtítulos sincronizados. Los parámetros más relevantes son:
--language es: indica el idioma del audio. Si se omite, Whisper lo detecta automáticamente, pero especificarlo mejora la precisión.--model medium: selecciona el tamaño del modelo. Los disponibles sontiny,base,small,mediumylarge. El modelomediumofrece un buen equilibrio entre precisión y velocidad para español.--output_format srt: formato de salida. También admitevtt,txt,tsvyjson.
Modelos disponibles
| Modelo | Parámetros | VRAM necesaria | Velocidad relativa |
|---|---|---|---|
| tiny | 39 M | ~1 GB | ~32x |
| base | 74 M | ~1 GB | ~16x |
| small | 244 M | ~2 GB | ~6x |
| medium | 769 M | ~5 GB | ~2x |
| large | 1550 M | ~10 GB | 1x |
Para los Cafés con OfiLibre usamos medium porque ofrece buena calidad en español sin requerir una GPU de gama alta. Si tu equipo tiene una tarjeta con al menos 10 GB de VRAM, large dará resultados aún mejores.
Ejemplo de salida (.srt)
1
00:00:01,000 --> 00:00:04,500
Bienvenidos a un nuevo Café con OfiLibre.
2
00:00:04,500 --> 00:00:08,200
Hoy vamos a hablar sobre licencias de software libre.
3
00:00:08,200 --> 00:00:12,800
Tenemos con nosotros a nuestra invitada, que nos va
a contar su experiencia con el proyecto.
Pyannote: saber quién habla en cada momento
La transcripción que genera Whisper no distingue entre hablantes. Para saber quién dice qué en cada momento, usamos Pyannote, una biblioteca de Python especializada en diarización de hablantes, publicada bajo licencia MIT.
La diarización es el proceso de segmentar un audio según las personas que intervienen, etiquetando cada fragmento con un identificador de hablante (SPEAKER_00, SPEAKER_01, etc.).
Instalación
pip install pyannote.audio
Pyannote requiere aceptar las condiciones de uso de los modelos en Hugging Face y obtener un token de acceso.
Uso desde Python
from pyannote.audio import Pipeline
pipeline = Pipeline.from_pretrained(
"pyannote/speaker-diarization-3.1",
use_auth_token="TU_TOKEN_HUGGINGFACE"
)
diarization = pipeline("cafe-2026-05-14.wav")
for turn, _, speaker in diarization.itertracks(yield_label=True):
print(f"{turn.start:.1f} - {turn.end:.1f}: {speaker}")
La salida tiene este aspecto:
0.0 - 4.5: SPEAKER_00
4.5 - 12.8: SPEAKER_01
12.8 - 15.3: SPEAKER_00
Combinar Whisper y Pyannote
El paso clave es cruzar los tiempos de los subtítulos de Whisper con los segmentos de diarización de Pyannote. Para cada bloque de subtítulo, buscamos qué hablante tiene la mayor superposición temporal y le asignamos su etiqueta:
import re
def parse_srt(srt_path):
"""Lee un archivo .srt y devuelve una lista de bloques."""
with open(srt_path) as f:
content = f.read()
pattern = r"(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n(.*?)(?=\n\n|\Z)"
return re.findall(pattern, content, re.DOTALL)
def time_to_seconds(t):
"""Convierte '00:01:23,456' a segundos."""
h, m, rest = t.split(":")
s, ms = rest.split(",")
return int(h)*3600 + int(m)*60 + int(s) + int(ms)/1000
def assign_speakers(srt_blocks, diarization):
"""Asigna un hablante a cada bloque de subtítulo."""
result = []
for idx, start, end, text in srt_blocks:
s = time_to_seconds(start)
e = time_to_seconds(end)
best_speaker = "UNKNOWN"
best_overlap = 0
for turn, _, speaker in diarization.itertracks(yield_label=True):
overlap = min(e, turn.end) - max(s, turn.start)
if overlap > best_overlap:
best_overlap = overlap
best_speaker = speaker
result.append((idx, start, end, f"[{best_speaker}] {text}"))
return result
El resultado es un archivo .srt donde cada bloque indica quién está hablando:
1
00:00:01,000 --> 00:00:04,500
[SPEAKER_00] Bienvenidos a un nuevo Café con OfiLibre.
2
00:00:04,500 --> 00:00:08,200
[SPEAKER_00] Hoy vamos a hablar sobre licencias.
3
00:00:08,200 --> 00:00:12,800
[SPEAKER_01] Muchas gracias por la invitación.
Los identificadores genéricos (SPEAKER_00, SPEAKER_01) se pueden sustituir manualmente por los nombres reales de los participantes con un simple buscar y reemplazar:
sed -i 's/SPEAKER_00/Jesús/g; s/SPEAKER_01/María/g' subtitulos.srt
FFmpeg: incrustar subtítulos en el vídeo
Una vez tenemos el archivo .srt con la transcripción y los hablantes identificados, usamos FFmpeg para incrustar los subtítulos en el vídeo. Ya hemos tratado FFmpeg en profundidad en nuestra guía de generación de vídeos, así que aquí nos centramos en el subtitulado.
Hay dos formas de añadir subtítulos:
Subtítulos blandos (soft subs)
Se empaquetan dentro del contenedor de vídeo como una pista separada. El espectador puede activarlos o desactivarlos:
ffmpeg -i video.mp4 -i subtitulos.srt \
-c copy -c:s mov_text \
video_con_subs.mp4
Subtítulos duros (hard subs)
Se “queman” directamente sobre la imagen del vídeo. Son siempre visibles y no se pueden desactivar, pero garantizan que se vean en cualquier reproductor:
ffmpeg -i video.mp4 \
-vf "subtitles=subtitulos.srt:force_style='FontSize=22,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2'" \
-c:a copy \
video_subtitulado.mp4
Los parámetros de force_style permiten personalizar la apariencia de los subtítulos usando la sintaxis de ASS/SSA. Los más útiles son:
FontSize: tamaño de la fuente.PrimaryColour: color del texto en formato&HAABBGGRR.OutlineColour: color del borde.Outline: grosor del borde en píxeles.FontName: tipografía (debe estar instalada en el sistema).
Para los Cafés con OfiLibre usamos subtítulos blandos para YouTube (la plataforma los gestiona nativamente) y duros para las versiones que se distribuyen por otros canales.
API de YouTube: subida automatizada
El último paso del flujo es subir el vídeo editado y subtitulado a YouTube. La API de YouTube Data v3 permite automatizar esta tarea desde un script.
Configuración previa
Antes de poder usar la API es necesario crear un proyecto en la Google Cloud Console, habilitar la API de YouTube Data v3 y obtener las credenciales OAuth 2.0. El proceso se resume en:
- Crear un proyecto en Google Cloud Console.
- Habilitar la API “YouTube Data API v3”.
- Configurar la pantalla de consentimiento OAuth.
- Crear credenciales de tipo “ID de cliente OAuth” para aplicación de escritorio.
- Descargar el archivo
client_secrets.json.
Dependencias
pip install google-api-python-client google-auth-oauthlib
Script de subida
import os
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
SCOPES = ["https://www.googleapis.com/auth/youtube.upload"]
def authenticate():
"""Autentica con OAuth 2.0 y devuelve el servicio."""
flow = InstalledAppFlow.from_client_secrets_file(
"client_secrets.json", SCOPES
)
credentials = flow.run_local_server(port=0)
return build("youtube", "v3", credentials=credentials)
def upload_video(youtube, filepath, title, description, tags):
"""Sube un vídeo a YouTube."""
body = {
"snippet": {
"title": title,
"description": description,
"tags": tags,
"categoryId": "27", # Educación
"defaultLanguage": "es",
},
"status": {
"privacyStatus": "unlisted", # No listado inicialmente
"selfDeclaredMadeForKids": False,
},
}
media = MediaFileUpload(filepath, chunksize=-1, resumable=True)
request = youtube.videos().insert(
part="snippet,status", body=body, media_body=media
)
response = request.execute()
print(f"Vídeo subido: https://www.youtube.com/watch?v={response['id']}")
return response["id"]
def upload_captions(youtube, video_id, srt_path, language="es"):
"""Sube subtítulos a un vídeo ya publicado."""
body = {
"snippet": {
"videoId": video_id,
"language": language,
"name": f"Subtítulos ({language})",
}
}
media = MediaFileUpload(srt_path)
youtube.captions().insert(
part="snippet", body=body, media_body=media
).execute()
print(f"Subtítulos ({language}) subidos correctamente.")
Uso
youtube = authenticate()
video_id = upload_video(
youtube,
filepath="cafe-2026-05-14-editado.mp4",
title="Licencias de software libre - Café con OfiLibre",
description="Charla sobre los tipos de licencias libres más comunes.",
tags=["software libre", "OfiLibre", "URJC", "licencias"],
)
upload_captions(youtube, video_id, "subtitulos.srt", language="es")
La primera vez que se ejecute, se abrirá una ventana del navegador para autorizar la aplicación. Las credenciales se pueden guardar en un archivo local para que las ejecuciones posteriores no requieran intervención manual.
Uniendo todo: el script completo
En la práctica, todo el flujo se encadena en un script bash que recibe como parámetro el archivo de vídeo editado y ejecuta todos los pasos de forma secuencial:
#!/bin/bash
# automatizar-cafe.sh - Flujo completo de postproducción
# Uso: ./automatizar-cafe.sh video_editado.mp4 "Título del café" "Descripción"
VIDEO="$1"
TITLE="$2"
DESC="$3"
BASENAME="${VIDEO%.*}"
echo "=== 1. Transcribiendo con Whisper ==="
whisper "$VIDEO" --language es --model medium --output_format srt \
--output_dir ./subs/
echo "=== 2. Diarizando con Pyannote ==="
python3 diarize.py "$VIDEO" "./subs/${BASENAME}.srt" \
--output "./subs/${BASENAME}_diarized.srt"
echo "=== 3. Incrustando subtítulos con FFmpeg ==="
ffmpeg -i "$VIDEO" -i "./subs/${BASENAME}_diarized.srt" \
-c copy -c:s mov_text \
"${BASENAME}_final.mp4"
echo "=== 4. Subiendo a YouTube ==="
python3 upload_youtube.py \
--video "${BASENAME}_final.mp4" \
--srt "./subs/${BASENAME}_diarized.srt" \
--title "$TITLE" \
--description "$DESC"
echo "=== Proceso completado ==="
Requisitos de hardware
Para ejecutar todo el flujo en un equipo local, las necesidades son moderadas:
- CPU: cualquier procesador moderno (i5/Ryzen 5 o superior). La transcripción en CPU tarda entre 3 y 5 veces la duración del vídeo.
- GPU (recomendada): una NVIDIA con al menos 4 GB de VRAM para el modelo
mediumde Whisper. Con GPU, la transcripción de un vídeo de 15 minutos tarda aproximadamente 2-3 minutos. - RAM: 8 GB mínimo, 16 GB recomendados.
- Almacenamiento: cada episodio editado ocupa entre 200 MB y 1 GB según la resolución.
Licencias del software utilizado
Todo el software mencionado en esta guía es libre o de código abierto:
- Whisper: licencia MIT.
- Pyannote: licencia MIT.
- FFmpeg: licencias LGPL / GPL.
- google-api-python-client: licencia Apache 2.0.
Esto significa que puedes usarlo, modificarlo y redistribuirlo libremente, tanto en proyectos personales como institucionales.
Conclusión
Automatizar la producción audiovisual no es solo una cuestión de eficiencia: es también una forma de garantizar la accesibilidad de los contenidos. Los subtítulos automáticos permiten que personas con discapacidad auditiva puedan seguir las charlas, y la diarización añade contexto sobre quién está hablando.
El flujo que hemos descrito — Whisper, Pyannote, FFmpeg, YouTube API — se ejecuta completamente en local con herramientas libres, no depende de servicios de terceros para el procesamiento y puede adaptarse a cualquier tipo de contenido audiovisual periódico.
Si quieres consultar el código completo de los scripts que usamos, puedes encontrarlos en nuestro repositorio. Y si produces contenido audiovisual en tu facultad o departamento, no dudes en contactar con la OfiLibre para que te ayudemos a poner en marcha un flujo similar.