Outils pour utilisateurs

Outils du site


issue71:python

Table des matières

1

Let's assume that you have decided to create a multimedia center for your family room. You have a dedicated computer for the wonderful program called XBMC. You've spent days ripping your DVD movies and TV series onto the computer. You have done the research and named the files the correct way. But let's say that one of your favorite shows is “NCIS,” and you have every episode that you can get on DVD. You found a place that provides the current episodes as well. You want to find out what the next episode is and when it will be broadcast. Plus, you want to create a list of all the TV episodes that you have to impress your friends. This is the project we will be starting this month. Our first task is to dig through the folder containing your TV shows, grabbing the series name, and each episode – including the name and season number, and the episode number. All this information will go into a database for easy storage. According to XBMC, you should name each of your tv episode files like this: Tv.Show.Name.SxxExx.Episode name here if you care.extension

Supposons que vous avez décidé de créer un centre multimédia pour votre salle de séjour. Vous avez un ordinateur dédié à l'excellent programme XBMC. Vous avez passé des jours à ripper vos DVD de films et séries TV sur l'ordinateur. Vous avez fait la recherche et nommé les fichiers correctement. Mais disons que l'une de vos séries préférées est « NCIS », et que vous avez tous les épisodes possibles sur DVD. Vous avez aussi trouvé un endroit qui propose les épisodes actuels. Vous voulez savoir quel sera le prochain épisode et quand il sera diffusé. De plus, vous souhaitez créer une liste de tous les épisodes de séries TV que vous avez pour épater vos amis.

C'est le projet que nous allons commencer ce mois-ci. Notre première tâche consiste à fouiller dans le dossier contenant vos émissions de télévision, en récupérant le nom de la série et chaque épisode - y compris le nom et le numéro de la saison et le numéro de l'épisode. Toutes ces informations iront dans une base de données pour faciliter le stockage.

D'après XBMC, vous devriez nommer vos fichiers comme ceci pour chaque épisode :

Tv.Série.Nom.SxxExx.Nom de l'épisode ici si vous voulez.extension

2

So, let's use the very first episode of NCIS as an example. The filename for an AVI file would be: NCIS.S01E01.Yankee White.avi and the very latest episode would be: NCIS.S10E17.Prime Suspect.avi If you have a show name that has more than one word, it could look like this: Doctor.Who.2005.S07E04.The Power of Three.mp4

Utilisons donc le tout premier épisode de NCIS (en VO) à titre d'exemple. Le nom de fichier pour un fichier AVI serait :

NCIS.S01E01.Yankee White.avi

et le tout dernier épisode serait :

NCIS.S10E17.Prime Suspect.avi

Si un nom d'émission contient plusieurs mots, il pourrait ressembler à ceci :

Doctor.Who.2005.S07E04.The Power of Three.mp4

3

The directory structure should be as follows: TVShows 2 Broke Girls Season 1 Episode 1 Episode 2 … Season 2 … Doctor Who 2005 Season 1 … Season 2 … and so on. Now that we know what we will be looking for and where it will be, let's move on.

La structure du répertoire devrait ressembler à ceci :

émissions      2 Broke Girls         saison 1            Episode 1            Episode 2             …         saison 2            …      Doctor Who 2005         saison 1            …         saison 2            …

et ainsi de suite. Maintenant que nous savons ce que nous allons chercher et où ça se trouve, nous pouvons continuer.

4

A very long time ago, we created a program to make a database of our MP3 files. That was back in issue #35 I believe, which was part number 9 of this series. We used a routine called WalkThePath to recursively dig through all the folders from a starting path, and pull out the filenames that had a “.mp3” extension. We will reuse most of that routine and modify it for our purposes. In this version, we will be looking for video files that have one of the following extensions: .avi .mkv .m4v .mp4 Which are very common extensions for video files in the media PC world.

Il y a très longtemps, nous avons créé un programme pour constituer une base de données contenant nos fichiers MP3. C'était dans le n° 35, je crois, au neuvième épisode de cette série. Nous avons utilisé une routine appelée ParcourirChemin pour entrer récursivement dans tous les dossiers à partir d'un chemin de départ, et récupérer les noms de fichiers avec l'extension « .mp3 ». Nous allons réutiliser une grande partie de cette routine et la modifier pour nos besoins. Dans cette version, nous rechercherons des fichiers vidéo qui ont une des extensions suivantes :

.avi .mkv .m4v .mp4

Ce sont des extensions très courantes pour les fichiers vidéo dans le monde des média PC.

5

Now we will get started with the first part of our project. Create a file called “tvfilesearch.py”. Be sure to save it when we are done this month, because we will be building on it next month. Let's start with our imports: import os from os.path import join, getsize, exists import sys import apsw import re As you can see, we are importing the os, sys and apsw libraries. We've used them all before. We are also importing the re library to support Regular Expressions. We'll touch on that quickly this time, but more in the next article.

Nous allons maintenant commencer avec la première partie de notre projet. Créez un fichier appelé “cherche_fichiers_tv.py​​”. Veillez à bien l'enregistrer quand nous aurons fini ce mois-ci, parce que nous allons repartir de là le mois prochain.

Commençons avec nos importations :

import os from os.path import join, getsize, exists import sys import apsw import re

Comme vous pouvez le voir, nous importons les bibliothèques os, sys et apsw. Nous les avons toutes déjà utilisées. Nous importons aussi la bibliothèque re pour le support des expressions régulières. Nous allons en parler rapidement cette fois-ci, nous approfondirons dans le prochain article.

6

Now, let's do our last two routines next (next page). All our other code will go in between the imports and these last two routines. This (next page, bottom right) is our main worker routine. In it, we create a connection to the SQLite database provided by apsw. Next we create a cursor to interact with it. Then we call the MakeDatabase routine which will create the database if it doesn't exist.

Maintenant, nous allons continuer avec nos deux dernières routines (page suivante). Tout le reste de notre code se trouvera entre les importations et ces deux dernières routines.

Voici (page suivante, en bas à droite) notre routine de travail principale. Nous y créons une connexion à la base de données SQLite fournie par apsw. Ensuite, nous créons un curseur pour interagir avec elle. Ensuite, nous appelons la routine FabriquerBase qui va créer la base de données si elle n'existe pas.

7

My TV files are located on two hard drives. So I created a list to hold the path names. If you have only one location, you can change the three lines to be as follows: startfolder = “/filepath/folder/” WalkThePath(startfolder) Next, we create our “standard” if name routine. #============================if name == 'main': main() Now all the dull stuff is done, so we can move on the the meat and potatoes of our project. We'll start with the MakeDataBase routine (middle right). Put it right after the imports.

Mes fichiers TV se trouvent sur deux disques durs. J'ai donc créé une liste pour contenir les chemins. Si vous avez un seul endroit, vous pouvez modifier les trois lignes comme suit :

  dossierDepart = "/chemin/dossier/"
  ParcourirChemin(dossierDepart)

Ensuite, nous créons notre routine ifname « standard ».

#============================if name == 'main':

  main()

Maintenant, tous les trucs ennuyeux sont faits, et nous pouvons passer au plat de résistance de notre projet. Nous allons commencer avec la routine FabriquerBase (au milieu à droite). Placez-la juste après les importations.

8

We discussed this routine before when we dealt with the MP3 scanner, so I'll just remind you that, in this routine, we check to see if the table exists, and if not, we create it. Now we'll create the WalkThePath routine (right, second from bottom). When we enter the routine (as we talked about way back when), we give the filepath that we are going to search through. We clear the showname variable, which we will use later, and open an error log file. Then we let the routine do its thing. We get back from the call (os.walk) a 3-tuple (directory path, directory names, filenames). The directory path is a string which is the path to the directory, directory names is a list of the names of subdirectories in the path, and the filenames is a list of non-directory names. We then parse through the list of filenames, checking to see if the filename ends with one of our target extensions. for file in [f for f in files if f.endswith (('.avi','mkv','mp4','m4v'))]:

Nous avons déjà discuté de cette routine lorsque nous avons traité le scanner MP3, donc je vais juste vous rappeler que, dans cette routine, nous vérifions pour voir si la table existe et, sinon, nous la créons.

Maintenant, nous allons créer la routine ParcourirChemin (à droite, deuxième à partir du bas).

Lorsque nous entrons dans la routine (comme nous l'avons expliqué à l'époque), nous indiquons le chemin que nous allons parcourir. Nous vidons la variable nomEmission, que nous utiliserons plus tard, et ouvrons un fichier de log d'erreur. Ensuite, nous laissons la routine faire son boulot. Nous récupérons de l'appel (os.walk) un triplet (chemin du répertoire, noms de répertoires, noms de fichiers). Chemin du répertoire est une chaîne contenant le chemin vers le répertoire, noms de répertoires est une liste des noms des sous-répertoires dans le chemin, et noms de fichiers est une liste de noms des non-répertoires. Nous analysons ensuite la liste des noms de fichiers, pour vérifier si le nom se termine par une de nos extensions cibles.

for fic in [f for f in fichiers if f.endswith (('.avi','mkv','mp4','m4v'))]:

9

Now, we split the filename into the extension and the filename (without the extension). Next, we call the GetSeasonEpisode routine to pull out the Season/Episode information that is embedded in the filename, assuming it is correctly formatted. OriginalFilename,ext = os.path.splitext(file) fl = file isok,data = GetSeasonEpisode(fl) GetSeasonEpisode returns a boolean and a list (in this case “data”) which holds the name of the series, the season, and the episode numbers. If a filename doesn't have the correct format, the “isok” boolean variable (top right) will be false.

Maintenant, nous découpons le nom de fichier en séparant l'extension et le nom du fichier (sans l'extension). Ensuite, nous appelons la routine RecupereSaisonEpisode pour avoir l'information de saison/épisode qui se trouve dans le nom du fichier, en supposant qu'il est correctement formaté.

NomFicOriginal,ext = os.path.splitext(fic)

fl = fic

estok,donnees = RecupereSaisonEpisode(fl)

RecupereSaisonEpisode retourne un booléen et une liste (dans ce cas « donnees ») qui contient le nom de la série, la saison et les numéros d'épisodes. Si un nom de fichier n'a pas le bon format, la variable booléenne « estok » (en haut à droite) sera fausse.

10

Next (middle right), we will check to see if the file is in the database. If so, we don't want to duplicate it. We simply check for the filename. We could go deeper and make sure the path is the same as well, but for this time, this is enough. If everything works as it should, the response from the query should only be a 1 or a 0. If it's a 0, then it's not there, and we will write the information to the database. If it is, we just move past. Notice the Try Except commands above and below. If something goes wrong, like some character that the database doesn't like, it will keep the program from aborting. We will, however, log the error so we can check it out later on.

Ensuite (au milieu à droite), nous vérifions si le fichier est dans la base de données. Si c'est le cas, il ne faut pas le dupliquer. Nous vérifions simplement le nom du fichier. Nous pourrions aller plus loin et vérifier que le chemin est aussi le même, mais pour cette fois, c'est assez.

Si tout fonctionne correctement, la réponse de la requête ne devrait être que 1 ou 0. Si c'est 0, alors il n'est pas présent et nous allons écrire l'information dans la base de données. Sinon, nous passons à la suite. Remarquez la commande try/except au-dessus et en-dessous. Si quelque chose va mal, comme un caractère que la base n'aime pas, cela empêchera le programme de s'arrêter. Cependant, nous enregistrerons l'erreur afin de pouvoir vérifier plus tard.

11

We are simply inserting a new record into the database or writing to the error file. # Close the log file efile.close # End of WalkThePath Now, let's look at the GetSeasonEpisode routine. #========================================= def GetSeasonEpisode(filename): filename = filename.upper() resp = re.search(r'(.*).S\d\dE\d\d(\.*)', filename, re.M|re.I)

Nous insérons simplement un nouvel enregistrement dans la base de données ou écrivons dans le fichier d'erreur.

             # ferme le fichier de log              ficerr.close       # Fin de ParcourirChemin

Maintenant, regardons la routine RecupereSaisonEpisode.

# =========================================
def RecupereSaisonEpisode(nomfic):
  nomfic = nomfic.upper()
  resp = re.search(r'(.*).S\d\dE\d\d(\.*)', nomfic, re.M|re.I)  

12

The re.search portion of the code is part of the re library. It uses a pattern string, and, in this case, the filename that we want to parse. The re.M|re.I are parameters that say that we want to use a multiline type search (re.M) combined with an ignore-case (re.I). As I said earlier, we'll deal with the regular expressions more next month, since our routine will match only one type of series|episode string. As for the search pattern we are looking for: “.S”, followed by two decimal numbers, followed by an uppercase “E”, then two more numbers, then a period. If our filename looked like “tvshow.S01E03.avi”, this would match. However, some people encode their shows like this “tvshow.s01e03.avi”, or “tvshow.103.avi”, which makes it harder to deal with. We'll modify this routine next month to cover the majority of the instances. The “r'” allows for a raw string to be used within the search. Continuing on, the search returns a match object that we can look at. “resp” is a response that is empty if there is no match, and, in this case, two groups of returned information. The first one will give us the characters up to the match, and the second including the match. So, in the case above, group(1) would be “tvshow”, and the second group would be “tvshow.S01E03.”. This is specified by the parens in the search “(.*)” and “(\.*)”. if resp: showname = resp.group(1)

La partie re.search du code vient de la bibliothèque re. Elle utilise un modèle de chaîne et, dans ce cas, le nom du fichier que l'on veut analyser. re.M|re.I sont des paramètres qui disent que nous voulons utiliser une recherche de type multiligne (re.M) indépendante de la casse (re.I). Comme je l'ai dit précédemment, nous parlerons plus des expressions régulières le mois prochain, car notre routine correspondra à un seul type de chaîne de série/épisode. En ce qui concerne le modèle de recherche, nous recherchons : « .S » suivi de deux chiffres, suivis par « E » puis deux autres chiffres, puis un point. Si notre nom de fichier ressemblait à « tvshow.S01E03.avi », cela correspondrait. Cependant, certaines personnes codent leurs émissions ainsi : «tvshow.s01e03.avi », ou « tvshow.103.avi », ce qui rend la recherche plus difficile. Nous allons modifier cette routine le mois prochain pour couvrir la majorité des cas. Le « r' » permet qu'une chaîne brute soit utilisée pour la recherche.

Ensuite, la recherche retourne un objet correspondant que nous pouvons regarder. « rep » est une réponse qui est vide si aucune correspondance n'est trouvée, et, dans ce cas, deux morceaux d'information retournés. Le premier va nous donner les caractères jusqu'à la chaîne recherchée, et le second contiendra cette chaîne. Ainsi, dans le cas ci-dessus, group(1) serait « tvshow » et le second groupe serait « tvshow.S01E03. ». Ceci est spécifié par les parenthèses de la recherche « (.*) » et « (\.*) ».

  si rep :
      nomEmission = rep.group(1)

13

We take the show name from group number one. Then we get the length of that so we can grab the series and episode string with a substring command. shownamelength = len(showname) + 1 se = filename[shownamelength:shownamelength+6] season = se[1:3] episode = se[4:6] Next, we replace any periods in the showname with a space – to be more “Human Readable”. showname = showname.replace(“.”,“ ”)

Nous récupérons le nom de l'émission dans le premier groupe. Puis nous calculons sa longueur de façon à pouvoir récupérer la série et l'épisode avec une commande de sous-chaîne.

      longueurNomEmission = len(nomEmission) + 1
      se = nomfic[longueurNomEmission:longueurNomEmission+6]
      saison = se[1:3]
      episode = se[4:6]

Ensuite, nous remplaçons tous les points de nomEmission par une espace, pour les rendre plus « lisibles par l'utilisateur ».

         nomEmission = nomEmission.replace(“.”,“ ”)

14

We create a list to include the show name, season and episode, and return it along with the True boolean to say things went well. ret = [showname,season,episode] return True,ret Otherwise, if we didn't find a match, we create our list containing no show name and two “-1” numbers, and this gets returned with a boolean False. else: ret = [“”,-1,-1] return False,ret

Nous créons une liste contenant le nom de l'émission, la saison et l'épisode, et la retournons avec le booléen True pour dire que les choses se sont bien passées.

      ret = [nomEmission,saison,episode]
      return True,ret

Sinon, si nous n'avons pas trouvé de correspondance, nous créons notre liste avec aucun nom de spectacle et deux « -1 », et la renvoyons avec un booléen False.

  else:
      ret = ["",-1,-1]
      return False,ret

15

That's all the code. Now let's see what the output would look like. Assuming your file structures are exactly like mine, some of the output on the screen would look like this… Season 02 Episode 04 SELECT count(pkid) as rowcount from TvShows where Filename = “InSecurity.S02E04.avi”; Series - INSECURITY File - InSecurity.S02E04.avi Season 01 Episode 08 SELECT count(pkid) as rowcount from TvShows where Filename = “Prime.Suspect.US.S01E08.Underwater.avi”; Series - PRIME SUSPECT US File - Prime.Suspect.US.S01E08.Underwater.avi and so on. You can shorten the output to keep the screen from driving you crazy if you would like. As we said earlier, each entry we find gets put to the database. Something like this: pkID | Series | Root Path | Filename | Season | Episode 2526 | NCIS | /extramedia/tv_files/NCIS/Season 7|NCIS.S07E04.Good.Cop.Bad.Cop.avi | 7 | 4 As always, the full code listing is available on PasteBin.com at http://pastebin.com/txmmagkL Next time, we will deal with more Season|Episode formats, and do some other things to flesh out our program. See you soon.

Voilà tout le code. Maintenant, regardons à quoi le résultat devrait ressembler. En supposant que votre structure de fichier est exactement comme la mienne, une partie de l'affichage devrait ressembler à ceci :

Saison 02 Episode 04 SELECT count(pkid) as nbLignes from EmissionsTV where NomFichier = “InSecurity.S02E04.avi”; Serie - INSECURITY Fichier - InSecurity.S02E04.avi Saison 01 Episode 08 SELECT count(pkid) as nbLignes from EmissionsTV where NomFichier = “Prime.Suspect.US.S01E08.Underwater.avi”; Serie - PRIME SUSPECT US Fichier - Prime.Suspect.US.S01E08.Underwater.avi

et ainsi de suite. Vous pouvez raccourcir la sortie si vous voulez pour éviter que l'écran ne vous rende fou. Comme nous le disions plus haut, chaque élément que nous trouvons sera placé dans la base de données. Quelque chose comme ceci :

pkID | Serie | Chemin Racine | Nom du fichier | Saison | Episode 2526 | NCIS | /extramedia/tv_files/NCIS/Season 7|NCIS.S07E04.Good.Cop.Bad.Cop.avi | 7 | 4

Comme toujours, l'intégralité du code est disponible sur PasteBin.com à http://pastebin.com/p25nwCZM

La prochaine fois, nous traiterons un peu plus les formats de saison/épisode et ferons d'autres choses pour étoffer notre programme.

À bientôt.

issue71/python.txt · Dernière modification : 2013/06/07 14:32 de auntiee