Forum Programmation.python Optimisation programme

Posté par  . Licence CC By‑SA.
0
7
jan.
2013

Bonsoir à tous,

Dans le cadre de l'optimisation de RasPyPlayer j'aurais besoin d'un peu d'aide. Je cherche des moyens pour accélérer la recherche les fichiers vidéos. Si vous avez des pistes je suis preneur !

Le code actuel :

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------#
# MediaScanner.py - Movies scanner for RasPyPlayer
#-------------------------------------------------------------------------#
VERSION = "1.0-devel"
#-------------------------------------------------------------------------#
# Auteur : Julien Pecqueur (JPEC)
# Email : jpec@julienpecqueur.net
# Site : http://raspyplayer.org
# Sources : https://github.com/jpec/RasPyPlayer
# Bugs : https://github.com/jpec/RasPyPlayer/issues
# License :
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#-------------------------------------------------------------------------#

#-------------------------------------------------------------------------#
# IMPORTATION DES MODULES                                                 #
#-------------------------------------------------------------------------#

import os
import sqlite3
from sys import argv
from time import time

#-------------------------------------------------------------------------#
# PARAMETRAGE PROGRAMME                                                   #
#-------------------------------------------------------------------------#

# DEBUG - Mode debug (0 - off / 1 - on) :
DEBUG = 0

# EXT - Extensions des fichiers vidéos à ajouter dans la librairie :
EXT = [".avi", ".mpg", ".mp4", ".wmv", ".mkv"]

# EXC - Répertoires à exclure de la recherche des vidéos :
EXC = ["Backup",
       "Musique",
       "Musique_old",
       "MacBookPro",
       "Temporary Items",
       ".TemporaryItems",
       "MacBook Pro de Julien.sparsebundle",
       "A VOIR"]

#-------------------------------------------------------------------------#
# DEFINITION DES CLASSES                                                  #
#-------------------------------------------------------------------------#

class Db(object):
    """Classe permettant la gestion de la base de données"""

    def __init__(self, db):
        """Constructeur de la classe Db"""
        self.db = db
        self.con = False
        self.cur = False
        self.top = False
        self.DBCREATE = "CREATE TABLE files (file, path)"
        self.DBADD = "INSERT INTO files VALUES (?, ?)"
        self.DBDROP = "DROP TABLE files"

    def openDb(self):
        """Ouvre la base de donnée, la créé le cas échéant"""
        sql = False
        bind = False
        if os.path.isfile(self.db):
            self.top = True
        self.con = sqlite3.connect(self.db)
        self.cur = self.con.cursor()
        return(True)

    def initDb(self):
        """Initialisation de la base de données"""
        if self.top:
            self.execSql(self.DBDROP, False)
        self.execSql(self.DBCREATE, False)
        self.top = True
        return(True)

    def closeDb(self):
        """Ferme la base de données"""
        self.cur.close()
        self.con.close()
        self.top = False
        return(True)

    def execSql(self, sql, bind):
        """Exécute une requête dans la base de données"""
        if sql:
            if bind:
                if DEBUG:
                    print(sql, bind)
                self.cur.execute(sql, bind)
            else:
                self.cur.execute(sql)
            self.con.commit()
            return(True)
        else:
            return(False)

#-------------------------------------------------------------------------#
# FONCTIONS                                                               #
#-------------------------------------------------------------------------#

def scanFiles(db, path):
    """Scan les répertoires et alimente la base de données"""
    print(path)
    for file in os.listdir(path):
        filepath = path+"/"+file
        if len(file) > 4 and file[-4: len(file)] in EXT \
           and file[0:1] != ".":
            # Fichier
            db.execSql(db.DBADD, (os.path.basename(file), filepath))
        elif os.path.isdir(filepath) and not file in EXC \
             and file[0:1] != ".":
            # Répertoire
            scanFiles(db, filepath)

#-------------------------------------------------------------------------#
# PROGRAMME PRINCIPAL                                                     #
#-------------------------------------------------------------------------#

if len(argv) == 2:
    t1 = time()
    path = argv[1]
    db = Db("RasPyPlayer.sqlite3")
    if db.openDb() and db.initDb():
        scanFiles(db, path)
    db.closeDb()
    t2 = time()
    print("Elapsed time : {} sec".format(t2 - t1))
else:
    print("Usage: {} /path/to/medias".format(argv[0]))

#-------------------------------------------------------------------------#
# EOF                                                                     #
#-------------------------------------------------------------------------#

  • # regarde si tu ne peux pas faire une simple recherche

    Posté par  . Évalué à 3.

    actuellement tu fais du recursif sur tes dossiers pour en tirer la liste des fichiers

    y a pas une option à python pour choper tous les fichiers d'un dossier et des sous dossiers.

    en shell ce serait un simple
    find -type f

    à rediriger vers un fichier plat qui contiendrait alors la liste des chemins/dossiers/fichiers.ext

    • [^] # Re: regarde si tu ne peux pas faire une simple recherche

      Posté par  . Évalué à 3.

      Il y a os.walk() pour ça. Il faut utiliser fnmatch pour matcher les noms de fichiers, os.walk() ne le supporte pas.
      Sinon il y a WalkDir de Nick Coghlan (core dev Python) qui est pas mal.

      Mais ça ne devrait pas accélérer, ça sera toujours O(nombre de fichiers à traiter) (en fait O(nombre de fichiers dans l'arborescence), mais le facteur dominant est le traitement des fichiers).

      Sinon, j'imagine que faire un commit à chaque requête n'est pas optimal, tu devrais pouvoir te contenter de le faire à la fin (dans closeDB).

  • # Divers points

    Posté par  (site web personnel) . Évalué à 4.

    Dans tes manipulations de chemins pour les fichiers, tu utilises directement des manipulations de chaînes. Or, dans la librairie standard de Python, il y a le module os.path qui contient une série de fonctions pour faire cela de façon propre, indépendamment de la plateforme.

    os.walk() a déjà été indiqué dans un autre post.

    Pour le pattern matching sur les noms de fichiers en globbing, il y a le module glob, fonction glob() - voir s'il peut prendre des expressions du genre *.(truc|machine|bidule) - si oui ça te zappe ton étape de filtrage (ou utiliser fnmatch qui fait à peu près la même chose que toi, mais en compilé).

    Tu pourrais éventuellement remplacer les listes de EXT et EXC par des set(), pour lesquels l'opération de test de présence est en O(1) - bon, là y'a pas beaucoup de données.

    Tu pourrais ne faire qu'un seul commit sur la base de données, tout à la fin, pour éviter de multiplier les temps de validation/flush sur disque (ou comme il a été indiqué, cumuler tous les résultats et faire une seule requête de mise à jour de la BD).

    Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

    • [^] # Re: Divers points

      Posté par  . Évalué à 1.

      je plussoie et j'ajoute comme dit dans le thread au dessus que le mieux c'est que ta fonction de recherche renvoie un liste et que l'insert soit fait par une autre méthode.

      Autre chose ton scanfile pourrait recevoir en paramètre un set qui contient l'ensemble des fichiers déjà connus, comme ça tu test avant d'insérer (ou renvoyer le fichier disons).

      Enfin juste un problème de style utilise plutôt self.con = None (et non False) c'est plus pythonique (si tu met False on pense que c'est un bool pas un objet).

  • # Les doublons ?

    Posté par  . Évalué à 2.

    J'imagine qu'on ne voit qu'un bout de ton code, mais quid des paths déjà trouvés précédemment ?
    Genre je lance 2 fois le programme sur le même path et je me retrouve avec tous les paths en double ?

  • # insert a la fin

    Posté par  . Évalué à 2.

    Je crois que cela a deja était dis, mais tu devrais :
    - dans un premier temps, scanner tous les fichiers et constituer la liste a ajouter dans une variable tableau
    - une fois la liste constituée, ajouter tout le contenu dans la BD.
    Il vaut mieux éviter de scanner un fichier (ou un rep) puis le mettre en base, puis scanner l'autre fichier, le mettre en base etc… ca fait des lectures / ecritures a des endroits différents très rapprocher (d'où grattage de disque dur).
    Si tu regroupe les lectures , puis les ecritures à la fin tu va éviter le "grattage".

    Si il y'a beaucoup de fichiers/rep a scanner, et que tu veux éviter de constituer en mémoire un énorme tableau, alors il vaut mieux écrire dans la BD par paquet : genre a chaque fois que 100 fichiers ont été scannés, tu les enregistre dans la BD (et tu commit).

  • # Merci

    Posté par  . Évalué à 4.

    Merci pour les pistes!

    Je vais utiliser os.walk et faire un commit à la fin.

    Je vous tiens au courant!

    • [^] # Re: Merci

      Posté par  . Évalué à 2.

      La modification du commit unique à la fin a été très bénéfique ! Je suis passé d'une quinzaine de minutes à 4,5 secondes !

      Reste à nettoyer le code et appliquer les bonnes pratiques de programmation Python !

      Merci.

  • # Playdar

    Posté par  (site web personnel) . Évalué à 2.

    Le projet est mort, mais tu pourrais t'inspirer de Playdar qui est a mon avis une idée excellente pour les gens qui ne veulent pas se casser la tête a configurer tout un tas de machines pour lire quelques fichiers. On en a déjà parlé dans ces colonnes.

    Le principe est simple : chaque machine qui possède du contenu lance un noeud playdar, indexe le contenu local et envoie un signal multicast (ou broadcast ? a verifier) sur le reseau pour signaler qu'il existe et qu'il a du contenu a partager. Ensuite, un autre noeud sur le même réseau peut écouter ces signaux et aller interroger tous les noeuds qu'il souhaite pour leur demander s'ils ont un certain fichier. Et si playdar ne tourne pas sur une machine, tu peux toujours l'interroger via d'autres moyens plus classiques en utilisant des resolvers qui vont interroger la machine d'une autre manière (ftp, http, …)

    playdar marche aujourd'hui uniquement pour la musique, mais le format peut être modifie pour intégrer tout ce que tu veux (ce n'est un problème que pour l'application, en charge de décoder le contenu; playdar ne gère que des localisations dans l'espace)

    playdar est en erlang, mais il y a une implementation en python qui pourrait t’intéresser.

    Et si tu veux voir a quoi ça ressemble, Tomahawk devrait te donner une idée de la chose un peu plus visuelle =]

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.