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" /]
