Automatize prévias de GIF animado na CLI
Para um diretório de vídeos, gere um GIF curto para cada arquivo e mantenha o caminho relativo dele
no diretório de saída. O script Bash abaixo cria uma prévia dos primeiros três segundos a 10 fps,
preserva arquivos existentes e retorna um status de falha se qualquer conversão falhar. Dois vídeos
chamados demo.mp4 em pastas diferentes recebem prévias separadas.
Configure as ferramentas no Linux
Use Bash, GNU findutils e coreutils, e FFmpeg com ffprobe. Os exemplos têm como alvo o Linux e um
sistema de arquivos que diferencia maiúsculas de minúsculas e suporta hard links; o macOS nativo e
o PowerShell estão fora do escopo testado deste passo a passo. Use vídeos SDR comuns com pixels
quadrados. O mapeamento de tons HDR e a correção de vídeo anamórfico exigem uma configuração de
filtros separada.
Estes exemplos foram testados com o FFmpeg 6.1.1 no Ubuntu 24.04 e com o FFmpeg n9.0.1 no Linux.
No Ubuntu ou Debian, instale as ferramentas com:
sudo apt-get update && sudo apt-get install ffmpeg bash findutils coreutils
O pacote FFmpeg do Ubuntu inclui as ferramentas de mídia. Confira as versões disponíveis no seu shell:
bash --version
ffmpeg -version
ffprobe -version
Escolha GIF quando o destino exigir
O GIF é útil para uma ilustração em loop na documentação ou para um sistema que aceita imagens, mas não consegue incorporar vídeo. Ele tem uma paleta limitada e não tem áudio. Um GIF pode ser muito maior que o MP4 de origem, então compare os arquivos reais antes de usá-lo para prévias na web. Se o seu destino suporta vídeo, manter um clipe de vídeo curto pode oferecer mais qualidade com um tamanho menor. A comparação entre GIF e vídeo do Google ilustra a possível diferença de tamanho.
Crie uma prévia com limites
Coloque um vídeo chamado input.mp4 no seu diretório de trabalho e execute:
(
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
)
Isso seleciona o primeiro fluxo de vídeo, limita a largura a 320 pixels sem ampliar entradas menores
e cria um GIF em loop infinito. O -t 3 do lado da entrada limita o segmento enviado aos
filtros; a duração da saída e o limite de quadros mantêm a prévia em no máximo três segundos e
30 quadros. Entradas curtas produzem prévias mais curtas. Veja as
regras de opções de entrada e saída do FFmpeg.
O filtro split envia os quadros redimensionados para palettegen e paletteuse: um gera uma
paleta a partir do clipe e o outro a aplica. Limitar a entrada é importante porque gerar uma paleta
para o vídeo inteiro atrasaria a saída e aumentaria o buffering. A
documentação do filtro de paleta descreve os controles de cor e de
dithering disponíveis.
A verificação do shell reporta um preview.gif existente como falha; -n também instrui o
FFmpeg a não sobrescrevê-lo. Em algumas versões, o FFmpeg pode retornar zero ao recusar uma
sobrescrita, então o status de saída dele sozinho não basta aqui. -nostdin desativa a entrada
interativa. Se a conversão falhar depois de criar o arquivo, ela pode deixar um GIF incompleto. O
script em lote abaixo evita publicar saída parcial convertendo primeiro para um arquivo temporário.
Inspecione um resultado bem-sucedido com:
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 uma entrada com pelo menos três segundos, espere 30 quadros e uma duração de 3.000000
segundos. Abra o GIF também: os metadados não dizem se a cena de abertura escolhida é útil.
Processe o diretório em lote sem perder nomes de arquivo
Salve este script completo como gif-previews.sh. Ele processa recursivamente arquivos regulares
terminados em .mp4, sem diferenciar maiúsculas de minúsculas e sem seguir entradas de links
simbólicos. Ele acrescenta .gif ao nome de arquivo relativo completo: videos/team/demo.mp4 se torna
gifs/team/demo.mp4.gif. Manter a extensão também diferencia demo.mp4 de demo.MP4 no
sistema de arquivos suportado.
#!/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
Execute-o a partir do diretório que contém sua pasta videos:
bash gif-previews.sh ./videos ./gifs
O script preserva espaços, quebras de linha, hifens iniciais e sinais de porcentagem literais nos
nomes de arquivo. A descoberta delimitada por null mantém cada nome de arquivo intacto, e o FFmpeg
não consegue consumir a entrada do loop por causa de -nostdin. Tanto a descoberta quanto a
conversão são executadas a partir do mesmo diretório de entrada resolvido fisicamente, inclusive
quando o argumento de diretório contém um link simbólico seguido de ...
Destinos existentes contam como falhas; eles nunca são ignorados nem substituídos silenciosamente.
A conversão usa -y apenas para o arquivo temporário privado. O GNU ln -T publica um
GIF concluído sem substituir um destino que apareça durante a conversão. O diretório temporário
fica no sistema de arquivos de saída para que os hard links possam funcionar. Use uma árvore de
saída que você controle, sem subdiretórios com links simbólicos nem montagens aninhadas.
Um lote misto mantém os GIFs gerados com sucesso e continua após falhas em arquivos individuais;
depois, encerra com status de saída 1. Um diretório de entrada vazio também encerra com status de
saída 1. Uma nova execução na mesma árvore de saída reporta os arquivos existentes como falhas;
escolha um novo diretório de saída para gerar o lote inteiro novamente. Falhas comuns e
interrupções limpam os arquivos temporários, mas um encerramento forçado ou uma queda de energia
pode deixar um diretório oculto .gif-preview.*.
Ajuste o orçamento de tamanho e qualidade
Comece com as configurações de três segundos, 320 pixels e 10 fps e inspecione clipes
representativos. Dimensões menores removem detalhes; uma taxa de quadros menor deixa o movimento
menos suave. Encurtar o clipe costuma economizar mais do que ajustar as cores. Se você alterar a
taxa ou a duração, atualize juntos os dois valores de -t e o limite -frames:v; para
dois segundos a 8 fps, use um limite de 16.
O dithering padrão paletteuse ajuda em gradientes, mas pode adicionar ruído visível. Vale a pena
comparar a configuração documentada dither=bayer com suas próprias filmagens. Esses controles trocam
detalhe, movimento e precisão de cor por tamanho; nenhum deles promete que o GIF vai superar um
codec de vídeo.
Diagnostique um lote com falhas
- Saída existente: escolha um diretório de saída novo ou remova deliberadamente apenas as prévias que você quer gerar novamente. Alterar as configurações de qualidade não sobrescreve resultados antigos.
- Vídeo inválido ou sem fluxo de vídeo: inspecione a origem indicada com
ffprobe. Renomear um arquivo corrompido para.mp4não o repara. O script verifica o segmento da prévia, não a integridade do vídeo inteiro. - Nenhum quadro ou uma abertura pouco útil: entradas muito curtas ou incomuns podem não gerar prévia; o script trata saída vazia como falha. Uma abertura preta exige um segmento diferente.
- Erros de sistema de arquivos ou de memória: verifique o espaço livre, as permissões de saída e o suporte a hard links. Reduza a duração do segmento e as dimensões para origens grandes. Um limite de três segundos no clipe não é um limite rígido para a memória do decodificador nem para o tempo de execução.
Use os caminhos de arquivo exibidos para inspecionar as falhas e depois revise os GIFs bem-sucedidos antes de colocá-los no seu catálogo de mídia.
