Table des matières
Introduction
Cet article présente un outil destiné à traiter en batch des images JPEG (ou JPG), pour :
- Modifier leur nom, par exemple le rendre égal à la date de prise de vue s'il s'agit de photos prises par APN (appareil photo numérique),
- Modifier leur date, par exemple la rendre aussi égale à la date de prise de vue,
- Modifier l'orientation de celles qui ne sont pas dans le bon sens,
- Incruster automatiquement un texte dans l'image (date, heure, nom du fichier, commentaire).
Il est utile notamment pour les images JPEG provenant d'APN, sous réserve qu'elles contiennent les infos EXIF (date de prise de vue, orientation), et aussi pour toutes images dont on aura modifié manuellement les noms et/ou les commentaires qu'on pourra alors incruster.
Remarque : pour les pressés qui n'ont vraiment pas le temps de lire les détails ni copier/coller les lignes de code vers des fichiers, essayez de télécharger et installer le contenu de :modif_images_1.3.tar.gz. D'ailleurs ceux qui sont moins pressés ont aussi le droit d'utiliser ce fichier …
Exemple de photos provenant d'un APN, avant traitement :
On voit ici 3 photos qui viennent juste d'être téléchargées d'un APN au moyen de flphoto2. On constate que les noms des images ne sont pas du tout intéressants, que les dates sont les mêmes (date du téléchargement) et qu'une des photos a été prise en mode portrait mais apparait couchée.
Quand on possède plusieurs centaines, voire plusieurs milliers de photos présentant ces inconvénients ça finit par représenter un vrai problème. D'où l'idée de cet outil qui permet de modifier l'ensemble des images de toute une arborescence de dossiers en un seul appel.
Article rédigé par Jeanmm
Version initiale : 12/6/2006.
Version 0.2 le 24/8/2006 : ajoût d'une option pour incruster l'heure dans les images.
Version 0.3 le 01/9/2006 : ajoût d'options pour incruster le nom du fichier et un commentaire dans les images, et choisir la taille des caractères incrustés; modification de l'IHM pour rendre toutes les options configurables par ajoût de boutons.
Version 1.0 et suivantes depuis le 03/09/2006 : installation simplifiée par mise à disposition d'une archive contenant tous les fichiers + des explications + un script d'installation; voir la liste des évolutions dans l'explication fournie.
Dernière version : 1.3 le 01/10/2006.
Solution proposée
On va donc proposer ici un jeu de scripts bash faisant appel au logiciel de traitement d'images ImageMagick et à l'outil jhead, ces scripts pouvant être appelés de manière manuelle, ou encapsulés dans un script Python pour réaliser une petite application avec IHM graphique. Exemple d'appel de l'application graphique :
Voici un exemple de résultat pour l'image redressée et une des images en mode paysage (modification des dates, des noms, redressement de l'image couchée, et incrustation de la date de prise de vue directement dans chaque image) :
On voit au-dessus de l'image redressée les noms et les dates modifiés : les noms indiquent directement la date et heure de prise de vue à la seconde près ! La date est visible en bas à gauche de l'image, en double couleur (blanc et noir décalés), donc lisible sur quasiment n'importe quel fond d'image. La taille des caractères incrustés dans les images est proportionnelle à la hauteur effective de chaque image selon le ratio souhaité, ce qui renforce encore leur lisibilité.
Autre exemple, avec de plus incrustation de l'heure de prise de vue et d'un commentaire personnel. Pour le commentaire, rien de plus simple, par exemple sous konqueror on fait un clic droit sur l'image, puis Propriétés / Méta-informations, et on tombe directement dans le champ où on peut taper le texte qu'on veut :
Le résultat est sympa, avec ici une taille de caractères incrustés plus importante que sur les images précédentes et leur ombre noire doublée :
Conseils :
- Il est vivement conseillé de commencer par lire et appliquer l'article Scripts de sauvegarde de données , car il contient des explications pour installer quelques logiciels complémentaires nécessaires à cet outil, ainsi que de nombreux conseils qui sont tout-à-fait valables ici; d'ailleurs on remarquera tout de suite les similitudes entre les deux outils, et on verra rapidement que cet outil est nettement plus compliqué que le premier, donc autant se “faire la main” par ordre croissant de difficultés. Ceci étant dit, depuis que l'archive.tar.gz est disponible l'installation est réellement devenue simple.
- Régler la date de son appareil avant de prendre des photos !
- Lors de la mise au point de ses images, les garder dans 2 dossiers séparés si on souhaite réaliser des incrustations, car elles sont irréversibles. Donc dans le 1er dossier on appliquera par exemple les modifs de date du fichier, nom du fichier, rotation et ajoût de commentaires. Dans le second on fera tous les essais d'incrustations complémentaires jusqu'à l'obtention du bon résultat; les ajustements porteront en général sur l'orthographe, la longueur et le contenu du texte et/ou la hauteur des caractères.
- Avant de traiter plusieurs centaines de photos, on commencera par un échantillon représentatif, en combinant par exemple mode portrait et mode paysage, texte court et texte long, format 3/4 standard et image très large ou très étroite…
Scripts Bash / Imagemagick / Jhead
Pour ImageMagick on trouvera toute info utile directement sur le site ImageMagick et bien sûr sur bien d'autres; le logiciel est probablement fourni dans votre distribution. Ce logiciel est composé d'un certain nombre d'outils d'analyse et de modification d'images, tous appelables en ligne de commande, donc faciles à insérer dans un script bash ou autre.
Nous allons nous servir d'une toute petite partie des immenses possibilités d'ImageMagick pour répondre à nos besoins, d'abord en créant des scripts bash séparés, chacun n'effectuant qu'un seul des traitements souhaités, puis en combinant les appels à ces scripts de manière plus souple à l'aide d'un script Python.
A noter aussi l'utilisation de jhead (“JPEG Header”) qui est un outil de listage des infos EXIF contenues dans les images. Pour lire ces infos EXIF on peut utiliser indifféremment ImageMagick ou Jhead, le second ayant l'avantage de faire ce travail plus rapidement. Si l'outil jhead n'existe pas sur le PC, on peut vérifier d'abord si la distribution le fournit, sinon aller voir ici : Projet Jhead . A noter une fonctionnalité de modification (pas encore) offerte ici : jhead permet de supprimer les images miniatures parfois contenues dans les entêtes EXIF, ce qui peut représenter un gain de place.
Les manipulations qu'on va détailler dans cet article sont celles décrites dans la fenêtre de l'outil présenté ci-dessus. L'utilisateur de ces outils a tout loisir pour rallonger les possibilités de traitements; on pourra par exemple imaginer la création de bordures ou cadres décoratifs, des effets de filtrages, la suppression des miniatures des entêtes EXIF, etc… A partir du moment où une commande de traitement par ImageMagick (ou un autre outil) est bien maîtrisée, il devient facile d'étendre ces scripts modulaires.
Script Bash principal
On a choisi d'encapsuler la plupart des scripts de détail dans un script bash principal. Copier les lignes ci-dessous dans un éditeur de texte et l'enregistrer avec pour nom jpeg-transfos dans un dossier accessible de tous, par exemple /usr/local/bin et ne pas oublier de le rendre exécutable.
La première action de ce script consiste à appeler le script “jpeg-comptage” présenté plus bas. Ce script va comme son nom l'indique compter les images à traiter, mais aussi appeler le script “jpeg-suppr-blancs” qu'on présentera aussi. La raison de ce dernier script est de supprimer tous les blancs (et aussi apostrophes) dans les noms de fichiers; ce contrôle est important car cela évitera que les autres scripts plantent par la suite. De manière générale je conseille de ne jamais utiliser de blancs, accents et autres caractères spéciaux dans les noms de fichiers, à la longue on s'épargne nombre de galères !
#!/bin/sh # # Modifications diverses sur des images JPG. # ------------------------------------------ # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net echo "" echo "Modifications/tests d'images JPG" echo "--------------------------------" echo "" if [ $3$4$5$6$7$8$9 -eq 0 ] ; then echo "Entrez les arguments positionnels suivants :" echo " 1 : profondeur de parcours des sous-dossiers (1 par defaut)" echo " 2 : option de niveau de listage d'infos (0, 1 ou 2)" echo " Puis : options de traitement ( 0/1 si binaire) :" echo " 3 : 0/1 Date fichier = date de prise de vue" echo " 4 : 0/1 Rotation si necessaire" echo " 5 : Nom du fichier :" echo " 0 : 'inchange'" echo " 1 : 'JJMMAA-HHMMSS.jpg'" echo " 2 : 'AAMMJJ-HHMMSS.jpg'" echo " 3 : 'JJMMAA-nom-ancien.jpg'" echo " 4 : 'AAMMJJ-nom-ancien.jpg'" echo " 6 a 10 : Incrustations :" echo " 6 : 0/1 'JJ/MM/AA'" echo " 7 : 0/1 'HHhMM'" echo " 8 : 0/1 'Nom'" echo " 9 : 0/1 'Commentaire'" echo " 10: Rapport hauteur d'image / hauteur caracteres, 30 par defaut" exit 0 fi echo "Dossier : $PWD" # Si un 1er argument est present c'est la profondeur du find # ---------------------------------------------------------- if [ 0$1 -ne 0 ] ; then max=$1 else max=1 fi echo "Profondeur maximale de parcours des dossiers : $max" # 2eme argument : liste d'infos # ----------------------------- bavard=$2 # Les arguments suivants sont les options de traitement # ----------------------------------------------------- if [ "0$3" == "0" ] ; then echo "Il faut au moins 1 option de traitement en argument 3" exit 1 fi # Comptage ==> supprime au passage les blancs et apostrophes dans les noms # Puis renomme les fichiers .JPG , .JPEG et .jpeg en .jpg # ---------------------------------------------------------------------------- jpeg-comptage $max if [ $? -ne 0 ] ; then exit 1 fi for a in `find . -maxdepth $max -type f -name "*.JPG"` ; do b=$(echo "$a" | sed -e 's/JPG$/jpg/') if [ ! -e "$b" ] ; then echo "mv $a $b" mv "$a" "$b" fi done for a in `find . -maxdepth $max -type f -name "*.JPEG"` ; do b=$(echo "$a" | sed -e 's/JPEG$/jpg/') if [ ! -e "$b" ] ; then echo "mv $a $b" mv "$a" "$b" fi done for a in `find . -maxdepth $max -type f -iname "*.jpeg"` ; do b=$(echo "$a" | sed -e 's/jpeg$/jpg/') if [ ! -e "$b" ] ; then echo "mv $a $b" mv "$a" "$b" fi done # Traitement des options choisies # ------------------------------- if [ "0$3" -eq "01" ] ; then jpeg-date $max $bavard fi if [ "0$4" -eq "01" ] ; then jpeg-rotation $max $bavard fi if [ "0$5" -eq "01" ] ; then jpeg-nom-JJMMAA-HHMMSS $max $bavard elif [ "0$5" -eq "02" ] ; then jpeg-nom-AAMMJJ-HHMMSS $max $bavard elif [ "0$5" -eq "03" ] ; then jpeg-prefixe-nom-JJMMAA $max $bavard elif [ "0$5" -eq "04" ] ; then jpeg-prefixe-nom-AAMMJJ $max $bavard fi if [ "0$6" == "01" ] ; then zz="1" else zz="0" fi if [ "0$7" == "01" ] ; then zz="$zz 1" else zz="$zz 0" fi if [ "0$8" == "01" ] ; then zz="$zz 1" else zz="$zz 0" fi if [ "0$9" == "01" ] ; then zz="$zz 1" else zz="$zz 0" fi if [ ! "$zz" == "0 0 0 0" ] ; then shift if [ 0$9 -eq 0 ] ; then tc=30 else tc=$9 fi jpeg-incrustations $max $zz $tc $bavard fi
Script de modification des dates
Le script ci-dessous détermine la date de création et modifie la date de fichier de chaque image à partir de l'info “EXIF” correspondante contenue dans le fichier. On propose 2 possibilités de commandes, l'une étant mise en commentaire :
- par ImageMagick et sa commande identify -format “%[EXIF:DateTime]“,
- par jhead; en fait cette seconde méthode est ici plus rapide.
Pour trouver les images on utilise la commande find qui parcourt le 1er dossier spécifié, et éventuellement les sous-dossiers de niveau souhaité (paramètre maxdepth). Attention : la commande find ne marche pas si des noms de fichiers contiennent des blancs.
L'info de date extraite de chaque image est mise au format nécessaire pour la commande touch grâce à l'éditeur awk. Pour le détail se reporter à la doc de ces 2 outils. C'est la commande touch qui effectue la modification de date effective.
Copier les lignes ci-dessous dans un éditeur de texte et l'enregistrer avec pour nom jpeg-dates dans le même dossier accessible de tous, et le rendre exécutable.
#!/bin/sh # # Modif des dates d'images JPG # ---------------------------- # # Transformations telles que les dates des fichiers # soient celles des infos Exif Date/Time. # # les suffixes jpeg, JPEG et JPG sont transormes en jpg. # # Boucle sur les seules images jpg du dossier courant # ou plusieurs niveaux de sous-dossiers selon la variable # max passee optionnellement en 1er argument du script. # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # Si un 1er argument est present c'est la profondeur find # ------------------------------------------------------- if [ 0$1 -ne 0 ] ; then max=$1 else max=1 fi # Si un 2eme argument est present on liste des infos en + # ------------------------------------------------------- if [ 0$2 -ne 0 ] ; then bavard=$2 else bavard=0 fi # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth $max -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg a traiter ?" && exit 0 if [ $bavard -eq 1 ] ; then if [ $countT -lt 2 ] ; then echo "Modif des dates d'images : $countT image trouvee" else echo "Modif des dates d'images : $countT images trouvees" fi cc=0 fi # Traitement des dates # -------------------- for i in `find . -maxdepth $max -type f -iname "*.jpg" | sort` ; do # Lecture de la date par ImageMagick-identify ou par jhead #Madate=`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($1,3,2) substr($1,6,2) substr($1,9,2) substr($2,1,2) substr($2,4,2) "." substr($2,7,2)}'` Madate=`jhead "$i" | awk ' $1=="Date/Time" {print substr($3,1,4) substr($3,6,2) substr($3,9,2) substr($4,1,2) substr($4,4,2) "." substr($4,7,2) }' ` if test -n "$Madate"; then if [ "$Madate" != "." ] ; then # mise de la date exif touch -t $Madate "$i" || echo "$i : date invalide ?" if [ $bavard -eq 1 ] ; then ls -la "$i" cc=$[$cc + 1] fi fi fi done if [ $bavard -eq 1 ] ; then if [ $cc -lt 2 ] ; then echo "Modif des dates d'images : $cc date modifiee" else echo "Modif des dates d'images : $cc dates modifiees" fi fi
Scripts de changement des noms de fichiers
AAMMJJ-HHMMSS.jpg
Le script qu'on va appeler jpeg-nom-AAMMJJ-HHMMSS permet de convertir tous les noms de fichiers en AAMMJJ-HHMMSS.jpg. Cette forme fait que le tri par noms de fichiers sera égal au tri par dates.
#!/bin/sh # # Modif de noms d'images JPG # --------------------------- # # Transformations telles que les noms des fichiers # soient conformes aux infos Exif Date/Time : # # AAMMJJ-HHMMSS.jpg # # les suffixes jpeg, JPEG et JPG sont transormes en jpg. # # Boucle sur les seules images jpg du dossier courant # ou plusieurs niveaux de sous-dossiers selon la variable # max passee optionnellement en 1er argument du script. # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # Si un 1er argument est present c'est la profondeur find # ------------------------------------------------------- if [ 0$1 -ne 0 ] ; then max=$1 else max=1 fi # Si un 2eme argument est present on liste des infos en + # ------------------------------------------------------- if [ 0$2 -ne 0 ] ; then bavard=$2 else bavard=0 fi # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth $max -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg a traiter ?" && exit 0 if [ $bavard -eq 1 ] ; then if [ $countT -lt 2 ] ; then echo "Modif des noms d'images : $countT image trouvee" else echo "Modif des noms d'images : $countT images trouvees" fi cc=0 fi # Traitement des dates # -------------------- for i in `find . -maxdepth $max -type f -iname "*.jpg" | sort` ; do # Lecture de la date par ImageMagick-identify #Madate=`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($1,3,2) substr($1,6,2) substr($1,9,2) "-" substr($2,1,2) substr($2,4,2) substr($2,7,2)}'` Madate=`jhead "$i" | awk ' $1=="Date/Time" {print substr($3,3,2) substr($3,6,2) substr($3,9,2) "-" substr($4,1,2) substr($4,4,2) substr($4,7,2) }' ` if test -n "$Madate"; then if [ "$Madate" != "-" ] ; then if [ "./$Madate.jpg" != "$i" ] ; then # mise de la date exif mv "$i" "$Madate.jpg" if [ $bavard -eq 1 ] ; then echo "$i renommee en ./$Madate.jpg" cc=$[$cc + 1] fi fi fi fi done if [ $bavard -eq 1 ] ; then if [ $cc -lt 2 ] ; then echo "Modif des noms d'images : $cc nom modifie" else echo "Modif des noms d'images : $cc noms modifies" fi fi
JJMMAA-HHMMSS.jpg
Le script qu'on va appeler jpeg-nom-JJMMAA-HHMMSS permet de convertir tous les noms de fichiers en JJMMAA-HHMMSS.jpg. Cette forme crée des dates plus conformes aux habitudes françaises
#!/bin/sh # # Modif de noms d'images JPG # --------------------------- # # Transformations telles que les noms des fichiers # soient conformes aux infos Exif Date/Time : # # JJMMAA-HHMMSS.jpg # # les suffixes jpeg, JPEG et JPG sont transormes en jpg. # # Boucle sur les seules images jpg du dossier courant # ou plusieurs niveaux de sous-dossiers selon la variable # max passee optionnellement en 1er argument du script. # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # Si un 1er argument est present c'est la profondeur find # ------------------------------------------------------- if [ 0$1 -ne 0 ] ; then max=$1 else max=1 fi # Si un 2eme argument est present on liste des infos en + # ------------------------------------------------------- if [ 0$2 -ne 0 ] ; then bavard=$2 else bavard=0 fi # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth $max -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg a traiter ?" && exit 0 if [ $bavard -eq 1 ] ; then if [ $countT -lt 2 ] ; then echo "Modif des noms d'images : $countT image trouvee" else echo "Modif des noms d'images : $countT images trouvees" fi cc=0 fi # Traitement des dates # -------------------- for i in `find . -maxdepth $max -type f -iname "*.jpg" | sort` ; do # Lecture de la date par ImageMagick-identify #Madate=`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($1,9,2) substr($1,6,2) substr($1,3,2) "-" substr($2,1,2) substr($2,4,2) substr($2,7,2)}'` Madate=`jhead "$i" | awk ' $1=="Date/Time" {print substr($3,9,2) substr($3,6,2) substr($3,3,2) "-" substr($4,1,2) substr($4,4,2) substr($4,7,2) }' ` if test -n "$Madate"; then if [ "$Madate" != "-" ] ; then if [ "./$Madate.jpg" != "$i" ] ; then # mise de la date exif mv "$i" "$Madate.jpg" if [ $bavard -eq 1 ] ; then echo "$i renomme en ./$Madate.jpg" cc=$[$cc + 1] fi fi fi fi done if [ $bavard -eq 1 ] ; then if [ $cc -lt 2 ] ; then echo "Modif des noms d'images : $cc nom modifie" else echo "Modif des noms d'images : $cc noms modifies" fi fi
AAMMJJ-ancien-nom.jpg
Le script qu'on va appeler jpeg-prefixe-nom-AAMMJJ permet de convertir tous les noms de fichiers en ajoutant le préfixe “AAMMJJ-“ aux noms courants. Cette forme peut être utile si on n'a pas réellement envie de modifier les noms existants qui peuvent avoir une vraie signification, on se contente de leur préfixer la date.
#!/bin/sh # # Modif de noms d'images JPG # --------------------------- # # Transformations telles que les noms des fichiers # aient des prefixes conformes aux dates Exif : # # nom.jpg ==> AAMMJJ-nom.jpg # # les suffixes jpeg, JPEG et JPG sont transormes en jpg. # # Boucle sur les seules images jpg du dossier courant # ou plusieurs niveaux de sous-dossiers selon la variable # max passee optionnellement en 1er argument du script. # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # Si un 1er argument est present c'est la profondeur find # ------------------------------------------------------- if [ 0$1 -ne 0 ] ; then max=$1 else max=1 fi # Si un 2eme argument est present on liste des infos en + # ------------------------------------------------------- if [ 0$2 -ne 0 ] ; then bavard=$2 else bavard=0 fi # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth $max -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg a traiter ?" && exit 0 if [ $bavard -eq 1 ] ; then if [ $countT -lt 2 ] ; then echo "Modif des noms d'images : $countT image trouvee" else echo "Modif des noms d'images : $countT images trouvees" fi cc=0 fi # Traitement des dates # -------------------- for i in `find . -maxdepth $max -type f -iname "*.jpg" | sort` ; do # Lecture de la date par ImageMagick-identify #Madate=`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($1,3,2) substr($1,6,2) substr($1,9,2) "-"}'` Madate=`jhead "$i" | awk ' $1=="Date/Time" {print substr($3,3,2) substr($3,6,2) substr($3,9,2) "-"}' ` Madate2=`echo "$i" | awk '{print substr($1,3,7)}'` i2=`echo "$i" | awk '{print substr($1,3)}'` if test -n "$Madate"; then if [ "$Madate" != "-" ] ; then if [ "$Madate2" != "$Madate" ] ; then # mise de la date exif mv "$i" "$Madate$i2" if [ $bavard -eq 1 ] ; then echo "$i renomme en ./$Madate$i2" cc=$[$cc + 1] fi fi fi fi done if [ $bavard -eq 1 ] ; then if [ $cc -lt 2 ] ; then echo "Modif des noms d'images : $cc nom modifie" else echo "Modif des noms d'images : $cc noms modifies" fi fi
JJMMAA-ancien-nom.jpg
Le script qu'on va appeler jpeg-prefixe-nom-JJMMAA permet de convertir tous les noms de fichiers en ajoutant le préfixe JJMMAA- aux noms courants. Cette forme est identique à la précédente, avec des dates de nouveau au format français
#!/bin/sh # # Modif de noms d'images JPG # --------------------------- # # Transformations telles que les noms des fichiers # aient des prefixes conformes aux dates Exif : # # nom.jpg ==> JJMMAA-nom.jpg # # les suffixes jpeg, JPEG et JPG sont transormes en jpg. # # Boucle sur les seules images jpg du dossier courant # ou plusieurs niveaux de sous-dossiers selon la variable # max passee optionnellement en 1er argument du script. # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # Si un 1er argument est present c'est la profondeur find # ------------------------------------------------------- if [ 0$1 -ne 0 ] ; then max=$1 else max=1 fi # Si un 2eme argument est present on liste des infos en + # ------------------------------------------------------- if [ 0$2 -ne 0 ] ; then bavard=$2 else bavard=0 fi # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth $max -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg a traiter ?" && exit 0 if [ $bavard -eq 1 ] ; then if [ $countT -lt 2 ] ; then echo "Modif des noms d'images : $countT image trouvee" else echo "Modif des noms d'images : $countT images trouvees" fi cc=0 fi # Traitement des dates # -------------------- for i in `find . -maxdepth $max -type f -iname "*.jpg" | sort` ; do # Lecture de la date par ImageMagick-identify #Madate=`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($1,9,2) substr($1,6,2) substr($1,3,2) "-"}'` Madate=`jhead "$i" | awk ' $1=="Date/Time" {print substr($3,9,2) substr($3,6,2) substr($3,3,2) "-"}' ` Madate2=`echo "$i" | awk '{print substr($1,3,7)}'` i2=`echo "$i" | awk '{print substr($1,3)}'` if test -n "$Madate"; then if [ "$Madate" != "-" ] ; then if [ "$Madate2" != "$Madate" ] ; then # mise de la date exif mv "$i" "$Madate$i2" if [ $bavard -eq 1 ] ; then echo "$i renomme en ./$Madate$i2" cc=$[$cc + 1] fi fi fi fi done if [ $bavard -eq 1 ] ; then if [ $cc -lt 2 ] ; then echo "Modif des noms d'images : $cc nom modifie" else echo "Modif des noms d'images : $cc noms modifies" fi fi
Script d'Incrustations dans l'image
Le script qu'on va appeler jpeg-incrustations permet d'incruster 1 à 4 choses dans chaque image selon les options cochées dans la fenêtre de l'outil :
- la date de prise de photo, sous forme : JJ/MM/AA, par exemple 21/05/06,
- l'heure de prise de photo, sous forme : HHhMM, par exemple 15h22,
- le nom du fichier, seulement le nom simple et sans le suffixe ”.jpg” (et avec les éventuels “_” remplacés par des blancs), par exemple une image appelée “Jour_de_soleil.jpg” donnera “Jour de soleil”,
- le commentaire associé à l'image, tel qu'on l'aura créé dans les propriétés de l'image. A noter que certains logiciels peuvent mettre un commentaire par défaut, par exemple “Created with The Gimp”.
La taille des caractères est calculée pour être proportionnelle à la hauteur de chaque image, selon le nombre qu'on aura indiqué dans la fenêtre (l'outil forcera une valeur > 4). Par exemple la valeur par défaut de 30 signifie que pour une image de 15 cm de haut les caractères feront 0,5 cm. Les calculs sont optimisés pour que sur un écran de format 4/3 les images affichées en mode plein-écran aient les textes toujours affichés avec la même hauteur, peu importe la proportion largeur/hauteur effective de chaque image.
On inscrit le texte si possible 3 fois , 1 fois en blanc et 2 fois en noir, légèrement décalés afin que ce soit visible sur quasiment n'importe quelles couleurs de fond.
L'outil convert utilisé pour réaliser ce travail fait partie de la suite ImageMagick.
#!/bin/sh # # Incrustation de la date, de l'heure, du nom de fichier et/ou du commentaire dans des images JPG # ----------------------------------------------------------------------------------------------- # # D'apres les infos Exif Date/Time/Comment et le nom xxxx.jpg, on incruste : # # JJ/MM/AA HHhMM xxxx commentaire # # en noir/blanc en bas a gauche de l'image. # # Toutes les combinaisons sont possibles en option # # les suffixes jpeg, JPEG et JPG sont transormes en jpg. # # Boucle sur les seules images jpg du dossier courant # ou plusieurs niveaux de sous-dossiers selon la variable # max passee en 1er argument du script. # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # Les arguments doivent etre numeriques et sont tous positionnels; # les noms de fichiers doivent etre sans blancs. # 1er argument : profondeur du find # --------------------------------- if [ ! 0$1 -eq 0 ] ; then max=$1 else max=1 fi # Les 5 arguments suivants sont dans l'ordre les champs optionnels : # - date # - heure # - nom de fichier # - commentaire # - taille caracteres (ratio hauteur d'image / taille des caracteres) # avec 0 : absence, 1 : present (sauf taille) # valeurs par defaut : 0 0 0 0 30 if [ 0$2$3$4$5 -eq 0 ] ; then echo "Aucune incrustation demandee ?!?" exit 1 fi blabla="" if [ 0$2 -eq 0 ] ; then Date=0 else Date=$2 blabla="$blabla Date" fi if [ 0$3 -eq 0 ] ; then Heure=0 else Heure=$3 blabla="$blabla Heure" fi if [ 0$4 -eq 0 ] ; then Nom=0 else Nom=$4 blabla="$blabla Nom" fi if [ 0$5 -eq 0 ] ; then Comm=0 else Comm=$5 blabla="$blabla Commentaire" fi # rapport taille image / taille car >= 5; 30 par defaut if [ 0$6 -eq 0 ] ; then tcc=30 else if [ 0$6 -lt 5 ] ; then tcc=5 else tcc=$6 fi fi # Si un 7eme argument est present on liste des infos en + # ------------------------------------------------------- if [ 0$7 -eq 0 ] ; then Bavard=0 else if [ $7 -gt 1 ] ; then set -x fi Bavard=$7 fi # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth $max -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg a traiter ?" && exit 0 if [ $Bavard -eq 1 ] ; then if [ $countT -lt 1 ] ; then echo "Incrustation de texte : aucune image trouvee" exit 0 elif [ $countT -lt 2 ] ; then echo "Incrustation avec ratio hauteur-image/texte de $tcc : $countT image trouvee" else echo "Incrustation avec ratio hauteur-image/texte de $tcc : $countT images trouvees" fi fi # Traitement # ---------- cc=0 # compteur d'images traitees for i in `find . -maxdepth $max -type f -iname "*.jpg" | sort` ; do # Lecture de la date d1="" d2="" d3="" if [ $Date -ne 0 ] ; then # Lecture de date par ImageMagick-identify #d1=" "`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($1,9,2)}'`"/" # ou lecture par jhead (laisser l'une des 2 en commentaire) d1=" "`jhead "$i" | awk ' $1=="Date/Time" {print substr($3,9,2) }' `"/" #d2=`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($1,6,2)}'` d2=`jhead "$i" | awk '$1=="Date/Time" {print substr($3,6,2)}'`"/" #d3=`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($1,3,2)}'` d3=`jhead "$i" | awk '$1=="Date/Time" {print substr($3,3,2)}'` if [ $d1 == "/" ] ; then d1="" fi if [ $d2 == "/" ] ; then d2="" fi fi # Lecture de l'heure d4="" d5="" if [ $Heure -ne 0 ] ; then #d4=" "`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($2,1,2)}'`"h" d4=" "`jhead "$i" | awk '$1=="Date/Time" {print substr($4,1,2)}'`"h" #d5=`identify -format "%[EXIF:DateTime]" "$i" | awk '{print substr($2,4,2)}'` d5=`jhead "$i" | awk '$1=="Date/Time" {print substr($4,4,2)}'` if [ $d4 == "h" ] ; then d4="" fi fi # Lecture du nom de fichier, sans prefixe ni suffixe ni "_" nn="" if [ $Nom -ne 0 ] ; then xx=$i yy=${xx##*/} zz=${yy%.*} nn=" $(echo "$zz" | tr "_" " ")" fi # Lecture du commentaire co="" if [ $Comm -ne 0 ] ; then # il semble que identify soit plus fiable pour le commentaire co=" "`identify -format "%c" "$i"` #co=" "`jhead "$i" | awk '$1=="Comment" {print substr($0,16,length($0)-15)}'` # protection des apostrophes avec un "\" : chaque "'" donne "\'" #co=$(echo "$zz" | sed "s/'/\\\'/g") fi # Lecture de la largeur et hauteur de l'image #x=`identify -format '%w' "$i"` #y=`identify -format '%h' "$i"` x=`jhead "$i" | awk ' $1=="Resolution" {print $3}' ` y=`jhead "$i" | awk ' $1=="Resolution" {print $5}' ` #set -x # Calcul jc = taille du jeu de caracteres selon les proportions de l'image # $tcc = rapport de base en entree tel que jc = y/tcc si y/x=3/4 jcmin=20 # taille minimale absolue; par ailleurs tcc >= 5 deja vrai # Pour avoir le meme aspect d'affichage quelle que soit l'image, on a : # si y/x >= 3/4 : jc = y/tcc # sinon : jc = y/tcc * (3/4) / (y/x) (l'image affichee diminue en hauteur, # il faut donc compenser en augmentant la taille des caracteres) # autrement dit : # si 4y >= 3x : jc = y/tcc # sinon : jc = y/tcc * 3x / 4y = 3x /tcc /4 # En fait la distinction points/pixels introduit un second rapport 4/3, et on aura : # si 4y >= 3x : jc = 4/3 * y/tcc # sinon : jc = 4/3 * y/tcc * 3x / 4y = x /tcc t1=$[$y * 4] t2=$[$x * 3] if [ $t1 -ge $t2 ] ; then jc=$[$y * 4 / $tcc / 3] else jc=$[$x / $tcc] fi if [ $jc -lt $jcmin ] ; then jc=$jcmin fi # positions des 2 ou 3 textes # (1 ou 2 inscriptions noires + 1 blanche) dc=$[($jc + 2) / 10] # decalage if [ $dc -lt 2 ] ; then dc=2 fi if [ $dc -ge 2 ] ; then dc2=$[$dc / 2] else dc2=0 fi x=$[$y/45] y1=$[$y-$x+$dc] y1b=$[$y-$x+$dc2] y2=$[$y-$x] x1=$x x1b=$[$x-$dc2] x2=$[$x-$dc] # Commandes d'incrustation des 2 textes if [ $dc2 -gt 0 ] ; then convert -font helvetica -fill black -pointsize $jc -draw "text $x1,$y1 \"$d1$d2$d3$d4$d5$nn$co\"" $i /tmp/tempo2.jpg convert -font helvetica -fill black -pointsize $jc -draw "text $x1b,$y1b \"$d1$d2$d3$d4$d5$nn$co\"" /tmp/tempo2.jpg /tmp/tempo.jpg convert -font helvetica -fill white -pointsize $jc -draw "text $x2,$y2 \"$d1$d2$d3$d4$d5$nn$co\"" /tmp/tempo.jpg $i.jpg rm -f /tmp/tempo2.jpg else convert -font helvetica -fill black -pointsize $jc -draw "text $x1,$y1 \"$d1$d2$d3$d4$d5$nn$co\"" $i /tmp/tempo.jpg convert -font helvetica -fill white -pointsize $jc -draw "text $x2,$y2 \"$d1$d2$d3$d4$d5$nn$co\"" /tmp/tempo.jpg $i.jpg fi #set +x # remise de la date d'origine touch -r "$i" "$i.jpg" && rm -f "$i" && mv "$i.jpg" "$i" rm -f /tmp/tempo.jpg if [ $Bavard -eq 1 ] ; then echo "Incrustation de$blabla dans $i" cc=$[$cc + 1] fi done if [ $Bavard -eq 1 ] ; then if [ $cc -lt 2 ] ; then echo "Incrustation de$blabla : $cc image modifiee" else echo "Incrustation de$blabla : $cc images modifiees" fi fi
Script de Rotations
Le script qu'on va appeler jpeg-rotation permet de tourner les images qui n'ont pas la bonne orientation. Le travail le plus courant consiste à redresser des images prises en mode portrait et qui apparaissent en mode paysage, mais toutes les corrections sont possibles, soit 7 corrections sur 8 orientations possibles en tout.
#!/bin/sh # # Rotation d'images JPG # --------------------- # # Transformations telles que : orientation Exif = 1 # # Les dates des fichiers sont preservees, et les # suffixes jpeg, JPEG et JPG sont transormes en jpg. # # Boucle sur les seules images jpg du dossier courant # ou plusieurs niveaux de sous-dossiers selon la variable # max passee optionnellement en 1er argument du script. # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # Si un 1er argument est present c'est la profondeur find # ------------------------------------------------------- if [ 0$1 -ne 0 ] ; then max=$1 else max=1 fi # Si un 2eme argument est present on liste des infos en + # ------------------------------------------------------- if [ 0$2 -ne 0 ] ; then bavard=$2 else bavard=0 fi # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth $max -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg a traiter ?" && exit 0 if [ $bavard -eq 1 ] ; then if [ $countT -lt 2 ] ; then echo "Orientation d'images : $countT image trouvee" else echo "Orientation d'images : $countT images trouvees" fi cc=0 fi # Traitement de l'orientation # --------------------------- for i in `find . -maxdepth $max -type f -iname "*.jpg" | sort` ; do # Lecture de l'orientation par ImageMagick-identify orientation=`identify -format "%[EXIF:Orientation]" $i ` # Test si orientation presente et != 1 if test -n "$orientation"; then if [ "$orientation" != "1" ] ; then # on tourne l'image et remet l'info d'orientation a 1 if [ $bavard -eq 1 ] ; then echo "Rotation de l'image $i (orientation exif = $orientation)" cc=$[$cc + 1] fi cp "$i" "$i.tmp.jpg" jhead -autorot "$i.tmp.jpg" >/dev/null # remise de la date d'origine touch -r "$i" "$i.tmp.jpg" && rm -f "$i" && mv "$i.tmp.jpg" "$i" fi fi done if [ $bavard -eq 1 ] ; then if [ $cc -lt 2 ] ; then echo "Orientation d'images : $cc image tournee" else echo "Orientation d'images : $cc images tournees" fi fi
Script de Comptage
Le script qu'on va appeler jpeg-comptage permet de compter le nombre d'images dans le dossier et les éventuels sous-dossiers souhaités. Il appelle aussi le script jpeg-suppr-blancs de renommage des fichiers qui comportent des blancs dans leurs noms.
#!/bin/sh # # Comptage d'images JPG # --------------------- # # les suffixes jpg, jpeg, JPG et JPEG sont traites. # # Boucle sur les seules images du dossier courant # ou plusieurs niveaux de sous-dossiers selon la variable # max passee optionnellement en 1er argument du script. # # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # Si un 1er argument est present c'est la profondeur find # ------------------------------------------------------- if [ 0$1 -ne 0 ] ; then max=$1 else max=1 fi # On supprime au prealable les blancs et apostrophes dans les noms de fichiers # ---------------------------------------------------------------------------- jpeg-suppr-blancs $max # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth $max -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done for ii in `find . -maxdepth $max -type f -iname "*.jpeg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image trouvee !" && exit 1 echo "Nombre total d'images : $countT" exit 0
Script de renommage des fichiers
Le script qu'on va appeler jpeg-suppr-blancs permet de renommer les fichiers qui comportent des blancs dans leurs noms. Au passage on supprime aussi les apostrophes, mais on pourrait encore l'enrichir pour ôter les accents et autres caractères parasites. Pour notre outil le contrôle de l'absence de blancs est déjà suffisant mais nécessaire, sinon les commandes “find” des autres outils auraient des effets inattendus.
#!/bin/sh # Suppression des blancs et apostrophes dans les noms des fichiers; # Les fichiers modifies sont listes. # Auteur : Jeanmm # Date : aout 2006 # Licence : GPL # Lien : http://www.root66.net # S'il y a 1 seul argument c'est la profondeur find; # s'il y a 0 argument, la profondeur find est mise a 1. # On constitue ensuite un script intermediaire avec tous les # fichiers identifies et on l'execute. if [ $# -lt 2 ] ; then if [ $# -eq 1 ] ; then max=$1 else max=1 fi # jpeg-suppr-blancs.txt = liste des fichiers # jpeg-suppr-blancs-tempo.sh = script rm -f jpeg-suppr-blancs.txt echo "#!/bin/sh" >jpeg-suppr-blancs-tempo.sh find . -maxdepth $max -type f -iname "*" >>jpeg-suppr-blancs.txt cat jpeg-suppr-blancs.txt | awk '{x = "\"" $0 "\""; gsub (" ","_",$0); y = "\"" $0 "\""; print "jpeg-suppr-blancs ", x, y >>"jpeg-suppr-blancs-tempo.sh"}' echo "exit 0" >>jpeg-suppr-blancs-tempo.sh sh jpeg-suppr-blancs-tempo.sh rm -f jpeg-suppr-blancs.txt jpeg-suppr-blancs-tempo.sh exit 0 # Appel avec 2 arguments ==> c'est le nom du fichier a renommer et le nom avec des "_" a la place des blancs else # Conversion des apostrophes en "_" au passage, # et factorisation des "_". b=$(echo $2 | tr "'" "_" | tr -s _ _ ) # On renomme finalement le fichier if [ "$1" != "$b" ] ; then echo "mv \"$1\" $b" mv "$1" $b fi fi
Scripts Python
Ah ! On va enfin donner le script principal qui va faire appel à tous les scripts ci-dessus pour créer notre petite application. Mais avant ça il est vivement conseillé de lire l'article Scripts de sauvegarde de données afin d'y apprendre tout à propos de Python, Pmw et TkInter : l'environnement général est en effet le même, et si Pmw ou TkInter manque ça ne marchera pas. On y lira aussi les recommandations pour créer/insérer une image décorative et effectuer les premiers tests.
En plus de ces conseils préliminaires indispensables on aura besoin ici de deux petits outils complémentaires, ceux qui affichent les fenêtres de fichiers et de dossiers quand on clique sur le bouton “Lister les images” ou la petite icône de dossier. D'ailleurs il faudra aussi se procurer une image représentant cette icône. Les outils complémentaires ont été empruntés à d'autres sources sur le web, preuve du caractère diversifié de ce logiciel, et de la souplesse de Python pour fédérer des briques de toutes origines.
Script Python principal
Commençons déjà par le script Python principal :
#!/usr/bin/python # -*- coding: iso-8859-1 -*- # Ce script affiche une fenetre de parametrage et de # lancement de scripts bash qui permettent plusieurs # manips sur des images jpeg (rotation, incrustations...). # Un script bash est termine quand il n'y a plus de fichier # stdout/stderr associe, les sorties eventuelles dans ces 2 # fichiers etant captees et affichees dans une zone de texte # deroulante sous la zone de parametrage. # Auteur : Jeanmm # Version : 1.3 # Date : 1er octobre 2006 # Licence : GPL # Lien : http://www.root66.net import sys, os, string from Tkinter import * from FileDialog import * import Pmw from DialogueDossiersJmm import DirBrowserDialog from DialogueFichiersEtDossiersJmm import PmwFileDialog #=========================================== # Debut de la partie a adapter a ses besoins #=========================================== # Titre de la fenetre MonTitre1 = "Modifications sur images jpg" # Les scripts Bash a executer; # tout passe quasiment par un script unique qui se charge de dispatcher # a d'autres scripts secondaires selon les traitements a realiser MonScript1 = 'jpeg-transfos' MonScript2 = 'jpeg-comptage' # Logos pour agrementer la fenetre MonLogoDossier = '/usr/local/divers/LogoDossier.gif' MonLogoLinux = '/usr/local/divers/LogoRoot66.gif' # Options de traitement (mettre 0/1 pour oui/non) # Ou modifier le contenu du fichier /home/nom/.root66/jpeg-transfos.txt MonDossier=os.path.join(os.getenv('HOME'), 'Images') sousDossiers=1 dateFichier=1 rotationImage=1 #nomFichier='inchange' nomFichier='JJMMAA-HHMMSS.jpg' #nomFichier='AAMMJJ-HHMMSS.jpg' #nomFichier='JJMMAA-nom-ancien.jpg' #nomFichier='AAMMJJ-nom-ancien.jpg' incrustationDate=0 incrustationHeure=0 incrustationNom=0 incrustationComm=0 tailleCar=32 # titre facultatif en debut de zone deroulante titre = """ Cet outil sert a modifier des images JPG en fonction de leurs infos EXIF ------------------------------------------------------------------------ Choisissez : - le dossier de base contenant les images a traiter (F3 permet de naviguer entre le dossier mis par defaut, le dossier courant de l'utilisateur et son home; F4 affiche la fenetre de navigation comme quand on clique sur le logo de dossier), - le nombre de niveaux de sous-dossiers qu'il faut traiter (1 par defaut ==> seul le dossier choisi sera traite), - au moins une des options parmi celles proposees, puis appuyez sur le bouton de traitement. A noter aussi : - F5 agit comme quand on clique sur le bouton Compter / Lister, - Alt+F4 termine le programme comme le bouton Quitter, - Les options par defaut sont modifiables dans .root66/jpeg-transfos.txt de son home. \n""" #=========================================== # Fin de la partie a adapter a ses besoins #=========================================== # Option generale de listage de numeros de lignes NumLi = 0 # 0=non, 1=oui # Dossiers accessibles cycliquement par F3 Dossiers = ['','',''] # dossier courant, courant au lancement, et home NumDos = 0 # Sous-programmes generaux # ======================== # Parcours de 3 dossiers predefinis par F3 # ---------------------------------------- def f3(ev=None): global Dossiers, NumDos NumDos = NumDos + 1 if NumDos == 3: NumDos = 0 entree1.delete(0,END) entree1.insert(END, Dossiers[NumDos]) # Parcours de dossiers general par F4 #------------------------------------ def f4(ev=None): texte2['text'] = "" if os.path.isdir(entree1.get()): rep = entree1.get() else: texte2.configure(fg='red', font=('Times', 12, 'bold')) texte2['text']="Nom de dossier invalide ?" entree1.focus_set() entree1.xview(END) return dirBrowserDialog = DirBrowserDialog(fen1, label = '', path = rep, hidedotfiles = 1, title = 'Selection de dossier') dir = dirBrowserDialog.activate() fen1.focus_set() if dir != None: entree1.delete(0,END) entree1.insert(END, dir) entree1.focus_set() entree1.xview(END) # Comptage/Listage de noms de fichiers JPG par bouton ou F5 #---------------------------------------------------------- # On les compte d'abord, avec listage d'infos sommaires, # puis on affiche une fenetre de parcours. def f5(ev=None): global ii # Numero de ligne texte2['text'] = "" if os.path.isdir(entree1.get()): rep=entree1.get() else: texte2.configure(fg='red', font=('Times', 12, 'bold')) texte2['text'] = "Nom de dossier invalide ?" entree1.focus_set() entree1.xview(END) return texte2.configure(fg='red', font=('Times', 12, 'bold')) fen1.update_idletasks() # Traitement du dossier try: os.chdir(entree1.get()) except: ii += 1 if NumLi: ligne = '%04d ' %(ii) zoneListage.insert(END, ligne) ligne = "Le Dossier n'existe pas ?!?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text'] = "Le Dossier n'existe pas ?!?" entree1.focus_set() return Dossiers[0] = entree1.get() # Argument 1 : Pofondeur de parcours des sous-dossiers # (1 par defaut = seulement le dossier courant) try: NbDos = int(entree2.get()) except: ii += 1 if NumLi: ligne = '%04d ' %(ii) zoneListage.insert(END, ligne) ligne = "Le nombre de sous-dossiers est invalide ?!?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text']="Le nombre de sous-dossiers est invalide ?!?" entree2.focus_set() return if NbDos < 1: # Nb >= 1 NbDos = 1 entree2.delete(0,END) entree2.insert(END, '1') # Lancement du script de comptage fin,fout = os.popen4(MonScript2 + ' ' + str(NbDos)) # Boucle de recuperation des resultats (txt='' => termine) txt = 'x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=fout.readline() ii += 1 if NumLi: ligne = '%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne = '%s' %(txt) else: ligne = '\n' if ligne[0:12] == 'Aucune image': texte2['text'] = 'Aucune image.jpg ?' zoneListage.insert(END, ligne, 'rouge') else: zoneListage.insert(END, ligne) zoneListage.see(END) fen1.update_idletasks() fin.close() fout.close() if texte2['text'][0:12] == 'Aucune image': pass else: texte2['text'] = '' # Affichage de fenetre de parcours des dossiers f0 = PmwFileDialog(fen1, info = "(nota : les dossiers debutant avec un '.' ne seront pas traites)") f0.title("Listage des images.jpg") fname = f0.askfilename(directory=rep, filter='*.jpg') # Actionnement des boutons radio # ------------------------------ def modifBoutonUnique(tag, etat): """ fonction appelee quand un bouton flipflop est modifie - tag = nom du bouton - etat = etat enfonce (si vrai) ou non """ global dateFichier, rotationImage, incrustationDate global incrustationHeure, incrustationNom, incrustationComm if tag == "Date fichier = date de prise de vue": dateFichier = etat elif tag == "Rotation si necessaire": rotationImage = etat elif tag == "JJ/MM/AA": incrustationDate = etat elif tag == "HHhMM": incrustationHeure = etat elif tag == "nom de fichier": incrustationNom = etat elif tag == "Commentaire": incrustationComm = etat def modifBoutonMultiple(tag): """ fonction appelee quand un bouton de choix parmi un groupe est modifie - tag = nom du bouton """ global nomFichier if tag == 'inchange': nomFichier = 0 elif tag == 'JJMMAA-HHMMSS.jpg': nomFichier = 1 elif tag == 'AAMMJJ-HHMMSS.jpg': nomFichier = 2 elif tag == 'JJMMAA-nom-ancien.jpg': nomFichier = 3 elif tag == 'AAMMJJ-nom-ancien.jpg': nomFichier = 4 # Rafraichissement de fenetre en cas de deplacement # ------------------------------------------------- def rafraichissement(ev): fen1.update_idletasks() # quitter le programme # -------------------- # on simule le clic sur le bouton quand il est selectionne + touche entree def quitter(ev=None): bouton2.invoke() # Sous-programmes de Traitement principal # --------------------------------------- def traiter_les_images(ev=None): global ii # Numero de ligne global Dossiers # Noms dossiers global tailleCar # Taille des caracteres a incruster texte2.configure(fg='red', font=('Times', 12, 'bold')) texte2['text'] = 'Traitement en cours...' fen1.update_idletasks() # Traitement du dossier try: os.chdir(entree1.get()) except: ii += 1 if NumLi: ligne = '%04d ' %(ii) zoneListage.insert(END, ligne) ligne = "Le Dossier n'existe pas ?!?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text'] = "Le Dossier n'existe pas ?!?" entree1.focus_set() return Dossiers[0] = entree1.get() # Argument 1 : Pofondeur de parcours des sous-dossiers # (1 par defaut = seulement le dossier courant) try: NbDos = int(entree2.get()) except: ii += 1 if NumLi: ligne = '%04d ' %(ii) zoneListage.insert(END, ligne) ligne = "Le nombre de sous-dossiers est invalide ?!?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text'] = "Le nombre de sous-dossiers est invalide ?!?" entree2.focus_set() return if NbDos < 1: # Nb >= 1 NbDos = 1 entree2.delete(0,END) entree2.insert(END, '1') mesOptions = str(NbDos) # Argument 2 : Listage d'infos mesOptions = mesOptions + ' 1' # Argument 3 : Date fichier mesOptions = mesOptions + ' ' + str(dateFichier) # Argument 4 : Rotation mesOptions = mesOptions + ' ' + str(rotationImage) # Argument 5 : Modification du nom de fichier mesOptions = mesOptions + ' ' + str(nomFichier) # Argument 6 : Incrustation Date mesOptions = mesOptions + ' ' + str(incrustationDate) # Argument 7 : Incrustation Heure mesOptions = mesOptions + ' ' + str(incrustationHeure) # Argument 8 : Incrustation Nom de fichier mesOptions = mesOptions + ' ' + str(incrustationNom) # Argument 9 : Incrustation Commentaire mesOptions = mesOptions + ' ' + str(incrustationComm) # Argument 10 : Hauteur des incrustations (rapport hauteur image / taille caracteres) try: tailleCar = int(entree3.get()) except: ii += 1 if NumLi: ligne = '%04d ' %(ii) zoneListage.insert(END, ligne) ligne = "Le rapport hauteur image / taille caracteres est invalide ?!?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text'] = "Le rapport hauteur image / taille caracteres est invalide ?!?" entree2.focus_set() return if tailleCar < 5: # Nb >= 5 tailleCar = 5 entree3.delete(0,END) entree3.insert(END, '5') mesOptions = mesOptions + ' ' + str(tailleCar) # Lancement du script fin,fout=os.popen4(MonScript1 + ' ' + mesOptions) # Boucle de recuperation des resultats (txt='' => termine) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt = fout.readline() ii += 1 if NumLi: ligne = '%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne = '%s' %(txt) else: ligne = '\n' if ligne[0:12] == 'Aucune image': texte2['text'] = 'Aucune image.jpg ?' zoneListage.insert(END, ligne, 'rouge') else: zoneListage.insert(END, ligne) zoneListage.see(END) fen1.update_idletasks() fin.close() fout.close() if texte2['text'][0:12] == 'Aucune image': pass else: texte2['text'] = 'OK - traitement termine' # =============== Programme principal ================= # Lecture du fichier de configuration ~/.root66/jpeg-transfos.txt # --------------------------------------------------------------- d1=os.path.join(os.getenv('HOME'), '.root66') f1=os.path.join(d1, 'jpeg-transfos.txt') if os.path.isfile(f1): try: fo1=open(f1, 'r') t="" while 1: # Lecture d'une ligne t=fo1.readline() # Ligne vide => fin de fichier if t=='': break # Suppression des caracteres de fins de lignes if t[-1]=='\n': # 'new-line' t=t[0:-1] # Separation et analyse des champs : parametre = z[0], valeur = z[1] z=string.split(t,'=') if z[0] == 'MonDossier' and z[1] != '': MonDossier = z[1] elif z[0] == 'sousDossiers' and z[1] != '': sousDossiers = int(z[1]) elif z[0] == 'dateFichier' and z[1] != '': dateFichier = int(z[1]) elif z[0] == 'rotationImage' and z[1] != '': rotationImage = int(z[1]) elif z[0] == 'nomFichier' and z[1] != '': nomFichier = z[1] elif z[0] == 'incrustationDate' and z[1] != '': incrustationDate = int(z[1]) elif z[0] == 'incrustationHeure' and z[1] != '': incrustationHeure = int(z[1]) elif z[0] == 'incrustationNom' and z[1] != '': incrustationNom = int(z[1]) elif z[0] == 'incrustationComm' and z[1] != '': incrustationComm = int(z[1]) elif z[0] == 'tailleCar' and z[1] != '': tailleCar = int(z[1]) fo1.close() except: print "Fichier de config invalide", t, "?" else: # On essaie de creer le dossier .root66 et le fichier if not os.path.isdir(d1): try: os.makedirs(d1) except: print "Impossible de creer le dossier " + d1 sys.exit(1) try: fo1=open(f1, 'w') fo1.write("# Fichier de configuration de l'outil jpeg-transfos.py\n") fo1.write("#\n") fo1.write("# Options de traitement :\n") fo1.write("# - mettre 1/0 pour oui/non,\n") fo1.write("# - mettre un '#' devant les lignes inutiles,\n") fo1.write("# - et pas de blancs autour des '='.\n") fo1.write("#\n") fo1.write("MonDossier=%s\n" %MonDossier) fo1.write("sousDossiers=%d\n" %sousDossiers) fo1.write("#\n") fo1.write("dateFichier=%d\n" %dateFichier) fo1.write("rotationImage=%d\n" %rotationImage) fo1.write("#\n") if nomFichier=='inchange': fo1.write("nomFichier=inchange\n") else: fo1.write("#nomFichier=inchange\n") if nomFichier=='JJMMAA-HHMMSS.jpg': fo1.write("nomFichier=JJMMAA-HHMMSS.jpg\n") else: fo1.write("#nomFichier=JJMMAA-HHMMSS.jpg\n") if nomFichier=='AAMMJJ-HHMMSS.jpg': fo1.write("nomFichier=AAMMJJ-HHMMSS.jpg\n") else: fo1.write("#nomFichier=AAMMJJ-HHMMSS.jpg\n") if nomFichier=='JJMMAA-nom-ancien.jpg': fo1.write("nomFichier=JJMMAA-nom-ancien.jpg\n") else: fo1.write("#nomFichier=JJMMAA-nom-ancien.jpg\n") if nomFichier=='AAMMJJ-nom-ancien.jpg': fo1.write("nomFichier=AAMMJJ-nom-ancien.jpg\n") else: fo1.write("#nomFichier=AAMMJJ-nom-ancien.jpg\n") fo1.write("#\n") fo1.write("incrustationDate=%d\n" %incrustationDate) fo1.write("incrustationHeure=%d\n" %incrustationHeure) fo1.write("incrustationNom=%d\n" %incrustationNom) fo1.write("incrustationComm=%d\n" %incrustationComm) fo1.write("tailleCar=%d\n" %tailleCar) fo1.close() except: print "Impossible de creer le fichier",f1 # Instanciation d'une fenetre Pmw (Python mega-widgets) # ----------------------------------------------------- fen1 = Pmw.initialise() fen1.title(MonTitre1) fen1.monOption = IntVar() # Dossiers pour touche F3 Dossiers[0] = MonDossier Dossiers[1] = os.getcwd() Dossiers[2] = os.getenv('HOME') fen1.bind("<F3>", f3) fen1.bind("<F4>", f4) fen1.bind("<F5>", f5) fen1.bind("<Alt-F4>", quitter) fen1.bind("<Configure>", rafraichissement) # creation des widgets de configuration/lancement du script # ========================================================= # Ligne 1 # ------- texte1 = Label(fen1, text=' Dossier :') texte1.grid(row=1, column=1, sticky=E, padx=2, pady=5) # Saisie de nom de dossier # alternatives : f3 pour 3 valeurs predefinies, f4 pour dialogue entree1 = Entry(fen1, width=74, bg='white', fg='dark blue') entree1.grid(row=1, column=2, columnspan=4, sticky=EW, padx=2, pady=5) entree1.insert(END, MonDossier) # valeur par defaut entree1.focus_set() # Bouton pour dialogue de parcours de dossier (aussi par f4) logo = PhotoImage(file=MonLogoDossier) bouton0 = Button(fen1, activebackground='DeepSkyBlue2', image=logo, command=f4) bouton0.grid(row=1, column=6, padx=0, pady=3) bouton0.bind("<Return>",f4) # Ligne 2 # ------- texte2 = Label(fen1, text='Sous-Dossiers :') texte2.grid(row=2, column=1, sticky=E, padx=2, pady=5) entree2 = Entry(fen1, width=4, bg='white', fg='dark blue') entree2.grid(row=2, column=2, sticky=W, padx=2, pady=5) entree2.insert(END, str(sousDossiers)) # valeur par defaut # Ligne 3 # ------- # widget RadioSelect pour 'Date fichier = date de prise de vue' zz = Pmw.RadioSelect(fen1, buttontype = 'checkbutton', labelpos = None, command = modifBoutonUnique, hull_borderwidth = 2, hull_relief = 'ridge', selectmode = 'multiple', ) zz.grid(row=3, column=2, columnspan=2, sticky='ew', padx=3, pady=3) zz.add('Date fichier = date de prise de vue') if dateFichier: zz.invoke('Date fichier = date de prise de vue') # widget RadioSelect pour 'Rotation si necessaire' zz = Pmw.RadioSelect(fen1, buttontype = 'checkbutton', labelpos = None, command = modifBoutonUnique, hull_borderwidth = 2, hull_relief = 'ridge', selectmode = 'multiple', ) zz.grid(row=3, column=4, columnspan=2, sticky='ew', padx=3, pady=3) # Ajout bouton zz.add('Rotation si necessaire') if rotationImage: zz.invoke('Rotation si necessaire') # Lignes 4 a 9 # ------------ # widget RadioSelect vertical, avec les choix de noms de fichiers # sur 2 colonnes du cote gauche zz = Pmw.RadioSelect(fen1, buttontype = 'radiobutton', orient = 'vertical', labelpos = 'n', command = modifBoutonMultiple, label_text = 'Nom de fichier', hull_borderwidth = 2, hull_relief = 'ridge', ) zz.grid(row=4, rowspan=6, column=2, columnspan=2, sticky='ew', padx=3, pady=3) zz.add('inchange') zz.add('JJMMAA-HHMMSS.jpg') zz.add('AAMMJJ-HHMMSS.jpg') zz.add('JJMMAA-nom-ancien.jpg') zz.add('AAMMJJ-nom-ancien.jpg') zz.invoke(nomFichier) # widget Group sur 2 colonnes du cote droit # pour englober tout ce qui touche aux incrustations gi = Pmw.Group(fen1, ring_borderwidth = 2, ring_relief = 'ridge', tag_pyclass = None ) gi.grid(row=4, rowspan=6, column=4, columnspan=2, sticky='ew', padx=3, pady=3) # widget RadioSelect vertical, avec les choix d'incrustations zz = Pmw.RadioSelect(gi.interior(), buttontype = 'checkbutton', orient = 'vertical', labelpos = 'n', command = modifBoutonUnique, label_text = "Incrustations dans l'image", selectmode = 'multiple', ) zz.grid(row=1, column=1, columnspan=2, sticky='ew', padx=3, pady=3) zz.add('JJ/MM/AA') zz.add('HHhMM') zz.add('nom de fichier') zz.add('Commentaire') if incrustationDate: zz.invoke('JJ/MM/AA') if incrustationHeure: zz.invoke('HHhMM') if incrustationNom: zz.invoke('nom de fichier') if incrustationComm: zz.invoke('Commentaire') texte3 = Label(gi.interior(), text="hauteur d'image / taille des caracteres :") texte3.grid(row=2, column=1, sticky='e', padx=3, pady=3) entree3 = Entry(gi.interior(), width=4, bg='white', fg='dark blue') entree3.grid(row=2, column=2, sticky='w', padx=3, pady=3) entree3.insert(END, str(tailleCar)) # valeur par defaut # Ligne 10 # -------- texte2 = Label(fen1, text='Dossier courant : ' + os.getcwd()) texte2.grid(row=10, column=2, columnspan=4, padx=3, pady=5) # Fins de Lignes 7 a 9 # -------------------- # Bouton pour lister les images bouton1a = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=14, text="Compter / Lister", command=f5) bouton1a.grid(row=7, column=6, columnspan=2, padx=3, pady=3) bouton1a.bind("<Return>",f5) # Bouton pour traiter les images bouton1b = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=14, text="Traiter", command=traiter_les_images) bouton1b.grid(row=8, column=6, columnspan=2, padx=3, pady=3) bouton1b.bind("<Return>",traiter_les_images) # Bouton pour quitter bouton2 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=14, text='Quitter', command=fen1.quit) bouton2.grid(row=9, column=6, columnspan=2, padx=3, pady=3) bouton2.bind("<Return>",quitter) # Logo en bout de lignes 1 a 3 # ---------------------------- # decoratif et facultatif can1 = Canvas(fen1, width=90, height=100, bg='AntiqueWhite3') photo = PhotoImage(file=MonLogoLinux) item = can1.create_image(45, 50, image=photo) can1.grid(row=1, column=7, rowspan=3, padx=3, pady=4) # Ligne 11 : Zone de listage des resultats des scripts # ---------------------------------------------------- # C'est un rectangle de la largeur totale de la fenetre, # avec ascenseurs automatiques si necessaire; # hauteur max : limitee selon taille d'ecran, pour que # toute la fenetre soit totalement visible (important pour # les ecrans de 600 ou 768 pixels). h = fen1.winfo_screenheight() - 426 - 48 if h > 400: h = 400 elif h < 100: h = 100 zoneListage = Pmw.ScrolledText(fen1, text_font = 'Courier 11 normal', text_bg = 'AliceBlue', text_padx = 10, text_pady = 10, text_wrap = 'none', borderframe = 1, borderframe_borderwidth = 3, borderframe_relief = RIDGE, usehullsize = 1, hull_width = 780, hull_height = h) zoneListage.grid(row=11, column=1, columnspan=7, padx=4, pady=4) zoneListage._textbox['takefocus'] = 0 ii = 0 # Compteur des lignes inserees # balise pour lignes rouges creees ici (en plus # des lignes noires generees par le script) zoneListage.tag_configure('rouge', foreground='red') zoneListage.insert(END, titre) # Lancement de l'application # -------------------------- fen1.mainloop()
Explications
Les seules lignes à modifier se situent entre :
#=========================================== # Debut de la partie a adapter a ses besoins #===========================================
et
#=========================================== # Fin de la partie a adapter a ses besoins #===========================================
On peut spécifier le titre de la fenêtre :
# Titre de la fenetre MonTitre1 = "Modifications sur images jpg"
Le nom du script bash principal est indiqué ici :
# Le(s) script(s) bash a executer MonScript1 = 'jpeg-transfos' MonScript2 = 'jpeg-comptage'
Les deux logos de la fenêtre sont donnés ici :
# Logos pour agrementer la fenetre MonLogoDossier = '/usr/local/divers/LogoDossier.gif' MonLogoLinux = '/usr/local/divers/LogoRoot66.gif'
Le fichier LogoDossier.gif est une petite image de 23×20 points obtenue par capture dans une autre application. Le pingouin LogoRoot66.gif est un fichier de 100×102 points; on peut mettre toutes sortes de décors d'environ 100×100 points.
On peut modifier les options affichées par défaut :
# Options de traitement (mettre 0/1 pour oui/non) # Ou modifier le contenu du fichier /home/nom/.root66/jpeg-transfos.txt MonDossier=os.path.join(os.getenv('HOME'), 'Images') sousDossiers=1 dateFichier=1 rotationImage=1 #nomFichier='inchange' nomFichier='JJMMAA-HHMMSS.jpg' #nomFichier='AAMMJJ-HHMMSS.jpg' #nomFichier='JJMMAA-nom-ancien.jpg' #nomFichier='AAMMJJ-nom-ancien.jpg' incrustationDate=0 incrustationHeure=0 incrustationNom=0 incrustationComm=0 tailleCar=32
Attention : Les options par défaut ci-dessus ne sont valables qu'une seule fois. Dès le 1er lancement l'outil crée le fichier /home/nom/.root66/jpeg-transfos.txt s'il n'existait pas. Le fichier est créé à partir des options ci-dessus, et c'est dans ce fichier qu'on pourra ensuite fixer ses options perso de manière stable, même si on installe ultérieurement une nouvelle version de l'outil.
On peut suggérer de spécifier un dossier par défaut également connu du logiciel de téléchargement des images d'APN, ce qui simplifie grandement l'utilisation de l'outil. Après modifications les images peuvent alors être déplacées vers des dossiers de classement définitifs autres. A noter que le script Python a été programmé pour modifier cette zone de 3 façons :
- par saisie directe,
- par la fenêtre liée au Logo Dossier, qu'on peut aussi afficher par touche F4,
- par touche F3, qui permet de passer cycliquement du dossier par défaut au dossier courant et au dossier home.
A noter aussi que la touche F5 a le même effet que le bouton “Compter / Lister les images”.
On pourra mettre toute explication utile en début de zone déroulante, bien visible au lancement de l'outil :
# titre facultatif en debut de zone deroulante titre = """ Cet outil sert a modifier des images JPG en fonction de leurs infos EXIF ------------------------------------------------------------------------ Choisissez : - le dossier de base contenant les images a traiter (F3 permet de naviguer entre le dossier mis par defaut, le dossier courant de l'utilisateur et son home; F4 affiche la fenetre de navigation comme quand on clique sur le logo de dossier), - le nombre de niveaux de sous-dossiers qu'il faut traiter (1 par defaut ==> seul le dossier choisi sera traite), - au moins une des options parmi celles proposees, puis appuyez sur le bouton de traitement. ...etc...\n"""
Remarques
L'outil démarre en lisant le fichier /home/nom/.root66/jpeg-transfos.txt (sauf la toute 1ère fois). Si on veut personnaliser l'outil, le mieux est de se le copier dans son espace perso, et de lancer cet outil perso, donc sans modifier l'outil commun. Seul le fichier jpeg-transfos.py serait à dupliquer, les autres pourront rester inchangés dans /usr/local/bin. On pourra par exemple prévoir un 1er outil qui soit préconfiguré pour les manips de base au téléchargement d'images APN (renommage, rotation, date), et un second outil qui soit préconfiguré pour les manips d'incrustations les plus usuelles. On peut se créer autant d'outils perso que souhaité. Pour un outil perso on conseille à minima de remplacer toutes les occurrences du fichier de configuration jpeg-transfos.txt par un nom différent, comme ça chaque outil disposera de sa propre configuration indépendante des autres.
Pour le reste des explications on se reportera à l'article sur l'outil de sauvegarde de fichiers. On conseille notamment d'effectuer les premiers tests en lançant l'outil en ligne de commande afin de visualiser et corriger d'éventuelles erreurs (paquetages non trouvés, dossiers inexistants, scripts non exécutables…).
Script pour Dialogue Dossiers
Ce code a été emprunté à source@mvista.com, mais on pourra trouver d'autres variantes ailleurs pour un résultat analogue. Si on utilise Python 2.4 on mettra par exemple ce script à un endroit acccessible par défaut par Python, par exemple ici : /usr/lib/python2.4/DialogueDossiersJmm.py .
L'objectif de ce script est de gérer une fenêtre de navigation dans des dossiers pour en sélectionner un; on pourra s'en servir comme brique de base dans d'autres outils. Exemple de présentation :
#!/usr/bin/python # -*- coding: iso-8859-1 -*- # # Le fichier d'origine a été modifié par : Jeanmm # Date : 6/3/2005 # Modifs : Traduction en Français + corrections de focus # Licence : GPL # # FILE: DirBrowser.py # # DESCRIPTION: # This file provides a generic Directory browser selection widget. # # AUTHOR: MontaVista Software, Inc. <source@mvista.com> # # Copyright 2001 MontaVista Software Inc. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 675 Mass Ave, Cambridge, MA 02139, USA. # import os import Tkinter import Pmw class DirBrowserDialog(Pmw.MegaToplevel): "Fenêtre de parcours de dossiers" def __init__(self, parent = None, **kw): cwd = os.getcwd() # Define the megawidget options. INITOPT = Pmw.INITOPT optiondefs = ( ('path', cwd, None), ('hidedotfiles', 1, INITOPT), ('label', None, INITOPT), #('labelmargin', 0, INITOPT), #('labelpos', None, INITOPT), ('borderx', 20, INITOPT), ('bordery', 20, INITOPT), ) self.defineoptions(kw, optiondefs) # Initialise the base class (after defining the options). Pmw.MegaToplevel.__init__(self, parent) interior = self.interior() self.transient(parent) self.ppath=os.sep # previous path for loop, and root path self.childframe = self.createcomponent('childframe', (), None, Tkinter.Frame, (interior,), borderwidth = 1, relief = 'raised', ) self.childframe.pack(expand = 1, fill = 'both', ) self.labelframe = self.createcomponent('labelframe', (), None, Tkinter.Frame, (self.childframe,), borderwidth = 2, relief = 'groove', ) self.labelframe.pack(padx = 10, pady = 10, expand = 1, fill = 'both') if self['label']: self.label = self.createcomponent('label', (), None, Tkinter.Label, (self.childframe,), text = self['label'], ) self.label.place(x = (10 + self['borderx']), y = 10, anchor = 'w') self.workframe = self.createcomponent('workframe', (), None, Tkinter.Frame, (self.labelframe,), #borderwidth = 2, #relief = 'groove', ) self.workframe.pack(padx = self['borderx'], pady = self['bordery'], expand = 1, fill = 'both', ) self.buttonframe = self.createcomponent('buttonframe', (), None, Tkinter.Frame, (interior,), borderwidth = 1, relief = 'raised', ) self.buttonframe.pack(expand = 0, fill = 'x', ) self.optbox = self.createcomponent('optbox', (), None, Pmw.OptionMenu, (self.workframe,), command = self.setpath, ) self.optbox.bind('<Configure>', self._setMinimumSize) self.listbox = self.createcomponent('listbox', (), None, Pmw.ScrolledListBox, (self.workframe,), dblclickcommand = self._select, ) path = self['path'] self.entry = self.createcomponent('entryfield', (), None, Pmw.EntryField, (self.workframe,), value = path, entry_bg = 'white', command = self.enteredpath, labelpos = 'nw', label_text = 'Dossier courant :', ) #self.createlabel(self.workframe, childCols = 1, childRows = 3) self.buttonbox = self.createcomponent('buttonbox', (), None, Pmw.ButtonBox, (self.buttonframe,), ) self.buttonbox.add('OK', text = 'OK', command = self.okbutton) self.buttonbox.add('Annuler', text = 'Annuler', command = self.cancelbutton) self.buttonbox.add('Nouveau Dossier', text = 'Nouveau Dossier', command = self.newdirbutton) self.buttonbox.alignbuttons() self.buttonbox.pack(expand = 1, fill = 'x') self.optbox.grid(row = 2, column = 2, sticky = 'ew') self.listbox.grid(row = 3, column = 2, sticky = 'news') self.entry.grid(row = 5, column = 2, sticky = 'ew') self.workframe.grid_rowconfigure(3, weight = 1) self.workframe.grid_rowconfigure(4, minsize = 20) self.workframe.grid_columnconfigure(2, weight = 1) self.setpath(self['path']) # Check keywords and initialise options. self.initialiseoptions() def setpath(self, path): path = os.path.abspath(os.path.expanduser(path)) if os.path.isfile(path): path = os.path.dirname(path) dirlist = [] hidedotfiles = self['hidedotfiles'] try: posix = (os.name == 'posix') for entry in os.listdir(path): entryPath = path + os.sep + entry if hidedotfiles and entry[0] == '.': # skip dot files if desired continue if not os.path.isdir(entryPath): # skip files continue if not os.access(entryPath, os.R_OK | os.X_OK): # skip directories we can't enter any way continue dirlist.append(entry) except: self.entry.setentry(self['path']) return self.entry.setentry(path) self['path'] = path pathlist = [] self.ppath='' # previous path (to stop loop, good for linux & windows) while path != self.ppath: pathlist.append(path) self.ppath=path path = os.path.dirname(path) # self.ppath = root path after loop self.optbox.setitems(pathlist, 0) dirlist.sort() if self['path'] != self.ppath: dirlist.insert(0, '..') self.listbox.setlist(dirlist) def _setMinimumSize(self, event): # If the optionmenu changes width, make sure it does not # shrink later. owidth = self.optbox.winfo_width() self.workframe.grid_columnconfigure(2, minsize = owidth) def _select(self): sel = self.listbox.getcurselection() if self['path'] == self.ppath: self['path'] = '' if len(sel) > 0: if sel[0] == '..': self.setpath(os.path.dirname(self['path'])) else: self.setpath(self['path'] + os.sep + sel[0]) def getcurpath(self): return self['path'] def enteredpath(self): self.setpath(self.entry.get()) def okbutton(self): self.deactivate(self['path']) def cancelbutton(self): self.deactivate(None) def newdirbutton(self): CreateDirectoryPopup(self.interior(), self['path']) self.setpath(self['path']) class CreateDirectoryPopup: def __init__(self, parent, path): self.path = path self.parent = parent self.newdirpopup = Pmw.PromptDialog(parent, buttons = ('OK', 'Annuler'), defaultbutton = 'OK', title = 'Nouveau Dossier', entryfield_labelpos = 'nw', label_text = 'Création de nouveau dossier dans\n%s\n' %self.path, command = self._buttonpress ) self.newdirpopup.transient(parent) self.newdirpopup.activate() self.parent.focus_set() def _buttonpress(self, button): if button == 'OK': newdirname = self.newdirpopup.get() dirlist = os.listdir(self.path) if newdirname in dirlist: ErrorPopup(self.parent, 'Erreur: "%s" existe déjà !'%newdirname) else: try: os.mkdir(self.path + os.sep + newdirname) except: ErrorPopup(self.parent, 'Erreur: création impossible de "%s"' %newdirname) else: self.newdirpopup.deactivate() else: self.newdirpopup.deactivate() def ErrorPopup(parent, message): error = Pmw.MessageDialog(parent, title = 'Erreur', message_text = message, defaultbutton = 0, ) error.activate() self.parent.focus_set() if __name__ == '__main__': rootWin = Tkinter.Tk() Pmw.initialise() rootWin.title('Demo') def buildBrowser(): # Create the hierarchical directory browser widget dirBrowserDialog = DirBrowserDialog(rootWin, #labelpos = 'nw', label = '', title = 'Sélection de dossier', #path = '~', #hidedotfiles = 0, ) dir = dirBrowserDialog.activate() rootWin.focus_set() print 'Dossier selectionne :', dir dirButton = Tkinter.Button(rootWin, text="Choisir un dossier", command=buildBrowser) dirButton.pack(side = 'left', padx = 10, pady = 10) exitButton = Tkinter.Button(rootWin, text="Quitter", command=rootWin.quit) exitButton.pack(side = 'left', padx = 10, pady = 10) rootWin.mainloop()