Example #1
0
class MPStreamer(QObject):

    RUN_BIT = 1
    FIN_BIT = 2
    ERROR_BIT = 4

    _READY = 0
    _RUNNING = RUN_BIT
    _FINISHED_SUC = FIN_BIT
    _FINISHED_ERR = ERROR_BIT | FIN_BIT

    _current_id = 0

    changedStatus = pyqtSignal(int)

    @classmethod
    def nextId(cls):
        """Generates unique id's.

        This class method is used to generate unique id's
        for all MPStreamer instances. (Not thread safe)
        :param cls: The 'class' object that called this method.
        """
        cid = cls._current_id
        cls._current_id += 1
        return cid

    def __init__(self, dlinfo, name="worker", mp_path="mplayer", wget="wget"):
        """Creates and initializes a new streamer object.

        :param dlinfo:  Download info container
        :param name:    Optional name of this streamer (This name
                        will be used in debug messages...).
        :param mp_path: Optional path to the mplayer (used to download
                        the stream).
        :param wget:    Optional path to the wget utility (to download
                        the stream).
        """
        QObject.__init__(self)
        self._info = dlinfo
        self._mplayer = mp_path
        self._wget = wget
        self._name = "%s_%02d" % (name, self.nextId())
        self._proc = QProcess()
        self._status = self._READY
        self._lerror = None
        self._doConnections()
        self._play_proc = QProcess()
        self._stream_proc = QProcess()

    def __str__(self):
        return self._info.getFilename()

    def _doConnections(self):
        self._proc.finished.connect(self._qprocessFinished)
        self._proc.stateChanged.connect(self._qprocessStateChanged)

    def _exists(self, filename):
        for i in os.listdir(self._info.destDir):
            basename = splitext(i)[0]
            if filename == basename:
                return True
        return False

    def start(self, overwrite=False, wget=False):
        # TODO: check if path exists and is accessible
        self._lerror = None
        self._info.setExtension(None)
        # remove extension
        if self._status & self.RUN_BIT:
            raise AlreadyRunningError(self._info.getPath())
        elif self._exists(self._info.getFilename()) and (not overwrite):
            raise FileExistsError(self._info.getPath())
        else:
            if self._status & self.FIN_BIT:
                _log.debug("Restarting Download of file '%s'" % self._info.getPath())
            if wget:
                args = ["-c", self._info.getSourceURL(), "-O", self._info.getPath()]
                _log.debug("Starting download using wget (%s)" % " ".join(args))
                self._proc.start(self._wget, args)
            else:
                args = ["-nolirc", "-dumpstream", "-dumpfile", self._info.getPath(), self._info.getSourceURL()]
                _log.debug("Starting download using mplayer (%s)" % " ".join(args))
                self._proc.start(self._mplayer, args)

    def _decode_state(self, state):
        """Helper function to decode numeric state

        :param state: Numeric state to decode
        :return: Textual state string

        This function is used for verbose debugging messages.
        """
        tokens = []
        if state & self.RUN_BIT:
            tokens.append("run")
        if state & self.ERROR_BIT:
            tokens.append("error")
        if state & self.FIN_BIT:
            tokens.append("fin")
        if len(tokens) == 0:
            return "none"
        else:
            return " | ".join(tokens)

    def _qprocessStateChanged(self, new_state):
        old_status = self._status
        _log.info("QProcess::stateChanged '%s'" % str(new_state))
        if new_state != QProcess.NotRunning:
            self._status = self._RUNNING
            _log.debug("QProcess is running!")
        else:
            self._status &= ~(self.RUN_BIT)
            _log.debug("QProcess has stopped!")
        if old_status != self._status:
            _log.debug(
                "Emit status change from '%s' to '%s'"
                % (self._decode_state(old_status), self._decode_state(self._status))
            )
            self.changedStatus.emit(self._status)

    def _receivedProcessErrorMsg(self):
        # BAD: I should find some better way to check...
        if self._lerror.find("Failed to open") != -1:
            return True
        else:
            return False

    def _qprocessFinished(self, exit_code, exit_status):
        _log.info("QProcess::finished code='%s', status='%s'" % (str(exit_code), str(exit_status)))
        _log.debug("stdout: '%s'" % str(self._proc.readAllStandardOutput()))
        self._lerror = str(self._proc.readAllStandardError())
        _log.debug("stderr: '%s'" % self._lerror)
        succeeded = False
        old_status = self._status
        self._status |= self.FIN_BIT
        if exit_status != QProcess.NormalExit:
            _log.debug("Process most likly crashed!")
            self._status |= self.ERROR_BIT
        elif exit_code != 0:
            # This doesn't really indicate an error... :(
            _log.debug("mplayer failed (exit-code: %d)!" % exit_code)
            self._status |= self.ERROR_BIT
        elif self._receivedProcessErrorMsg():
            _log.debug("mplayer couldn't open url")
            self._status |= self.ERROR_BIT
        else:
            path = self._info.getPath()
            try:
                ext = filetype.ExtGuesser(path).get()
            except filetype.InvalidPathError as ex:
                _log.critical("Downloaded file '%s' seems to not exist." % ex.path)
                self._status = self._FINISHED_ERR
            except filetype.ExternalProgramError:
                _log.critical("External program to guess ext. failed!")
                self._status = self._FINISHED_ERR
            else:
                newpath = "%s.%s" % (path, ext)
                _log.debug("Renaming '%s' to '%s'." % (path, newpath))
                os.rename(path, newpath)
                self._info.setExtension(ext)
                succeeded = True

        if self._status != old_status:
            self.changedStatus.emit(self._status)
        self._info.finished.emit(succeeded)

    def playStream(self):
        # TODO:
        # maybe check QProcess::state() should be QProcess::NotRunning
        _log.debug("Trying to start mplayer and play stream")
        self._stream_proc.setStandardInputFile(self._info.getPath())
        self._stream_proc.start(self._mplayer, ["-fs", "-"])

    def playFile(self):
        # TODO:
        # maybe check QProcess::state() should be QProcess::NotRunning
        _log.debug("Trying to start mplayer and play a file")
        args = ["-fs", self._info.getPath()]
        _log.debug("mplayer start argument: %s" % str(args))
        self._play_proc.start(self._mplayer, args)

    def getSize(self, inc_unit=False):
        path = self._info.getPath()
        try:
            size = getsize(path)
        except os.error as ex:
            _log.debug("Couldn't retrieve file size for '%s' -> %s" % (path, str(ex)))
        else:
            if inc_unit:
                idx = 0
                for (i, v) in enumerate(_SIZE_MAX):
                    if (size // v) > 0:
                        return u"%d %s" % ((size / v), _SIZE_PREFIX[i])

    def kill(self):
        self._proc.kill()

    def discard(self):
        self._info.removed.emit()

    def getStatus(self):
        return self._status