Table des matières
Introduction
Les scripts Python et Bash présentés dans cet article servent à créer des diaporamas à partir d'images JPEG. Ces outils sont très proches de celui présenté ici : Modifier des images JPEG. Il est plus que recommandé de commencer par lire et appliquer cet article pour les raisons suivantes :
- Les images produites sont directement utiles à la création des diaporamas,
- Les logiciels nécessaires (Python, Pmw, fenêtres de dialogues) sont exactement les mêmes, donc commencer par les installer en suivant les conseils correspondants.
Cet article se bornera donc à présenter les compléments. En fait on va présenter 2 outils légèrement distincts, identiques du point de vue aspect, mais avec des nuances de traitements :
- Le 1er pourra produire des diaporamas dont on spécifiera la durée de chaque diapo en secondes, avec possibilité d'ajoûter des diapos à un film existant, et estimation/mesure de la durée nécessaire à la création du diaporama;
- Le 2ème pourra produire des diaporamas dont on spécifiera la durée de chaque diapo en nombre de “frames” (images), sachant qu'il y en a 25 par seconde, et possibilité de tronquer la durée du film si la bande sonore est plus longue que les diapos. On a donc là un outil pour adapter très précisément les durées des diapos, par exemple pour que la durée totale s'adapte à une bande sonore donnée avec une extrême précision. Par contre on ne pourra pas prolonger un diaporama existant comme précédemment.
Dans les 2 cas on pourra ensuite graver un CD au format SVCD, format lisible par certains lecteurs DVD de salon (mais pas tous !). Le SVCD semble être très prisé notamment en Chine, car il permet de diffuser des films sur supports CD, moins chers et plus répandus, au détriment d'une qualité moindre. La résolution des images est cependant adaptée aux téléviseurs classiques actuels. Sinon les diaporamas produits sont lisibles par tout logiciel multimédia Linux (Totem, Mplayer, Amarok, Xine, etc…).
L'article peut paraître long et compliqué, mais en fait l'essentiel consiste à effectuer quelques copier/coller dans des fichiers. Ceux qui voudront en savoir plus pourront bien sûr décortiquer les commandes d'appel des différents logiciels mis en oeuvre, de quoi passer un moment assez long et ardu… Pour les pressés qui n'ont pas besoin d'IHM ni des options annexes de gravure de CD, les scripts bash principaux seront suffisants, et on pourra même les simplifier un peu.
Article rédigé par Jeanmm
Version initiale : 14/8/2006.
Solution proposée
On va donc proposer ici un jeu de scripts Python et Bash qui vont nous permettre de réaliser les outils suivants :
Marrant, non ? On dirait un jeu des 7 différences
Pré-requis
Logiciels
Les logiciels suivants doivent être disponibles :
- Python, Pmw, scripts de sélection de fichier et de dossier : voir le détail dans l'article Modifier des images JPEG,
- ImageMagick (outils convert, identify et composite),
- mjpegtools (outils jpeg2yuv, yuvscaler, mpeg2enc, mplex),
- vcdimager, cdrecord, cdrdao,
- éventuellement : audacity, Gimp,
- lecteur vidéo (totem, mplayer,…).
On vérifiera si les paquetages correspondants sont disponibles dans sa distribution, sinon on pourra fouiller sur le net pour se procurer ces logiciels. Pour ceux qui veulent en savoir un peu plus sur ces outils, voici quelques liens :
Pour ceux qui ont une Mandriva, étudier aussi le script images2mpg; il n'a été découvert qu'après rédaction de ces scripts. Il se peut que ce script existe aussi dans d'autres distributions.
Images de fond et sons
Il est de plus nécessaire de disposer de 2 fichiers particuliers dans le dossier de travail (dans les scripts qui suivent ce dossier s'appelle Diaporama dans le home de l'utilisateur) :
- un fichier sonore, dont les scripts supposent disposer, sous le nom “musique.mp2” : attention, il s'agit bien d'un format mp2, beaucoup plus fiable que le mp3 pour ce genre de manips (certains fichiers mp3 fonctionnent, mais pas tous, l'outil de multiplexage mplex étant relativement exigeant sur les propriétés de la bande sonore à ajoûter aux images);
- un fond d'écran sur lequel chaque diapo sera superposée; ici on doit avoir un tel fond dans un fichier nommé “fond720x576.ppm”; il est par exemple très facile de se créer un tel fond avec Gimp en étalant un motif ou une couleur de fond sur une nouvelle image de la bonne taille (720 de large, 576 de haut) et enregistrée au format ppm.
Pour obtenir un fichier mp2 à partir d'un ficher wav, on pourra utiliser la commande bash suivante :
cat fichier.wav | mp2enc -v 1 -V -o fichier.mp2
Et si on part d'un fichier mp3 on pourra d'abord le convertir en wav avec audacity, puis du wav en mp2 avec la manip ci-dessus. Audacity est un superbe outil d'édition de fichiers musicaux, à essayer !
Les scripts présentés dans cet article utilisent des noms de fichiers fixes, mais rien n'empêche de les adapter à ses besoins, par exemple en rendant ces données aussi configurables.
Images pour diapos
Les images à transformer en diapos doivent être copiées dans le dossier de travail avant d'utiliser les scripts. Tous les fichiers de suffixes JEPG, JPG, jpeg et jpg seront pris, renommés en jpg, avec suppression d'éventuels blancs dans les noms, puis triés alphabétiquement (ces traitements pourront être supprimés des scripts si on n'en a pas besoin).
On pourra se servir de l'outil Modifier des images JPEG pour modifier les noms des images en AAMMJJ-HHMMSS.jpg, ce qui fera correspondre le tri alphabétique au tri par dates.
Script Bash principal 1
Avant d'utiliser les scripts de manière routinière, il est nécessaire de les tester en mode ligne de commande, en ne mettant que 2 images dans le dossier de travail (pas la peine de perdre du temps…).
La plupart des boutons des fenêtres présentées ci-dessus déclenchent des scripts Bash dont le déroulement sera visible dans la zone de texte en bas de la fenêtre. Commençons donc par le script bash principal du 1er outil (configuration en secondes); on copiera en root le texte suivant dans /usr/local/bin/diapo-Secondes.sh :
#!/bin/bash ################################################################### # Script pour créer un diaporama MPEG / SVCD à partir d'images JPEG ################################################################### # On traite tous les fichiers.jpg d'un dossier accessible en écriture. # (en fait tous fichiers de suffixes .jpg, .jpeg, .JPG et .JPEG) # Auteur : Jeanmm # Date : août 2006 # Licence : GPL # Lien : www.root66.net # Consommations en fichiers (estimations) : # - en images cibles de taille normale : # - Ficher.mpg : 0.13 Mo/s, soit 0.67 Mo / image de 5s # - en images cibles de demi-taille : # - Ficher.mpg : 0.12 Mo/s, soit 0.62 Mo / image de 5s # 3 paramètres possibles : # - durée de chaque diapo # - nom du dossier des images et films # - oui/non pour prolonger un film existant echo "" echo "Creation de film mpg a partir d'images jpg" echo "------------------------------------------" echo "" # Dossier des images jpg et des vidéos créées (param 2 si existant) # ----------------------------------------------------------------- if [ ! 0$2 == 0 ] ; then cd $2 fi echo "Dossier des images et videos : $PWD" # Dossier des fichiers utiles autres # ---------------------------------- # (images de fond, musiques, fichiers temporaires...) # On pourra prévoir un dossier à part dfu=$PWD # Taille des images cibles PAL/SECAM : # ------------------------------------ # mettre un commentaire devant les lignes non souhaitées; # la taille normale sert aux films SVCD, de format "-f 4"; # la demie-taille sert aux films VCD, de format "-f 1". # Taille normale = 720x576, avec marges de 10+10 tailleM=720x576 taille=700x556 tailleX=700 tailleY=556 # Demi-taille totale = 352x288, avec marges de 10+10 #tailleM=352x288 #taille=332x268 #tailleX=332 #tailleY=268 # IMPORTANT : des fichiers fond$tailleM.ppm doivent exister # dans le dossier $dfu, de taillesM exactes ci-dessus. # Par ailleurs il faut y mettre aussi un fichier musique.mp2 # pour l'ajoût de musique de fond. # Suppression des blancs dans les noms des fichiers # ------------------------------------------------- for a in `find . -maxdepth 1 -type f -name "*"` ; do b=$(echo "$a" | sed -e 's/[[:blank:]]/_/g') if [ ! -e "$b" ] ; then echo "mv \"$a\" $b" mv "$a" $b fi done # On renomme les fichiers .JPG , .JPEG et .jpeg en .jpg # ----------------------------------------------------- for a in `find . -maxdepth 1 -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 1 -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 1 -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 # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth 1 -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg dans le dossier ?" && exit 0 echo "Nombre total d'images : $countT" # Durée de chaque diapo en secondes # --------------------------------- # (valeur modifiable par paramètre d'appel N° 1 sinon par question) duree=8 if [ ! 0$1 == 0 ] ; then duree=$1 else echo "" echo -n "Choisissez la durée des diapos (${duree} secondes par defaut) : " read duree2 if [ ! "x${duree2}" == "x" ] ; then duree=$duree2 fi fi nbsec=$[ $duree * $countT ] nbmin=`echo $nbsec | awk '{printf "%.1f", $1/60}'` echo "Duree de chaque diapo : $duree secondes, soit en tout $nbsec secondes / $nbmin minutes" # Test si film existant # --------------------- # si param 3 on ne pose pas la question de garder un film existant if [ -f diaporama.mpg ] ; then ooo=`ls -la diaporama.mpg | awk '{printf "%.1f", $5/1024/1024}'` if [ -f diaporama-duree.txt ] ; then # nbsec0 = nb de secondes déjà disponibles nbsec0=`tail -n 1 diaporama-duree.txt` nbmin0=`echo $nbsec0 | awk '{printf "%.1f", $1/60}'` echo "Le film diaporama.mpg existe deja : $ooo Mo / $nbsec0 secondes / $nbmin0 minutes" else # on suppose qu'un Mo correspond à 8 secondes nbsec0=`echo $ooo | awk '{printf "%d", $1*8}' ` nbmin0=`echo $nbsec0 | awk '{printf "%.1f", $1/60}'` echo "Le film diaporama.mpg existe deja : $ooo Mo" echo "duree estimee : $nbsec0 secondes / $nbmin0 minutes" fi if [ ! 0$3 == 0 ] ; then ooo=$3 else echo -n "==> tapez 'oui' pour le prolonger : " read ooo fi if [ ! "x$ooo" == "xoui" ] ; then rm -f diaporama.mpg rm -f diaporama-duree.txt 2>/dev/null nbsec0=0 fi else nbsec0=0 fi # inits divers # ------------ # nb total d'images vidéo (25 par seconde) nbframes=$[ $duree * 25 ] rm -f $dfu/diaporama.log $dfu/video*.mpg $dfu/diaporama-x*.mpg diaporama-SVCD.mpg 2>/dev/null # Calculs de temps estimatifs # --------------------------- # unite en secondes, unite100 = 100 * unite (entier) if [ -f $dfu/diaporama-unite-calcul.txt ] ; then unite100=`cat $dfu/diaporama-unite-calcul.txt` unite=`echo "$unite100" | awk '{printf "%.2f", $1/100}'` else unite=6 unite100=600 echo "600" >$dfu/diaporama-unite-calcul.txt fi echo "--------------------------------------------------------------------------" nbest=`echo "$nbsec $unite100" | awk '{printf "%.0f", $1*$2/100}'` nbest2=`echo "$nbsec $unite100" | awk '{printf "%.1f", $1*$2/6000}'` echo "Duree de traitement estimee : $nbest secondes / $nbest2 minutes," echo "avec ${unite} secondes de traitement par seconde de film." h1=`date +"%H"` m1=`date +"%M"` s1=`date +"%S"` st=`echo "$h1 $m1 $s1 $nbest" | awk '{printf "%.2d", $1*3600+$2*60+$3+$4}'` h2=`echo "$st" | awk '{printf "%0.2d", $st/3600}'` m2=`echo "$st $h2" | awk '{printf "%0.2d", ($1-$2*3600)/60}'` s2=`echo "$st $h2 $m2" | awk '{printf "%0.2d", $1-$2*3600-$3*60}'` echo "Il est ${h1}h ${m1}m ${s1}s ==> fin estimee à ${h2}h ${m2}m ${s2}s." echo "----------------------------------------------------------" # Boucle sur chaque image # ----------------------- # Les images sont triées par noms, on a donc par exemple intérêt à les # avoir nommées "AAMMJJ-HHMMSS.jpg" count=0 for ii in `find . -maxdepth 1 -type f -iname "*.jpg" | sort` ; do counti=$[$count + 1] # Conversion de taille d'image echo "Conversion de l'image `printf "%3.3d" $counti`/`printf "%3.3d" $countT` : $ii" convert -resize $taille $ii /tmp/tempo1.jpg 2>>$dfu/diaporama.log # calculs pour centrer l'image sur le fond, avec marges +10+10 x=`identify -format '%w' /tmp/tempo1.jpg` y=`identify -format '%h' /tmp/tempo1.jpg` dx=$[ ( $tailleX - $x ) / 2 + 10 ] dy=$[ ( $tailleY - $y ) / 2 + 10 ] # fusion image + fond => tempo2.jpg composite -geometry +$dx+$dy /tmp/tempo1.jpg $dfu/fond$tailleM.ppm /tmp/tempo2.jpg 2>>$dfu/diaporama.log # Conversion en film élémentaire => $dfu/videoXXX.mpg jpeg2yuv -v 0 -f 25 -n 1 -l $nbframes -I p -j /tmp/tempo2.jpg 2>>$dfu/diaporama.log | yuvscaler -v 0 -O SVCD 2>>$dfu/diaporama.log | mpeg2enc -v 0 -a 2 -f 4 -V 230 --correct-svcd-hds --interlace-mode 1 -o $dfu/video`printf "%3.3d" $count`.mpg 2>>$dfu/diaporama.log count=$[$count + 1] done # Concaténation des $dfu/videoXXX.mpg => diaporama.mpg ; le fait # de mettre >> permet l'ajout si le fichier existe. echo "Creation des videos" cat $dfu/video*.mpg >>diaporama.mpg && rm -f $dfu/video*.mpg 2>/dev/null # Ajout de son (seul le mp2 semble convenir; voir fin de script) # le son est obligatoire pour concaténer proprement et autoriser # les étapes ultérieures vcdimager/cdrdao pour la gravure. nbsecT=$[ $nbsec+$nbsec0 ] echo "$nbsecT" >diaporama-duree.txt mplex -f 4 -l $nbsecT -b 230 $dfu/musique.mp2 diaporama.mpg -o $dfu/diaporama-x%3.3d.mpg -v 1 2>>$dfu/diaporama.log # concaténation des portions (en nombre aléatoire) cat $dfu/diaporama-x*.mpg >diaporama-SVCD.mpg && rm -f $dfu/diaporama-x*.mpg 2>/dev/null # Suppression de log si vide test -s $dfu/diaporama.log || rm -f $dfu/diaporama.log # Temps final et ajustement de l'estimation future # ------------------------------------------------ echo "----------------------------------------------------------" h3=`date +"%H"` m3=`date +"%M"` s3=`date +"%S"` echo "Il est ${h3}h ${m3}m ${s3}s" st2=`echo "$h1 $m1 $s1 $h3 $m3 $s3" | awk '{printf "%d", ($4-$1)*3600+($5-$2)*60+($6-$3)}'` if [ "$st2" != "$nbest" ] ; then echo "Duree necessaire : $st2 secondes aulieu de $nbest secondes," unite2=`echo "$st2 $nbsec" | awk '{printf "%.2f", $1/$2}'` unite200=`echo "$st2 $nbsec" | awk '{printf "%d", $1/$2*100}'` err=`echo "$st2 $nbest" | awk '{printf "%+.1f", ($1-$2)*100/$2}'` echo "$unite200" >$dfu/diaporama-unite-calcul.txt echo "soit ${unite2} secondes de traitement / seconde de film" echo "l'estimation de temps etait juste à ${err}% pres" else echo "L'estimation de temps etait correcte !" fi echo "----------------------------------------------------------" ooo=`ls -la diaporama.mpg | awk '{printf "%.1f", $5/1024/1024}'` echo "Film de base : diaporama.mpg, de taille $ooo Mo" ooo=`ls -la diaporama-SVCD.mpg | awk '{printf "%.1f", $5/1024/1024}'` echo "Film SVCD : diaporama-SVCD.mpg, de taille $ooo Mo" nbmin=`echo $nbsecT | awk '{printf "%.1f", $1/60}'` echo "Duree totale : $nbsecT secondes / $nbmin minutes" echo "Le film diaporama-SVCD.mpg peut etre grave." echo "Traitement termine :)" exit 0 # Infos diverses : # Comment obtenir du mp2 à partir de wav : # cat musique.wav | mp2enc -v 1 -V -o musique.mp2 # Conversion de mp3 en wav : par audacity (export wav) ou ripage konqueror
On rendra le script exécutable, par exemple par commande :
chmod +x /usr/local/bin/diapo-Secondes.sh
Si on a préparé tous les fichiers dans le dossier de travail (image de fond, fichier de musique, images à convertir en diapos), lancer le script en ligne de commande pour vérifier le déroulement. A noter que les erreurs sont mises dans un fichier diaporama.log.
Si tout se déroule bien on aura les diaporamas suivants dans le dossier :
- diaporama.mpg : succession d'images sans le son;
- diaporama-SVCD.mpg : succession d'images avec le son;
L'option qui permet d'ajoûter des diapos à un diaporama existant consiste à étendre le fichier diaporama.mpg puis à recréer complètement le fichier diaporama-SVCD.mpg à partir du nouveau diaporama.mpg. A noter qu'un fichier diaporama.mpg créé à partir du 2ème outil qu'on présentera ci-dessous est tout-à-fait compatible et peut aussi être étendu grâce à ce 1er script. D'ailleurs on pourrait créer un outil unique regroupant toutes les options des 2 outils.
On pourra tester le résultat en démarrant un lecteur multimédia sur ces fichiers. Si tout est OK, on pourra passer à l'étape suivante, l'encapsulation de ce script dans l'outil Python avec fenêtre graphique.
Script Python principal 1
Avant d'utiliser les scripts Python de manière routinière, il est aussi nécessaire de les tester en mode ligne de commande.
Pour le script Python principal du 1er outil (configuration en secondes); on copiera en root le texte suivant dans /usr/local/bin/diapo-Secondes.py :
#!/usr/bin/python # -*- coding: iso-8859-1 -*- # Ce script Python affiche une fenêtre de paramétrage et de lancement de scripts bash # qui permettent de créer un diaporama à partir d'images JPG, et éventuellement # un CD de type SVCD contenant ce diaporama. # # Auteur : Jeanmm # Date : août 2006 # Licence : GPL # Lien : www.root66.net import sys, os, string from Tkinter import * import Pmw import tkMessageBox from DialogueDossiersJmm import DirBrowserDialog from DialogueFichiersEtDossiersJmm import PmwFileDialog # Données générales # ----------------- # Titre de la fenetre MonTitre1 = "Creation de diaporama" # Dossier par défaut MonDossier = os.path.join(os.getenv('HOME'), 'Diaporama') # Logos pour agrementer la fenetre MonLogoDossier = '/usr/local/divers/LogoDossier.gif' MonLogo = '/usr/local/divers/decor.gif' # Les scripts bash à exécuter MonScript0 = 'diapo-test.sh' MonScript1 = 'diapo-Secondes.sh' MonScript2 = 'cd-effacement' MonScript3 = 'cd-gravure-SVCD' MonScript4 = 'cd-ejection' # Option générale de listage de numéros de lignes NumLi = 0 # 0=non, 1=oui ii=0 # Nom de l'option choisie (prolonger ou créer film) nomOption='oui' # 'oui' ==> on prolonge # Dossiers accessibles cycliquement par F3 Dossiers=['','',''] # dossier courant, courant au lancement, et home NumDos=0 # Sous-programmes divers # ====================== # Parcours cyclique de 3 noms de dossiers par F3 # ---------------------------------------------- def f3(ev=None): global NumDos NumDos=NumDos+1 if NumDos==3: NumDos=0 entree1.delete(0,END) entree1.insert(END, Dossiers[NumDos]) # Parcours de dossiers général 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']="Le Dossier n'existe pas ?!?" entree1.focus_set() entree1.xview(END) return dirBrowserDialog = DirBrowserDialog(fen1, label = '', path = entree1.get(), title = 'Selection de dossier') dir = dirBrowserDialog.activate() fen1.focus_set() #print 'Dossier sélectionné :', dir if dir != None: entree1.delete(0,END) entree1.insert(END, dir) entree1.xview(END) entree1.focus_set() # Recherche de fichiers JPG par F5 #--------------------------------- def f5(ev=None): texte2['text']="" f0=PmwFileDialog(fen1) f0.title("Listage des noms d'images.jpg") fname=f0.askfilename(directory=entree1.get(), filter='*.jpg') # Rafraichissement de fenêtre en cas de déplacement # ------------------------------------------------ def rafraichissement(ev): fen1.update_idletasks() # Lecture d'une ligne de fichier # ------------------------------ # Comme readline, mais en plus de '\n' on teste '\r'. # Permet de découper les sorties du programme de gravure # en plusieurs lignes écrites au fur et à mesure, par exemple : # Wrote 1 of 4 MB (Buffers 100% 99%). # Wrote 2 of 4 MB (Buffers 100% 99%). # Wrote ... # aulieu d'une ligne unique en fin de gravure : # Wrote 1 of 4 MB (Buffers 100% 99%).\rWrote 2 of 4 MB (Buffers 100% 99%).\rWrote ... def lecture_ligne(): global fout # fichier lu (stdout de script en cours) txt=fout.read(1) # lecture/test de chaque caractère ligne='' # ligne couramment lue while txt: if txt=='\n': ligne = ligne + txt break elif txt=='\r': ligne = ligne + '\n' break else: ligne = ligne + txt txt=fout.read(1) # caractère suivant return ligne # quitter le programme # -------------------- # on simule le clic sur le bouton Quitter quand il est sélectionné + touche entrée def quitter(ev=None): bouton3.invoke() # Sous-programmes de Traitement # ============================= # Test des options et fichiers # ---------------------------- def tests_options(ev=None): global ii # Numéro de ligne global nomOption # Nom de l'option choisie global Dossiers # Noms dossiers texte2.configure(fg='red', font=('Times', 12, 'bold')) # Option choisie if nomOption=='': texte2['text']="Choisissez d'abord une option" return texte2['text']='Tests en cours...' fen1.update_idletasks() # Traitement du dossier try: os.chdir(entree1.get()) except: ii=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() # Durée de chaque diapo try: dureeDiapo = int(entree2.get()) except: ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) ligne="La duree de chaque diapo est invalide !?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text']="La duree de chaque diapo est invalide ?!?" entree2.focus_set() return if dureeDiapo < 1: # Nb >= 1 dureeDiapo=1 entree2.delete(0,END) entree2.insert(END, '1') # Lancement du script 0 avec arg dossier fin,fout=os.popen4(MonScript0 + ' ' + entree1.get()) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) texte2['text']='OK - ' while txt: txt=fout.readline() ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne='%s' %(txt) else: ligne='\n' if ligne[0:16]=='Aucune image.jpg': 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() MonFic=entree1.get() if MonFic[-1:0]=='/': Pass else: MonFic = MonFic + '/' MonFic = MonFic + 'diaporama.mpg' if os.path.isfile(MonFic): texte2['text']= texte2['text'] + "le film diaporama.mpg existe" else: texte2['text']= texte2['text'] + "le film diaporama.mpg n'existe pas" zoneListage.see(END) fen1.update_idletasks() # Création de diaporama # --------------------- def creation_diaporama(ev=None): global ii # Numéro de ligne global nomOption # Nom de l'option choisie global Dossiers # Noms dossiers texte2.configure(fg='red', font=('Times', 12, 'bold')) # Option choisie if nomOption=='': texte2['text']="Choisissez d'abord une option" return texte2['text']='Creation de diaporama en cours...' fen1.update_idletasks() # Traitement du dossier try: os.chdir(entree1.get()) except: ii=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() # Durée de chaque diapo try: dureeDiapo = int(entree2.get()) except: ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) ligne="La duree de chaque diapo est invalide !?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text']="La duree de chaque diapo est invalide ?!?" entree2.focus_set() return if dureeDiapo < 1: # Nb >= 1 dureeDiapo=1 entree2.delete(0,END) entree2.insert(END, '1') # Lancement du script avec args duree + dossier + option oui/non fin,fout=os.popen4(MonScript1 + ' ' + str(dureeDiapo) + ' ' + entree1.get() + ' ' + nomOption) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=fout.readline() ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne='%s' %(txt) else: ligne='\n' if ligne[0:16]=='Aucune image.jpg': texte2['text']='Aucune image.jpg dans le dossier ?' zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) fin.close() fout.close() return else: zoneListage.insert(END, ligne) zoneListage.see(END) fen1.update_idletasks() fin.close() fout.close() texte2['text']='OK - diaporama cree' fen1.update_idletasks() # Effacement d'un CD-RW # --------------------- def effacer_cd(ev=None): global ii # Numéro de ligne texte2.configure(fg='red', font=('Times', 12, 'bold')) texte2['text']='Effacement de CD-RW en cours...' fen1.update_idletasks() if tkMessageBox.askokcancel("Effacement de CD", "Confirmez ou annulez l'effacement"): pass else: texte2['text']='OK' fen1.update_idletasks() return # Lancement du script fin,fout=os.popen4(MonScript2) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=fout.readline() ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne='%s' %(txt) else: ligne='\n' if txt[0:17]=='cdrecord: No disk': texte2['text']='Pas de CD dans le graveur ?' txt='Pas de CD dans le graveur ?\n' zoneListage.insert(END, txt,'rouge') zoneListage.see(END) fin.close() fout.close() return else: zoneListage.insert(END, ligne) zoneListage.see(END) fen1.update_idletasks() fin.close() fout.close() ligne='OK - CD efface\n' zoneListage.insert(END, ligne) zoneListage.see(END) texte2['text']='OK - CD efface' fen1.update_idletasks() # Gravude d'un CD-SVCD # -------------------- def graver_SVCD(ev=None): global ii # Numéro de ligne global fout # fichier stdout du script texte2.configure(fg='red', font=('Times', 12, 'bold')) MonFic = entree1.get() + '/diaporama-SVCD.mpg' if os.path.isfile(MonFic): pass else: texte2['text']= "le film " + MonFic + " n'existe pas ?" return texte2['text']='Gravure de SVCD en cours...' fen1.update_idletasks() # Lancement du script fin,fout=os.popen4(MonScript3 + ' ' + MonFic) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=lecture_ligne() # lecture perso avec test '\r' ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne='%s' %(txt) else: ligne='\n' if ligne[0:8]=='++ WARN:': pass elif ligne[0:23]=='WARNING: Unit not ready': texte2['text']='Pas de CD dans le graveur ?' ligne='Pas de CD dans le graveur ?\n' zoneListage.insert(END, ligne,'rouge') zoneListage.see(END) fin.close() fout.close() return elif ligne[0:24]=='Disk seems to be written': texte2['text']='Le CD semble deja grave ?' ligne='Le CD semble deja grave ?\n' zoneListage.insert(END, ligne,'rouge') zoneListage.see(END) fin.close() fout.close() return elif ligne[-2:]=='\\r': ligne=ligne[0:-2] + '\n' zoneListage.insert(END, ligne) else: zoneListage.insert(END, ligne) zoneListage.see(END) fen1.update_idletasks() fin.close() fout.close() texte2['text']='OK - CD grave' fen1.update_idletasks() # Ejection d'un CD # -------------------- def ejecter_CD(ev=None): global ii # Numéro de ligne texte2.configure(fg='red', font=('Times', 12, 'bold')) texte2['text']='Ejection de CD en cours...' fen1.update_idletasks() # Lancement du script fin,fout=os.popen4(MonScript4) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=fout.readline() ligne='OK, CD ejecte\n' zoneListage.insert(END, ligne) zoneListage.see(END) fin.close() fout.close() texte2['text']='OK, CD ejecte' fen1.update_idletasks() # ========================================================= # ----------- Programme principal ----------- # ========================================================= # Instanciation d'une fenêtre Pmw (Python méga-widgets) # ----------------------------------------------------- fen1 = Pmw.initialise() fen1.title(MonTitre1) fen1.bind("<Alt-F4>", quitter) fen1.bind("<Configure>", rafraichissement) # 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) # creation des widgets de configuration/lancement des scripts # =========================================================== # Ligne 1 # ------- # dossier des images/films texte1 = Label(fen1, text=' Dossier :') texte1.grid(row=1, column=1, sticky=E, padx=5, pady=5) # Saisie de nom de dossier # alternatives : f3 pour 3 valeurs prédéfinies, f4 pour dialogue entree1 = Entry(fen1, width=70, bg='white') entree1.grid(row=1, column=2, columnspan=2, sticky=W, padx=5, pady=5) entree1.insert(END, MonDossier) 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=4, sticky=W, padx=0, pady=3) bouton0.bind("<Return>",f4) # Ligne 2 # ------- # durée de chaque diapo texte2 = Label(fen1, text=' Duree :') texte2.grid(row=2, column=1, sticky=E, padx=5, pady=5) entree2 = Entry(fen1, width=4, bg='white') entree2.grid(row=2, column=2, sticky=W, padx=5, pady=5) entree2.insert(END, '6') # Ligne 3 # ------- # option de prolongement/écrasement de film texte3 = Label(fen1, text=' Option :') texte3.grid(row=3, column=1, sticky=E, padx=5, pady=5) class ZoneRadio(Frame): """Utilisation de widgets 'boutons radio'""" def __init__(self, boss=None): """Création d'un champ d'entrée avec 2 boutons radio""" Frame.__init__(self) self.pack() self.configure(relief=GROOVE, bd=2) # Nom français et nom technique des quatre styles de police : texteOption =["Prolonger le film existant", "Creer un nouveau film"] valeurOption =["oui", "non"] # Le style actuel est mémorisé dans un 'objet-variable' Tkinter ; self.opt = StringVar() self.opt.set(valeurOption[0]) # Création des boutons radio for n in range(2): bout = Radiobutton(self, text = texteOption[n], variable = self.opt, value = valeurOption[n], command = self.modifOption) bout.pack(side=LEFT, padx=5) def modifOption(self): global nomOption nomOption = self.opt.get() zoneradio1=ZoneRadio() zoneradio1.grid(row=3, column=2, columnspan=2, sticky=W, padx=5, pady=5) # Ligne 4 # ------- # messages divers texte2 = Label(fen1, text='Dossier courant : ' + os.getcwd()) texte2.grid(row=4, column=2, columnspan=2, padx=5, pady=5) # Ligne 5 # ------- # boutons d'actions pour diaporama et bouton quitter # bouton de lancement de tests bouton1 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=30, text="Tester les options et fichiers", command=tests_options) bouton1.grid(row=5, column=2, padx=5, pady=5) bouton1.bind("<Return>",tests_options) # bouton de création de diaporama bouton2 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=30, text="Creer un Diaporama", command=creation_diaporama) bouton2.grid(row=5, column=3, padx=5, pady=5) bouton2.bind("<Return>",creation_diaporama) # bouton pour quitter bouton3 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=15, text='Quitter', command=fen1.quit) bouton3.grid(row=5, column=4, columnspan=2, padx=5, pady=5) bouton3.bind("<Return>",quitter) # Ligne 6 # ------- # boutons d'actions pour gravure # bouton pour effacer un CD-RW bouton4 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=30, text="Effacer un CD-RW", command=effacer_cd) bouton4.grid(row=6, column=2, padx=5, pady=5) bouton4.bind("<Return>",effacer_cd) # bouton pour graver un CD bouton5 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=30, text="Graver un SVCD", command=graver_SVCD) bouton5.grid(row=6, column=3, padx=5, pady=5) bouton5.bind("<Return>",graver_SVCD) # bouton pour éjecter un CD bouton6 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=15, text="Ejecter le CD", command=ejecter_CD) bouton6.grid(row=6, column=4, columnspan=2, padx=5, pady=5) bouton6.bind("<Return>",ejecter_CD) # Logo en bout de lignes 1 à 4 # ---------------------------- can1 = Canvas(fen1, width=90, height=106, bg='AntiqueWhite3') photo = PhotoImage(file=MonLogo) item = can1.create_image(45, 53, image=photo) can1.grid(row=1, column=5, rowspan=4, padx=4, pady=4) # Zone de listage des résultats du script # --------------------------------------- # C'est un rectangle de la largeur totale de la fenêtre, # avec ascenseurs automatiques si nécessaire. 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 = 710, hull_height = 350) zoneListage.grid(row=7, column=1, columnspan=5, padx=4, pady=4) zoneListage._textbox['takefocus'] = 0 ii=0 # Compteur des lignes insérées # balise pour lignes rouges créées ici (en plus # des lignes noires générées par le script) zoneListage.tag_configure('rouge', foreground='red') # texte d'explications en début de zone titre = """ Cet outil sert à créer un Diaporama à partir d'images JPG --------------------------------------------------------- Choisissez : - le dossier de base contenant les images à traiter et les films créés, - la durée de chaque diapo en secondes, - si un éventuel film existant doit être prolongé (en gardant les diapos précédentes inchangées en début de film), ou si on le supprime/recrée. Un 1er bouton permet de tester si un film existe déjà, s'il y a des images dans le dossier, et aussi si les autres options choisies sont correctes. Il est donc conseillé de l'actionner systématiquement. Un second bouton permet ensuite de créer effectivement un diaporama à partir des images contenues dans le dossier. Trois autres boutons permettent de simplifier la manipulation des CD : - effacement de CD-RW - gravure d'un SVCD à partir du film-diaporama du dossier courant - éjection du CD. \n""" zoneListage.insert(END, titre) # Lancement de l'application # -------------------------- fen1.mainloop()
Si on lance une première fois ce script tout seul ça plantera. Il est en effet nécessaire d'avoir installé Pmw et les scripts DialogueDossiersJmm.py et DialogueFichiersEtDossiersJmm comme expliqué ici : Modifier des images JPEG.
Si tout marche la fenêtre va s'afficher, et on pourra appuyer sur le bouton “Creer un Diaporama” pour lancer le script bash qu'on vient de mettre au point à l'étape précédente.
Ouf ! Pour ceux qui en sont arrivé ici le plus dur est fait ! Il resta à ajoûter les petits scripts bash complémentaires pour que les autres boutons fassent aussi leur effet.
Scripts Bash complémentaires
Test des Options et Fichiers
Pour que le bouton “Tester les options et fichiers” aie un effet on va créer le script /usr/local/bin/diapo-test.sh à partir des lignes suivantes :
#!/bin/bash # # Script pour tester si des images JPG et un diaporama MPEG existent. # On traite tous les fichiers.jpg d'un dossier. # Auteur : Jeanmm # Date : août 2006 # Licence : GPL # Lien : www.root66.net # 1 paramètre possible : # - nom du dossier des images et films echo "" echo "Test si des images et autres fichiers necessaires existent" echo "----------------------------------------------------------" echo "" # Dossier des images jpg et des vidéos créées (param 1 si existant) # ----------------------------------------------------------------- if [ ! 0$1 == 0 ] ; then cd $1 fi echo "Dossier des images et videos : $PWD" # Comptage des images # ------------------- countT=0 for ii in `find . -maxdepth 1 -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg dans le dossier ?" || echo "Nombre total d'images : $countT" # Test si image de fond existante # ------------------------------- if [ -f fond720x576.ppm ] ; then echo "Le fichier fond720x576.ppm existe" else echo "Le fichier fond720x576.ppm n'existe pas !" fi # Test si film existant # --------------------- if [ -f diaporama.mpg ] ; then echo "Le fichier diaporama.mpg existe" else echo "Le fichier diaporama.mpg n'existe pas" fi # Test si musique existante # ------------------------- if [ -f musique.mp2 ] ; then echo "Le fichier musique.mp2 existe" else echo "Le fichier musique.mp2 n'existe pas !" fi
Exemple de résultat :
Test si des images et autres fichiers necessaires existent ---------------------------------------------------------- Dossier des images et videos : /home/toto/Diaporama Nombre total d'images : 2 Le fichier fond720x576.ppm existe Le fichier diaporama.mpg existe Le fichier musique.mp2 existe
Effacer un CD-RW
Pour que le bouton “Effacer un CD-RW” aie un effet on va créer le script /usr/local/bin/cd-effacement à partir des lignes suivantes :
#!/bin/bash # Script pour effacer un CD-RW echo "umount /mnt/cdrom" umount /mnt/cdrom echo "cdrecord blank=fast gracetime=2 dev=/dev/hdc" cdrecord blank=fast gracetime=2 dev=/dev/hdc
Nota : Pour ce script et les 2 suivants, si le graveur ne se trouve pas en /dev/hdc on adaptera évidemment.
Graver un SVCD
Pour que le bouton “Graver un SVCD” aie un effet on va créer le script /usr/local/bin/cd-gravure-SVCD à partir des lignes suivantes :
#!/bin/bash # Script pour graver un SVCD # argument : le film SVCD à convertir/graver # ------------------------------------------ if [ ! "0$1" == "0" ] ; then film=$1 else echo "Indiquer le nom du film !" exit 1 fi echo "==> vcdimager -q -t svcd -c videocd.cue -b videocd.bin $film" vcdimager -q -t svcd -c videocd.cue -b videocd.bin $film echo "==> cdrdao write -n --device /dev/hdc --driver generic-mmc videocd.cue" cdrdao write -n --device /dev/hdc --driver generic-mmc videocd.cue
Nota : ce script va créer les fichiers videocd.cue videocd.bin dans le dossier courant, ne pas s'en inquiéter.
Ejecter un CD
Pour que le bouton “Ejecter le CD” aie un effet on va créer le script /usr/local/bin/cd-ejection à partir des lignes suivantes :
#!/bin/bash # Script pour éjecter un CD umount /dev/hdc cdrecord -eject dev=/dev/hdc
Script Bash principal 2
Le script bash principal du 2ème outil (configuration de la durée des diapos en nombre d'images (ou “frames”)) est très proche du 1er présenté ci-dessus. On ne répétera donc pas les commentaires.
Pour ce script, on copiera en root le texte suivant dans /usr/local/bin/diapo-Frames.sh :
#!/bin/sh ############################################################ # Script pour créer un diaporama MPEG à partir d'images JPEG ############################################################ # Particularité : on peut régler la durée de chaque diapo au 25ème de # seconde près. # On traite tous les fichiers.jpg du dossier courant accessible en écriture. # (en fait tous fichiers de suffixes .jpg, .jpeg, .JPG et .JPEG) # Auteur : jeanmm # Date : août 2006 # Licence : GPL # Lien : www.root66.net # Consommations en fichiers (estimations) : # - en images cibles de taille maxi : # - Ficher.mpg : 0.13 Mo/s, soit 0.67 Mo / image de 5s # - en images cibles de demi-taille : # - Ficher.mpg : 0.12 Mo/s, soit 0.62 Mo / image de 5s # Le script produit les fichiers suivants : # - diaporama.mpg : sans audio # - diaporama-SVCD.mpg : avec audio (si possible) #set -x # Paramètres # ---------- nbframes=150 # durée par défaut en nombre d'images (il en faut 25/seconde) musique=musique.mp2 # fichier musical optionnel tronquer=non # si oui : tronquer la vidéo s'il y a de la musique > durée des diapos # Durée en nombre d'images (paramètre d'appel N° 1) : if [ ! 0$1 == 0 ] ; then nbframes=$1 fi # Tronquer la vidéo (paramètre d'appel N° 2) : if [ ! 0$2 == 0 ] ; then tronquer=$2 fi # Dossier de travail (paramètre d'appel N° 3) : if [ ! 0$3 == 0 ] ; then dossier=$3 cd $dossier else dossier=$PWD fi # Taille des images cibles : # -------------------------- # (mettre en commentaire les lignes non souhaitées) # Taille standard SVCD (téléviseurs) = 720x576, avec marges de 10+10 tailleM=720x576 taille=700x556 tailleX=700 tailleY=556 # Demi-taille SVCD = 352x288, avec marges de 10+10 #tailleM=352x288 #taille=332x268 #tailleX=332 #tailleY=268 # IMPORTANT : le fichier fond$tailleM.ppm doivent exister # dans le dossier courant, de tailleM exacte ci-dessus. # Test/Résumé des options # ----------------------- echo "Dossier courant : $dossier" echo "Duree de chaque diapo : $nbframes images" if [ -f fond$tailleM.ppm ] then echo "Image de fond : fond$tailleM.ppm" else echo "Pas d'image de fond fond$tailleM.ppm trouvee !" exit 1 fi if [ -f $musique ] then echo "Fichier musical : $musique" if [ $tronquer == "oui" ] then echo "Video tronquee si musique > duree des diapos" fi else echo "Pas de diaporama musical car absence du fichier $musique" fi # Suppression des blancs dans les noms des fichiers # ------------------------------------------------- for a in * ; do b=$(echo $a | sed -e 's/[[:blank:]]/_/g') if [ ! -e "$b" ] ; then echo "mv \"$a\" $b" mv "$a" $b fi done # On renomme les fichiers .JPG , .JPEG et .jpeg en .jpg # ----------------------------------------------------- for a in `find . -maxdepth 1 -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 1 -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 1 -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 # Comptage des images et init du traitement # ----------------------------------------- countT=0 for ii in `find . -maxdepth 1 -type f -iname "*.jpg"` ; do countT=$[$countT + 1] done test $countT -eq 0 && echo "Aucune image.jpg a traiter ?" && exit 0 rm -f diaporama.log 2>/dev/null rm -f video*.mpg 2>/dev/null # Boucle sur chaque image # ----------------------- count=0 for ii in `find . -maxdepth 1 -type f -iname "*.jpg" | sort` ; do counti=$[$count + 1] # Conversion d'image : taille + image de fond => tempo2.jpg echo "Conversion de l'image `printf "%3.3d" $counti`/`printf "%3.3d" $countT` : $ii" convert -resize $taille $ii /tmp/tempo1.jpg 2>>diaporama.log # calculs pour centrer l'image sur le fond, avec marges +5+5 x=`identify -format '%w' /tmp/tempo1.jpg` y=`identify -format '%h' /tmp/tempo1.jpg` dx=$[ ( $tailleX - $x ) / 2 + 10 ] dy=$[ ( $tailleY - $y ) / 2 + 10 ] #echo " taille : $taille, soit ${x}x$y+$dx+$dy" composite -geometry +$dx+$dy /tmp/tempo1.jpg fond$tailleM.ppm /tmp/tempo2.jpg 2>>diaporama.log # Conversion en film élémentaire => videoXXX.mpg jpeg2yuv -v 0 -f 25 -n 1 -l $nbframes -I p -j /tmp/tempo2.jpg 2>>diaporama.log | yuvscaler -v 0 -O SVCD 2>>diaporama.log | mpeg2enc -v 0 -a 2 -f 4 -o video`printf "%3.3d" $count`.mpg 2>>diaporama.log count=$[$count + 1] done # dernier morceau provisoire si on tronque if [ $tronquer == "oui" ] then jpeg2yuv -v 0 -f 25 -n 1 -l 25 -I p -j /tmp/tempo2.jpg 2>>diaporama.log | yuvscaler -v 0 -O SVCD 2>>diaporama.log | mpeg2enc -v 0 -a 2 -f 4 -o video`printf "%3.3d" $count`.mpg 2>>diaporama.log count=$[$count + 1] fi # Concaténation des videoXXX.mpg => diaporama.mpg (sans audio) echo "Creation de la video : diaporama.mpg" cat video*.mpg >diaporama.mpg rm -f video*.mpg 2>/dev/null # Ajoût du son if [ -f $musique ] then echo "Creation de la video sonore : diaporama-SVCD.mpg" mplex -f 4 $musique diaporama.mpg -o diaporama-x%3.3d.mpg -v 0 2>>diaporama.log # suppr du dernier tronçon if [ $tronquer == "oui" ] then rm -f `ls diaporama-x*.mpg | sort | tail -n 1` 2>/dev/null fi # concaténation des portions cat diaporama-x*.mpg >diaporama-SVCD.mpg rm -f diaporama-x*.mpg fi # Suppression de log si vide test -s diaporama.log || rm -f diaporama.log ls -latr diaporama*.mpg echo "Traitement termine" exit 0
Script Python principal 2
Ce second script Python est tout-à-fait analogue au précédent. Il nécessite le script bash principal N° 2 ci-dessus, plus les mêmes scripts bash complémentaires.
Pour ce script on copiera en root le texte suivant dans /usr/local/bin/diapo-Frames.py :
#!/usr/bin/python # -*- coding: iso-8859-1 -*- # Ce script Python affiche une fenêtre de paramétrage et de lancement de scripts bash # qui permettent de créer un diaporama à partir d'images JPG, et éventuellement # un CD de type SVCD contenant ce diaporama. # # Particularité : on peut régler la durée de chaque diapo au 25ème de seconde près : # on indique le nombre d'images (ou "frames") par diapo, chaque seconde de vidéo # étant une succession de 25 images. # # Auteur : jeanmm # Date : août 2006 # Licence : GPL # Lien : www.root66.net import sys, os, string from Tkinter import * import Pmw import tkMessageBox from DialogueDossiersJmm import DirBrowserDialog from DialogueFichiersEtDossiersJmm import PmwFileDialog # Données générales # ----------------- # Titre de la fenetre MonTitre1 = "Creation de diaporama" # Dossier par défaut MonDossier = os.path.join(os.getenv('HOME'), 'Diaporama') # Logos pour agrementer la fenetre MonLogoDossier = '/usr/local/divers/LogoDossier.gif' MonLogo = '/usr/local/divers/decor.gif' # Les scripts bash à exécuter MonScript0 = 'diapo-test.sh' MonScript1 = 'diapo-Frames.sh' MonScript2 = 'cd-effacement' MonScript3 = 'cd-gravure-SVCD' MonScript4 = 'cd-ejection' # Option générale de listage de numéros de lignes NumLi = 0 # 0=non, 1=oui ii=0 # Nom de l'option choisie (tronquer le film ou non) nomOption='non' # 'oui' ==> on ne tronque pas # Dossiers accessibles cycliquement par F3 Dossiers=['','',''] # dossier courant, courant au lancement, et home NumDos=0 # Sous-programmes divers # ====================== # Parcours cyclique de 3 noms de dossiers par F3 # ---------------------------------------------- def f3(ev=None): global NumDos NumDos=NumDos+1 if NumDos==3: NumDos=0 entree1.delete(0,END) entree1.insert(END, Dossiers[NumDos]) # Parcours de dossiers général 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']="Le Dossier n'existe pas ?!?" entree1.focus_set() entree1.xview(END) return dirBrowserDialog = DirBrowserDialog(fen1, label = '', path = entree1.get(), title = 'Selection de dossier') dir = dirBrowserDialog.activate() fen1.focus_set() #print 'Dossier sélectionné :', dir if dir != None: entree1.delete(0,END) entree1.insert(END, dir) entree1.xview(END) entree1.focus_set() # Recherche de fichiers JPG par F5 #--------------------------------- def f5(ev=None): texte2['text']="" f0=PmwFileDialog(fen1) f0.title("Listage des noms d'images.jpg") fname=f0.askfilename(directory=entree1.get(), filter='*.jpg') # Rafraichissement de fenêtre en cas de déplacement # ------------------------------------------------ def rafraichissement(ev): fen1.update_idletasks() # Lecture d'une ligne de fichier # ------------------------------ # Comme readline, mais en plus de '\n' on teste '\r'. # Permet de découper les sorties du programme de gravure # en plusieurs lignes écrites au fur et à mesure, par exemple : # Wrote 1 of 4 MB (Buffers 100% 99%). # Wrote 2 of 4 MB (Buffers 100% 99%). # Wrote ... # aulieu d'une ligne unique en fin de gravure : # Wrote 1 of 4 MB (Buffers 100% 99%).\rWrote 2 of 4 MB (Buffers 100% 99%).\rWrote ... def lecture_ligne(): global fout # fichier lu (stdout de script en cours) txt=fout.read(1) # lecture/test de chaque caractère ligne='' # ligne couramment lue while txt: if txt=='\n': ligne = ligne + txt break elif txt=='\r': ligne = ligne + '\n' break else: ligne = ligne + txt txt=fout.read(1) # caractère suivant return ligne # quitter le programme # -------------------- # on simule le clic sur le bouton Quitter quand il est sélectionné + touche entrée def quitter(ev=None): bouton3.invoke() # Sous-programmes de Traitement # ============================= # Test des options et fichiers # ---------------------------- def tests_options(ev=None): global ii # Numéro de ligne global nomOption # Nom de l'option choisie global Dossiers # Noms dossiers texte2.configure(fg='red', font=('Times', 12, 'bold')) # Option choisie if nomOption=='': texte2['text']="Choisissez d'abord une option" return texte2['text']='Tests en cours...' fen1.update_idletasks() # Traitement du dossier try: os.chdir(entree1.get()) except: ii=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() # Durée de chaque diapo try: dureeDiapo = int(entree2.get()) except: ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) ligne="Le nombre d'images de chaque diapo est invalide !?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text']="La duree de chaque diapo est invalide ?!?" entree2.focus_set() return if dureeDiapo < 1: # Nb >= 1 dureeDiapo=1 entree2.delete(0,END) entree2.insert(END, '1') # Lancement du script 0 avec arg dossier fin,fout=os.popen4(MonScript0 + ' ' + entree1.get()) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) texte2['text']='OK - ' while txt: txt=fout.readline() ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne='%s' %(txt) else: ligne='\n' if ligne[0:16]=='Aucune image.jpg': 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() MonFic=entree1.get() if MonFic[-1:0]=='/': Pass else: MonFic = MonFic + '/' MonFic = MonFic + 'diaporama.mpg' if os.path.isfile(MonFic): texte2['text']= texte2['text'] + "le film diaporama.mpg existe" else: texte2['text']= texte2['text'] + "le film diaporama.mpg n'existe pas" zoneListage.see(END) fen1.update_idletasks() # Création de diaporama # --------------------- def creation_diaporama(ev=None): global ii # Numéro de ligne global nomOption # Nom de l'option choisie global Dossiers # Noms dossiers texte2.configure(fg='red', font=('Times', 12, 'bold')) # Option choisie if nomOption=='': texte2['text']="Choisissez d'abord une option" return texte2['text']='Creation de diaporama en cours...' fen1.update_idletasks() # Traitement du dossier try: os.chdir(entree1.get()) except: ii=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() # Durée de chaque diapo (en images) try: dureeDiapo = int(entree2.get()) except: ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) ligne="Le nombre d'images de chaque diapo est invalide !?\n\n" zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) texte2['text']="Le nombre d'images de chaque diapo est invalide ?!?" entree2.focus_set() return if dureeDiapo < 1: # Nb >= 1 dureeDiapo=1 entree2.delete(0,END) entree2.insert(END, '1') # Lancement du script avec args duree + option oui/non + dossier fin,fout=os.popen4(MonScript1 + ' ' + str(dureeDiapo) + ' ' + nomOption + ' ' + entree1.get()) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=fout.readline() ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne='%s' %(txt) else: ligne='\n' if ligne[0:16]=='Aucune image.jpg': texte2['text']='Aucune image.jpg dans le dossier ?' zoneListage.insert(END, ligne, 'rouge') zoneListage.see(END) fin.close() fout.close() return else: zoneListage.insert(END, ligne) zoneListage.see(END) fen1.update_idletasks() fin.close() fout.close() texte2['text']='OK - diaporama cree' fen1.update_idletasks() # Effacement d'un CD-RW # --------------------- def effacer_cd(ev=None): global ii # Numéro de ligne texte2.configure(fg='red', font=('Times', 12, 'bold')) texte2['text']='Effacement de CD-RW en cours...' fen1.update_idletasks() if tkMessageBox.askokcancel("Effacement de CD", "Confirmez ou annulez l'effacement"): pass else: texte2['text']='OK' fen1.update_idletasks() return # Lancement du script fin,fout=os.popen4(MonScript2) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=fout.readline() ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne='%s' %(txt) else: ligne='\n' if txt[0:17]=='cdrecord: No disk': texte2['text']='Pas de CD dans le graveur ?' txt='Pas de CD dans le graveur ?\n' zoneListage.insert(END, txt,'rouge') zoneListage.see(END) fin.close() fout.close() return else: zoneListage.insert(END, ligne) zoneListage.see(END) fen1.update_idletasks() fin.close() fout.close() ligne='OK - CD efface\n' zoneListage.insert(END, ligne) zoneListage.see(END) texte2['text']='OK - CD efface' fen1.update_idletasks() # Gravude d'un CD-SVCD # -------------------- def graver_SVCD(ev=None): global ii # Numéro de ligne global fout # fichier stdout du script texte2.configure(fg='red', font=('Times', 12, 'bold')) MonFic = entree1.get() + '/diaporama-SVCD.mpg' if os.path.isfile(MonFic): pass else: texte2['text']= "le film " + MonFic + " n'existe pas ?" return texte2['text']='Gravure de SVCD en cours...' fen1.update_idletasks() # Lancement du script fin,fout=os.popen4(MonScript3 + ' ' + MonFic) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=lecture_ligne() # lecture perso avec test '\r' ii=ii+1 if NumLi: ligne='%04d ' %(ii) zoneListage.insert(END, ligne) if txt: ligne='%s' %(txt) else: ligne='\n' if ligne[0:8]=='++ WARN:': pass elif ligne[0:23]=='WARNING: Unit not ready': texte2['text']='Pas de CD dans le graveur ?' ligne='Pas de CD dans le graveur ?\n' zoneListage.insert(END, ligne,'rouge') zoneListage.see(END) fin.close() fout.close() return elif ligne[0:24]=='Disk seems to be written': texte2['text']='Le CD semble deja grave ?' ligne='Le CD semble deja grave ?\n' zoneListage.insert(END, ligne,'rouge') zoneListage.see(END) fin.close() fout.close() return elif ligne[-2:]=='\\r': ligne=ligne[0:-2] + '\n' zoneListage.insert(END, ligne) else: zoneListage.insert(END, ligne) zoneListage.see(END) fen1.update_idletasks() fin.close() fout.close() texte2['text']='OK - CD grave' fen1.update_idletasks() # Ejection d'un CD # -------------------- def ejecter_CD(ev=None): global ii # Numéro de ligne texte2.configure(fg='red', font=('Times', 12, 'bold')) texte2['text']='Ejection de CD en cours...' fen1.update_idletasks() # Lancement du script fin,fout=os.popen4(MonScript4) # Boucle de récupération des résultats (txt='' => terminé) txt='x' # Contenu de chaque ligne produite (stdout + stderr) while txt: txt=fout.readline() ligne='OK, CD ejecte\n' zoneListage.insert(END, ligne) zoneListage.see(END) fin.close() fout.close() texte2['text']='OK, CD ejecte' fen1.update_idletasks() # ========================================================= # ----------- Programme principal ----------- # ========================================================= # Instanciation d'une fenêtre Pmw (Python méga-widgets) # ----------------------------------------------------- fen1 = Pmw.initialise() fen1.title(MonTitre1) fen1.bind("<Alt-F4>", quitter) fen1.bind("<Configure>", rafraichissement) # 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) # creation des widgets de configuration/lancement des scripts # =========================================================== # Ligne 1 # ------- # dossier des images/films texte1 = Label(fen1, text=' Dossier :') texte1.grid(row=1, column=1, sticky=E, padx=5, pady=5) # Saisie de nom de dossier # alternatives : f3 pour 3 valeurs prédéfinies, f4 pour dialogue entree1 = Entry(fen1, width=70, bg='white') entree1.grid(row=1, column=2, columnspan=2, sticky=W, padx=5, pady=5) entree1.insert(END, MonDossier) 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=4, sticky=W, padx=0, pady=3) bouton0.bind("<Return>",f4) # Ligne 2 # ------- # durée de chaque diapo en Images (25 par seconde) texte2 = Label(fen1, text=' Images :') texte2.grid(row=2, column=1, sticky=E, padx=5, pady=5) entree2 = Entry(fen1, width=8, bg='white') entree2.grid(row=2, column=2, sticky=W, padx=5, pady=5) entree2.insert(END, '150') texte2b = Label(fen1, text='(25 images par seconde) ') texte2b.grid(row=2, column=2, sticky=E, padx=5, pady=5) # Ligne 3 # ------- # option Tronquer la vidéo texte3 = Label(fen1, text=' Option :') texte3.grid(row=3, column=1, sticky=E, padx=5, pady=5) class ZoneRadio(Frame): """Utilisation de widgets 'boutons radio'""" def __init__(self, boss=None): """Création d'un champ d'entrée avec 2 boutons radio""" Frame.__init__(self) self.pack() self.configure(relief=GROOVE, bd=2) # Nom français et nom technique des quatre styles de police : texteOption =["Ne pas tronquer la video", "Tronquer"] valeurOption =["non", "oui"] # Le style actuel est mémorisé dans un 'objet-variable' Tkinter ; self.opt = StringVar() self.opt.set(valeurOption[0]) # Création des boutons radio for n in range(2): bout = Radiobutton(self, text = texteOption[n], variable = self.opt, value = valeurOption[n], command = self.modifOption) bout.pack(side=LEFT, padx=5) def modifOption(self): global nomOption nomOption = self.opt.get() zoneradio1=ZoneRadio() zoneradio1.grid(row=3, column=2, columnspan=2, sticky=W, padx=5, pady=5) # Ligne 4 # ------- # messages divers texte2 = Label(fen1, text='Dossier courant : ' + os.getcwd()) texte2.grid(row=4, column=2, columnspan=2, padx=5, pady=5) # Ligne 5 # ------- # boutons d'actions pour diaporama et bouton quitter # bouton de lancement de tests bouton1 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=30, text="Tester les options et fichiers", command=tests_options) bouton1.grid(row=5, column=2, padx=5, pady=5) bouton1.bind("<Return>",tests_options) # bouton de création de diaporama bouton2 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=30, text="Creer un Diaporama", command=creation_diaporama) bouton2.grid(row=5, column=3, padx=5, pady=5) bouton2.bind("<Return>",creation_diaporama) # bouton pour quitter bouton3 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=15, text='Quitter', command=fen1.quit) bouton3.grid(row=5, column=4, columnspan=2, padx=5, pady=5) bouton3.bind("<Return>",quitter) # Ligne 6 # ------- # boutons d'actions pour gravure # bouton pour effacer un CD-RW bouton4 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=30, text="Effacer un CD-RW", command=effacer_cd) bouton4.grid(row=6, column=2, padx=5, pady=5) bouton4.bind("<Return>",effacer_cd) # bouton pour graver un CD bouton5 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=30, text="Graver un SVCD", command=graver_SVCD) bouton5.grid(row=6, column=3, padx=5, pady=5) bouton5.bind("<Return>",graver_SVCD) # bouton pour éjecter un CD bouton6 = Button(fen1, bg='DeepSkyBlue4', activebackground='DeepSkyBlue2', fg='white', width=15, text="Ejecter le CD", command=ejecter_CD) bouton6.grid(row=6, column=4, columnspan=2, padx=5, pady=5) bouton6.bind("<Return>",ejecter_CD) # Logo en bout de lignes 1 à 4 # ---------------------------- can1 = Canvas(fen1, width=90, height=106, bg='AntiqueWhite3') photo = PhotoImage(file=MonLogo) item = can1.create_image(45, 53, image=photo) can1.grid(row=1, column=5, rowspan=4, padx=4, pady=4) # Zone de listage des résultats du script # --------------------------------------- # C'est un rectangle de la largeur totale de la fenêtre, # avec ascenseurs automatiques si nécessaire. 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 = 710, hull_height = 350) zoneListage.grid(row=7, column=1, columnspan=5, padx=4, pady=4) zoneListage._textbox['takefocus'] = 0 ii=0 # Compteur des lignes insérées # balise pour lignes rouges créées ici (en plus # des lignes noires générées par le script) zoneListage.tag_configure('rouge', foreground='red') # texte d'explications en début de zone titre = """ Cet outil sert à créer un Diaporama à partir d'images JPG --------------------------------------------------------- Choisissez : - le dossier de base contenant les images à traiter et les films créés, - la durée de chaque diapo en nombre d'images (il en faut 25 par seconde), - si le fichier son (musique.mp2) est plus long que la durée d'affichage des images, on peut choisir de tronquer le film à la fin de la dernière diapo. Un 1er bouton permet de tester si un film existe déjà, s'il y a des images dans le dossier, et aussi si les autres options choisies sont correctes. Il est donc conseillé de l'actionner systématiquement. Un second bouton permet ensuite de créer effectivement un diaporama à partir des images contenues dans le dossier. Trois autres boutons permettent de simplifier la manipulation des CD : - effacement de CD-RW - gravure d'un SVCD à partir du film-diaporama du dossier courant - éjection du CD. \n""" zoneListage.insert(END, titre) # Lancement de l'application # -------------------------- fen1.mainloop()
Compléments
Calculer avec Python en mode interactif
Voici une petite astuce pour utiliser Python comme calculette. En effet, Python peut servir à beaucoup de choses, comme construire des applications comme on vient de le voir, mais Python est aussi très pratique pour effectuer des choses simples en mode ligne de commande. Ici on va s'en servir pour calculer les durées des diapos pour l'outil 2.
Supposons qu'on aie un morceau de musique qui dure 7 minutes 44 secondes, et qu'on veuille s'en servir pour créer un diaporama de 72 photos. Voici une façon de calculer la durée de chaque diapo pour que ça tombe pile poil :
python -i Python 2.4.1 (#2, Aug 25 2005, 18:20:57) [GCC 4.0.1 (4.0.1-2mdk for Mandriva Linux release 2006.0)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> 7*60+44 464 >>> _*25 11600 >>> _/72 161 >>>
Pour démarrer le mode interactif il suffit de taper “python -i”; on peut taper une vraie ligne de code Python après chaque ">>>". Si on ne met pas “nom-de-variable = valeur-ou-formule” mais seulement “valeur-ou-formule”, c'est comme si on avait fait “_ = valeur-ou-formule” (Python affecte le dernier résultat à une variable par défaut appelée “_”). Ici on a donc calculé que la musique dure 464 secondes, soit 11600 images (avec 25 images par seconde), et si on divise par 72 on voit qu'il faudra créer le diaporama en indiquant 161. Avec une telle précision on n'a pas besoin de tronquer; la dernière diapo aura pour durée 161 + le reste de la division de 11600 par 161. On termine Python en tapant Ctrl+Z.
Le même calcul en une seule ligne et avec le reste :
>>> # avec chiffres derrière la virgule : ... (7*60+44)*25/72.0 161.11111111111111 >>> # ou bien : quotient + reste en un seul calcul : ... divmod((7*60+44)*25, 72) (161, 8)
Créer des icônes sur le bureau
Une fois au point, les 2 outils Python présentés ici peuvent bien sûr être liés à des icônes sur le bureau, sans passer par des appels en ligne de commande.
Bon diaporamas !