Compacteur d'images jpeg par lot

Description

CompactImages v.2.1
(Compacteur d'images Jpeg par lot)

Lassé d'appeler une à une avec Gimp toutes mes photos jpeg pour en réduire le poids,j'ai écrit ce
programme qui lit et réécrit tous les fichier Jpeg (ou jpg) d'un répertoire donné.

Cette version est une amélioration de la précédente avec les caractéristiques suivantes :

Mémorisation du répertoire précédemment ouvert dans la même session,
Réglage possible du taux de compression sur toute la plage autorisée en Jpeg,
Possibilité de demander une sauvegarde des originaux avant compression,
Visualisation multiple des images par un simple clic dans la liste,
Affichage de la barre de progression du traitement,
Possibilité d'interrompre le traitement à tout moment,
Liste des images non traitées (images protégées ou en erreur) en fin de traitement,
Affichage automatique du nom de répertoire de sauvegarde des originaux,
Affichage du nombre et du poids total des images avant et après traitement

Ce programme est écrit en python avec Tkinter et PIL ; il est compatible avec la compilation py2exe
sous windows et c'est sous cette forme compilée que je le distribue à mes amis windowsiens.
J'espère qu'il vous sera aussi utile que pour moi.
Yves Le Chevalier

Source / Exemple :


#Compacteur images JPEG  v.2.1
# -*- coding: utf-8 -*-
from Tkinter import *
from winsound import * 
from PIL import Image, ImageTk
import glob
import os
import tkFileDialog
import  string
import Tix
import shutil
import tkMessageBox

global repert, dirinit, nbfich, tailaff, taux, chtaux, indsauv
taux = 85
dirinit = ''
indsauv='N'
     
def Exploration() :
    global repert, dirinit, nbfich,tailaff, tailrepe
    list1.delete(0,END)
    nbfich=0
    lab2.configure(text='')
    lab3.configure(text='')
    lab4.configure(text='')
    lab5.configure(text='')
    lab6.configure(text='')
    lab7.configure(text='')
    lab8.configure(text='')
    lab9.configure(text='')
    button1.config(bg='SlateGray2',text='',state=DISABLED,relief=FLAT)
    repert=tkFileDialog.askdirectory(title='Choisir le répertoire à traiter',initialdir=dirinit)
    dirinit=repert
    repert=repert+'\\*.*'
    for fichier in glob.glob(repert):
        file, ext = os.path.splitext(fichier)
        tailfich = os.path.getsize(fichier)
        tailfiaff = tailfich/1024
        chaine='%4d'%tailfiaff+' Ko   ^ '+fichier
        if ext=='.jpg' or ext=='.JPG'  or ext=='.jpeg' or ext=='.JPEG' :
            list1.insert(END,chaine)
            nbfich=nbfich+1
    Taille_Repert()
    tailrepe = tailaff
    if nbfich > 0 :
        lab2.configure(text='Taille cumulée des images = '+tailaff)
        lab3.configure(text='Nombre d''\'images = '+str(nbfich))
        button1.config(bg='yellow',text='Valider',state=NORMAL,relief=RAISED)
    list1.select_set(0)
    list1.bind('<ButtonRelease>',Affiche_image1)
    
def Affiche_image1(e) :
    if nbfich > 0 :
        index = map(int,list1.curselection())[0]
        fichima = string.strip(list1.get(index))
        nom=''
        ind=0
        i=0
        while i< len(fichima) :
            if ind==1 :          
                nom=nom+fichima[i]
            if fichima[i]=='^' :
                ind=1
                i=i+1
            i=i+1   
        im=Image.open(nom)
        im.load()
        im.thumbnail((300,300),Image.ANTIALIAS)
        fenaff = Toplevel()
        if im.size[0] < 100 :
            larim=100
        else : larim=im.size[0]
        if im.size[1] < 20 :
            hauim=20
        else : hauim=im.size[1] 
        fenaff.geometry(str(larim)+"x"+str(hauim))
        fenaff.resizable(width=False, height=False)
        fenaff.config(bg='lightgrey')
        canaff = Canvas(fenaff,width=im.size[0],height=im.size[1])
        canaff.pack()
        nombase = os.path.basename(nom)
        fenaff.title(nombase)       
        vignette = ImageTk.PhotoImage(im)
        canaff.create_image(0,0,anchor=NW,image=vignette)
        canaff.monimage = vignette
        canaff.configure()
        boeff.config(text="Fermer vues",bg='DarkOliveGreen3', fg='white',state=NORMAL,relief=RAISED)
    
def Arreter_validation() :
    global ifin3,nbok
    ifin3=1
    lab8.configure(text='Traitement interrompu :  '+str(nbok)+' images traitées.')
    
def Validation() :
    global dirinit, repert, nbfich, ficherr, tailaff, tailrep, tailrepe,ifin3,nbok,indsauv,isav
    ifin3=0
    fen3=Tix.Tk()
    geo1=fen1.winfo_geometry()
    geox=fen1.winfo_rootx()
    geoy=fen1.winfo_rooty() 
    fen3.geometry("400x120+"+str(geox+55)+"+"+str(geoy+190)) 
    fen3.title(repert)
    labb=Label(fen3,text='Progression du compactage', fg='blue', font=('Arial', 10))
    labb.pack(side=TOP)
    meter=Tix.Meter(fen3,value=0.)
    meter.pack()
    labf=Label(fen3,text='', fg='black', font=('Arial',9))
    labf.pack(side=BOTTOM)
    Bfin3=Button(fen3,text="Arrêter",bg='pink',fg='blue', command=lambda : Arreter_validation())
    Bfin3.pack(side=BOTTOM,pady=10)
    nberr=0
    nbok = 0
    ficherr=[]       # liste des fichiers en erreur écriture
    if indsauv=='O':
        isav=1
        while os.path.isdir(dirinit+'/Originaux'+str(isav)+'/') :
             isav=isav+1
        os.mkdir(dirinit+'/Originaux'+str(isav)+'/')       
    if repert <> '' and nbfich>0 :
        lab4.configure(text='Traitement en cours.......Patience, merci.')
        fram5.update_idletasks()
        i=0
        for fichier in glob.glob(repert):
            file, ext = os.path.splitext(fichier)
            if ext=='.jpg' or ext=='.JPG'  or ext=='.jpeg' or ext=='.JPEG' :
                if ifin3==1 :
                    break
                i=i+1
                meter.config(value=float(i)/nbfich)
                meter.update()
                labf.configure(text=file)
                im=Image.open(fichier)
                chem,fich=os.path.split(fichier)
                if indsauv=='O' :
                    shutil.copy(fichier,chem+'/Originaux'+str(isav)+'/'+fich)
                try :
                    im.save(file+'.jpg',quality=taux)
                    nbok = nbok+1
                except IOError :
                    nberr = nberr+1
                    ficherr.append(file)                    
        button1.config(text="",bg='SlateGray2',state=DISABLED,relief=FLAT)
        lab4.configure(text='Résultat du traitement :')
        Taille_Repert()
        if nbok==0 :
            lab5.configure(text=' Aucune image traitée')
        elif tailaff == tailrepe  :
            lab5.configure(text=' Ce dossier est déjà compacté')
        else :
            lab5.configure(text=str(nbok)+'  image(s) analysée(s)')
            lab6.configure(text='Nouvelle taille cumulée des images = '+tailaff)
        if indsauv=='O':
            lab9.configure(text='Originaux dans : '+dirinit+'/Originaux'+str(isav))
        nbfich=0
        if nberr>0 :
            Beep(800,100)
            lab7.configure(text=str(nberr)+'   erreur(s)')
            Trt_Erreur(nberr,ficherr)            
        repert=''
        list1.delete(0,END)
        fen3.destroy()
    
def Taille_Repert() :
    global repert, tailaff, tailrep
    tailrep=0
    for fichier in glob.glob(repert) :
        file, ext = os.path.splitext(fichier)
        tailfich = os.path.getsize(fichier)
        if ext=='.jpg' or ext=='.JPG'  or ext=='.jpeg' or ext=='.JPEG' :
            tailrep=tailrep+tailfich
    tailrep1 = tailrep/1024/1024.0
    tailaff = '%.2f' % tailrep1 +'  Mo'
    
def Trt_Erreur(nberr,ficherr) :
    fen2=Toplevel()
    geo1=fen1.winfo_geometry()
    geox=fen1.winfo_rootx()
    geoy=fen1.winfo_rooty() 
    fen2.geometry("400x350+"+str(geox+55)+"+"+str(geoy+170))
    fen2.title("CompactImages : Erreurs de traitement")
    labf2=Label(fen2,text=str(nberr)+'  images ne pouvant être traitées \n (Images peut-être en lecture seule)', fg='blue', font=('Arial', 9))
    labf2.pack(side=TOP,pady=10)
    fram4=Frame(fen2,borderwidth=0)
    scrollf2 =Scrollbar(fram4,orient=VERTICAL)
    listf2 = Listbox(fram4,bg='yellow',width=60,height=15,selectmode='single', yscrollcommand=scrollf2.set)
    scrollf2.config(command  =  listf2.yview)
    scrollf2.pack(side=LEFT,fill=Y)
    listf2.pack(side=LEFT)
    fram4.pack()
    bouf2=Button(fen2,text="Fermer",bg='brown',fg='white', command=fen2.destroy)
    bouf2.pack(side=BOTTOM,pady=10)
    for x in ficherr :
        listf2.insert(END,x)
    fen2.grab_set()
    fen2.wait_window()

def Affich_Infos() :	
    feninf = Toplevel()
    feninf.config(bg='SlateGray4')
    geo1=fen1.winfo_geometry()
    geox=fen1.winfo_rootx()
    geoy=fen1.winfo_rooty() 
    feninf.geometry("350x390+"+str(geox+75)+"+"+str(geoy+160))
    feninf.title("À propos de CompactImages")
    labinf=Label(feninf,bg='SlateGray4', fg='white',width=50,font=('Arial', 9),
        text= "\nCompactImages v.2.1\n\n"
                "Programme écrit en Python / Tkinter \n"
                 "et distribué sous licence GNU GPL.\n\n"
                "© 2008  Yves Le Chevalier\n\n"
                "Ce programme comprime toutes les images JPG (jpeg)\n"
                "d'un répertoire donné, sans modifier leurs dimensions.\n"
                 "Il ne comprime pas les images protégées (en lecture seule).\n\n"
                 "Le niveau de compression est réglé à 85 par défaut, mais\n"
                 "peut être modifié sur toute la plage de compression Jpeg.\n\n"
                 "La sauvegarde est facultative et provoque la création d'un\n"
                 "sous-répertoire '''Originaux1'''. Si vous exécutez un second\n"
                 "traitement sur le même répertoire, la sauvegarde se fait alors\n"
                 "dans un sous-répertoire '''Originaux2''', puis la troisième fois\n"
                 " dans un sous-répertoire '''Originaux3''' et ainsi de suite.      \n"
                 "Toutes les images analysées sont alors sauvegardées.     \n"
                 "Attention :  Par défaut, la sauvegarde n'est pas activée.")
    labinf.pack(pady=5)
    bouf3=Button(feninf, text="Fermer", command=feninf.destroy,bg="orange", fg='brown')
    bouf3.pack(side=BOTTOM,pady=10)
    feninf.grab_set()
    feninf.wait_window()

def Modif_Taux() :
    global can2, fentau, choixtau
    fentau = Toplevel()
    fentau.config(bg='SlateGray3')
    geo1=fen1.winfo_geometry()
    geox=fen1.winfo_rootx()
    geoy=fen1.winfo_rooty() 
    fentau.geometry("350x330+"+str(geox+75)+"+"+str(geoy+170))
    fentau.title("Taux de compression")
    labtau=Label(fentau,bg='SlateGray3', fg='black',width=50,font=('Arial', 9),
        text= "\nModification du taux de compression\n\n"
                "ATTENTION : le niveau de compression Jpeg varie de 1 à 100 \n"
                 "1 = compression maximale ; 100= aucune compression.\n\n"
                 "La compression optimale se situe vers 85 (valeur par défaut).\n\n"
                "Vous pouvez changer le niveau de compression, sachant que\n"
                "si vous comprimez trop, vous perdez de la qualité et si vous ne \n"
                 "comprimez pas assez, vous ne gagnez pas en volume.\n"
                 "Il vaut mieux rester dans une plage de 75 à 90\n")
    labtau.pack(pady=0)
    can2 = Canvas(fentau, width=200, height=50, bg='SlateGray3')
    can2.pack(pady=20)
    curs= Scale(can2, from_=1, to=100, length=150, sliderlength=7,
                resolution=1, showvalue=1, orient=HORIZONTAL,
                label="Réglage du niveau :",command=choix_taux,font=('Arial', 8),bg='SlateGray3',fg='blue')
    curs.set (85)
    curs.pack(side=TOP)
    boutau=Button(can2, text="OK", command=set_taux,font=('Arial', 8),bg="orange", fg='brown')
    boutau.pack(side=BOTTOM)
    fentau.grab_set()
    fentau.wait_window()
    
def choix_taux(valcurs) :
    global chtaux
    chtaux=float(valcurs)
    if chtaux < 75 or chtaux > 90 :
        fentau.config(bg='red')
    else :  fentau.config(bg='green')

def set_taux() :
    global chtaux, taux
    taux=int(chtaux)
    if taux < 75 or taux > 90 :
        boutau.config(text ="Niveau "+'% d' %taux,bg='red',fg='black')
        Beep(800,100)
    else : boutau.config(text ="Niveau "+'% d' %taux,bg='green',fg='black')
    if taux <> 85 :
        lab3a.configure(text='ATTENTION : Niveau compression à '+str(taux))
    else : lab3a.configure(text='')
    can2.destroy()
    fentau.destroy()

def quitter_prog() :
    global fenqui
    fenqui=Toplevel()
    fenqui.config(bg='yellow',bd=3,relief='groove')
    geo1=fen1.winfo_geometry()
    geox=fen1.winfo_rootx()
    geoy=fen1.winfo_rooty() 
    fenqui.geometry("200x60+"+str(geox+160)+"+"+str(geoy+350))
    fenqui.resizable(width=False, height=False)
    fenqui.overrideredirect(1)
    labqui=Label(fenqui,bg='yellow',fg='black',font=('Arial', 11),text="Quitter CompactImage ?")
    labqui.place(x=17,y=1)
    repon=StringVar()
    bououi=Button(fenqui,text="Oui", bg='pink', fg='black',command=confir_quitter)
    bounon=Button(fenqui,text="Non", bg='pink', fg='black',command=continu_prog)
    bououi.place(x=40,y=25)
    bounon.place(x=120,y=25)
    fenqui.grab_set()

def confir_quitter() :
    fen1.destroy()

def continu_prog() :
    global fenqui
    fenqui.destroy()

def sauvegarde() :
    global indsauv
    if indsauv =='N' :
        indsauv='O'
        labsau1.configure(text='Avec sauvegarde',fg='black')
        bosav.config(text='Sans',bg='SeaGreen4', fg='white')
    else :
        indsauv='N'
        labsau1.configure(text='Sans sauvegarde',fg='grey50')
        bosav.config(text='Avec',bg='firebrick4', fg='white')

def effacer_vues() :
    for widget in fen1.winfo_children(): 
        if isinstance(widget,Toplevel): 
            widget.destroy() 
    boeff.config(text="",bg='SlateGray2',state=DISABLED,relief=FLAT)
    
def close_window() :
    if tkMessageBox.askokcancel :
        quitter_prog()
    
# main
fen1 = Tk(className=" CompactImages   v_2,1")
fen1.protocol("WM_DELETE_WINDOW", close_window)
fen1.tk_setPalette(background = 'SlateGray2')
fen1.geometry("500x800+550+100")
fen1.resizable(width=False, height=False)
can1 = Canvas(fen1, width=20, height=20, bg='lightgrey')
signat=PhotoImage(file='YLC.gif')
sign=can1.create_image(12,12, image=signat)
can1.pack(side=BOTTOM,anchor=E)

fram1=Frame(fen1,borderwidth=0)
button2 = Button(fram1, text = 'Répertoire',bg='snow', command = Exploration)
button2.grid(row=0,column=0,pady=20)
boutau=Button(fram1,text=" Niveau "+'% d' %taux, bg='green', fg='black',command=Modif_Taux)
boutau.grid(row=0,column=1,padx=30)
bouinf=Button(fram1,text="Information", bg="seagreen4", fg='yellow',command=Affich_Infos)
bouinf.grid(row=0,column=2,padx=30)
bouf1=Button(fram1,text="Quitter", command=quitter_prog,bg='salmon', fg='black')
bouf1.grid(row=0,column=3)
labsau1=Label(fram1,text='Sans sauvegarde', fg='grey50', font=('Arial',11))
labsau1.grid(row=1,column=0)
bosav=Button(fram1,text="Avec", command=sauvegarde,bg='firebrick4', fg='white')
bosav.grid(row=1,column=1)
boeff=Button(fram1,text="",command=effacer_vues,state=DISABLED,relief=FLAT)
boeff.grid(row=1,column=3)
fram1.pack(side=TOP)

fram2=Frame(fen1,borderwidth=0)
labbid1=Label(fram2,text='')
labbid1.pack(side=TOP)
lab1=Label(fram2,text='Liste des images (Jpg ou Jpeg) à comprimer', fg='blue', font=('Arial', 12))
lab1.pack()
lab1b=Label(fram2,text='(Cliquer sur une ligne pour voir l''image)', fg='black', font=('Arial', 9))
lab1b.pack()
scrolly =Scrollbar(fram2,orient=VERTICAL)
list1 = Listbox(fram2,bg="SlateGray1",width=70,height=31,selectmode='single', yscrollcommand=scrolly.set)
scrolly.config(command  =  list1.yview)
scrolly.pack(side=LEFT,fill=Y)
list1.pack()
fram2.pack()

fram3=Frame(fen1)
lab3=Label(fram3,fg='blue',font=('Arial', 9))
lab3.grid(row=0,column=1,padx=5)
lab2=Label(fram3,fg='blue',font=('Arial', 9))
lab2.grid(row=0,column=0,padx=15)
lab3a=Label(fram3,fg='red',font=('Arial', 9))
lab3a.grid(row=1,column=0,padx=5)
button1 = Button(fram3, text = '', command = Validation,state=DISABLED,relief=FLAT)
button1.grid(row=1,column=1,padx=35,pady=5)
fram3.pack()

fram5=Frame(fen1)
lab4=Label(fram5,fg='black', font=('Arial', 10))
lab4.grid(row=0,column=0,pady=5)
lab5=Label(fram5,fg='blue', font=('Arial', 9))
lab5.grid(row=0,column=1,sticky=W)
lab6=Label(fram5,fg='blue', font=('Arial', 9))
lab6.grid(row=1,column=0)
lab7=Label(fram5,fg='red', font=('Arial', 9))
lab7.grid(row=1,column=1)
lab8=Label(fram5,fg='red', font=('Arial', 10))
lab8.grid(row=2,column=0,sticky=W)
lab9=Label(fram5,fg='black', font=('Arial', 9))
lab9.grid(row=3,column=0)
fram5.pack()

fen1.mainloop()

Conclusion :


C'est à vous de la donner ; moi, je trouve ça très pratique.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.