Compresser un Jpeg à une taille donnée

7 05 2009

S’il est possible d’indiquer un niveau de qualité lorsque l’on crée un Jpeg, il n’est pas possible d’indiquer la taille du fichier au final. Ce problème a été corrigé  avec le Jpeg2000 mais reste entier avec le Jpeg.

Je vous propose donc un script Python tournant sous Ubuntu ou autre Linux et qui remplit cette fonction.

Pré-requis

Le paquet libjpeg-progs doit être installé :

sudo apt-get install libjpeg-progs

Le script utilise les commandes cjpeg, djpeg et jpegtran.

Téléchargement

Télécharger jpegoptim

Installation

Copiez le script dans un fichier « jpegoptim » placé dans le répertoire ~/bin avec les droits en exécution.

Utilisation

Le script s’utilise en ligne de commande. Il n’y a pas d’interface graphique.

La forme de la commande est la suivante :

jpegoptim <taille en Kio> <fichier1> [<fichier2> …]

Par exemple, pour forcer une taille de 3 Kio au fichier test.jpg, on tapera :

jpegoptim 3 test.jpg

Note 1 : Le script privilégie la qualité de l’image. Toute donnée superflue sera perdue. Cela veut dire que toutes les données Exif  et les commentaires sont supprimés.

Note 2 : Le script écrase les fichiers donnés en paramètres.

Note 3 : Il faut rester réaliste quant à la taille exigée ! Le script fait tout son possible pour coller au plus  près de la valeur fournie mais il ne pourra pas descendre en dessous de la qualité 0 ou monter au dessus de la qualité 100. Ainsi, une taille trop basse ou trop haute ne pourra jamais être obtenue.

Dans le cas d’une taille trop basse, vous pourrez voir apparaître une ou plusieurs fois la ligne suivante :

Caution: quantization tables are too coarse for baseline JPEG

Explications

Bien sûr, il n’y a pas de miracle : il n’existe pas de fonctionnalité cachée permettant de spécifier la taille finale. Le script présenté ici effectue ce que tout un chacun ferait dans pareille situation : on tatonne en essayant plusieurs valeurs de qualité jusqu’à tomber sur la bonne.

Là où ce script se montre plus rusé que l’utilisateur lambda, c’est qu’il va procéder par dichotomie et ainsi limiter le nombre d’essais au strict nécessaire.

De plus, les paramètres donnés aux utilitaires cjpeg, djpeg et jpegtran génère le Jpeg le plus petit pour un niveau de qualité donné.

Le script

Le script est articulé autour de 4 fonctions :

  • shell_command : la commande à exécuter
  • compute : exécute la commande et retourne une valeur indiquant le résultat
  • generate : exécute la commande
  • find_param : fonction dichotomique générique

shell_command

La fonction shell_command exécute la ligne de commande suivante :

djpeg -pnm -dct float fichier.jpg | \
cjpeg -quality 999 -dct float -restart 0 -sample 1x1 | \
jpegtran -trim -optimize -restart 0

Arguments :

  • -pnm : djpeg décompresse le Jpeg dans un fichier au format PNM.
  • -dct float : djpeg utilise les nombres à virgules flottantes pour travailler, c’est la méthode la plus précise, celle qui donne la meilleure qualité, au détriment du temps de calcul le plus grand (mais aujourd’hui, faut pas exagérer, on a des bonnes machines).
  • -quality 999 : Niveau de qualité demandé.
  • -restart 0 : Supprime les marqueurs de redémarrage du Jpeg. En les utilisant, une erreur dans le Jpeg ne perdra les données que jusqu’au prochain marqueur de redémarrage. En les supprimant, on gagne encore un peu plus sur le poids final.
  • -sample 1×1 : Opte pour la meilleure qualité au niveau des couleurs.
  • -trim : Autorise certaines pertes de la part de jpegtran si besoin.
  • -optimize : Effectue une optimisation du Jpeg.

Le résultat de la commande est renvoyée par la fonction.

compute

Retourne la longueur d’un fichier traité par la fonction shell_command. L’utilité de cette fonction est uniquement d’être appelée par la fonction find_param.

C’est cette fonction qui sera appelée plusieurs fois jusqu’à obtenir la taille désirée.

generate

Prend un fichier en entrée et applique la fonction shell_command. Cette fonction est appelée une fois le niveau de qualité trouvé pour générer le fichier final.

find_param

La fonction find_param implémente une recherche dichotomique générique. Elle n’est pas spécifique au script jpegoptim.

Elle prend comme paramètres une valeur minimum, une valeur maximum, le résultat désiré, la fonction à appeler pour le traitement et ses paramètres fixes.

Pour plus d’infos, la page Dichotomie de Wikipedia est très bien réalisée.

Code source

#!/usr/bin/env python
# coding=utf8
import sys
from subprocess import Popen,PIPE

# Fonction recompressant un JPEG avec une qualité donnée
def shell_command(quality,filename):
  p1=Popen(["djpeg","-pnm","-dct","float",filename],stdout=PIPE)
  p2=Popen(["cjpeg","-quality",str(quality),"-dct","float","-restart",
            "0","-sample","1x1"],stdin=p1.stdout,stdout=PIPE)
  p3=Popen(["jpegtran","-trim","-optimize","-restart","0"],
           stdin=p2.stdout,stdout=PIPE)

  return p3.communicate()[0]

# Fonction de calcul de taille de fichier, utilisée par find_param
def compute(quality,filename):
  return len(shell_command(quality,filename))

# Prend un fichier en entrée et applique la fonction shell_command
def generate(quality,filename_in,filename_out):
  output=shell_command(quality,filename_in)

  f=open(filename_out,"w")
  f.write(output)
  f.close()

# Fonction générique de recherche dichotomique
def find_param(minimum,maximum,search_result,function,func_parms):
  index      =(minimum+maximum)/2
  best_index =index
  best_result=function(best_index,func_parms)
  result     =best_result

  while index not in (minimum,maximum) and best_result!=search_result:
    result=function(index,func_parms)

    if result<search_result: minimum=index
    else:                    maximum=index

    if abs(search_result-best_result)>abs(search_result-result):
      best_result=result
      best_index =index

    index=(minimum+maximum)/2

  if abs(search_result-best_result)>abs(search_result-result):
    best_result=result
    best_index =index

  return best_index

# Passe en revue tous les arguments sauf le premier qui est la taille maxi
for arg in sys.argv[2:]:
  quality=find_param(0,100,int(sys.argv[1])*1024,compute,arg)
  generate(quality,arg,arg)

Actions

Information

5 responses

8 09 2010
Se simplifier la compression d’images ! | Dnartreb89

[…] soient en PNG ou en JPG. Je me suis donc construit mon propre script en m’appuyant sur le script de Zigazou pour obtenir une compression en taille fixe pour le JPG, et sur ce tutoriel de sebsauvage pour […]

23 09 2011
Compresser un Jpeg à une taille donnée… en préservant les métadonnées « SILOPOLIS Blog

[…] Je vous livre donc ma version adaptée du script Python de Zigazou: […]

23 09 2011
silopolis

Bonjour et tout d’abord merci d’avoir partagé ce script et pour le temps qu’il m’a fait gagné !
Devant pour ma part conserver les métadonnées j’en suis arrivé à une version très légèrement adaptée utilisant ImageMagick que vous trouverez ici:

http://silopolis.wordpress.com/2011/09/23/compresser-un-jpeg-a-une-taille-donnee-en-preservant-les-metadonnees/

Merci encore

23 09 2011
zigazou

Content que ça ait rendu service !

C’est vrai que ma priorité était sur la taille du fichier et que les métadonnées sont passées à la trappe du coup😉

@+

24 09 2011
silopolis

Et un fier service même !
Entre temps, comme ça m’énervait un « petit peu », j’ai trouvé au moins une partie de la solution à mon problème avec exiftool. Je referai un post ou complèterai le premier avec cette solution… à suivre.

Merci encore

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s




%d blogueurs aiment cette page :