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 NeoX . É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 neologix . É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).
[^] # Re: regarde si tu ne peux pas faire une simple recherche
Posté par M.Poil (site web personnel) . Évalué à 1.
Ou alors tu fais un seul unique INSERT en utilisant une syntaxe de type
Après faudrait voir ce qui prend du temps dans ton programme, est-ce l'insertion en base ou le scan récursif … ou les deux :D
Is it a Bird? Is it a Plane?? No, it's Super Poil !!!
[^] # Re: regarde si tu ne peux pas faire une simple recherche
Posté par M.Poil (site web personnel) . Évalué à 2.
En PHP j'utilisais preg_find qui était super pratique, en cherchant sur google on tombe sur un exemple de os.walk sur stackoverflow
Is it a Bird? Is it a Plane?? No, it's Super Poil !!!
[^] # Re: regarde si tu ne peux pas faire une simple recherche
Posté par niol (site web personnel) . Évalué à 2.
Je confirme : le commit à la fin avec sqlite3 ça va tout changer.
[^] # Re: regarde si tu ne peux pas faire une simple recherche
Posté par Xaapyks . Évalué à 2.
+1 ça coute très cher en sqlite de démarrer des transactions à répétition.
Soit tu insères tout à la fin, soit tu démarres une transaction, fais tes multiples insert et tu la commit à la fin. Le gain sera appréciable je pense.
[^] # Re: regarde si tu ne peux pas faire une simple recherche
Posté par fanto30 . Évalué à 1.
Et aussi je dirais que best practice, couplage faible, responsabilités, toussa koi, la methode scanFiles n'a pas à faire les insertions en BDD (même si on sent un peu d'injection de dépendances avec la ref à db qui est passée).
Je verrais plutôt que la méthode retourne une liste de paths à insérer, surtout que comme déjà dit les insertions dans sqlite ca fait mal au FS.
# Divers points
Posté par lolop (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).
Votez les 30 juin et 7 juillet, en connaissance de cause. http://www.pointal.net/VotesDeputesRN
[^] # Re: Divers points
Posté par Alex G. . É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 fanto30 . É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 jemore . É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).
[^] # Re: insert a la fin
Posté par reno . Évalué à 2.
S'il y a un grand nombre de fichiers sur un disque dur, on peut même trier les fichiers par leur emplacement sur le disque..
Cf http://simula.no/research/nd/publications/Simula.ND.399/simula_pdf_file
As an example, a tar of the Linux kernel tree was 82.5 seconds using GNU tar, while our modified tar completed in 17.9 seconds.
# Merci
Posté par JPEC . É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 JPEC . É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 rakoo (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.