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