]> code.delx.au - bluplayer/blobdiff - bluplayer.py
Run mplayer with -nocache, makes seeking more reliable
[bluplayer] / bluplayer.py
index 42db223d0d3b298e38665d6cd1548b934483e8c8..9c76010d328c8fede5cee4e231f9b43872e032ca 100755 (executable)
@@ -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):
@@ -115,21 +137,37 @@ class MakeMkvCon(object):
 
 class MPlayer(QObject):
        play_finished = pyqtSignal()
-       fatal_error = pyqtSignal(str)
+       fatal_error = pyqtSignal(str, str)
 
-       def play(self, video_url):
-               logging.info("Running mplayer: %s", video_url)
-               try:
-                       cmd = [
-                               MPLAYER_PATH,
+       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",
-                               "-lavdopts", "threads=%s" % subprocess.check_output("nproc").strip(),
+                               "-nocache",
+                               "-lavdopts", "threads=%s" % get_num_cpus(),
                                "-volume", "100",
-                               video_url,
                        ]
-                       subprocess.check_call(cmd)
+
+       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(format_exception("MPlayer failed to play the video", e))
+                       self.fatal_error.emit("MPlayer failed to play the video.", fmt_exc(e))
                finally:
                        self.play_finished.emit()
 
@@ -138,10 +176,7 @@ 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/"
@@ -171,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)
@@ -184,35 +222,41 @@ 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):
@@ -236,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)
@@ -259,10 +304,6 @@ class PlayerWindow(QWidget):
        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
@@ -283,10 +324,43 @@ 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")
@@ -301,37 +375,36 @@ 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()
        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)
 
-       makemkv_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)
 
-       loading_window = QProgressDialog(
-               "Loading BluRay disc. Please wait...",
-               "Exit",
-               0, 0, # 'infinite' style progress bar
-               player_window
-       )
-       loading_window.setWindowTitle("Loading disc")
-       loading_window.setWindowModality(Qt.WindowModal);
-       loading_window.show()
-       loading_window.canceled.connect(qApp.quit)
-       makemkv.title_load_complete.connect(loading_window.reset)
+       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 application")
        makemkv_thread.start()