Transmite medios remotos a FFmpeg mediante cURL
Envía una descarga HTTP de cURL a FFmpeg mediante un pipe para extraer miniaturas y audio sin conservar una copia local del video de entrada. El MP4 debe permitir la lectura secuencial. Este tutorial crea un clip de prueba conocido, lo sirve por HTTP y procesa esa respuesta para obtener imágenes JPEG y un archivo WAV.
Descripción general
Usa Bash en Linux, cURL, Python 3 y una compilación de FFmpeg con los codificadores
libx264 y AAC. Los comandos siguientes se probaron con FFmpeg 6.1.1 y 9.0.1.
Python proporciona el servidor HTTP local y no es necesario si ya tienes una URL remota adecuada.
El ejemplo crea un MP4 «faststart» con sus metadatos moov antes de los
datos multimedia. La documentación de MP4 de FFmpeg explica esta
disposición. Un pipe no permite retroceder a una posición anterior, por lo que un MP4 común con
los metadatos al final puede fallar incluso después de que cURL haya enviado todo el archivo.
Si desconoces la disposición, usa el ejemplo de descarga previa que aparece más abajo. Añadir
-f mp4 identifica el formato; no permite acceder a posiciones arbitrarias
de la entrada.
Ejecuta cada bloque desde el mismo directorio padre en Bash. Los paréntesis limitan los cambios de opciones del shell a un subshell. Cada bloque de procesamiento requiere un directorio de salida nuevo y rechaza una nueva ejecución en uno existente. Las ejecuciones fallidas pueden dejar archivos parciales en ese directorio nuevo; examínalos antes de usarlos y elige un nuevo directorio para otro intento.
Configura el pipeline
Prepara un clip y un origen HTTP
Crea un clip de 6,4 segundos: rojo durante dos segundos, verde lima durante dos segundos y luego azul. Su audio es silencioso hasta un breve tono de 880 Hz en los últimos 0,2 segundos, lo que permite detectar al escucharlo si la extracción de audio está incompleta.
(
set -euo pipefail
mkdir curl-media-demo
ffmpeg -nostdin -n -hide_banner -loglevel warning -filter_threads 1 \
-f lavfi -i "color=c=red:s=160x90:r=10:d=6.4" \
-f lavfi -i "aevalsrc='if(gte(t,6.2),0.25*sin(2*PI*880*t),0)':s=48000:d=6.4" \
-vf "drawbox=c=lime:t=fill:enable='gte(t,2)',drawbox=c=blue:t=fill:enable='gte(t,4)'" \
-map 0:v:0 -map 1:a:0 -c:v libx264 -pix_fmt yuv420p -threads 1 \
-c:a aac -movflags +faststart curl-media-demo/input.mp4
)
En esta terminal, sirve únicamente el directorio de prueba en la dirección de loopback:
(
set -eu
test -f curl-media-demo/input.mp4
python3 -m http.server 8765 --bind 127.0.0.1 --directory curl-media-demo
)
Mantén ese servidor en ejecución mientras usas una segunda terminal para los siguientes bloques. Si el puerto 8765 está ocupado, elige uno disponible y cámbialo tanto en el comando del servidor como en las URL. Detén el servidor con Ctrl+C al terminar.
Extrae miniaturas y audio de una sola descarga
En la segunda terminal, vuelve al mismo directorio padre y ejecuta:
(
set -euo pipefail
mkdir outputs
curl -fsSL "http://127.0.0.1:8765/input.mp4" | \
ffmpeg -nostdin -n -hide_banner -loglevel warning -filter_threads 1 -i pipe:0 \
-map 0:v:0 -vf "fps=1" -c:v mjpeg -q:v 2 -threads 1 -frame_pts 1 \
outputs/thumbnail_%03d.jpg \
-map 0:a:0 -c:a pcm_s16le outputs/audio.wav
)
Para este clip de prueba, deberías obtener seis JPEG de 160×90, con nombres desde
thumbnail_000.jpg hasta thumbnail_005.jpg.
Los dos primeros son rojos, los dos siguientes verde lima y los dos últimos azules. Reproduce
outputs/audio.wav: debería ser silencioso hasta el tono cercano al final de la
grabación de 6,4 segundos. La decodificación AAC puede añadir una pequeña cantidad de relleno
al final del WAV.
Explicación
pipe:0 lee la entrada estándar. Las opciones explícitas
-map seleccionan el primer flujo de video y el primero de audio, con una
salida para cada uno. Un video sin audio hace que falle la asignación obligatoria de audio, en
lugar de omitir el WAV silenciosamente. La comprobación de que el directorio sea nuevo protege
la secuencia de imágenes frente a resultados de ejecuciones anteriores. La opción
-n de FFmpeg protege los nombres de archivo MP4 y WAV; una secuencia
image2 puede reemplazar JPEG individuales. Mantén la comprobación del directorio y ejecuta estos
ejemplos de forma secuencial.
El filtro fps elimina o duplica fotogramas para producir un
fotograma por segundo. Su redondeo predeterminado genera seis fotogramas para este clip de
prueba, por lo que la fracción de segundo final no recibe una miniatura independiente.
-frame_pts 1 usa las marcas de tiempo de presentación de salida como números
en los nombres de archivo, según la base de tiempo de salida. Aquí esa base es de un segundo,
por lo que los números representan tiempos de salida de 0–5 segundos. No son los números de
fotograma originales ni garantizan el fotograma exacto elegido de la fuente. Consulta las
opciones de image2.
La opción -f de cURL hace que un error HTTP como 404 provoque el fallo
de la transferencia, mientras que -sS oculta el indicador de progreso
y conserva los mensajes de error. La opción pipefail de Bash propaga el
fallo de una descarga incluso si FFmpeg termina correctamente. Ninguna de estas opciones
demuestra que el servidor haya enviado contenido multimedia íntegro.
Usos avanzados
Selecciona cambios de escena y registra sus tiempos
Para muestrear los cambios de color en lugar de usar una frecuencia fija, ejecuta una descarga independiente en un directorio nuevo:
(
set -euo pipefail
mkdir scenes
curl -fsSL "http://127.0.0.1:8765/input.mp4" | \
ffmpeg -nostdin -n -hide_banner -loglevel warning -filter_threads 1 -i pipe:0 \
-map 0:v:0 \
-vf "select='gt(scene,0.3)',metadata=print:key=lavfi.scene_score:file=scenes/time.txt" \
-fps_mode vfr -c:v mjpeg -q:v 2 -threads 1 scenes/scene_%03d.jpg
)
Para este clip, scene_001.jpg debería ser verde lima y
scene_002.jpg debería ser azul. Las entradas pts_time
correspondientes en scenes/time.txt son 2 y 4 segundos. Estas son las marcas de
tiempo de los fotogramas seleccionados en la línea de tiempo de entrada de FFmpeg, que comienza
en cero para este clip de prueba. No son horas de captura del reloj, y los números secuenciales
de los JPEG no son marcas de tiempo.
El filtro select compara una puntuación de escena con el umbral.
Una puntuación superior a 0,3 es una heurística para detectar un cambio visual, no un límite de
escena semántico. Es posible que no se seleccione ningún fotograma de un clip.
-fps_mode vfr evita rellenar los espacios entre los fotogramas seleccionados
con duplicados.
Solución de problemas
Si el pipe informa de un archivo parcial o no encuentra paquetes utilizables, es posible que el MP4 necesite acceso a posiciones arbitrarias. Descárgalo en un directorio nuevo y luego procesa el archivo descargado. Esto también separa los fallos de red de los de decodificación:
(
set -euo pipefail
mkdir download
curl -fsSL --retry 3 "http://127.0.0.1:8765/input.mp4" -o download/input.mp4
ffmpeg -nostdin -n -hide_banner -loglevel warning -filter_threads 1 -i download/input.mp4 \
-map 0:v:0 -vf "fps=1" -c:v mjpeg -q:v 2 -threads 1 -frame_pts 1 \
download/thumbnail_%03d.jpg \
-map 0:a:0 -c:a pcm_s16le download/audio.wav
)
Con la URL de prueba, esto produce las mismas miniaturas y el mismo audio que el pipe. Usa los
reintentos de cURL con -o para descargar un archivo, en lugar de usarlos
con un pipe hacia un decodificador. cURL no puede restablecer los bytes ya escritos en un pipe
antes de reintentar; su documentación sobre reintentos describe
esta distinción. El bloque se detiene antes de la decodificación si la descarga falla.
Una respuesta HTTP 200 y un estado de salida cero tampoco demuestran que el contenido multimedia esté completo. FFmpeg puede informar de daños durante la decodificación y aun así devolver cero, o recuperar fotogramas y muestras dañados. Lee sus advertencias, compara la cantidad y los colores esperados de las miniaturas y comprueba la duración del audio y el tono final. Para tu propio contenido multimedia, usa expectativas conocidas de duración y contenido o una suma de comprobación de un publicador de confianza; estos ejemplos no realizan una comprobación de integridad. Descargar primero resuelve el acceso a posiciones arbitrarias, no la corrupción.
Consideraciones de rendimiento
El pipe evita almacenar el MP4 de entrada, pero los JPEG y el WAV sin comprimir siguen ocupando espacio en disco. FFmpeg procesa los datos a medida que llegan; la velocidad de la red y la de decodificación determinan la rapidez de ese procesamiento. Esto es una descarga de archivo por HTTP, no una garantía de comportamiento en tiempo real ni de transmisión en directo. Reducir la frecuencia de las miniaturas disminuye la cantidad de imágenes guardadas, mientras que la detección de escenas sigue examinando los fotogramas decodificados.
Para otro contenedor de entrada, consulta la documentación del protocolo pipe de FFmpeg y comprueba si el demultiplexor puede leerlo secuencialmente. Mantén disponible la alternativa de descargar primero cuando no puedas controlar la disposición del MP4.
