Wiki Root66

Le Wiki de Root66, tuto, infos et astuces

Outils pour utilisateurs

Outils du site


modifier_des_images_jpeg

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 :

:wiki-modif-images1.jpg

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 8-)
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 :

:wiki-modif-images12.jpg

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) :

:wiki-modif-images4.jpg :wiki-modif-images5.jpg

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 :

:wiki-modif-images8.jpg

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 :

:wiki-modif-images10.jpg

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 :

:wiki-modif-images6.jpg

#!/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()
modifier_des_images_jpeg.txt · Dernière modification : 2021/10/18 16:09 de 127.0.0.1