#!/usr/bin/python2 try: import os, getopt, sys, tempfile, shutil, subprocess from subprocess import CalledProcessError except: print('Eunektes requires the following standard modules to be present in order to function: os, getopt, sys, tempfile, shutil, subprocess\nIf your version of python2 doesn\'t include these, try upgrading to a more recent version of python2') sys.exit(2) try: from PyKDE4.kdecore import * from PyKDE4.kdeui import * from PyKDE4.kio import KDirSelectDialog from PyKDE4 import kdecore, kdeui from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4 import QtCore, QtGui except: print("Eunektes requires PyQt4 and PyKDE4 in order to work, please make sure these are installed and try to run Eunektes again.") sys.exit(2) class StreamCopyError(Exception): pass class MountFailure(Exception): pass class BadUserInput(Exception): pass class DumperError(Exception): pass class ShowTitles(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.title_tree = QTreeWidget() self.title_tree.setColumnCount(2) self.layout = QVBoxLayout() self.setLayout(self.layout) self.layout.addWidget(self.title_tree) self.show() def populate(self): for title in self._data['track']: title_widget = QTreeWidgetItem() title_widget.setText(0, 'Title: %s' % title['ix']) title_widget.setText(1, '%s' % self.convert_seconds_string(title['length'])) self.title_tree.addTopLevelItem(title_widget) for chapter in title['chapter']: chapter_widget = QTreeWidgetItem() chapter_widget.setText(0, 'Chapter: %s' % chapter['ix']) chapter_widget.setText(1, '%s' % self.convert_seconds_string(chapter['length'])) title_widget.addChild(chapter_widget) def convert_seconds_string(self, seconds): t_seconds = int(seconds) % 60 minutes = (int(seconds) - t_seconds)/ 60 t_minutes = int(minutes) % 60 hours = int(t_minutes) / 60 return_string = "" if hours > 0: return_string = str(hours) + 'h ' if minutes > 0: return_string = return_string + str(t_minutes) + 'm ' elif hours > 0 and minutes == 0: return_string = return_string + str(t_minutes) + 'm ' return_string = return_string + str(t_seconds)+ 's' return return_string def setData(self, data): self._data = data class RunCommand(QThread): def reset(self): self.with_output = False self.command = '' self.results = {} def check_output(self): try: output = subprocess.check_output(self.command,shell=True) output = output.decode('utf-8') output = output.strip() self.results['ecode']=0 self.results['output']=output except CalledProcessError as e: self.results['ecode'] = e.returncode self.results['output'] = e.output def check_call(self): try: subprocess.check_call(self.command, shell=True) self.results['ecode'] = 0 except CalledProcessError as e: self.results['ecode'] = e.returncode def run(self): if self.with_output == True: self.check_output() else: self.check_call() class Eunektes(QWidget): def __init__(self): QWidget.__init__(self, parent=None) self.title_name = '' self.window = KMainWindow() self.window.setCentralWidget(self) self.stacked_layout = QStackedLayout(self) self.page1 = QWidget() self.page2 = QWidget() self.input_layout = QFormLayout(self.page1) self.progress_layout = QVBoxLayout(self.page2) self.stacked_layout.addWidget(self.page1) #index 0 self.stacked_layout.addWidget(self.page2) #index 1 self.stacked_layout.setCurrentIndex(0) self.device_widget = QWidget(self) self.device_layout = QHBoxLayout() self.device_widget.setLayout(self.device_layout) self.device_title = QPushButton() self.device_title.clicked.connect(self.show_title_tree) self.device_combo = QComboBox(self) devfiles = os.listdir('/dev/') drives = [] for f in devfiles: if f[0:2] == 'sr': drives.append('/dev/' +f) drives.sort() self.device_combo.addItems(drives) self.device_combo.editTextChanged.connect(self.start_timer) self.device_combo.currentIndexChanged.connect(self.start_timer) self.device_combo.setEditable(True) self.device_combo.setToolTip("""This is where you put the path to your optical device \nAn ISO or folder with VIDEO_TS may also work.""") self.device_layout.addWidget(self.device_combo) self.device_layout.addWidget(self.device_title) self.input_layout.addRow("Device: ",self.device_widget) self.titles_line_edit = QLineEdit(self) self.titles_line_edit.setToolTip("""You enter the title(s):chapters you want here.\nexample: 2:1-5,3:2,2:6-10\nTitles are separated by commas, if you want to specify chapters\nthen you put a colon, then the starting chapter 'dash' ending chapter\nIf the ending chapter is not specified, it plays from start chapter to the end of the title.\nThis way, you can get different parts of the same title if you need to\nexample: 2:1-9,2:10-17,2:18-25\nThis would get title 2, chapters 1-9 to a file, title 2, chapters 10-17 to another file, and so on.""") self.input_layout.addRow("Title(s): ", self.titles_line_edit) self.copy_first_checkbox = QCheckBox(self) self.copy_first_checkbox.setChecked(False) self.copy_first_checkbox.setToolTip("""This will mirror the disk to your hard drive with vobcopy before proceeding. \nIt can be slower or faster depending on various things.\nCertain dvd copy protection make vobcopy fail, so it's not as reliable as letting mencoder read directly from the disk.""") self.input_layout.addRow("Copy to disk first?: ",self.copy_first_checkbox) self.destination_folder_line_edit = MyDirRequester(self) self.destination_folder_line_edit.setText(os.environ['HOME']) self.input_layout.addRow("Destination Directory: ",self.destination_folder_line_edit) self.file_name_line_edit = QLineEdit(self) self.file_name_line_edit.setToolTip("""The output filename, without extension ... if multiple titles\n and/or chapters are involved, it will add that info accordingly.\nIt can also accept a comma separated list of titles - the number of names must match the number of titles if you go this route.""") self.input_layout.addRow("Output Filename:", self.file_name_line_edit) fps_tooltip = "You'll have to play with this one to get it right ... there's no reliable way to detect it so you'll have to experiment and see what works best for the title. At worst if you get it wrong I think the subtitles might be out of sync. I could be wrong though." self.fps24_button = QRadioButton(self) self.fps24_button.setText("24fps film (non-telecine)") self.fps24_button.setToolTip(fps_tooltip) self.fps24_button.setChecked(True) self.fps30_button = QRadioButton(self) self.fps30_button.setText("30fps progressive (telecined)") self.fps30_button.setToolTip(fps_tooltip) self.framerate_group = QButtonGroup(self) self.framerate_group.addButton(self.fps24_button) self.framerate_group.addButton(self.fps30_button) self.input_layout.addRow(self.fps24_button, self.fps30_button) self.input_button_box = QDialogButtonBox(self) self.input_button_box.addButton(QDialogButtonBox.Reset) self.input_button_box.button(QDialogButtonBox.Reset).clicked.connect(self.reset_form) self.run_button = QPushButton(self) self.run_button.setText("&Run") self.input_button_box.addButton(self.run_button, QDialogButtonBox.YesRole) self.run_button.clicked.connect(self.run) self.input_layout.addRow(self.input_button_box) self.progress_label = QLabel() self.progress_label.setAlignment(Qt.AlignHCenter) self.progress_table = QTableWidget() self.progress_table.setColumnCount(3) #self.progress_table.setShowGrid(False) self.progress_table.horizontalHeader().setVisible(False) self.progress_table.verticalHeader().setVisible(False) self.progress_button_box = QDialogButtonBox(self) self.progress_button_box.addButton(QDialogButtonBox.Cancel) self.progress_button_box.button(QDialogButtonBox.Cancel).clicked.connect(self.cancel_run) self.progress_button_box.addButton(QDialogButtonBox.Ok) self.progress_button_box.button(QDialogButtonBox.Ok).setVisible(False) self.progress_button_box.button(QDialogButtonBox.Ok).clicked.connect(self.progress_okay) self.progress_button_box.button(QDialogButtonBox.Cancel).setEnabled(False) self.progress_layout.addWidget(self.progress_label) self.progress_layout.addWidget(self.progress_table) self.progress_layout.addWidget(self.progress_button_box) self.preview_title_timer = QTimer() self.preview_title_timer.timeout.connect(self.update_device_title) self.window.show() self.update_device_title() self.check_dependencies() def quit_q_loop(self): print("Quitting Event Loop") self.q.quit() def check_dependencies(self): try: self.run_command_nolog('which mplayer &> /dev/null') except CalledProcessError: self.show_error("Eunektes can't find mplayer on your system - exiting.") try: self.run_command_nolog('which mencoder &> /dev/null') except CalledProcessError: self.show_error("Eunektes can't find mencoder on your system - exiting") try: self.run_command_nolog('which lsdvd &> /dev/null') except CalledProcessError: self.show_error("Eunektes can't find lsdvd on your system - disabling title info feature", False) self.device_title.setEnabled(False) try: self.run_command_nolog('which dvdxchap &> /dev/null') except CalledProcessError: self.show_error("Eunektes can't find dvdxchap on your system - exiting") try: self.run_command_nolog('which mkvmerge &> /dev/null') except CalledProcessError: self.show_error("Eunektes can't find mkvmerge on your system - exiting") try: self.run_command_nolog('which vobcopy &> /dev/null') except CalledProcessError: self.show_error("Eunektes can't find vobcopy on your system - disabling copy first feature", False) self.copy_first_checkbox.setEnabled(False) try: self.run_command_nolog('which ffmpeg &> /dev/null') except CalledProcessError: self.show_error("Eunektes can't find ffmpeg on your system - disabling re-encode problematic video feature", False) self.has_ffmpeg = False else: self.has_ffmpeg = True def start_timer(self): self.preview_title_timer.start(500) def show_title_tree(self): #if self.device_title == 'FOLDER_MODE': # tree = self.run-command_with_output('lsdvd -Oy -c %s') tree = self.run_command_with_output('lsdvd -Oy -c %s' % self.device_combo.currentText()) tree = eval(tree[8:]) dialog = ShowTitles(self) dialog.setData(tree) dialog.populate() dialog.show() def cancel_run(self): """I should probably implement this eventually.""" pass def add_row(self, title, chapters, message): current = self.progress_table.rowCount() self.progress_table.insertRow(current) if chapters != False: title_string = 'Title: %s - Chapters: %s' % (str(title), str(chapters)) else: title_string = 'Title: %s' % str(title) title_item = QTableWidgetItem(title_string) self.progress_table.setItem(current, 0, title_item) self.progress_table.setItem(current, 1, QTableWidgetItem(message)) self.progress_table.setCellWidget(current, 2, QProgressBar()) self.progress_table.cellWidget(current, 2).setRange(0, 0) self.progress_table.resizeColumnsToContents() self.progress_table.scrollToItem(title_item) return current def update_row(self, row, success=True): if success: self.progress_table.cellWidget(row, 2).setMaximum(1) self.progress_table.cellWidget(row, 2).setValue(1) else: #self.progress_table.removeCellWidget(row, 2) #self.progress_table.cellWidget(row, 2).destroy() self.progress_table.cellWidget(row, 2).setVisible(False) self.progress_table.setCellWidget(row, 2, QLabel('Failed')) self.progress_table.cellWidget(row, 2).setVisible(True) #self.progress_table.cellWidget(row, 2).hide() #self.progress_table.setCellWidget()row, 2, QTableWidgetItem('Failed')) def run_command_with_output(self,command): self.vlog("Now running command: %s" % command) return self.run_command_with_output_nolog(command) def run_command_with_output_nolog(self, command): q = QEventLoop() run_command_thread = RunCommand() run_command_thread.reset() run_command_thread.finished.connect(q.quit) run_command_thread.command = command run_command_thread.with_output = True run_command_thread.start() q.exec_() results = run_command_thread.results output = subprocess.check_output(command,shell=True) output = output.decode('utf-8') output = output.strip() return output def update_device_title(self, *myint): try: title_name = self.get_title_name(self.device_combo.currentText()) if title_name.strip() == '': if os.path.isdir(str(self.device_combo.currentText()) + '/VIDEO_TS'): title_name = 'FOLDER_MODE' if title_name.strip() != '': self.device_title.setText(title_name) else: self.device_title.setText('') return title_name except CalledProcessError: self.device_title.setText('') def reset_form(self): self.fps24_button.setChecked(True) self.titles_line_edit.setText('') self.copy_first_checkbox.setChecked(False) self.file_name_line_edit.setText('') def run_command(self, command): self.vlog("Now running command: %s" % command) self.run_command_nolog(command) def run_command_nolog(self, command): q = QEventLoop() run_command_thread = RunCommand() run_command_thread.reset() run_command_thread.finished.connect(q.quit) run_command_thread.command = command run_command_thread.with_output = True run_command_thread.start() q.exec_() results = run_command_thread.results if results['ecode'] != 0: raise CalledProcessError(results['ecode'], '') def reset_progress(self): for x in range(self.progress_table.rowCount()): self.progress_table.removeRow(x) #self.progress_table.setColumnCount(3) self.progress_label.setText('') self.stacked_layout.setCurrentIndex(1) self.progress_button_box.button(QDialogButtonBox.Ok).setVisible(False) self.progress_button_box.button(QDialogButtonBox.Cancel).setVisible(True) def run(self): try: device = str(self.device_combo.currentText()) #this way, it ignores blkid failing when it is a directory with video_ts folder_mode = False if os.path.isdir(device + '/' + 'VIDEO_TS'): folder_mode = True if not folder_mode: if device.strip() == '': raise BadUserInput('Invalid device specified') title_name = self.get_title_name(device) if title_name.strip() == '': raise BadUserInput("Couldn't get title_name from device") else: title_name='FOLDER_MODE' if str(self.titles_line_edit.text()).strip() == '': raise BadUserInput("Title selection seems invalid - remember that no spaces are allowed") #May add further title error checking later ... but it will prove to be rather complicated self.reset_progress() unsplit_tracks = str(self.titles_line_edit.text()).split(',') tracks = list() for x, track in enumerate(unsplit_tracks): tempdict = dict() try: tempdict['number'], tempdict['chapters'] = track.split(':') except ValueError: tempdict['number'] = track tempdict['chapters'] = False finally: tracks.append(tempdict) del tempdict file_names = str(self.file_name_line_edit.text()).split(',') for x, file_name in enumerate(file_names): if str(file_name).strip() == '': if x == 0: file_names[x] = title_name else: file_names[x] = title_name + '_' + titles[x]['number'] if titles[x]['chapters'] != False: file_names[x] = file_names[x] + '_' + titles[x]['chapters'] if len(tracks) != len(file_names) and len(file_names) != 1: raise BadInputError("You need to specify as many file names as you do tracks, else specify one file name and it will alter accordingly.") """TODO: make this call cancel_run when that function is functional, instead of exiting""" if len(tracks) > 1: if len(file_names) == len(tracks): for x, track in enumerate(tracks): tracks[x]['file_name'] = file_names[x] elif len(file_names) == 1: for x, track in enumerate(tracks): tracks[x]['file_name'] = file_names[0] + '_' + tracks[x]['number'] if tracks[x]['chapters'] != False: tracks[x]['file_name'] = tracks[x]['file_name'] + '_' + tracks[x]['chapters'] elif len(tracks) == 1: tracks[0]['file_name'] = file_names[0] if not folder_mode: if self.copy_first_checkbox.isChecked() == True: copy_first = True else: copy_first = False else: copy_first = False if self.fps24_button.isChecked() == True: fps = '24000/1001' else: fps = '30000/1001' destination_folder = str(self.destination_folder_line_edit.url()) if not folder_mode: self.progress_label.setText(title_name) else: self.progress_label.setText('Folder mode - title not available') if copy_first: copy_first_row = self.add_row('*',False,'Mirror disk to hard drive') mirror_dir = self.do_vobcopy(destination_folder, device, title_name) device = '%s/%s' % (mirror_dir, title_name.upper()) self.update_row(copy_first_row) for track in tracks: self.dump(track['number'],track['chapters'], track['file_name'], title_name, destination_folder, fps, device) self.progress_button_box.button(QDialogButtonBox.Ok).setVisible(True) self.progress_button_box.button(QDialogButtonBox.Cancel).setVisible(False) if copy_first: shutil.rmtree(mirror_dir) except BadUserInput as e: self.show_error("Bad user input detected: \n%s" % e.message) except DumperError as e: self.show_error("Dumper had an error", False) self.update_row(self.progress_table.rowCount()-1, False) self.progress_button_box.button(QDialogButtonBox.Ok).setVisible(True) self.progress_button_box.button(QDialogButtonBox.Cancel).setVisible(False) def get_title_name(self, device): try: title_name = self.run_command_with_output_nolog("""blkid -o export %s | grep LABEL | cut -d '=' -f 2""" % device) return title_name except CalledProcessError: self.show_error("blkid failed to obtain the disk title for the device %s" % device) def show_error(self,error_message, exit=True): print(error_message) self.log(error_message) QMessageBox.critical(self,"A Critical Error Has Occurred",error_message) #if exit: #sys.exit(2) def do_vobcopy(self, destination_folder, device, title_name): if not os.path.exists('/media/%s' % title_name): self.show_error("Eunektes now expects disks to be automounted to the appropriate directory, /media/.\nIt formerly relied on halevt-mount to do this, but in light of hal's deprecation I decided to change things.\nI'm currently relying on udev+udiskie now.", False) try: self.vlog('Trying to mount with kdesu') self.run_command('kdesu mount %s /media/%s' % (device, title_name)) except CalledProcessError as e: self.show_error('Failed to mount') mirror_dir = tempfile.mkdtemp(dir=destination_folder) self.vlog("Now running vobcopy:\n'vobcopy /media/%s -F 64 - l -m -o %s'" % (title_name,mirror_dir)) try: self.run_command('vobcopy /media/%s -F 64 -l -m -o %s' % (title_name,mirror_dir)) self.device = '%s/%s/' % (mirror_dir, title_name) return mirror_dir except CalledProcessError as e: self.vlog(e.output) self.show_error("Vobcopy has exited with an error.") def get_stream_info(self, device, title_name, title_number, dvd_method): """This function gets the stream info for the DVD in question, returning a list of dicts. each dict contains streamtype,langcode, and streamid""" print('\n\nGetting stream info for %s: title %s\n\n' % (title_name,title_number)) streams = [] mplayer_output = self.run_command_with_output('mplayer -dvd-device %s %s://%s -frames 0' % (device,dvd_method, title_number)).split('\n') for line in mplayer_output: if line.find('audio stream') > -1: buf_language = '' buf_aid = '' buf = line buf = buf.replace(' format','\nformat') buf = buf.replace(' language','\nlanguage') buf = buf.replace(' aid','\naid') buf = buf.split('\n') for line2 in buf: if line2.find('language') > -1: buf_language = line2.split(': ')[1] elif line2.find('aid') > -1: buf_aid = line2.split(': ')[1].rstrip('.') streams.append(dict(streamtype='audio',langcode=buf_language,streamid=buf_aid)) elif line.find('subtitle ( sid )') > -1: buf_language = '' buf_sid = '' buf = line buf = buf.replace(' language','\nlanguage') buf = buf.split('\n') for line2 in buf: if line2.find('subtitle') > -1: buf_sid = line2.split(': ')[1] elif line2.find('language') > -1: buf_language = line2.split(': ')[1] streams.append(dict(streamtype='subtitle',langcode=buf_language,streamid=buf_sid)) self.vlog(str(streams)) return streams def log(self,message): """Print some info to a log file""" device = self.device_combo.currentText() title_name = self.get_title_name(device) with open('%s/eunektes.%s.log' % (os.environ['HOME'], title_name),'a') as f: f.write(str(message)) def vlog(self,message): """Print some info to a log file, and the terminal""" print('\n\n\n' + str(message) + '\n\n\n') self.log(str(message) + '\n') def dump(self,title_number,chapters, file_name, title_name, destination_folder, fps, device): """Heavy lifting is done here -- dumps the streams, muxes them together""" dvd_method = 'dvd' stream_info_row = self.add_row(title_number, chapters,'Getting stream information...') try: streams = self.get_stream_info(device, title_name, title_number, dvd_method) except CalledProcessError: dvd_method = '-nocache dvdnav' try: streams = self.get_stream_info(title_number, dvd_method) except: raise DumperError('Unknown problem while getting stream info, check the console output for more info?') except: raise DumperError('Unknown problem while getting stream info, check the console output for more info?') self.update_row(stream_info_row) temp_dir = tempfile.mkdtemp(dir=destination_folder) """Grab streams""" self.vlog("Getting video stream ...") if chapters != False: chapters_text = '-chapter %s' % chapters else: chapters_text = '' video_row = self.add_row(title_number,chapters,'Getting video stream') try: self.run_command('mencoder %s -quiet -dvd-speed 20 -dvd-device %s %s://%s -mc 0 -noskip -ofps %s -vf harddup -ovc copy -of rawvideo -oac copy -o %s/videostream.m2v' % (chapters_text, device, dvd_method, title_number,fps, temp_dir)) if not os.path.exists('%s/videostream.m2v' % temp_dir): raise StreamCopyError("Mencoder ended okay, but no file has been output.") else: if os.path.getsize('%s/videostream.m2v' % temp_dir) == 0: raise StreamCopyError("Mencoder ended okay, but an empty file has been output") except CalledProcessError as e: if not e.returncode == -11: if dvd_method == 'dvd': answer = QMessageBox.question(self, "Question", "mencoder has exited with an error.\nUnder certain circumstances this can be caused by goofy copy protection stuffs\nShould Eunektes retry using an alternate method (dvdnav)\n(Note: this may cause other problems)?", QMessageBox.No, QMessageBox.Yes) if answer == QMessageBox.Yes: dvd_method=' -nocache dvdnav' self.run_command('mencoder %s -quiet -dvd-speed 20 -dvd-device %s %s://%s -mc 0 -noskip -ofps %s -vf harddup -ovc copy -of rawvideo -oac copy -o %s/videostream.m2v' % (chapters_text, device, dvd_method, title_number,fps, temp_dir)) else: raise DumperError("Mencoder exited with a non-zero exit status") raise DumperError("Mencoder exited with a non-zero exit status: %s" % str(e.returncode)) except StreamCopyError: raise DumperError("The following problem occurred while trying to dump the video stream for %s #%s\n" % title_name,str(title_number)) self.update_row(video_row) for stream in streams: if stream['streamtype'] == 'audio': self.vlog("Getting audio stream %s ..." % stream['streamid']) audio_row = self.add_row(title_number, chapters, 'Getting audio stream %s' % stream['streamid']) try: self.run_command("mencoder %s -quiet -aid %s -dvd-speed 20 -dvd-device %s %s://%s -ovc copy -oac copy -of rawaudio -o %s/audiostream_%s.ac3" % (chapters_text,stream['streamid'],device,dvd_method, title_number,temp_dir,stream['streamid'])) if not os.path.exists('%s/audiostream_%s.ac3' % (temp_dir,stream['streamid'])): raise StreamCopyError("Mencoder exited okay, but the stream it was supposed to output doesn't exist.") else: if not os.path.getsize('%s/audiostream_%s.ac3' % (temp_dir,stream['streamid'])) > 0: raise StreamCopyError("Mencoder created a stream file for audiostream_%s.ac3, but it's empty." % stream['streamid']) except CalledProcessError as e: #if not e.returncode == -11: raise DumperError("Mencoder exited with a non-zero status while trying to copy the audio stream %s" % stream['streamid']) except StreamCopyError as e: self.vlog(e.output) raise DumperError("The following problem occurred while trying to dump the audio stream %s for %s #%s\n%s" % (stream['streamid'],title_name,str(title_number),str(e))) self.update_row(audio_row) del audio_row elif stream['streamtype'] == 'subtitle': self.vlog("Getting subtitle stream %s ..." % stream['streamid']) subrow = self.add_row(title_number, chapters,'Getting subtitle stream %s' % stream['streamid'] ) try: self.run_command("mencoder %s -o /dev/null -oac copy -ovc copy -vobsubout %s/subtitle_%s -sid %s -dvd-speed 20 -dvd-device %s %s://%s" % (chapters_text,temp_dir,stream['streamid'],stream['streamid'],device,dvd_method, title_number) ) except CalledProcessError as e: #if not e.returncode == -11: self.vlog(e.output) raise DumperError("Mencoder exited with a non-zero status while trying to copy the subtitle stream %s for %s #%s\n%s" % (stream['streamid'],title_name,str(title_number),str(e))) #except StreamCopyError as e: #self.show_error("the following problem occurred while trying to dump the audio stream %s for %s #%s\n%s" % (stream['streamid'],self.title_name,str(title_number),str(e))) self.update_row(subrow) del subrow self.vlog("Getting chapter information ...") chapter_row = self.add_row(title_number,chapters,'Getting chapter information') if chapters != False: chapters_text = '-c %s' % chapters else: chapters_text = '' try: self.run_command("dvdxchap -t %s %s %s > %s/chapters.txt" % (title_number, chapters_text, device, temp_dir)) except CalledProcessError: raise DumperError("dvdxchap ended with a nonzero exit status while getting chapter information.") self.update_row(chapter_row) """Mux Streams""" self.vlog("Merging streams ...") merge_command = 'mkvmerge -o "%s/%s.mkv" --chapter-language "eng" --chapters "%s/chapters.txt" "%s/videostream.m2v" ' % (destination_folder, file_name, temp_dir, temp_dir) for stream in streams: if stream['streamtype'] == 'audio': size = 0 try: size = os.path.getsize( '%s/audiostream_%s.ac3' % ( temp_dir, stream['streamid'] ) ) except: size = 0 if size > 0: default_track = 'no' verbose_language = '' if stream['langcode'] == 'en' or stream['langcode'] == 'eng': default_track = 'yes' verbose_language = 'English' elif stream['langcode'] == 'ja' or stream['langcode'] == 'jpn': verbose_language = 'Japanese' elif stream['langcode'] == 'es' or stream['langcode'] == 'spa': verbose_language = 'Spanish' elif stream['langcode'] == 'fr' or stream['langcode'] == 'fre': verbose_language = 'French' else: default_track = False merge_command = merge_command + ' --language "0:%s" --default-track "0:%s" --track-name "0:%s" %s/audiostream_%s.ac3' % (stream['langcode'], default_track, verbose_language, temp_dir, stream['streamid']) elif stream['streamtype'] == 'subtitle': merge_command = merge_command + ' --language "0:%s" --default-track "0:no" --forced-track "0:no" "%s/subtitle_%s.idx"' % (stream['langcode'], temp_dir, stream['streamid']) mkv_row = self.add_row(title_number, chapters, 'Muxing streams') try: mkvmerge_output = self.run_command(merge_command) except CalledProcessError as e: #When mkvmerge ends with returncode 1, it's just warnings and can usually be ignored. if e.returncode > 1: self.vlog(e.output) if self.has_ffmpeg: answer = QMessageBox.question(self, "Question", "mkvmerge has exited with an error.\nUnder certain circumstances this can be caused by a bad video file, and in these circumstances re-encoding the video may help.\nShould Eunektes try re-encoding the video and then try to mux again?", QMessageBox.No, QMessageBox.Yes) if answer == QMessageBox.Yes: self.update_row(mkv_row, False) ffmpeg_row = self.add_row(title_number, chapters, 'Re-encoding video with ffmpeg') try: self.run_command('ffmpeg -sameq -i %s/videostream.m2v %s/redo.m2v' % (temp_dir, temp_dir)) except CalledProcessError: raise DumperError('ffmpeg exited with an error.') finally: self.update_row(ffmpeg_row) merge_command = merge_command.replace('videostream.m2v', 'redo.m2v') mkv_row = self.add_row(title_number, chapters, 'Muxing Streams') try: mkvmerge_output = self.run_command(merge_command) except CalledProcessError as e: if e.returncode >1: raise DumperError('mkvmerge has exited with an error (again)') else: self.update_row(mkv_row) else: sys.exit(2) else: raise DumperError("mkvmerge has exited with an error.\nIf ffmpeg were installed Eunektes would try re-encoding the video next, but you don't seem to have it.") self.update_row(mkv_row) """ Cleanup """ shutil.rmtree(temp_dir) def progress_okay(self): self.stacked_layout.setCurrentIndex(0) self.progress_button_box.button(QDialogButtonBox.Ok).setVisible(False) qt_resource_data = "\ \x00\x00\x01\x7b\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00\x28\x2d\x0f\x53\ \x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ \x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\ \x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\ \x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\ \x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x78\x50\x4c\x54\ \x45\xff\xff\xff\x00\x00\x00\x41\x87\xdb\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x49\x8c\xdc\x42\x88\xdb\x44\x89\xdc\x8c\xb7\xe9\x42\ \x88\xdb\x43\x88\xdb\x42\x87\xdb\x41\x87\xdb\x23\x6c\xc1\x29\x71\ \xc4\x31\x77\xca\x3a\x7e\xcf\x41\x87\xdb\x43\x85\xd5\x4b\x8c\xda\ \x54\x90\xdd\x54\x93\xe0\x63\x9a\xe1\x72\xa5\xe4\x78\xa8\xe6\x79\ \xa8\xe4\x80\xae\xe9\x8b\xb6\xec\xa0\xc5\xf2\xab\xc9\xed\xb4\xcf\ \xef\xb5\xd0\xef\xbe\xd6\xf1\xc0\xd7\xf1\xc7\xdb\xf3\xca\xdd\xf3\ \xcb\xdf\xf4\xce\xe0\xf5\xcf\xe1\xf5\x85\x15\xd0\xe2\x00\x00\x00\ \x0e\x74\x52\x4e\x53\x00\x1f\x22\x3c\x3e\x3f\xcb\xfa\xfb\xfb\xfc\ \xfc\xfd\xfe\x44\x5d\xe9\xac\x00\x00\x00\x5b\x49\x44\x41\x54\x78\ \xda\x85\xc8\x47\x16\x40\x40\x10\x05\xc0\x8f\x91\xc3\xd0\x72\xce\ \xdc\xff\x86\x6c\x3c\x3d\x36\x6a\x59\xf8\x67\xd3\xcd\xd2\xdf\xf0\ \xf7\xf3\x3c\x5c\x87\x1e\xf0\xd6\x8d\x23\x04\xf3\xc2\x11\xc2\x71\ \xe2\xee\x18\x14\x84\xe8\x03\xb2\x51\x48\xc4\x55\xce\x54\x31\x92\ \xba\x64\xea\x04\x69\xd7\x32\x5d\x8a\xac\x57\x64\x28\x3e\xa0\x19\ \x42\x98\x0f\x61\x68\x17\x5f\x0a\x13\x7b\xd9\xfa\xee\x91\x00\x00\ \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ " qt_resource_name = "\ \x00\x06\ \x07\x03\x7d\xc3\ \x00\x69\ \x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ \x00\x0f\ \x04\x18\x42\x07\ \x00\x66\ \x00\x6f\x00\x6c\x00\x64\x00\x65\x00\x72\x00\x2d\x00\x6f\x00\x70\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ " def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() class MyDirRequester(QWidget): def __init__(self, parent = None): QWidget.__init__(self, parent) self.setObjectName("MyDirRequester") self.widget = QWidget() self.layout = QHBoxLayout(self.widget) self.editor = KLineEdit(self.widget) self.editor.setReadOnly(True) self.editor.setObjectName("Editor") icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(":/images/folder-open.png")) self.openFolder = QtGui.QToolButton() self.openFolder.setIcon(icon) self.openFolder.setObjectName("openFolder") self.selecterDialog = KDirSelectDialog() self.layout.addWidget(self.editor) self.layout.addWidget(self.openFolder) self.setLayout(self.layout) self.openFolder.clicked.connect(self.launchOpener) self.editor.textChanged.connect(self.reportChange) QtCore.QObject.connect(self, QtCore.SIGNAL("directoryChanged()"),self.doNothing) def retranslateUi(self, MyDirRequester): MyDirRequester.setWindowTitle(kdecore.i18n("Form")) def launchOpener(self): unstringed = self.selecterDialog.selectDirectory(KUrl(self.editor.text())) stringed = QString(unstringed.url()) self.editor.setText(stringed) def reportChange(self): self.emit(QtCore.SIGNAL("directoryChanged()")) def clear(self): self.editor.clear() def url(self): return KUrl(self.editor.text()).path() def setText(self, text): self.editor.setText(text) def doNothing(self): pass app_name = "eunektes" catalog = "" program_name = ki18n("Eunektes") version = "1.3" description = ki18n("Dump DVDs to mkv") license = KAboutData.License_GPL copyright = ki18n("(c) 2010-2011 Dustin Widmann") text = ki18n("none") home_page = "" bug_email = "1m.0n.f1r3@gmail.com" aboutData = KAboutData(app_name, catalog, program_name, version, description, license, copyright, text, home_page, bug_email) KCmdLineArgs.init(sys.argv, aboutData) app=KApplication() eunektes = Eunektes() sys.exit(app.exec_())