X-Git-Url: https://code.delx.au/bluplayer/blobdiff_plain/15c5bfc8441aa2fa2a1a53d2cc59b489249d396a..553c4340bd0e699f05c6d23d317a46c8c6f3f987:/bluplayer.py diff --git a/bluplayer.py b/bluplayer.py index 15f6c97..9c76010 100755 --- a/bluplayer.py +++ b/bluplayer.py @@ -17,10 +17,26 @@ 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("MPLAYER_PATH", find_path("mplayer")) +set_env_config("MPLAYER_OPTS", None) def grab_xml(url): @@ -47,15 +63,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 +89,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,14 +135,48 @@ class MakeMkvCon(object): self.buf += data +class MPlayer(QObject): + play_finished = pyqtSignal() + fatal_error = pyqtSignal(str, str) + + def run(self): + logging.info("mplayer thread started") + + if not os.path.isfile(MPLAYER_PATH): + self.fatal_error.emit( + "MPlayer was not found.", + "Please install MPlayer. If you already have done so you " + + "may set the MPLAYER_PATH environment variable to the " + + "absolute path to the mplayer executable." + ) + return + + if MPLAYER_OPTS: + self.opts = MPLAYER_OPTS.split() + else: + self.opts = [ + "-fs", + "-nocache", + "-lavdopts", "threads=%s" % get_num_cpus(), + "-volume", "100", + ] + + def play(self, video_url): + video_url = str(video_url) + logging.info("Running mplayer: %s", video_url) + try: + subprocess.check_call([MPLAYER_PATH] + self.opts + [video_url]) + except Exception, e: + self.fatal_error.emit("MPlayer 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/" @@ -161,7 +206,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) @@ -174,42 +222,52 @@ class MakeMkv(QObject): 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) @@ -222,6 +280,7 @@ 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)" % (int(title.id)+1, title.duration) @@ -239,21 +298,25 @@ class PlayerWindow(QWidget): 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): @@ -261,14 +324,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) @@ -278,28 +375,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) + + mplayer = MPlayer() + mplayer_thread = QThread() + mplayer.moveToThread(mplayer_thread) + mplayer_thread.started.connect(mplayer.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(mplayer.play) + mplayer.play_finished.connect(player_window.set_play_finished) + mplayer.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() + mplayer_thread.start() result = app.exec_() logging.info("Shutting down") - worker_thread.quit() + makemkv_thread.quit() + mplayer_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 mplayer thread") + mplayer_thread.wait(2000) logging.info("Exiting...") sys.exit(result)