Archive for the ‘Python 3’ Category.

Python can save your ass!

Définition du besoin

Je suis fan de la série NCIS… Et cette semaine j’ai récupéré la première saison en VO. J’ai téléchargé le pack de sous-titres sur www.tvsubtitles.net. Je copie les épisodes et leur sous-titre sur ma FreeBox et lance le premier épisode.

Et la, c’est le drame! Un gros décalage de 2 secondes environ entre la bande sonore et le sous-titre :(

Un collègue du  boulot m’a donné une idée : écrire un programme pour mettre à jour les sous-titres!

En effet, les fichiers de sous-titre sont de simple fichier textes qui sont composés de la manière suivante :

1
00:01:16,577 --> 00:01:18,579
NCIS ENQUETES SPECIALES

2
00:01:53,572 --> 00:01:56,408
"L'affrontement"

3
00:02:01,288 --> 00:02:02,706
Réveille-le.

Il suffit donc d’écrire un petit programme qui lit le fichier et qui analyse les lignes. Si la ligne est une ligne du type : “00:02:01,288 –> 00:02:02,706″ et si c’est le cas, il faut extraire les valeurs numériques pour créer des objets de type date avec et ainsi pouvoir faire le calcul pour ajouter/soustraire un delay. Enfin on écrit chaque ligne dans un nouveau fichier texte.

Comme une série se compose de 23 épisodes et que je suis une loutre, il faut aussi que le programme se débrouille tout seul pour trouver les fichiers sous-titres et les corriger les uns après les autres…

Et oui, Python peut vous sauver la vie!

Résultat

Ce petit programme en Python3 : reprise_sous_titres.py.

#!/usr/bin/python3
#--------------------------------------------------------------------#
# Programme :   reprise_sous_titres.py                               #
# Auteur :      Julien PECQUEUR (JPEC)                               #
# Home :        http://julienpecqueur.com                            #
# License :     GPL                                                  #
#                                                                    #
# Description :                                                      #
# Ce programme va reprendre tous les fichiers de type sous-titre     #
# contenus dans le répertoire REPERTOIRE et va leur appliquer un     #
# décalage DECALAGE.                                                 #
#--------------------------------------------------------------------#
#--------------------------------------------------------------------#
# IMPORT DES MODULES                                                 #
#--------------------------------------------------------------------#
import os           # Fonctions système
import datetime     # Fonctions date/heure
import re           # Expressions régulières
#--------------------------------------------------------------------#
# PARAMETRAGE                                                        #
#--------------------------------------------------------------------#
# REPERTOIRE    Chemin du répertoire dans lequel se trouvent les
#               fichiers sous-titre.
REPERTOIRE = "/home/jpec/"
# EXTENSION     Extension des fichiers de type sous-titre.
EXTENSION = ".srt"
# MASQUE        Format d'une ligne avec le positionnement horaire.
#               ex: 00:02:18,639 --> 00:02:21,099
MASQUE = "([0-9][0-9]):([0-9][0-9]):([0-9][0-9]),([0-9][0-9][0-9])"+\
         " --> " + \
         "([0-9][0-9]):([0-9][0-9]):([0-9][0-9]),([0-9][0-9][0-9])"
# DECALAGE_*    Valeurs du décalage pour les sous-titres.
DECALAGE_HEU = 0        # Heures
DECALAGE_MIN = 0        # Minutes
DECALAGE_SEC = 2        # Secondes
DECALAGE_MSC = 500000   # Micro-secondes
# SUPPR_SOURCE  Suppression du fichier source? ("OUI" ou "NON")
SUPPR_SOURCE = "NON"
#--------------------------------------------------------------------#
# FONCTIONS                                                          #
#--------------------------------------------------------------------#
def corriger(Fichier):
    # Fonction corriger(Nom du fichier) - Elle corrige le fichier de
    # sous-titre passé en paramêtre en lui appliquant le décalage
    # définit : DECALAGE_*.
    print("*** Correction fichier '" + Fichier + "\'...")
    FichierSource = open(REPERTOIRE + Fichier, \
                         'r', \
                         encoding="iso8859_15")
    # Fichier temporaire :
    FichierCible = open(REPERTOIRE + Fichier + ".tmp", \
                        'w', \
                        encoding="iso8859_15")
    # On boucle sur les lignes du fichier source pour alimenter le
    # fichier temporaire :
    for Ligne in FichierSource:
        if Regex.match(Ligne):
            # On est sur une ligne de positionnement horaire :
            StartH = int(Regex.match(Ligne).group(1))
            StartM = int(Regex.match(Ligne).group(2))
            StartS = int(Regex.match(Ligne).group(3))
            Startm = int(Regex.match(Ligne).group(4))*1000
            EndH = int(Regex.match(Ligne).group(5))
            EndM = int(Regex.match(Ligne).group(6))
            EndS = int(Regex.match(Ligne).group(7))
            Endm = int(Regex.match(Ligne).group(8))*1000
            Start = datetime.datetime(2000, 1, 1, \
                                      StartH,
                                      StartM,
                                      StartS,
                                      Startm)
            End = datetime.datetime(2000, 1, 1, \
                                    EndH,
                                    EndM,
                                    EndS,
                                    Endm)
            Start = Start + Delay
            End = End + Delay
            StartH = Start.strftime('%H')
            StartM = Start.strftime('%M')
            StartS = Start.strftime('%S')
            Startm = Start.strftime('%f')[0:3]
            EndH = End.strftime('%H')
            EndM = End.strftime('%M')
            EndS = End.strftime('%S')
            Endm = End.strftime('%f')   [0:3]
            # Construction de la nouvelle ligne :
            Ligne = StartH +":"+ StartM +":"+ StartS +","+ Startm \
                    + " --> " + \
                    EndH +":"+ EndM +":"+ EndS +","+ Endm + "\n"
        FichierCible.write(Ligne)
    # Fermeture des fichiers :
    FichierSource.close
    FichierCible.close
    # Gestion copie/remplace :
    if SUPPR_SOURCE == "OUI":
        print("*** Suppression fichier source \'" + Fichier + "\'...")
        os.remove(REPERTOIRE + Fichier)
        print("*** '" + Fichier + ".tmp\' -> \'" + Fichier + "\'...")
        os.rename(REPERTOIRE + Fichier + ".tmp", REPERTOIRE + Fichier)
    print("*** \'" + Fichier + "\' DONE !")
#--------------------------------------------------------------------#
# CODE                                                               #
#--------------------------------------------------------------------#
# Récupération de la liste des fichiers du répertoire de travail :
print("*** Liste des fichiers de \'" + REPERTOIRE + "\'...")
ListeFichiers = os.listdir(REPERTOIRE)
ListeFichiers.sort()
# Création de l'expression régulière pour le masque :
Regex = re.compile(MASQUE)
# Création du décalage pour les sous-titres :
Delay = datetime.timedelta(hours=DECALAGE_HEU, \
                           minutes=DECALAGE_MIN, \
                           seconds=DECALAGE_SEC, \
                           microseconds=DECALAGE_MSC)
# Boucle sur les fichiers du répertoire de travail. Si c'est un
# fichier de type sous-titre, alors on appelle la fonction corriger :
print("*** Boucle sur les fichiers de type \'" + EXTENSION + "\'...")
for Fichier in ListeFichiers:
    if Fichier[len(Fichier)-len(EXTENSION):len(Fichier)] == EXTENSION:
        corriger(Fichier)
#--------------------------------------------------------------------#
# FIN DU PROGRAMME                                                   #
#--------------------------------------------------------------------#

Python, MySQLdb & 1and1.fr

Je bosse actuellement sur un petit programme en Python… Il doit aller rechercher des infos dans les tables MySQL de ce blog. J’utilise le module MySQLdb pour Python2.6. Ça fonctionne très bien sur ma base MySQL de developpement @localhost mais pas sur la base de production située sur un serveur distant.

Message d’erreur :

Traceback (most recent call last):
  File "/home/jpec/python2/WordPress.py", line 56, in <module>
    Blog.Connecter()
  File "/home/jpec/python2/WordPress.py", line 39, in Connecter
    db =     self.DB )
  File "/usr/lib/python2.6/site-packages/MySQLdb/__init__.py", line 81, in Connect
    return Connection(*args, **kwargs)
  File "/usr/lib/python2.6/site-packages/MySQLdb/connections.py", line 188, in __init__
    super(Connection, self).__init__(*args, **kwargs2)
OperationalError: (2005, "Unknown MySQL server host 'dbxxxxx.1and1.fr' (1)"

A première vue, il semblerait que le module n’arrive pas à résoudre le nom de domaine. J’ai donc commencé à farfouiller et je me suis rendu compte que c’était tout simplement 1and1.fr qui bloque l’accès aux bases depuis l’extérieur. Le seul moyen d’accéder aux bases et de passer par l’interface d’administration PHPMyAdmin préinstallée…

Les bases de données MySQL sont stockées sur un serveur dédié aux bases de données. Ce dernier est protégé par un pare-feu pour assurer la sécurité de vos données. Vous ne pouvez accéder à ce serveur de bases de données et à votre base de données qu’à partir de votre site Web. Vous ne pouvez pas vous connecter directement à une base de données MySQL à partir de votre PC (connexion ODBC externe).

Pas GLOP :(

Traceback (most recent call last):
File “/home/jpec/python2/WordPress.py”, line 56, in <module>
Blog.Connecter()
File “/home/jpec/python2/WordPress.py”, line 39, in Connecter
db =     self.DB )
File “/usr/lib/python2.6/site-packages/MySQLdb/__init__.py”, line 81, in Connect
return Connection(*args, **kwargs)
File “/usr/lib/python2.6/site-packages/MySQLdb/connections.py”, line 188, in __init__
super(Connection, self).__init__(*args, **kwargs2)
OperationalError: (2005, “Unknown MySQL server host ‘db1613.1and1.fr’ (1)”)

MyMCTk.py : Release v0.2

J’ai enfin résolu le problème d’affichage et je peux donc enfin releaser la version 0.2 (première version avec une interface graphique) de mon MediaCenter ultra-léger et utra-simple!

Il est programmé en Python 3 avec la librairie graphique Tk et a pour but de me former à ce langage…

Screenshot :

MyMCTk

MyMCTk

Installation :

1. Télécharger l’archive sur mon dépôt : MyMCTk.tar.

2. Installez Python 3 selon les commandes habituelles de votre distribution Linux (yaourt python3 pour Archlinux) ou en le téléchargeant sur le site officiel de Python : http://python.org.

3. Installez Mplayer (lecteur multimédia en ligne de commande) de la même manière ou en le téléchargeant sur le site officiel : http://www.mplayerhq.hu.

4. Décompressez l’archive dans le répertoire de votre choix :

tar -vxf mon_fichier.tar

5. Éditez le fichier MyMCTk.py avec votre éditeur de texte préféré (VIM par exemple) :

#------------------------------------------------------#
# PARAMETRAGE                                          #
#------------------------------------------------------#
# Répertoire contenant les films :
FILMSDIR = '/home/monutilisateur/monrepertoiredesfilms/'
#
# Commande pour récupérer les films du FTP :
FTPGET= ''
#
# Commande pour lancer mplayer en plein écran :
MPLAYER  = 'mplayer -fs '
#
# Commande pour éteindre le pc :
SHUTDOWN = 'sudo poweroff'
#
# Autres constantes :
TILDE    = '\''

Vous devez indiquer le lien du répertoire qui contient les films dans FILMDIR, puis au besoin renseigner la commande FTPGET qui vous permet de récupérer les films d’un FTP (ou d’un lecteur réseau, d’un disque usb…) et de les placer dans le répertoire des films FILMDIR.

Normalement vous n’avez pas à changer les autres constantes (sauf si vous voulez remplacer mplayer par un autre player (VLC, etc.).

6. Rendez le fichier exécutable :

chmod +x MyMCTk.py

7. Exécutez le programme :

./MyMCTk.py

Et voilà normalement MyMCTk doit être fonctionnel ;)

8. [ FACULTATIF ] Si vous désirez pouvoir lancer MyMCTk depuis n’importe quel répertoire vous pouvez créer un lien symbolique dans /usr/sbin :

Vous devez être placé dans /usr/sbin/ en root :
ln -s /votre_chemin_vers_le_fichier/MyMCTk.py

Bugs :

Vous pouvez remonter les bugs rencontrés et les suggestions ici même (commentaire ou email)…

Les points sur lesquels je suis en train de travailler :

  • Prise en charge des fichiers avec des apostrophes dans le nom,
  • Ajout d’un bouton pour effacer un film,
  • Ajout d’un bouton pour renommer un film,
  • Rafraichissement de la liste…

MyMCTk.py – Tkinter issue :(

Depuis hier je me prends la tête un problème avec Tkinter :

Tk issue :(

Tk issue :(

La taille de la listbox (zone ou sont affichés les titres des films) ne s’adapte à la taille de la fenêtre qu’en largeur!

J’ai essayé de nombreuses combinaison avec les fill=”", les side= et les expand=”" sans aucun succès…

Voici les extraits du code :

#--------------
# DECLARATIONS
#--------------
# Liste contenant les différents fichiers du répertoire :
ListeRepertoire = []
#
# Fenêtre principale :
Fenetre = tkinter.Tk()
#
# Division verticale de la fenêtre :
Division = tkinter.PanedWindow(orient=tkinter.VERTICAL)
#
# Panneau Supérieur :
PanneauSup = tkinter.Label(Division)
#
# Panneau Inférieur :
PanneauInf = tkinter.PanedWindow(orient=tkinter.HORIZONTAL)
PanneauInf1 = tkinter.Label(PanneauInf)
PanneauInf2 = tkinter.Label(PanneauInf)
PanneauInf3 = tkinter.Label(PanneauInf)
PanneauInf4 = tkinter.Label(PanneauInf)
PanneauInf5 = tkinter.Label(PanneauInf)
#
# Ascenseur de la fenêtre principale :
Ascenseur = tkinter.Scrollbar(PanneauSup,orient=tkinter.VERTICAL)
#
# Liste des films de la fenêtre principale :
Liste = tkinter.Listbox(PanneauSup, yscrollcommand=Ascenseur.set)
#
# Bouton Play de la fenêtre principale :
BoutonPlay = tkinter.Button(PanneauInf1,text='Play')
#
# Bouton Mise à jour FTP de la fenêtre principale :
BoutonFtp = tkinter.Button(PanneauInf2,text='FTP')
#
# Bouton Quit de la fenêtre principale :
BoutonQuit = tkinter.Button(PanneauInf3,text='Quit')
#
# Bouton Shutdown de la fenêtre principale :
BoutonShutdown = tkinter.Button(PanneauInf4,text='Bye')
#
#------------
# PROGRAMME
#------------
Division.pack(expand="yes", fill="both")
Division.add(PanneauSup)
PanneauInf.pack(expand="false", fill="none")
PanneauInf.add(PanneauInf1)
PanneauInf.add(PanneauInf2)
PanneauInf.add(PanneauInf3)
PanneauInf.add(PanneauInf4)
PanneauInf.add(PanneauInf5)
Division.add(PanneauInf)
#
# On affiche l'ascenceur et on le place sur la gauche :
Ascenseur.pack(side=tkinter.LEFT,fill=tkinter.Y)
Ascenseur.config(command=Liste.yview)
#
# Affichage de la liste :
Liste.pack(expand="yes", fill="both")
#
# On affiche le bouton Play :
BoutonPlay.pack(fill="none")
#
# On affiche le bouton FTP :
BoutonFtp.pack(fill="none")
#
# On affiche le bouton Quit :
BoutonQuit.pack(fill="none")
#
# On affiche le bouton Shutdown :
BoutonShutdown.pack(fill="none")

Si quelqu’un a une idée ou une suggestion pour corriger ce problème je suis tout ouie ;)

MyMCTk.py – Bientôt la release :)

J’ai eu un peu de temps libre ce soir… J’ai donc pu travailler un peu sur mon media-center!

Comme promis, j’ai commencé l’interface graphique pour une utilisation simplifiée pour les non-geek ;) J’ai choisit la librairie Tkinter de Python pour la simplicité (elle est fournie avec Python). Le media-center fonctionne actuellement mais j’ai encore quelques problèmes graphiques à résoudre (voir le problème d’ajustement automatique de la taille de la liste en hauteur qui créé des blancs au dessous et au dessus des boutons).

Dès que ce problème est résolu, je fignole le code, le design et je release le media-center…

En attendant, un petit aperçu :

Aperçu de MyMCTk.py (en developpement)

Aperçu de MyMCTk.py (en developpement)

Release prévue ce weekend !! ;)