Automatiza vistas previas GIF animadas desde la CLI
Para un directorio de videos, genera un GIF breve por archivo y conserva su ruta relativa en el
directorio de salida. El script de Bash que sigue muestra los primeros tres segundos a 10 fps,
conserva los archivos existentes y devuelve un estado de error si falla alguna conversión. Dos
videos llamados demo.mp4 en carpetas distintas obtienen vistas previas separadas.
Configura las herramientas de Linux
Usa Bash, GNU findutils y coreutils, y FFmpeg con ffprobe. Los ejemplos están
pensados para Linux y un sistema de archivos que distinga mayúsculas de minúsculas y admita enlaces
duros; macOS nativo y PowerShell quedan fuera del alcance probado de este tutorial. Usa videos SDR
convencionales con píxeles cuadrados. El mapeo de tonos HDR y la corrección de video anamórfico
requieren una configuración de filtros aparte.
Estos ejemplos se probaron con FFmpeg 6.1.1 en Ubuntu 24.04 y FFmpeg n9.0.1 en Linux.
En Ubuntu o Debian, instala las herramientas con:
sudo apt-get update && sudo apt-get install ffmpeg bash findutils coreutils
El paquete FFmpeg de Ubuntu incluye las herramientas multimedia. Comprueba las versiones disponibles en tu shell:
bash --version
ffmpeg -version
ffprobe -version
Elige GIF cuando el destino lo necesite
GIF es útil para una ilustración en bucle en la documentación o en un sistema que acepte imágenes, pero no permita incorporar video. Tiene una paleta limitada y no incluye audio. Un GIF puede ser mucho más grande que su archivo MP4 de origen, así que compara archivos reales antes de usarlo para vistas previas web. Si tu destino admite video, conservar un clip breve puede ofrecer mejor calidad con un tamaño menor. La comparación de GIF y video de Google ilustra la posible diferencia de tamaño.
Crea una vista previa con límites
Coloca un video llamado input.mp4 en tu directorio de trabajo y luego ejecuta:
(
if [[ -e ./preview.gif || -L ./preview.gif ]]; then
printf 'Refusing existing output: ./preview.gif\n' >&2
exit 1
fi
ffmpeg -hide_banner -loglevel error -nostdin -n -xerror -abort_on empty_output \
-t 3 -i ./input.mp4 -map 0:v:0 \
-vf "fps=10,scale='min(320,iw)':-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" \
-t 3 -frames:v 30 -loop 0 -f gif ./preview.gif
)
Esto selecciona el primer flujo de video, limita el ancho a 320 píxeles sin ampliar las entradas
más pequeñas y crea un GIF que se repite indefinidamente. La opción de entrada
-t 3 delimita el segmento que se envía a los filtros; la duración de salida y
el límite de fotogramas mantienen la vista previa en un máximo de tres segundos y 30 fotogramas.
Las entradas cortas producen vistas previas más breves. Consulta las
reglas de las opciones de entrada y salida de FFmpeg.
La división envía los fotogramas escalados tanto a palettegen como a
paletteuse: uno construye una paleta a partir del clip y el otro la aplica.
Limitar la entrada es importante porque generar una paleta para todo el video retrasaría la salida
y aumentaría el uso de búferes. La
documentación de los filtros de paleta describe los controles de
color y tramado disponibles.
La comprobación del shell informa como error la existencia de preview.gif;
-n también le indica a FFmpeg que no lo sobrescriba. En algunas versiones,
FFmpeg puede devolver cero al negarse a sobrescribir un archivo, por lo que su estado por sí solo
no basta en este caso. -nostdin desactiva la entrada interactiva. Si la conversión
falla después de crear el archivo, puede dejar un GIF incompleto. El script por lotes que sigue
evita publicar resultados parciales al convertir primero a un archivo temporal.
Inspecciona un resultado correcto con:
ffprobe -v error -select_streams v:0 -count_frames \
-show_entries stream=width,height,nb_read_frames:format=duration,size \
-of json ./preview.gif
Para una entrada que dure al menos tres segundos, el resultado esperado es de 30 fotogramas y una
duración de 3.000000 segundos. Abre también el GIF: los metadatos no te indican
si la escena inicial elegida es útil.
Procesa el directorio por lotes sin perder nombres de archivo
Guarda este script completo como gif-previews.sh. Procesa de forma recursiva los
archivos regulares que terminan en .mp4, sin distinguir mayúsculas de
minúsculas ni seguir entradas de enlaces simbólicos. Añade .gif al nombre
relativo completo del archivo: videos/team/demo.mp4 pasa a ser
gifs/team/demo.mp4.gif. Conservar la extensión también permite distinguir
demo.mp4 de demo.MP4 en el sistema de archivos compatible.
#!/usr/bin/env bash
set -euo pipefail
if (( $# > 2 )); then
printf 'Usage: bash gif-previews.sh [video-directory] [output-directory]\n' >&2
exit 1
fi
VIDEO_DIR=${1:-./videos}
OUTPUT_DIR=${2:-./gifs}
command -v ffmpeg >/dev/null
# Resolve both directory arguments from the caller's working directory.
start_dir=$PWD
cd -P -- "$VIDEO_DIR"
VIDEO_DIR=$PWD
cd -P -- "$start_dir"
mkdir -p -- "$OUTPUT_DIR"
cd -P -- "$OUTPUT_DIR"
OUTPUT_DIR=$PWD
cd -P -- "$VIDEO_DIR"
work=$(mktemp -d -- "$OUTPUT_DIR/.gif-preview.XXXXXX")
trap 'rm -rf -- "$work"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
# Finish discovery first so a failed find cannot look like an empty successful batch.
find . -type f -iname '*.mp4' -print0 > "$work/files"
if [[ ! -s "$work/files" ]]; then
printf 'No MP4 files found.\n' >&2
exit 1
fi
generated=0
failed=0
while IFS= read -r -d '' video; do
output="$OUTPUT_DIR/${video#./}.gif"
if [[ -e "$output" || -L "$output" ]]; then
printf 'Refusing existing output: %q\n' "$output" >&2
failed=$((failed + 1))
continue
fi
if mkdir -p -- "${output%/*}" &&
ffmpeg -hide_banner -loglevel error -nostdin -y -xerror -abort_on empty_output \
-t 3 -i "$video" -map 0:v:0 \
-vf "fps=10,scale='min(320,iw)':-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" \
-t 3 -frames:v 30 -loop 0 -f gif "$work/preview.gif" &&
ln -T -- "$work/preview.gif" "$output"; then
printf 'Generated: %q\n' "$output"
generated=$((generated + 1))
else
printf 'Failed: %q\n' "$video" >&2
failed=$((failed + 1))
fi
# Unlink the temporary name before reuse: published files share its inode.
rm -f -- "$work/preview.gif"
done < "$work/files"
printf 'Generated: %d; failed: %d\n' "$generated" "$failed"
if (( failed > 0 )); then
exit 1
fi
Ejecútalo desde el directorio que contiene tu carpeta videos:
bash gif-previews.sh ./videos ./gifs
El script conserva los espacios, los saltos de línea, los guiones iniciales y los signos de
porcentaje literales de los nombres de archivo. La detección delimitada por caracteres nulos
mantiene intacto cada nombre, y FFmpeg no puede consumir la entrada del bucle gracias a
-nostdin. Tanto la detección como la conversión se ejecutan desde el mismo
directorio de entrada, cuya ruta física se ha resuelto, incluso cuando el argumento del directorio
contiene un enlace simbólico seguido de ...
Los destinos existentes cuentan como fallos; nunca se omiten ni se reemplazan sin aviso. La
conversión usa -y solo para el archivo temporal privado. La herramienta
ln -T de GNU publica un GIF completo sin reemplazar un destino que aparezca
durante la conversión. El directorio temporal reside en el sistema de archivos de salida para que
puedan funcionar los enlaces duros. Usa un árbol de salida que controles, sin subdirectorios que
sean enlaces simbólicos ni montajes anidados.
Un lote mixto conserva los GIF que se generaron correctamente y continúa tras los fallos de
archivos individuales; luego finaliza con el estado 1. Un directorio de entrada vacío también
finaliza con el estado 1. Una nueva ejecución sobre el mismo árbol de salida informa como fallos
los archivos existentes; elige un nuevo directorio de salida para regenerar todo el lote. Los
fallos habituales y las interrupciones eliminan los archivos temporales, pero una terminación
forzada o una pérdida de energía pueden dejar un directorio oculto .gif-preview.*.
Ajusta el equilibrio entre tamaño y calidad
Empieza con los ajustes de tres segundos, 320 píxeles y 10 fps, e inspecciona clips representativos.
Las dimensiones menores eliminan detalles; una menor tasa de fotogramas hace que el movimiento sea
menos fluido. Acortar el clip suele ahorrar más espacio que ajustar los colores. Si cambias la
tasa o la duración, actualiza a la vez ambos valores de -t y el límite de
-frames:v; para dos segundos a 8 fps, usa un límite de 16.
El tramado paletteuse predeterminado mejora los degradados, pero puede añadir
ruido visible. Vale la pena comparar su ajuste documentado dither=bayer con tus
propias grabaciones. Estos controles sacrifican detalle, movimiento y precisión del color para
reducir el tamaño; ninguno garantiza que GIF supere a un códec de video.
Diagnostica un lote fallido
- Archivos de salida existentes: elige un directorio de salida nuevo o elimina de forma deliberada solo las vistas previas que quieras regenerar. Cambiar los ajustes de calidad no sobrescribe los resultados anteriores.
- Video no válido o sin flujo de video: inspecciona el archivo de origen indicado con
ffprobe. Cambiar el nombre de un archivo dañado para que termine en.mp4no lo repara. El script comprueba el segmento de la vista previa, no la integridad de todo el video. - Sin fotogramas o con un inicio poco útil: las entradas muy cortas o inusuales pueden no generar una vista previa; el script trata la salida vacía como un fallo. Un inicio en negro requiere un segmento diferente.
- Errores del sistema de archivos o de memoria: comprueba el espacio libre, los permisos de salida y la compatibilidad con enlaces duros. Reduce la duración del segmento y las dimensiones para los archivos de origen grandes. Limitar el clip a tres segundos no establece un límite estricto para la memoria del decodificador ni para el tiempo de ejecución.
Usa las rutas de archivo que se muestran para inspeccionar los fallos y luego revisa los GIF que se generaron correctamente antes de incorporarlos a tu catálogo multimedia.
