ImageMagick est la solution de manipulation d’images la plus souple et versatile à disposition. Après pas mal de tests, Max en avait conclu qu’appeler la lib avec un bon subprocess était simplement plus rapide et donnait des résultats de meilleure qualité que d’utiliser PIL en Python ou GD en PHP.
Et je peux vous dire qu’il en a fait des tests pour obtenir le thumbnail des nichons parfait. C’est un perfectionniste quand il s’agit des tétons.
En général, on trouve ImageMagick dans les dépôts. Par exemple, sous debian :
sudo apt-get install imagemagick |
Mais il n’est pas rare que Max compile la bête histoire d’avoir la même version partout :
wget http://mirror.checkdomain.de/imagemagick/ImageMagick-6.8.7-4.tar.gz ; tar -xvf ImageMagick-6.8.7-4.tar.gz ; cd ImageMagick-6.8.7-4 ./configure make && make install ln -s /usr/local/bin/convert /usr/bin/convert ln -s /usr/local/bin/identify /usr/bin/identify |
Après tout ça, vous aurez accès à plusieurs commandes, qui permettent des manipulations d’images diverses.
Par exemple, identify
permet de récupérer des informations sur l’image telles que le format, la profondeur des couleurs… Nous on l’utilise surtout pour récupérer la taille :
identify -format "%[fx:w],%[fx:h]" "chemin/vers/image" |
On peut utiliser un wrapper tel que wand pour utiliser ça depuis Python. Mais il est très facile de se faire un wrapper vite fait à la main :
import envoy from minibelt import normalize class ImageMagicError(Exception): def __init__(self, result, encoding): msg = normalize(result.std_err.decode(encoding)) super(ImageMagicError, self).__init__(msg) def size(img, encoding=sys.stdout.encoding): r = envoy.run('identify -format "%[fx:w],%[fx:h]" "{}"'.format(img)) if r.status_code != 0: raise ImageMagicError(r, encoding) w, h = r.std_out.strip().split(',') return int(w), int(h) |
Notez que j’utilise minibelt et envoy (un wrapper pour subprocess), car je suis une grosse feignasse.
Pour faire un thumbnail, la recette est plus compliquée, car ImageMagick est à l’image, ce que Perl est à la programmation. There’s_more_than_one_way_to_do_it ©.
Au final, celui qui nous a donné le meilleur résultat en qualité est cette suite d’arcanes secrètes transmise d’oreille de druide à oreille de druide :
convert chemin/image/originale -thumbnail largeurxhauteur^ -gravity center -extent largeurxhauteur -quality 80 chemin/vers/resultat.jpg |
Ceci va redimensionner l’image en conservant le ratio, et si le résultat n’est pas adaptée aux dimensions finales, va en découper une partie tout en préservant le centre de l’image. La sortie sera un jpg avec un taux de qualité de 80%.
Là encore, un petit wrapper ne fait pas de mal :
def thumb(img, width, heigth, output, crop=False, encoding=sys.stdout.encoding): if crop: if crop is True: crop = 'center' crop = "-gravity {} -extent {}x{}".format(crop, width, heigth) else: crop = '' cmd = ("convert {img} -thumbnail {width}x{heigth}^ {crop} -quality 80 {output}").format( img=img, width=width, heigth=heigth, crop=crop, output=output, ) r = envoy.run(cmd) if r.status_code != 0: raise ImageMagicError(r, encoding) return r |
Bref, ce genre de petit bricolage est loin d’être propre, mais il s’est montré fort robuste et efficace au fil des années. Quand on fait des dizaines de milliers de screenshots de films de cul tous les jours, c’est important :-)
Et pour faire des captures d’images à intervalle régulier dans une vidéo, vous avez aussi une recette de druide?
Je laisse Max répondre à celle là, c’est lui qui a la serpe.
La référence à perl c’est pas plutôt:
There’s_more_than_one_way_to_do_it
Sinon merci pour l’article, direct dans mes bookmarks
@ Ludovic Gasc : je suppose que tu veux des gifs :
mplayer video.mp4 -vf scale=largeur:hauteur -vo gif89a:fps=nombreentier:output=sortie.gif
Après il y a -ss et -endpos pour définir le début et le temps à attendre avant de couper.
@Oyo: lol, je code tellement souvent en Python que j’ai mis le moto de Python à la place de celui de perl.
Wow. J’ai tiqué sur le
super(ImageMagicError, self)
(j’ai pensé que ça ferait un stack overflow), ai lu la doc de super, et dormirais moins bête ce soir.C’est quelque chose que vous faites automatiquement, gérer le cas des héritages en diamant, ou c’est un cas à part, ici ?
Je rajoute envoy à la liste des libs géniales que je dois tester.
@baronsed: Non, c’est pour avoir des screenshots à intervalle régulier de la vidéo au format jpg, comme on peut voir sur les sites Web de vidéo.
+1 avec Ludovic
Je vois bien comment on peut faire ça avec ffmpeg, mais je me dit qu’il y a peut être des solutions plus “propres”.
@groug : c’est un réflexe.
@Ludovic : on utilise ffmpeg aussi. C’est rapide, multithreadable…
j’utilise mediainfo ou ffprobes pour avoir le temps de la video que je divise par le nb de screenshots voulu, ensuite j’extrait l’image avec l’option -ss dans ffmpeg, pas trouvé plus simple pour le moment :(
on peut aussi le faire avec le nombre de frames et l’option -v de ffmpeg si ma mémoire est bonne.
ce qui peut etre interessant pour les videos c’est detecter les changements de scene.
C’est ce que j’utilise aussi dans mes scripts. Mais est-ce que vous avez une solution portable (pour Windows) et qui marche en python 3 ?
Ha et pour faire des screenshots à intervalle régulier, regardez ffmpegthumbnailer.
Tous nos serveurs sont sous linux, donc on a jamais eu le use case.
Je profite de tomber sur un pro du python qui utilise envoy! ^^
Est-ce que tu connais un moyen d’éviter les process zombies avec envoy.connect?
En fait, le pb semble venir de subprocess.Popen…j’arrive à dégager le process zombie en faisant un p._process.wait() (p étant mon objet envoy.ConnectedCommand) ou un os.wait(), mais du coup, je ne récupère jamais le résultat de ma commande.
Est-ce que ça vous parle, Ô grands mages du python?
(désolé d’être HS, mais une occasion de parler envoy.connect, je pouvais pas rater ça!)
J’en connais pas. En même temps, si tu as un processus zombie, tu peux pas récupérer le résultat de ta commande de toutes façon…
C’est sur oui. Le but était plutôt d’éviter la zombification du process.
En utilisant le module ‘signal’, on peut intercepter la fin du process et éviter ça…mais pas directement avec ‘envoy’, qui semble être à l’abandon.
Ça me parait trop gros pour un langage si habituellement parfait qu’est python! On a le PID, je comprend pas qu’on puisse pas intercepter le signal de fin de process avec Popen alors qu’on l’a facilement via un wait()….je me demande si c’est pas un vieux bug qui a été résolu depuis ou si c’est lié à ma plateforme (je suis en 2.6 sous Solaris…et ouais, je suis au boulot!).
Je ferais d’autres test chez moi et si je trouve un moyen, je ferais tourner l’info!
C’est peut être possible, après tout, je ne connais pas l’API par coeur. Et c’est vrai que c’est suspect que ça le soit pas.
@Syl
t’as essayé Pexpect et la doc ici ?
Je ne connais pas envoy, j’utilise pexpect depuis quelques temps que je trouve pas mal.
@Max: Super! Je viens de tester ça! C’est plus riche que ‘envoy’. A première vue, j’avais le même problème qu’avec envoy, étant donné qu’en arrière plan, c’est toujours subprocess.Popen qui est utilisé, mais j’ai quand même fini par trouver une solution satisfaisante.
J’ai mis un timeout court à la lecture du retour de mon process et je boucle sur la fonction read() de pexpect:
J’en profite pour dire que les infos qui accompagnent l’exception TIMEOUT du module ‘pexpect’ sont super riches et permettent de voir si votre process est freezé en affichant les 100 dernier caractères du buffer de sortie.
Merci pour le tuyau Max!
@syl
Content que ça t’ai aidé :)
Est-il possible d’installer ImageMagick sur Android pour l’utiliser avec SL4A?
Je ne sais pas, mais puisqu’il y a un native toolkit sur android, je pense qu’un bon bidouilleur pourrait le faire.
à noter que les commandes appelant le shell ainsi sont dangereuse si le chemin du fichier est passé par un utilisateur non sur.