Trier ses films par genre - interface zenity ou ligne de commande

Contenu du snippet

Après avoir pendant plusieurs années collecté des centaines de films, ma bibliothèque devenait trop conséquente. Je voulais les trier par genre, mais quelle tâche fastidieuse.
Voici donc un programme en Python dans le but de trier les films par genre.

Le programme liste tous les films contenus dans le dossier à traiter, puis un par un effectue une recherche sur allociné pour en trouver le genre, puis créé le dossier du genre et enfin le déplace dedans.

Interface Zenity, les fonctions ont étés trouvées dans le module PyZenity disponible sur le net

Modifications :
Prise en charge totale de l'interface graphique
Choix du genre si plusieurs sont disponibles
Choix du genre parmi ceux déjà existant si le film n'a pas pu être trouvé

Pour l'utiliser (ligne de commande) : python film_ordener.py /dossier/a/traiter
ou : python film_ordener.py /dossier/a/traiter debug=1 #Pour avoir le debug

Pour l'utiliser (interface) on peut aussi créer un lanceur : python film_ordener.py

Source / Exemple :

#! /usr/bin/python
# -*- coding: utf-8 -*-

################################################################################
# Name: film_ordener.py
# Created: 15/01/2012
################################################################################
# Zenity fonctions
# Licence: MIT Licence
# 
# Copyright (c) 2005 Brian Ramos
# Permission is hereby granted, free of charge, to any person obtaining a copy 
# of this software and associated documentation files (the "Software"), to 
# deal in the Software without restriction, including without limitation the 
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 
# sell copies of the Software, and to permit persons to whom the Software is 
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in 
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
################################################################################

import glob
import urllib2
import os
import sys
import re
from subprocess import Popen, PIPE

__author__="hugo"
__date__ ="$25 mai 2011 19:14:28$"

zen_exec = 'zenity'

def run_zenity(type, *args): 
    """#Lance une commande zenity avec des arguments"""
    return Popen([zen_exec, type] + list(args), stdin=PIPE, stdout=PIPE)

def get_folder(): 
    """#Affiche à user une boite de dialogue pour selectionner un repertoire"""
    args = ['--directory']
    p = run_zenity('--file-selection', *args)

    if p.wait() == 0:
        return p.stdout.read().strip().split('|')[0]

def info(text): 
    """#Affiche une boite de dialogue avec un texte
    text - texte à afficher"""
    run_zenity('--info', '--text=%s' % text).wait()

def question(text):
    """#Affiche une boite de dialogue avec une question
    text - question à afficher"""
    return run_zenity('--question', '--text={0}'.format(text)).wait() == 0



def process(text='', percentage=0, auto_close=False, pulsate=False):
    """Show a progress dialog to the user.

    This will raise a Zenity Progress Dialog.  It returns a callback that 
    accepts two arguments.  The first is a numeric value of the percent 
    complete.  The second is a message about the progress.

    NOTE: This function sends the SIGHUP signal if the user hits the cancel 
          button.  You must connect to this signal if you do not want your 
          application to exit.

    text - The initial message about the progress.
    percentage - The initial percentage to set the progress bar to.
    auto_close - True if the dialog should close automatically if it reaches 
                 100%.
    pulsate - True is the status should pulsate instead of progress."""

    args = []
    if text:
        args.append('--text=%s' % text)
    if percentage:
        args.append('--percentage=%s' % percentage)
    if auto_close:
        args.append('--auto-close=%s' % auto_close)
    if pulsate:
        args.append('--pulsate=%s' % pulsate)

    p = Popen([zen_exec, '--progress'] + args, stdin=PIPE, stdout=PIPE)

    def update(percent, message=''):
        if type(percent) == float:
            percent = int(percent * 100)
        p.stdin.write(str(percent) + 'n')
        if message:
            p.stdin.write('# %sn' % message)
        return p.returncode

    return update

def selection(column_names, title=None, boolstyle=None, editable=False, 
         select_col=None, sep='|', data=[]):
    """Present a list of items to select.
    
    This will raise a Zenity List Dialog populated with the colomns and rows 
    specified and return either the cell or row that was selected or None if 
    the user hit cancel.
    
    column_names - A tuple or list containing the names of the columns.
    title - The title of the dialog box.
    boolstyle - Whether the first columns should be a bool option ("checklist",
                "radiolist") or None if it should be a text field.
    editable - True if the user can edit the cells.
    select_col - The column number of the selected cell to return or "ALL" to 
                 return the entire row.
    sep - Token to use as the row separator when parsing Zenity's return. 
          Cells should not contain this token.
    data - A list or tuple of tuples that contain the cells in the row.  The 
           size of the row's tuple must be equal to the number of columns."""

    args = []
    for column in column_names:
        args.append('--column=%s' % column)
    
    if title:
        args.append('--title=%s' % title)
        args.append('--text=%s' % title)
    
    if boolstyle:
        if boolstyle != 'checklist' or boolstyle != 'radiolist':
            raise ValueError('"%s" is not a proper boolean column style.'
                             % boolstyle)
        args.append('--' + boolstyle)
    if editable:
        args.append('--editable')
    if select_col:
        args.append('--print-column=%s' % select_col)
    if sep != '|':
        args.append('--separator=%s' % sep)
    
    for datum in data:
        args.append(str(datum))
    
    p = run_zenity('--list', *args)

    if p.wait() == 0:
        return p.stdout.read().strip().split(sep)


def search(path,dir,i,taille):
    """ Fonction principale. Paramètres : chemin du fichier, dossier de travail, iteration n°, nombre de films.
         Cette fonction traite le chemin pour récupérer le nom du film, le formatte pour une recherche sur allociné.
         Puis récupère le film sur allociné et ouvre la page de ce film pour en tirer le genre du film."""
    name = path.replace(dir,"").lower() #Enleve le chemin absolue et le met en minuscules
    string = name_regexp1.sub("",name) #Retire l'extension du nom du fichier
    string = name_regexp2.sub("+",string) #Remplace les [.-_| ] par des '+' dans le nom de fichier
    
    if debug==True:
        print string
    the_url = "http://www.allocine.fr/recherche/?q={0}".format(string) #Lance la recherche sur Allociné
    req = urllib2.Request(the_url)

    try:
        handle = urllib2.urlopen(req)
    except IOError, e:
        if hasattr(e, 'reason'):
            print 'Nous avons échoué à joindre le serveur.'
            print 'Raison: ', e.reason
        elif hasattr(e, 'code'):
            print 'Le serveur n'a pu satisfaire la demande. Niveau search(film)'
            print 'Code d' erreur : ', e.code
    else:
        result = handle.read()

        if "<a href='/film/fichefilm_gen_cfilm" in result:
            id = result.split("<a href='/film/fichefilm_gen_cfilm=")[1].split(".html")[0] 
            #Recupere le 'id' du film sur Allociné

            lien = "http://www.allocine.fr/film/fichefilm_gen_cfilm={0}.html".format(id)
            if debug == True:
                print lien
            req = urllib2.Request(lien)

            try:
                handle = urllib2.urlopen(req)
            except IOError, e:
                if hasattr(e, 'reason'):
                    print 'Nous avons échoué à joindre le serveur.'
                    print 'Raison: ', e.reason
                elif hasattr(e, 'code'):
                    print 'Le serveur n'a pu satisfaire la demande. Niveau search(genre)'
                    print 'Code d' erreur : ', e.code
            else:
                result = handle.read()
                if '<span itemprop="genre">' in result:
                    #genre = result.split("<span itemprop="genre">")[1].split("</span>")[0]
                    genres = genre_regexp.findall(result) #Recupere tous les genres proposés par Allociné
                    if automatique ==False and display==0 and len(genres) > 1:
                        genre = selection(['Genre'],"Genres pour "+name,data=genres)[0] #Demande à user de choisir le genre correspondant pour le film
                    else:
                        genre = genres[0]

                    if debug == True:
                        print genre
                        print i

                    if display == 0:
                        processus(float(i)/float(taille),"({0} / {1}) Ajouté à la liste {2}".format(i,taille,path+" ("+genre+")")) #Change le statut de la barre de progression
                    else:
                        print "({0} / {1}) Ajouté à la liste {2}".format(i,taille,path+" ("+genre+")")
                    ordonner(dir,path,genre,name)
                else:
                    if display == 0:
                        processus(float(i)/float(taille),"({0} / {1}) Impossible de trouver le genre pour {2}".format(i,taille,name))
                    else:
                        print "({0} / {1}) Impossible de trouver le genre pour {2}".format(i,taille,name)
        else:
            if display == 0:
                processus(float(i)/float(taille),"({0} / {1}) Impossible de trouver le film {2}".format(i,taille,name))
                answ = question("({0} / {1}) Impossible de trouver le film {2}.nChoisir un genre parmi ceux existant ?".format(i,taille,name))
                if answ==True:
                    dirs = os.listdir(dir)
                    for indval in enumerate(dirs):
                        #print indval
                        if os.path.isfile(dir+dirs[indval[0]]):
                            del dirs[indval[0]]
                    #print dirs
                    genre = selection(['Genre'],"Genre pour "+name,data=dirs)[0] #Demande à user de choisir le genre correspondant pour le film
                    ordonner(dir,path,genre,name)
            else:
                print "({0} / {1}) Impossible de trouver le genre pour {2}".format(i,taille,name)

def ordonner(dir,path,genre,name):
    """ En paramètres : dossier de travail, chemin du fichier, genre du film, nom du film.
        Effectue le tri en fonction du genre du film """
    if genre not in os.listdir(dir):
        os.mkdir(dir+genre, 0775) #Créer le dossier du genre s'il n'éxiste pas
    os.rename(path,dir+genre+os.sep+name) #Déplace le fichier dans le dossier du genre
    #if display == 0:
        #info("Fichier %s déplacé avec succes" %name)
    #else:
        #print "Fichier %s déplacé avec succes" %name


if __name__ == "__main__":
    """ Fonction Main : recupere les arguments, liste les fichiers dans le repertoire choisi. Créer la liste des extensions. Et lance le traitement"""
    display = 0 #0: fenetre, 1: console
    debug = False
    extensions = ("avi","mp4","mpeg","divx","mkv","flv")
    extension_regexp = ""
    liste = []
    automatique = True

    if (len(sys.argv) >= 2) and (sys.argv[2] == "debug=1" or sys.argv[2] == "debug=True"):
        debug = True
        display = 1
        automatique = True

    if display == 0:
        dir = get_folder()
        dir += os.sep
    else:
        dir = sys.argv[1]

    try:
        j=0
        while j<len(extensions):
            liste += glob.glob(dir+'*.'+extensions[j])
            if j < len(extensions)-1:
                extension_regexp += extensions[j]+"|"
            else:
                extension_regexp += extensions[j]
            j += 1
        if debug==True:
            print extension_regexp
            print liste

    except IOError, e:
        if hasattr(e, 'reason'):
            print 'Le chemin est faux ou n'existe pas.'
            print 'Raison: ', e.reason
        elif hasattr(e, 'code'):
            print 'Le serveur n'a pu satisfaire la demande. Niveau main'
            print 'Code d' erreur : ', e.code
    
    taille = len(liste)
    i = 0
    genre_regexp = re.compile("<span itemprop="genre">([wséèà-]+)</span>")
    name_regexp1 = re.compile(".("+extension_regexp+")")
    name_regexp2 = re.compile("[-_|.s]")

    if display == 0:
        string = "Auteur : Hugo RoddenIl y a {0} films à traiter dans le dossier {1}.nVoulez-vous procéder au rangement de vos films par Genre d'après Allociné ?".format(taille,dir)
        continuer = question(string)
        if continuer==True:
            mode = question("Le traitement est par défaut automatique. Voulez-vous passer en manuel ? C'est-à-dire choisir le genre de chaque film si plusieurs genres sont trouvés")
            if mode == True:
                automatique = False
            processus = process("Le traitement va commencer...",0,True)
            for file in liste:
                i = i+1
                search(file,dir,i,taille)
            info("Le travail est fini")
        else:
            info("Comme tu veux ...")
    else:
        print "Auteur : {0} <{1}>".format("Hugo Rodde","rodde.hugo@gmail.com")
        print "Il y a {0} films à traiter dans le dossier {1}. Voulez-vous procéder au rangement de vos films par Genre d'après Allociné ? (O/n)".format(taille,dir)

        continuer = raw_input()
        if continuer=="o" or continuer=="O":
            for file in liste:
                i = i+1
                search(file,dir,i,taille)
            print "Done !"
        else:
            print "Comme tu veux ..."


Conclusion :

Simple et robuste bien qu'incomplet

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.