import re import subprocess from syncplay.players.mplayer import MplayerPlayer from syncplay.messages import getMessage from syncplay import constants import os, sys, time class MpvPlayer(MplayerPlayer): RE_VERSION = re.compile('.*mpv (\d)\.(\d)\.\d.*') osdMessageSeparator = "\\n" @staticmethod def run(client, playerPath, filePath, args): try: ver = MpvPlayer.RE_VERSION.search(subprocess.check_output([playerPath, '--version'])) except: ver = None constants.MPV_NEW_VERSION = ver is None or int(ver.group(1)) > 0 or int(ver.group(2)) >= 6 if constants.MPV_NEW_VERSION: return NewMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) else: return OldMpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args) @staticmethod def getStartupArgs(path, userArgs): args = constants.MPV_ARGS if userArgs: args.extend(userArgs) args.extend(constants.MPV_SLAVE_ARGS) if constants.MPV_NEW_VERSION: args.extend(constants.MPV_SLAVE_ARGS_NEW) return args @staticmethod def getDefaultPlayerPathsList(): l = [] for path in constants.MPV_PATHS: p = MpvPlayer.getExpandedPath(path) if p: l.append(p) return l @staticmethod def isValidPlayerPath(path): if "mpv" in path and MpvPlayer.getExpandedPath(path): return True return False @staticmethod def getExpandedPath(playerPath): if not os.path.isfile(playerPath): if os.path.isfile(playerPath + u"mpv.exe"): playerPath += u"mpv.exe" return playerPath elif os.path.isfile(playerPath + u"\\mpv.exe"): playerPath += u"\\mpv.exe" return playerPath if os.access(playerPath, os.X_OK): return playerPath for path in os.environ['PATH'].split(':'): path = os.path.join(os.path.realpath(path), playerPath) if os.access(path, os.X_OK): return path @staticmethod def getIconPath(path): return constants.MPV_ICONPATH @staticmethod def getPlayerPathErrors(playerPath, filePath): return None class OldMpvPlayer(MpvPlayer): POSITION_QUERY = 'time-pos' OSD_QUERY = 'show_text' def _setProperty(self, property_, value): self._listener.sendLine("no-osd set {} {}".format(property_, value)) def setPaused(self, value): if self._paused <> value: self._paused = not self._paused self._listener.sendLine('cycle pause') def mpvVersionErrorCheck(self, line): if "Error parsing option" in line or "Error parsing commandline option" in line: self.quitReason = getMessage("mpv-version-error") elif "Could not open pipe at '/dev/stdin'" in line: self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-version-error"), True) self.drop() elif "[ytdl_hook] Your version of youtube-dl is too old" in line: self._client.ui.showErrorMessage(line) def _handleUnknownLine(self, line): self.mpvVersionErrorCheck(line) if "Playing: " in line: newpath = line[9:] oldpath = self._filepath if newpath != oldpath and oldpath is not None: self.reactor.callFromThread(self._onFileUpdate) if self._paused != self._client.getGlobalPaused(): self.setPaused(self._client.getGlobalPaused()) self.setPosition(self._client.getGlobalPosition()) class NewMpvPlayer(OldMpvPlayer): lastResetTime = None lastMPVPositionUpdate = None def setPaused(self, value): if self._paused <> value: self._paused = not self._paused self._listener.sendLine('cycle pause') if value == False: self.lastMPVPositionUpdate = time.time() def _getProperty(self, property_): floatProperties = ['length','time-pos'] if property_ in floatProperties: propertyID = u"={}".format(property_) else: propertyID = property_ self._listener.sendLine(u"print_text ""ANS_{}=${{{}}}""".format(property_, propertyID)) def getCalculatedPosition(self): if self.fileLoaded == False: return self._client.getGlobalPosition() if self.lastMPVPositionUpdate is None: return self._client.getGlobalPosition() diff = time.time() - self.lastMPVPositionUpdate if diff > constants.MPV_UNRESPONSIVE_THRESHOLD: self.reactor.callFromThread(self._client.ui.showErrorMessage, getMessage("mpv-unresponsive-error").format(int(diff)), True) self.drop() if diff > constants.PLAYER_ASK_DELAY and not self._paused: self._client.ui.showDebugMessage("mpv did not response in time, so assuming position is {} ({}+{})".format(self._position + diff, self._position, diff)) return self._position + diff else: return self._position def _storePosition(self, value): self.lastMPVPositionUpdate = time.time() if self._recentlyReset(): self._position = 0 elif self._fileIsLoaded(): self._position = value else: self._position = self._client.getGlobalPosition() def _storePauseState(self, value): if self._fileIsLoaded(): self._paused = value else: self._paused = self._client.getGlobalPaused() def askForStatus(self): self._positionAsk.clear() self._pausedAsk.clear() self._getPaused() self._getPosition() self._positionAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._pausedAsk.wait(constants.MPV_LOCK_WAIT_TIME) self._client.updatePlayerStatus(self._paused if self.fileLoaded else self._client.getGlobalPaused(), self.getCalculatedPosition()) def _preparePlayer(self): if self.delayedFilePath: self.openFile(self.delayedFilePath) self.setPaused(True) self.reactor.callLater(0, self._client.initPlayer, self) def _clearFileLoaded(self): self.fileLoaded = False self.lastLoadedTime = None def _loadFile(self, filePath): self._clearFileLoaded() self._listener.sendLine(u'loadfile {}'.format(self._quoteArg(filePath))) def setPosition(self, value): super(self.__class__, self).setPosition(value) self.lastMPVPositionUpdate = time.time() def openFile(self, filePath, resetPosition=False): if resetPosition: self.lastResetTime = time.time() self._loadFile(filePath) if self._paused != self._client.getGlobalPaused(): self.setPaused(self._client.getGlobalPaused()) self.setPosition(self._client.getGlobalPosition()) def _handleUnknownLine(self, line): self.mpvVersionErrorCheck(line) if line == "" or "Playing:" in line: self._clearFileLoaded() elif line == "": self._onFileUpdate() def _recentlyReset(self): if not self.lastResetTime: return False elif time.time() < self.lastResetTime + constants.MPV_NEWFILE_IGNORE_TIME: return True else: return False def _onFileUpdate(self): self.fileLoaded = True self.lastLoadedTime = time.time() self.reactor.callFromThread(self._client.updateFile, self._filename, self._duration, self._filepath) if not (self._recentlyReset()): self.reactor.callFromThread(self.setPosition, self._client.getGlobalPosition()) if self._paused != self._client.getGlobalPaused(): self.reactor.callFromThread(self._client.getGlobalPaused) def _fileIsLoaded(self): if self.fileLoaded == True and self.lastLoadedTime != None and time.time() > (self.lastLoadedTime + constants.MPV_NEWFILE_IGNORE_TIME): return True else: return False