X-Git-Url: https://code.delx.au/bluplayer/blobdiff_plain/868b9817a05d10d05fdb97a382030ac8d3c6ff26..HEAD:/bluplayer.py diff --git a/bluplayer.py b/bluplayer.py index 9259b83..0236424 100755 --- a/bluplayer.py +++ b/bluplayer.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2 import csv import errno @@ -17,10 +17,27 @@ XHTML = "http://www.w3.org/1999/xhtml" NS = { "xhtml": XHTML, } +DEFAULT_PATH = "/usr/local/bin:/usr/bin:/bin" -MAKEMKV_DIR = os.environ.get("MAKEMKV_DIR", os.path.expanduser("~/.makemkv_install")) -MAKEMKVCON_PATH = os.environ.get("MAKEMKVCON_PATH", os.path.join(MAKEMKV_DIR, "current", "makemkvcon")) -MPLAYER_PATH = os.environ.get("MPLAYER_PATH", "mplayer") + +def set_env_config(name, value): + value = os.environ.get(name, value) + globals()[name] = value + +def find_path(binary): + value = "" + paths = os.environ.get("PATH", DEFAULT_PATH).split(":") + for path in paths: + path = os.path.join(path, binary) + if os.path.isfile(path): + value = path + break + return value + +set_env_config("MAKEMKVCON_PATH", find_path("makemkvcon")) +set_env_config("PLAYER_NAME", "vlc") +set_env_config("PLAYER_PATH", find_path(PLAYER_NAME)) +set_env_config("PLAYER_OPTS", None) def grab_xml(url): @@ -47,15 +64,21 @@ def parse_doc(doc): i += 2 return d +def get_num_cpus(): + logging.info("Determing number of CPUs") + try: + return subprocess.check_output("nproc").strip() + except Exception, e: + logging.warn("Unable to run nproc: %s", fmt_exc(e)) + return 1 + def grab_dict(url): doc = grab_xml(url) d = parse_doc(doc) return d -def format_exception(msg, e=None): - msg = "%s\n%s: %s" % (msg, e.__class__.__name__, str(e)) - logging.error(msg) - return msg +def fmt_exc(e): + return "%s: %s" % (e.__class__.__name__, str(e)) class Title(object): @@ -67,17 +90,6 @@ class Title(object): def __str__(self): return "Title%s: %s %s" % (self.id, self.duration, self.video_url) - def play(self): - cmd = [ - MPLAYER_PATH, - "-fs", - "-lavdopts", "threads=%s" % subprocess.check_output("nproc").strip(), - "-volume", "100", - self.video_url, - ] - logging.info("Running mplayer: %s", self.video_url) - subprocess.check_call(cmd) - class MakeMkvCon(object): def __init__(self, params): @@ -124,16 +136,47 @@ class MakeMkvCon(object): self.buf += data +class Player(QObject): + play_finished = pyqtSignal() + fatal_error = pyqtSignal(str, str) + + def run(self): + logging.info("player thread started") + + if not os.path.isfile(PLAYER_PATH): + self.fatal_error.emit( + PLAYER_NAME + " was not found.", + "Please install it. If you already have done so you " + + "may set the PLAYER_PATH environment variable to the " + + "absolute path to the player executable." + ) + return + + if PLAYER_OPTS: + self.opts = PLAYER_OPTS.split() + else: + self.opts = [ + "--fullscreen", + ] + + def play(self, video_url): + video_url = str(video_url) + logging.info("Running player: %s", video_url) + try: + subprocess.check_call([PLAYER_PATH] + self.opts + [video_url]) + except Exception, e: + self.fatal_error.emit("Failed to play the video.", fmt_exc(e)) + finally: + self.play_finished.emit() + + class MakeMkv(QObject): title_loaded = pyqtSignal(Title) + title_load_complete = pyqtSignal() status = pyqtSignal(str) - fatal_error = pyqtSignal(str) - - def install(self): - raise NotImplementedError("auto-install not implemented") + fatal_error = pyqtSignal(str, str) def find_disc(self): - self.url = "http://192.168.1.114:51001/" makemkvcon = MakeMkvCon(["info", "disc:9999"]) disc_number = None for key, line in makemkvcon: @@ -160,7 +203,10 @@ class MakeMkv(QObject): self.status.emit(line[3]) if makemkvcon.status != 0: - self.fatal_error.emit("MakeMKV exited with error status: %s" % makemkvcon.status) + self.fatal_error.emit( + "MakeMKV quit unexpectedly.", + "makemkvcon exited with code %s" % makemkvcon.status + ) def load_titles(self, url): home_page = grab_dict(url) @@ -170,44 +216,55 @@ class MakeMkv(QObject): title_page = grab_dict(title_list_page["title%d" % i]) title = Title(title_page) self.title_loaded.emit(title) + self.title_load_complete.emit() def run(self): - logging.info("MakeMKV thread started") + logging.info("makemkv thread started") if not os.path.isfile(MAKEMKVCON_PATH): - try: - self.install() - except Exception, e: - self.fatal_error.emit(format_exception("Failed to install MakeMKV", e)) - raise + self.fatal_error.emit( + "MakeMKV was not found.", + "Please install MakeMKV. If you already have done so you " + + "may set the MAKEMKVCON_PATH environment variable to the " + + "absolute path to the makemkvcon executable." + ) + return try: disc_number = self.find_disc() except Exception, e: - self.fatal_error.emit(format_exception("Error searching for disc", e)) + self.fatal_error.emit("Error searching for disc.", fmt_exc(e)) raise if not disc_number: - self.fatal_error.emit("No disc found, please insert a disc and try again.") + self.fatal_error.emit( + "No disc found.", + "Please insert a BluRay disc and try again." + ) return try: self.run_stream(disc_number) except Exception, e: - self.fatal_error.emit(format_exception("Failed to start MakeMKV", e)) + self.fatal_error.emit("Failed to start MakeMKV.", fmt_exc(e)) raise - logging.info("MakeMKV thread finished") + logging.info("makemkv thread finished") class PlayerWindow(QWidget): + fatal_error = pyqtSignal(str, str) + video_selected = pyqtSignal(str) + def __init__(self): QWidget.__init__(self) + self.title_map = {} + self.is_playing = False self.list_widget = QListWidget(self) self.list_widget.itemActivated.connect(self.handle_activated) - self.list_widget.setFocus() + self.list_widget.setEnabled(False) self.log = QTextEdit(self) self.log.setReadOnly(True) @@ -220,29 +277,43 @@ class PlayerWindow(QWidget): self.layout = QVBoxLayout(self) self.layout.addWidget(self.splitter) self.setWindowTitle("BluPlayer") + self.resize(1200, 700) def add_title(self, title): - name = "Title %s (%s)" % (title.id, title.duration) + name = "Title %s (%s)" % (int(title.id)+1, title.duration) self.list_widget.addItem(name) - if not self.title_map: - # select the first item - self.list_widget.setCurrentItem(self.list_widget.item(0)) self.title_map[name] = title + def select_longest_title(self): + longest_title = None + longest_item = None + for i in xrange(self.list_widget.count()): + item = self.list_widget.item(i) + name = str(item.text()) + title = self.title_map[name] + if longest_title is None or title.duration > longest_title.duration: + longest_title = title + longest_item = item + self.list_widget.setCurrentItem(longest_item) + self.list_widget.setEnabled(True) + self.list_widget.setFocus() + def add_log_entry(self, text): self.log.append(text) - def popup_fatal_error(self, text): - QMessageBox.critical(None, "Fatal error", text) - qApp.quit() - def handle_activated(self, item): + if self.is_playing: + return name = str(item.text()) title = self.title_map[name] - try: - title.play() - except Exception, e: - popup_error_exit("MPlayer failed to play the video", e) + self.is_playing = True + self.list_widget.setEnabled(False) + self.video_selected.emit(title.video_url) + + def set_play_finished(self): + self.is_playing = False + self.list_widget.setEnabled(True) + self.list_widget.setFocus() def keyPressEvent(self, e): if e.key() in (Qt.Key_Escape, Qt.Key_Backspace): @@ -250,14 +321,48 @@ class PlayerWindow(QWidget): return QWidget.keyPressEvent(self, e) +class LoadingDialog(QProgressDialog): + def __init__(self, parent): + QProgressDialog.__init__(self, parent) + self.setWindowModality(Qt.WindowModal); + self.setWindowTitle("Loading disc") + self.setLabelText("Loading BluRay disc. Please wait...") + self.setCancelButtonText("Exit") + self.setMinimum(0) + self.setMaximum(0) + +class ErrorDialog(QMessageBox): + fatal_error = pyqtSignal(str, str) + + def __init__(self, parent): + QMessageBox.__init__(self, parent) + self.setStandardButtons(QMessageBox.Ok) + self.setDefaultButton(QMessageBox.Ok) + self.fatal_error.connect(self.configure_popup) + self.setWindowTitle("Fatal error") + self.has_run = False + + def configure_popup(self, text, detail): + if self.has_run: + return + self.has_run = True + self.setText(text) + self.setInformativeText(detail) + QTimer.singleShot(0, self.show_and_exit) + + def show_and_exit(self): + logging.info("showing and exiting") + self.exec_() + qApp.quit() def killall_makemkvcon(): - logging.info("killing makemkvcon") - subprocess.Popen(["killall", "makemkvcon"]).wait() + logging.info("Stopping any makemkvcon processes") + subprocess.Popen(["killall", "--quiet", "makemkvcon"]).wait() def main(): logging.basicConfig(format="%(levelname)s: %(message)s") logging.getLogger().setLevel(logging.DEBUG) + logging.info("Configuring application") app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(True) @@ -267,27 +372,50 @@ def main(): app.setFont(default_font) player_window = PlayerWindow() - player_window.resize(1200, 700) player_window.show() + loading_dialog = LoadingDialog(player_window) + loading_dialog.show() + + error_dialog = ErrorDialog(player_window) + makemkv = MakeMkv() - worker_thread = QThread() - makemkv.moveToThread(worker_thread) + makemkv_thread = QThread() + makemkv.moveToThread(makemkv_thread) + makemkv_thread.started.connect(makemkv.run) + + player = Player() + player_thread = QThread() + player.moveToThread(player_thread) + player_thread.started.connect(player.run) - worker_thread.started.connect(makemkv.run) makemkv.title_loaded.connect(player_window.add_title) + makemkv.title_load_complete.connect(player_window.select_longest_title) makemkv.status.connect(player_window.add_log_entry) - makemkv.fatal_error.connect(player_window.popup_fatal_error) + makemkv.fatal_error.connect(error_dialog.fatal_error) + makemkv.title_load_complete.connect(loading_dialog.reset) + + player_window.video_selected.connect(player.play) + player.play_finished.connect(player_window.set_play_finished) + player.fatal_error.connect(error_dialog.fatal_error) + + player_window.fatal_error.connect(error_dialog.fatal_error) + error_dialog.fatal_error.connect(loading_dialog.reset) + loading_dialog.canceled.connect(qApp.quit) - logging.info("Starting worker thread") - worker_thread.start() + logging.info("Starting application") + makemkv_thread.start() + player_thread.start() result = app.exec_() logging.info("Shutting down") - worker_thread.quit() + makemkv_thread.quit() + player_thread.quit() killall_makemkvcon() - logging.info("Waiting for worker thread") - worker_thread.wait(2000) + logging.info("Waiting for makemkv thread") + makemkv_thread.wait(2000) + logging.info("Waiting for player thread") + player_thread.wait(2000) logging.info("Exiting...") sys.exit(result)