Optimiza JPEG por lotes con jpegtran sin ocultar fallos
Usa jpegtran para cambiar la codificación de JPEG existentes sin otra pasada de compresión con pérdida.
Para procesar un lote local, guarda los resultados en un directorio nuevo y comprueba el estado de
salida de cada conversión. El script de Bash que aparece a continuación conserva los originales y
los metadatos, crea JPEG progresivos y devuelve un estado de fallo si falla alguna imagen seleccionada.
Elige qué conservar
jpegtran trabaja con coeficientes DCT comprimidos en lugar de decodificar los píxeles y volver a comprimirlos.
Esto conserva los datos de imagen del JPEG existente; no puede recuperar el detalle perdido al
codificar el JPEG por primera vez. La guía de uso de libjpeg-turbo
explica esta distinción.
Este flujo de trabajo usa -optimize para la codificación Huffman y -progressive para múltiples barridos de la imagen.
Un visor compatible puede mostrar una vista previa inicial antes de recibir el JPEG progresivo
completo. Ninguna de las opciones garantiza un archivo más pequeño, así que compara el número de
bytes antes de reemplazar los archivos.
La política de metadatos es -copy all: conservar los marcadores adicionales, incluidos EXIF y los perfiles ICC.
Esto es importante porque la orientación EXIF y los perfiles de color afectan la forma en que los
visores muestran una imagen. También conserva información potencialmente privada, como las
coordenadas GPS. Estos resultados están destinados a una revisión local, no a una subida pública
automática.
Usar -copy none descartaría esos marcadores y podría cambiar la orientación o el color mostrados, aunque se conserven los coeficientes.
-copy icc conserva el perfil de color, pero también elimina la orientación EXIF.
Elige un flujo de trabajo aparte para eliminar metadatos si lo necesitas; este procesamiento por
lotes no rota los píxeles ni normaliza la orientación. Consulta las
opciones de copia de marcadores.
Comprueba las herramientas
Usa Bash y jpegtran de libjpeg-turbo en Linux, junto con las utilidades habituales mkdir, mv, rm y wc.
El ejemplo se probó con Bash 5.3.15 y libjpeg-turbo 3.2.0. Instala las herramientas de línea de comandos
de libjpeg-turbo mediante tu distribución o los
paquetes de versiones del proyecto y comprueba qué implementación tienes:
bash --version
jpegtran -version
El segundo comando debería identificar libjpeg-turbo. Otros proyectos también distribuyen un
programa llamado jpegtran; sus opciones y su salida pueden ser diferentes.
Aquí se admiten como entrada JPEG comunes de 8 bits basados en DCT, tanto secuenciales como progresivos.
Procesa un lote en un directorio nuevo
Guarda este script como optimize_images.sh en el directorio que contiene tus JPEG.
Ejecútalo con Bash, no con sh. Selecciona archivos regulares que terminan en .jpg o .jpeg,
incluidos los que tienen extensiones en mayúsculas y los archivos ocultos, sin entrar en
subdirectorios ni seguir enlaces simbólicos. Admite nombres de archivo con espacios, guiones
iniciales y caracteres % literales.
El directorio de salida no debe existir previamente. Cada resultado correcto conserva su nombre de
archivo original dentro de optimized/. Un archivo temporal contiene cada conversión hasta que jpegtran se completa correctamente,
de modo que una conversión fallida no se convierte en un resultado final. Ejecuta el script en un
directorio que controles, sin que otros procesos modifiquen sus archivos mientras se procesa el lote.
#!/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"
Desde ese mismo directorio, ejecuta:
bash optimize_images.sh
Un estado de salida de cero significa que todos los archivos seleccionados se convirtieron y se
movieron a su destino. Las imágenes no válidas y las advertencias de conversión provocan un fallo
debido a -strict. Los diagnósticos permanecen visibles, el procesamiento continúa con las imágenes restantes
y su estado final es distinto de cero. Los resultados correctos de un lote con fallos permanecen
disponibles para su inspección. Una selección vacía también devuelve un estado distinto de cero y no
crea ningún directorio de salida.
Una nueva ejecución rechaza cualquier directorio, archivo o enlace simbólico optimized existente y lo deja intacto.
Revisa los resultados anteriores y muévelos a otro lugar antes de volver a intentarlo. En una tarea
de compilación, usa la invocación anterior como un paso cuyo fallo detenga el despliegue; añadir
|| echo ... volvería a ocultar ese fallo. Este es un flujo de trabajo para archivos locales, no un entorno
aislado para subidas no confiables.
Mide el resultado
Para una entrada llamada photo.jpg, compara los dos archivos después de procesar correctamente un lote:
wc -c -- ./photo.jpg ./optimized/photo.jpg
Las dos primeras filas muestran el número de bytes del original y del resultado; la última fila muestra su suma. No hay un porcentaje de ahorro fijo. Un JPEG pequeño o un archivo ya optimizado puede aumentar de tamaño al codificarse de forma progresiva. Este script conserva ese resultado para su revisión en lugar de sustituirlo por el original sin avisar. Abre pares representativos en el visor que usa tu público y comprueba la orientación, el color y el tamaño. Que los datos de imagen decodificados coincidan no basta para comprobar cómo maneja los metadatos un visor.
Comprende los fallos y los límites de -perfect
Si un JPEG está dañado o truncado, inspecciona el diagnóstico y recupera la imagen de origen antes de volver a ejecutar el script. No descartes stderr ni aceptes un archivo parcial solo porque se hayan escrito datos. Si no se puede crear el directorio de salida, comprueba si ya existe y si tienes permiso de escritura en su directorio superior.
-perfect es una comprobación para transformaciones geométricas, no un ajuste adicional de calidad ni una comprobación de integridad de archivos.
No es necesario para la optimización de entropía ni para la conversión progresiva de este lote.
Al rotar o voltear, rechaza las transformaciones cuyos bloques de borde no se pueden transformar por
completo. Con el muestreo habitual 4:2:0, la iMCU mide 16 × 16 píxeles: una rotación de 90 grados en
sentido horario requiere que la altura sea divisible por 16; una rotación de 270 grados lo requiere
para la anchura, y una de 180 grados, para ambas dimensiones. Otros factores de muestreo cambian esos
límites. Estas condiciones se indican explícitamente en el
código de transformación de libjpeg-turbo.
-trim descarta los píxeles de los bordes que no se pueden transformar, por lo que implica una decisión de recorte.
No es una forma de conservar la imagen completa después de que -perfect rechace una rotación.
Rotar los píxeles también exige definir deliberadamente una política para la orientación EXIF y las
miniaturas; copiar los metadatos existentes no corrige automáticamente la orientación.
Decide cuándo conviene recodificar
Si el ahorro medido es demasiado pequeño, cambiar las dimensiones, reducir la calidad JPEG o
convertir a WebP o AVIF es una tarea diferente. Esas opciones requieren decodificar y codificar con
una herramienta de imágenes adecuada y evaluar la apariencia resultante. Usa este procesamiento
por lotes con jpegtran cuando sea obligatorio conservar los datos de imagen del JPEG existente;
usa un flujo de recodificación aparte cuando puedas aceptar cambios en ellos.
