Flujo CLI de subtítulos: genera, convierte e incrusta
Proporciona al siguiente script un directorio de videos MP4 con voz en inglés. Produce subtítulos SRT sincronizados, una copia WebVTT, un MKV con una pista de subtítulos separada y un MP4 con subtítulos incrustados en sus fotogramas. Whisper.cpp se encarga de la transcripción en la CPU; FFmpeg, de la conversión de subtítulos y los videos de salida. Trata los resultados como borradores que debes revisar antes de publicarlos.
Elige subtítulos seleccionables o incrustados
| Pista de subtítulos seleccionable | Subtítulos incrustados | |
|---|---|---|
| El espectador puede desactivarlos | Sí, en un reproductor compatible con la pista | No |
| Cambiar el texto | Reemplazar la pista sin codificar el video | Codificar un nuevo video |
| Procesamiento de video | Copiar el flujo de video existente | Renderizar el texto y codificar nuevos fotogramas |
| Requisito de reproducción | Compatibilidad con el contenedor y los subtítulos | Compatibilidad con el códec de video de salida |
Usa el MKV para una pista seleccionable y el MP4 con subtítulos incrustados cuando el texto deba permanecer visible sin un control para desactivarlo. Ninguna opción garantiza que todos los reproductores sean compatibles con el video resultante. El tamaño del archivo depende del origen y de los ajustes de codificación; incrustar texto no siempre lo aumenta.
Instala las herramientas de transcripción en CPU
Usa un sistema Linux con Bash y permiso para instalar paquetes. Estos comandos están destinados a
Ubuntu 24.04 y usan la versión v1.9.4 de Whisper.cpp.
El modelo tiny.en, exclusivo para inglés, mantiene ligero este ejemplo;
no garantiza la precisión. Necesitas acceso a internet para la instalación y la descarga del modelo;
después, la transcripción se ejecuta localmente.
Instala FFmpeg, un compilador de C++, CMake y una fuente para renderizar:
(
sudo apt-get update &&
sudo apt-get install -y build-essential cmake curl ca-certificates tar ffmpeg fonts-liberation
)
Desde un directorio de trabajo con permiso de escritura, crea subtitle-tools.
El subshell mantiene sin cambios tu directorio actual y las opciones del shell. Si ese directorio
ya existe, la preparación se detiene antes de instalar o escribir en él; elige otro directorio de
trabajo en lugar de eliminar el trabajo existente.
(
mkdir subtitle-tools &&
cd subtitle-tools &&
curl -fsSLo whisper.tar.gz https://github.com/ggml-org/whisper.cpp/archive/refs/tags/v1.9.4.tar.gz &&
mkdir source &&
tar -xzf whisper.tar.gz -C source --strip-components=1 &&
cmake -S source -B build -DCMAKE_BUILD_TYPE=Release \
-DGGML_CUDA=OFF -DGGML_VULKAN=OFF -DGGML_NATIVE=OFF \
-DBUILD_SHARED_LIBS=OFF -DWHISPER_BUILD_TESTS=OFF &&
cmake --build build --target whisper-cli -j2 &&
curl -fsSLo ggml-tiny.en.bin \
https://huggingface.co/ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-tiny.en.bin &&
printf '%s %s\n' \
921e4cf8686fdd993dcd081a5da5b6c365bfde1162e72b08d75ac75289920b1f \
ggml-tiny.en.bin | sha256sum -c -
)
Esto usa el flujo de compilación con CMake y preparación de audio de Whisper.cpp. Su CLI acepta el WAV PCM mono de 16 kHz y 16 bits que el script extrae de cada video. Otras herramientas que integran Whisper, incluida SubsAI, tienen su propia sintaxis de comandos; aquí no son necesarias.
Para probar el flujo de trabajo sin aportar una grabación, crea un video a partir de la muestra del discurso de JFK incluida en la versión. Ejecuta esto desde el mismo directorio de trabajo que usaste para la preparación:
(
mkdir demo-videos &&
ffmpeg -nostdin -n -v error -f lavfi -i color=c=black:s=640x360:r=24 \
-i ./subtitle-tools/source/samples/jfk.wav \
-map 0:v:0 -map 1:a:0 -c:v libx264 -threads 2 -c:a aac -shortest \
./demo-videos/example.mp4
)
Automatiza todo con un script de shell
Guarda lo siguiente como subtitle-tools/subtitles.sh. Procesa solo archivos regulares del nivel
superior con el sufijo en minúsculas .mp4 y omite las entradas que sean
enlaces simbólicos. Cada entrada recibe un nuevo directorio adyacente, como
example.mp4.subtitles. Se admiten espacios, comillas y guiones iniciales en los nombres de
archivo; se rechazan las rutas que contienen caracteres de salto de línea. Ejecuta un lote a la vez.
Los nombres de archivo fijos dentro de cada directorio de resultados mantienen la ruta original fuera de la sintaxis independiente de filtros de FFmpeg. El script conserva una copia del origen y un archivo de audio PCM, así que reserva espacio en disco para ellos y para ambos videos de salida.
#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C
fail() { printf '%s\n' "$*" >&2; exit 1; }
[[ $# -eq 1 ]] || fail 'Usage: bash subtitles.sh VIDEO_DIRECTORY'
[[ $PWD != *$'\n'* && $1 != *$'\n'* && ${BASH_SOURCE[0]} != *$'\n'* ]] ||
fail 'Newline characters in paths are not supported'
input_directory=$1
[[ $input_directory == /* ]] || input_directory=$PWD/$input_directory
case ${BASH_SOURCE[0]} in
*/*) tool_directory=${BASH_SOURCE[0]%/*} ;;
*) tool_directory=. ;;
esac
cd -P -- "$tool_directory"
tool_directory=$PWD
[[ $tool_directory != *$'\n'* ]] || fail 'Newline characters in paths are not supported'
whisper=$tool_directory/build/bin/whisper-cli
model=$tool_directory/ggml-tiny.en.bin
[[ -x $whisper && -s $model ]] || fail 'Complete the subtitle-tools setup first'
cd -P -- "$input_directory"
[[ $PWD != *$'\n'* ]] || fail 'Newline characters in paths are not supported'
shopt -s nullglob
process_video() (
local video=$1
local output=$video.subtitles
mkdir -- "$output"
cp -- "$video" "$output/source.mp4"
cd -- "$output"
ffmpeg -nostdin -n -v error -xerror -i source.mp4 -map 0:a:0 \
-ar 16000 -ac 1 -c:a pcm_s16le audio.wav
"$whisper" --no-gpu -t 2 -p 1 -l en -m "$model" -f audio.wav -osrt -of captions
[[ -s captions.srt ]] || fail 'No subtitle file was produced'
grep -q -- '-->' captions.srt || fail 'No timed captions were produced; check for speech'
ffmpeg -nostdin -n -v error -i captions.srt -map 0:s:0 -c:s webvtt captions.vtt
ffmpeg -nostdin -n -v error -i source.mp4 -i captions.srt \
-map 0:v:0 -map 0:a:0 -map 1:s:0 -c copy -metadata:s:s:0 language=eng soft.mkv
ffmpeg -nostdin -n -v error -xerror -i source.mp4 \
-vf "subtitles=captions.srt:force_style='Fontname=Liberation Sans,FontSize=24'" \
-map 0:v:0 -map 0:a:0 -c:v libx264 -threads 2 -crf 20 -preset veryfast \
-c:a copy burned.mp4
)
count=0
current_video='setup'
trap 'status=$?; if [[ $status -ne 0 ]]; then printf "Failed while processing: %s\n" "$current_video" >&2; fi' EXIT
for video in "$PWD"/*.mp4; do
[[ -f $video && ! -L $video ]] || continue
[[ $video != *$'\n'* ]] || fail 'Newline characters in paths are not supported'
current_video=$video
printf 'Processing: %s\n' "$video"
process_video "$video"
count=$((count + 1))
printf 'Created: %s.subtitles\n' "$video"
done
[[ $count -gt 0 ]] || fail 'No regular .mp4 files found'
Ejecútalo desde el directorio de trabajo donde instalaste las herramientas, que es distinto del directorio de entrada:
bash ./subtitle-tools/subtitles.sh ./demo-videos
Reemplaza ./demo-videos por tu directorio de grabaciones. El script resuelve ese
directorio antes de seleccionar archivos; no procesa los MP4 del directorio desde el que lo invocas.
También puedes pasar rutas absolutas tanto para el script guardado como para el directorio de entrada.
Un directorio vacío devuelve un error. Un video sin flujo de audio, un error de decodificación o un fallo en una conversión posterior detiene el lote e indica la entrada actual. Un fallo puede dejar archivos parciales en su directorio de resultados. Los directorios de resultados existentes provocan un error antes de copiar o transcribir ese clip, por lo que las ejecuciones repetidas conservan las salidas anteriores. Este script no permite reanudar el procesamiento: para volver a intentar con un clip corregido, colócalo en un directorio separado sin resultados existentes.
Un audio silencioso no es lo mismo que la ausencia de un flujo de audio. El modelo puede no devolver
ningún segmento de subtítulos, o devolver etiquetas de sonidos no verbales o texto inventado. Por
ejemplo, tiny.en puede producir un segmento [BLANK_AUDIO]
que se extienda más allá de la grabación. El script comprueba que haya segmentos con marcas de
tiempo; no valida sus palabras ni su sincronización. Escucha el audio y comprueba los finales de
los segmentos con respecto a la duración del video antes de publicarlo.
FFmpeg también puede recuperar algunos archivos multimedia dañados en lugar de informar de un error. Que los comandos se ejecuten correctamente no certifica que la entrada esté intacta ni que la transcripción sea precisa.
Revisa los subtítulos SRT y WebVTT
En demo-videos/example.mp4.subtitles, abre captions.srt y
captions.vtt. El SRT tiene segmentos numerados con los milisegundos separados por
comas; el VTT comienza con WEBVTT y usa puntos decimales en sus marcas de
tiempo. Ambos deben contener las frases de la muestra «ask not what your country can do for you»
y «ask what you can do for your country». Compara las palabras con el audio, especialmente los
nombres, la puntuación y el inicio y el final de cada segmento.
La conversión del script usa el codificador de subtítulos webvtt de FFmpeg.
No necesitas Subtitle Edit CLI para esta conversión de SRT a WebVTT. Un archivo WebVTT independiente
está listo para usarse como pista de subtítulos en un reproductor web compatible; generar el archivo
por sí solo no lo integra en un sitio web.
Revisa la pista de subtítulos seleccionable
Abre soft.mkv en un reproductor compatible con Matroska y subtítulos SRT;
luego, selecciona su pista de subtítulos en inglés. El script asigna explícitamente el primer flujo
de video, el primer flujo de audio y los subtítulos generados. El
modo de copia de flujos -c copy de FFmpeg
copia esos flujos al MKV sin codificar el video. Esto realiza la multiplexación sin una instalación
adicional de MKVToolNix.
Incrusta subtítulos de forma permanente con FFmpeg
Abre burned.mp4 sin activar un archivo de subtítulos externo. El texto ya
debería aparecer en la imagen del video. El
filtro subtitles basado en libass de FFmpeg
renderiza el SRT, y libx264 codifica los fotogramas modificados. Por eso,
incrustar subtítulos requiere codificar el video, mientras que añadir una pista seleccionable no.
El audio se copia en ambas salidas.
Para la muestra con fondo negro, también puedes decodificar un fotograma a los dos segundos. El
nuevo preview.png debería contener texto blanco visible cerca de la parte
inferior; el video de origen en ese instante es completamente negro:
(
cd ./demo-videos/example.mp4.subtitles || exit
[[ ! -e preview.png && ! -L preview.png ]] || { printf 'preview.png already exists\n' >&2; exit 1; }
ffmpeg -nostdin -n -v error -ss 2 -i burned.mp4 -frames:v 1 -threads 2 preview.png
)
La opción -n impide reemplazar una
vista previa existente. Editar captions.srt después no cambia ninguno de los
videos ni el VTT que ya generaste. Conserva los subtítulos corregidos y renderiza nuevas salidas
antes de publicarlas.
Consejos de rendimiento y escalabilidad
Mide la transcripción y la incrustación por separado con tus propias grabaciones. Este ejemplo usa deliberadamente dos hilos de CPU y ninguna GPU; no predice la velocidad de procesamiento en otro hardware. Si el borrador omite parte del habla, compara un modelo de inglés más grande con un clip representativo y revisa también su salida. Un modelo más grande no sustituye la revisión de la transcripción.
Conserva el SRT revisado junto con la grabación para poder cambiar el formato de entrega sin volver a transcribir. El script por lotes siempre transcribe al crear nuevos directorios de resultados; no reutiliza automáticamente tus correcciones.
Conclusión
Si ya tienes subtítulos revisados y quieres un servicio gestionado para incrustarlos, nuestro
Robot 🤖 /video/subtitle
acepta subtítulos SRT o WebVTT existentes. No genera la transcripción. Sube el video en el campo de
formulario input_video y el archivo de subtítulos en
input_srt; estas
Assembly Instructions mantienen esas entradas separadas:
{
"steps": {
"subtitle_video": {
"robot": "/video/subtitle",
"use": {
"steps": [
{ "name": ":original", "fields": "input_video", "as": "video" },
{ "name": ":original", "fields": "input_srt", "as": "subtitles" }
]
},
"subtitles_type": "burned",
"font_size": 24,
"position": "bottom",
"font_color": "FFFFFF",
"border_style": "outline"
}
}
}
