edit 17/02/2011 : MAJ du code
La PS3 et MKV ne faisant pas bon ménage,
on est obligé d’extraire les tracks (vidéo/audio/sous-titres ) du conteneur via mkvextract (et mkvinfo).
On fait :
mkvinfo fichier.mkv
pour connaitre les ID des tracks , puis:
mkvextract tracks fichier.mkv 1: fichier.mp4 2:fichier.mp3
pour les extraire.
Mais n’étant pas très fan des lignes de commandes, j’ai fait un petit script python qui prend en paramètre le nom du fichier(chemin inclus) et qui va s’occuper de taper ces deux commandes .
Je n’extraie ni les chapitres, ni les covers : le script permet juste d’extraire les vidéos/audio/sous titres.
#-*- coding:utf-8 -*- import os import re import sys from subprocess import Popen,PIPE,STDOUT VIDEO_EXTENSIONS = {'V_MPEG4/ISO/AVC':'mp4','default' : ''} AUDIO_EXTENSIONS = {'A_AAC':'aac','default':''} SUB_EXTENSIONS = {'default':'srt'} EXTENSIONS = {'video':VIDEO_EXTENSIONS,'audio':AUDIO_EXTENSIONS,'subtitles':SUB_EXTENSIONS} def get_extension(type,codec): """ """ if codec in EXTENSIONS[type].keys(): return EXTENSIONS[type][codec] return EXTENSIONS[type]['default'] def get_filename(new_names,filename,type,codec): """ obligé d'avoir un nom de fichier qui n'existe pas puisque mkvextract n'écrase pas mais retourne une erreur """ new_name = (".").join(filename.split('.')[:-1]) extension = get_extension(type,codec) number = 0 while os.path.exists("%s.%d.%s"%(new_name,number,extension)) or "%s.%d.%s"%(new_name,number,extension) in new_names : number += 1 return "%s.%d.%s"%(new_name,number,extension) def get_tracks(filename): """ """ ps = Popen(['mkvinfo',filename],stdout=PIPE,stderr=STDOUT) output = ps.communicate()[0] if ps.returncode == 0: results = re.findall( r"""(?mx) ^\|\s*[+]\sTrack\snumber:\s(\d+)\s (?:^.+\s)*? ^\|\s*[+]\sTrack\stype:\s*(.+)\s (?:^.+\s)*? ^\|\s*[+]\sCodec\sID:\s*(.+)\s (?:^.+\s)*? (?:^(?:\|\s*[+]\sLanguage:\s*(.+)\s|\|\s*[+]\sA\strack$)) """,output) return results return () def extract(filename,tracks): """ tracks => ((number,type,codec),... ) """ cmd = ['mkvextract','tracks',filename] new_names = [] for track in tracks: name = get_filename(new_names,filename,track[1],track[2]) new_names.append(name) cmd.append("%s:%s"%(track[0],name)) ps = Popen(cmd,stdout=PIPE,stderr=STDOUT) output = ps.communicate()[0] if ps.returncode == 0: return 0 #print output return output if __name__ == '__main__': file = sys.argv[1] tracks = get_tracks(file) if tracks: extract(file,tracks)
Le script est simple, donc pas grand chose à dire si ce n’est :
* l’utilisation de Popen() pour éxécuter commande: la sortie standard/d’erreur peut être redirigée dans un fichier, un pipe,etc. On passe la commande sous forme de liste (par ex. pour un « ls -l« , on passera ["ls","-l"])
* dans la regex , j’utilise (?:…) Ça permet de matcher l’expression régulière mais sans rien retourner/capturer
* la fonction get_filename() n’est pas très jolie, mais j’ai pas réussi à faire mieux
Et j’y ai donc rajouté un petit gui (en PyQt), qui permet de choisir quels tracks on veut extraire. Il est fait un peu à l’arrache (j’aurais pu mettre ma QTableWidget dans un autre fichier)
Le voici :
* fichier qt_thread.py :
from mkv import extract from PyQt4.QtCore import QEvent, QThread from PyQt4.QtGui import QApplication class MyEvent(QEvent): """ """ def __init__(self,elements): """ """ QEvent.__init__(self,QEvent.User) self.elements = elements class MyThread(QThread): """ """ def __init__(self,receiver,filename,tracks): QThread.__init__(self) self.receiver = receiver self.filename = filename self.tracks = tracks def run(self): """ """ extract(self.filename,self.tracks) QApplication.postEvent(self.receiver,MyEvent(self.tracks))et gui.py :
#-*- coding:utf-8 -*- from mkv import get_tracks from qt_thread import MyThread from PyQt4.QtCore import Qt, SIGNAL from PyQt4.QtGui import * import os import sys class MainWindow(QMainWindow): """ La fenêtre principale""" def __init__(self): """ """ QMainWindow.__init__(self) self.setWindowTitle(u"mkvextract gui") self.resize(430,600) self.draw() self.center() self.thread = None self.filename = None def draw(self): """ """ main_widget = QWidget(self) main_layout = QGridLayout(main_widget) self.open_button = QPushButton(u"Choisir un .mkv") self.extract_button = QPushButton(u"Extraire") self.extract_button.setDisabled(True) self.info_label = QLabel() self.info_label.setAlignment(Qt.AlignCenter) self.table_widget = QTableWidget(main_widget) self.draw_table() main_layout.addWidget(self.open_button,0,0) main_layout.addWidget(self.info_label,0,1) main_layout.addWidget(self.table_widget,1,0,2,0) main_layout.addWidget(self.extract_button,2,0,1,2) self.setCentralWidget(main_widget) self.connect(self.open_button,SIGNAL("clicked()"),self.open_file) self.connect(self.extract_button,SIGNAL("clicked()"),self.extract) def draw_table(self): """ """ self.table_widget.setSelectionBehavior( QAbstractItemView.SelectRows )#sélectionne toute la ligne self.table_widget.setColumnCount(4) self.table_widget.setHorizontalHeaderItem(0,QTableWidgetItem(u"ID")) self.table_widget.setHorizontalHeaderItem(1,QTableWidgetItem(u"Type")) self.table_widget.setHorizontalHeaderItem(2,QTableWidgetItem(u"Codec")) self.table_widget.setHorizontalHeaderItem(3,QTableWidgetItem(u"Code Langue")) self.table_widget.verticalHeader().hide()# cache les numéros de lignes self.table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)#pas d'inline editing def add_row(self,element): """ """ count = self.table_widget.rowCount() self.table_widget.insertRow(count) self.table_widget.setItem(count,0,QTableWidgetItem(element[0])) self.table_widget.setItem(count,1,QTableWidgetItem(element[1])) self.table_widget.setItem(count,2,QTableWidgetItem(element[2])) self.table_widget.setItem(count,3,QTableWidgetItem(element[3])) def center(self): """ """ screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2) def open_file(self): """ """ file_dialog = QFileDialog(self,caption=u"Choisissez un .mkv",filter=u"Conteneur(*.mkv)") file_dialog.setFileMode(QFileDialog.ExistingFile)#pour sélectionner un seul fichier file_dialog.setViewMode(QFileDialog.Detail) # vue "détails" if file_dialog.exec_(): self.filename = file_dialog.selectedFiles()[0] self.info_label.setText(os.path.split(unicode(self.filename.toUtf8()))[-1]) #pas de thread vu que ca va être assez rapide tracks = get_tracks(self.filename) for element in tracks: self.add_row(element) self.extract_button.setDisabled(False) def customEvent(self,event): """ """ self.open_button.setDisabled(False) self.info_label.setText(u"Extraction terminée") self.table_widget.clearSelection() count = self.table_widget.rowCount() for i in range(count-1,-1,-1): self.table_widget.removeRow(i) self.filename = None self.table_widget.setDisabled(False) def extract(self): """ """ selected = self.table_widget.selectedItems() if selected: tracks = [] for i in range(0,len(selected),4): tracks.append([ unicode(item.text().toUtf8()) for item in selected[i:i+4] ]) self.extract_button.setDisabled(True) self.open_button.setDisabled(True) self.table_widget.setDisabled(True) self.info_label.setText(u"Extraction en cours") self.thread = MyThread(self,unicode(self.filename.toUtf8()),tracks) self.thread.start() if __name__ == '__main__': appli = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(appli.exec_())Le code est lui aussi assez bateau, 2 petites choses :
* j’utilise QThread/postEvent pour threader l’appel de la commande mkvextract
* setEditTriggers(QAbstractItemView.NoEditTriggers) pour ne pas autoriser l’inline editing de la table Et puis voilà . L’archive : mkvextract_gui.py.tar Et une petite vidéo du gui (assez pourri): [hana-flv-player video="http://qui-saura.fr/wp-content/uploads/css/out.flv" width="400" height="330" description="GUI mkvextract" player="4" autoload="true" autoplay="false" loop="false" autorewind="true" /]