Otimize JPEGs em lote com jpegtran sem esconder falhas
Use jpegtran para mudar a forma como JPEGs existentes são codificados sem outra
passagem de compressão com perdas. Para um lote local, grave em um novo diretório e verifique o
status de saída de cada conversão. O script Bash abaixo mantém seus originais e metadados, cria
JPEGs progressivos e retorna um status de falha se alguma imagem selecionada falhar.
Escolha o que preservar
jpegtran trabalha com os coeficientes DCT comprimidos em vez de decodificar
pixels e recomprimi-los. Isso preserva os dados de imagem do JPEG existente; não é possível restaurar
detalhes perdidos quando o JPEG foi codificado pela primeira vez. O
guia de uso do libjpeg-turbo explica essa distinção.
Este fluxo de trabalho usa -optimize para a codificação de Huffman e
-progressive para múltiplas varreduras da imagem. Um visualizador compatível pode
exibir uma prévia inicial antes de receber o JPEG progressivo completo. Nenhuma das flags garante um
arquivo menor, então compare as contagens de bytes antes de substituir os assets.
A política de metadados é -copy all: manter marcadores extras, incluindo EXIF e
perfis ICC. Isso importa porque a orientação EXIF e os perfis de cor afetam como os visualizadores
exibem uma imagem. Essa política também mantém informações potencialmente privadas, como coordenadas
de GPS. Estas saídas são para revisão local, não para upload público automático.
Usar -copy none descartaria esses marcadores, podendo alterar a orientação ou a
cor exibidas mesmo que os coeficientes sejam preservados. -copy icc mantém o
perfil de cor, mas ainda descarta a orientação EXIF. Escolha um fluxo de trabalho separado de remoção
de metadados se precisar de um; este lote não rotaciona pixels nem normaliza a orientação. Veja as
opções de cópia de marcadores.
Verifique as ferramentas
Use Bash e o jpegtran do libjpeg-turbo no Linux, com os utilitários habituais
mkdir, mv, rm e wc.
O exemplo foi testado com Bash 5.3.15 e libjpeg-turbo 3.2.0. Instale as ferramentas de linha de
comando do libjpeg-turbo pela sua distribuição ou pelos
pacotes de lançamento do projeto e, em seguida, verifique qual
implementação você tem:
bash --version
jpegtran -version
O segundo comando deve identificar o libjpeg-turbo. Outros projetos também distribuem um programa
chamado jpegtran; as opções e a saída deles podem ser diferentes. O escopo de
entrada aqui são JPEGs comuns de 8 bits baseados em DCT, incluindo arquivos sequenciais e progressivos.
Execute um lote em um novo diretório
Salve isto como optimize_images.sh no diretório que contém seus JPEGs. Execute-o com
Bash, não com sh. Ele seleciona arquivos regulares terminados em
.jpg ou .jpeg, incluindo extensões em maiúsculas e
arquivos ocultos, sem descer em subdiretórios nem seguir entradas de links simbólicos. Nomes de
arquivo com espaços, hífens iniciais e caracteres % literais são
suportados.
O diretório de saída não pode já existir. Cada saída bem-sucedida mantém seu nome de arquivo original
em optimized/. Um arquivo temporário guarda cada conversão até que
jpegtran seja bem-sucedido, então uma conversão com falha não se torna uma saída
finalizada. Execute isto em um diretório que você controla, sem outros processos alterando os
arquivos dele enquanto o lote é executado.
#!/usr/bin/env bash
set -u
if (( $# != 0 )); then
printf 'Run this script without arguments in your JPEG directory.\n' >&2
exit 2
fi
if ! command -v jpegtran >/dev/null; then
printf 'jpegtran is not installed or is missing from PATH.\n' >&2
exit 2
fi
shopt -s nullglob nocaseglob dotglob
images=()
for img in ./*.jpg ./*.jpeg; do
if [[ -f "$img" && ! -L "$img" ]]; then
images+=("$img")
fi
done
if (( ${#images[@]} == 0 )); then
printf 'No JPEG files selected.\n' >&2
exit 1
fi
if ! mkdir -- ./optimized; then
printf 'Cannot create optimized; move any previous results aside before rerunning.\n' >&2
exit 1
fi
temporary=./optimized/.jpegtran.tmp
trap 'rm -f -- "$temporary"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
status=0
for img in "${images[@]}"; do
output="./optimized/${img#./}"
if jpegtran -strict -copy all -optimize -progressive -outfile "$temporary" "$img" &&
mv -- "$temporary" "$output"; then
printf 'Wrote %q\n' "$output"
else
printf 'Failed: %q\n' "$img" >&2
status=1
fi
done
exit "$status"
A partir desse mesmo diretório, execute:
bash optimize_images.sh
Um status de saída zero significa que todos os arquivos selecionados foram convertidos e movidos para
o destino. Imagens inválidas e avisos de conversão falham por causa de -strict.
Os diagnósticos permanecem visíveis, o lote continua com as imagens restantes e seu status final é
diferente de zero. As saídas bem-sucedidas de um lote misto continuam disponíveis para inspeção. Uma
seleção vazia também retorna um status diferente de zero e não cria nenhum diretório de saída.
Uma nova execução recusa um diretório, arquivo ou link simbólico optimized
existente e o deixa intacto. Revise os resultados anteriores e mova-os você mesmo para outro lugar
antes de tentar novamente. Em uma tarefa de build, use a invocação acima como uma etapa cuja falha
interrompe o deploy; adicionar || echo ... esconderia essa falha novamente.
Este é um fluxo de trabalho local de assets, não uma sandbox para uploads não confiáveis.
Meça o resultado
Para uma entrada chamada photo.jpg, compare os dois arquivos após um lote
bem-sucedido:
wc -c -- ./photo.jpg ./optimized/photo.jpg
As duas primeiras linhas são as contagens de bytes do original e da saída; a última linha é a soma delas. Não há uma porcentagem fixa de economia. Um JPEG pequeno ou um arquivo já otimizado pode crescer quando codificado de forma progressiva. Este script mantém esse resultado para revisão em vez de substituí-lo silenciosamente pelo original. Abra pares representativos no visualizador que seu público usa, verificando orientação e cor, além do tamanho. Dados de imagem decodificados idênticos não testam, por si só, o tratamento de metadados de um visualizador.
Entenda as falhas e os limites de -perfect
Para um JPEG corrompido ou truncado, inspecione o diagnóstico e recupere a imagem de origem antes de executar novamente. Não descarte o stderr nem aceite um arquivo parcial só porque algo foi gravado. Se o diretório de saída não puder ser criado, verifique se ele já existe e se você tem permissão de escrita no diretório pai.
-perfect é uma verificação para transformações geométricas, não uma
configuração extra de qualidade nem uma verificação de integridade de arquivo. Ele é desnecessário
para a otimização de entropia e a conversão progressiva do lote. Para rotação ou espelhamento, ele
rejeita transformações cujos blocos de borda não podem ser transformados por completo. Com a
amostragem comum 4:2:0, o iMCU tem 16 × 16 pixels: uma rotação de 90 graus no sentido horário exige
que a altura seja divisível por 16, uma rotação de 270 graus exige isso da largura e uma rotação de
180 graus exige ambas. Outros fatores de amostragem mudam esses limites. Essas condições estão
explícitas no código de transformação do libjpeg-turbo.
-trim descarta pixels de borda não transformáveis, então é uma decisão de
corte. Não é uma forma de preservar a imagem completa depois que -perfect recusa
uma rotação. Rotacionar pixels também exige uma política deliberada para a orientação EXIF e as
miniaturas; copiar os metadados existentes não é uma correção automática de orientação.
Decida quando recodificar em vez disso
Se a economia medida for pequena demais, alterar dimensões, reduzir a qualidade do JPEG ou converter
para WebP ou AVIF é uma tarefa diferente. Essas escolhas exigem decodificar e codificar com uma
ferramenta de imagem adequada e avaliar a aparência resultante. Use este lote com
jpegtran quando manter os dados de imagem do JPEG existente for a restrição; use
um fluxo de trabalho separado de recodificação quando você puder aceitar alterações nesses dados.
