Ejemplo n.º 1
0
    def __init__(self, media=None, partIndex=0):
        self.media = media
        self.part = None
        self.forceTranscode = False
        self.isDirectPlayable = False
        self.videoStream = None
        self.audioStream = None
        self.subtitleStream = None
        self.isSelected = False
        self.subtitleDecision = self.SUBTITLES_DEFAULT

        self.sorts = util.AttributeDict()

        if media:
            self.indirectHeaders = media.indirectHeaders
            self.part = media.parts[partIndex]
            if self.part:
                # We generally just rely on PMS to have told us selected streams, so
                # initialize our streams accordingly.

                self.videoStream = self.part.getSelectedStreamOfType(plexstream.PlexStream.TYPE_VIDEO)
                self.audioStream = self.part.getSelectedStreamOfType(plexstream.PlexStream.TYPE_AUDIO)
                self.subtitleStream = self.part.getSelectedStreamOfType(plexstream.PlexStream.TYPE_SUBTITLE)
            else:
                util.WARN_LOG("Media does not contain a valid part")

            util.LOG("Choice media: {0} part:{1}".format(media, partIndex))
            for streamType in ("videoStream", "audioStream", "subtitleStream"):
                attr = getattr(self, streamType)
                if attr:
                    util.LOG("Choice {0}: {1}".format(streamType, repr(attr)))
        else:
            util.WARN_LOG("Could not create media choice for invalid media")
Ejemplo n.º 2
0
    def getServerDecision(self):
        directPlay = not (self.metadata and self.metadata.isTranscoded)
        decisionPath = self.getDecisionPath(directPlay)
        newDecision = None

        if decisionPath:
            server = self.metadata.transcodeServer or self.item.getServer()
            request = plexrequest.PlexRequest(server, decisionPath)
            response = request.getWithTimeout(10)

            if response.isSuccess() and response.container:
                decision = serverdecision.ServerDecision(self, response, self)

                if decision.isSuccess():
                    util.LOG("MDE: Server was happy with client's original decision. {0}".format(decision))
                elif decision.isDecision(True):
                    util.WARN_LOG("MDE: Server was unhappy with client's original decision. {0}".format(decision))
                    return decision.getDecision()
                else:
                    util.LOG("MDE: Server was unbiased about the decision. {0}".format(decision))

                # Check if the server has provided a new media item to use it. If
                # there is no item, then we'll continue along as if there was no
                # decision made.
                newDecision = decision.getDecision(False)
            else:
                util.WARN_LOG("MDE: Server failed to provide a decision")
        else:
            util.WARN_LOG("MDE: Server or item does not support decisions")

        return newDecision or self
Ejemplo n.º 3
0
    def getPostWithTimeout(self, seconds=10, body=None):
        if self._cancel:
            return

        self.logRequest(body, seconds, False)
        try:
            if self.method == 'PUT':
                res = self.session.put(self.url, timeout=seconds, stream=True)
            elif self.method == 'DELETE':
                res = self.session.delete(self.url,
                                          timeout=seconds,
                                          stream=True)
            elif self.method == 'HEAD':
                res = self.session.head(self.url, timeout=seconds, stream=True)
            elif self.method == 'POST' or body is not None:
                res = self.session.post(self.url,
                                        data=body,
                                        timeout=seconds,
                                        stream=True)
            else:
                res = self.session.get(self.url, timeout=seconds, stream=True)

            self.currentResponse = res

            if self._cancel:
                return None

            util.LOG("Got a {0} from {1}".format(res.status_code,
                                                 util.cleanToken(self.url)))
            # self.event = msg
            return res
        except Exception, e:
            util.WARN_LOG(
                "Request to {0} errored out after {1} ms: {2}".format(
                    self.url, seconds, e.message))
    def chooseMedia(self, item, forceUpdate=False):
        # If we've already evaluated this item, use our previous choice.
        if not forceUpdate and item.mediaChoice is not None and item.mediaChoice.media is not None and not item.mediaChoice.media.isIndirect(
        ):
            return item.mediaChoice

        # See if we're missing media/stream details for this item.
        if item.isLibraryItem() and item.isVideoItem() and len(
                item.media) > 0 and not item.media[0].hasStreams():
            # TODO(schuyler): Fetch the details
            util.WARN_LOG("Can't make media choice, missing details")

        # Take a first pass through the media items to create an array of candidates
        # that we'll evaluate more completely. If we find a forced item, we use it.
        # If we find an indirect, we only keep a single candidate.
        indirect = False
        candidates = []
        maxResolution = item.settings.getMaxResolution(item.getQualityType())
        for mediaIndex in range(len(item.media)):
            media = item.media[mediaIndex]
            media.mediaIndex = mediaIndex
            if media.isSelected():
                candidates = []
                candidates.append(media)
                break
            if media.isIndirect():
                # Only add indirect media if the resolution fits. We cannot
                # exit early as the user may have selected media.

                indirect = True
                if media.getVideoResolution() <= maxResolution:
                    candidates.append(media)

            elif media.isAccessible():
                # Only consider testing available media
                candidates.append(media)

        # Only use the first indirect media item
        if indirect and candidates:
            candidates = candidates[0]

        # Make sure we have at least one valid item, regardless of availability
        if len(candidates) == 0:
            candidates.append(item.media[0])

        # Now that we have an array of candidates, evaluate them completely.
        choices = []
        for media in candidates:
            choice = None
            if media is not None:
                if item.isVideoItem():
                    choice = self.evaluateMediaVideo(item, media)
                elif item.isMusicItem():
                    choice = self.evaluateMediaMusic(item, media)
                else:
                    choice = mediachoice.MediaChoice(media)
                choices.append(choice)
        item.mediaChoice = self.sortChoices(choices)[-1]
        util.LOG("MDE: MediaChoice: {0}".format(item.mediaChoice))
        return item.mediaChoice
Ejemplo n.º 5
0
    def getImageTranscodeURL(self, path, width, height, **extraOpts):
        if not path:
            return ''

        params = ("&width=%s&height=%s" % (width, height)) + ''.join(
            ["&%s=%s" % (key, extraOpts[key]) for key in extraOpts])

        if "://" in path:
            imageUrl = self.convertUrlToLoopBack(path)
        else:
            imageUrl = "http://127.0.0.1:" + self.getLocalServerPort() + path

        path = "/photo/:/transcode?url=" + compat.quote_plus(imageUrl) + params

        # Try to use a better server to transcode for synced servers
        if self.synced:
            import plexservermanager
            selectedServer = plexservermanager.MANAGER.getTranscodeServer(
                "photo")
            if selectedServer:
                return selectedServer.buildUrl(path, True)

        if self.activeConnection:
            return self.activeConnection.simpleBuildUrl(self, path)
        else:
            util.WARN_LOG("Server connection is None, returning an empty url")
            return ""
Ejemplo n.º 6
0
    def query(self, path, method=None, **kwargs):
        method = method or self.session.get
        url = self.buildUrl(path, includeToken=True)
        # If URL is empty, try refresh resources and return empty set for now
        if not url:
            util.WARN_LOG(
                "Empty server url, returning None and refreshing resources")
            plexapp.refreshResources(True)
            return None
        util.LOG('{0} {1}'.format(
            method.__name__.upper(),
            re.sub('X-Plex-Token=[^&]+', 'X-Plex-Token=****', url)))
        try:
            response = method(url, **kwargs)
            if response.status_code not in (200, 201):
                codename = http.status_codes.get(response.status_code,
                                                 ['Unknown'])[0]
                raise exceptions.BadRequest('({0}) {1}'.format(
                    response.status_code, codename))
            data = response.text.encode('utf8')
        except asyncadapter.TimeoutException:
            util.ERROR()
            plexapp.refreshResources(True)
            return None
        except http.requests.ConnectionError:
            util.ERROR()
            return None

        return ElementTree.fromstring(data) if data else None
Ejemplo n.º 7
0
    def sendTimelineToServer(self, timelineType, timeline, time):
        if not hasattr(timeline.item,
                       'getServer') or not timeline.item.getServer():
            return

        serverTimeline = self.getServerTimeline(timelineType)

        # Only send timeline if it's the first, item changes, playstate changes or timer pops
        itemsEqual = timeline.item and serverTimeline.item and timeline.item.ratingKey == serverTimeline.item.ratingKey
        if itemsEqual and timeline.state == serverTimeline.state and not serverTimeline.isExpired(
        ):
            return

        serverTimeline.reset()
        serverTimeline.item = timeline.item
        serverTimeline.state = timeline.state

        # Ignore sending timelines for multi part media with no duration
        obj = timeline.choice
        if obj and obj.part and obj.part.duration.asInt(
        ) == 0 and obj.media.parts and len(obj.media.parts) > 1:
            util.WARN_LOG(
                "Timeline not supported: the current part doesn't have a valid duration"
            )
            return

        # It's possible with timers and in player seeking for the time to be greater than the
        # duration, which causes a 400, so in that case we'll set the time to the duration.
        duration = timeline.item.duration.asInt() or timeline.duration
        if time > duration:
            time = duration

        params = util.AttributeDict()
        params["time"] = time
        params["duration"] = duration
        params["state"] = timeline.state
        params["guid"] = timeline.item.guid
        params["ratingKey"] = timeline.item.ratingKey
        params["url"] = timeline.item.url
        params["key"] = timeline.item.key
        params["containerKey"] = timeline.item.container.address
        if timeline.playQueue:
            params["playQueueItemID"] = timeline.playQueue.selectedId

        path = "/:/timeline"
        for paramKey in params:
            if params[paramKey]:
                path = http.addUrlParam(
                    path, paramKey + "=" + urllib.quote(str(params[paramKey])))

        request = plexrequest.PlexRequest(timeline.item.getServer(), path)
        context = request.createRequestContext(
            "timelineUpdate", callback.Callable(self.onTimelineResponse))
        context.playQueue = timeline.playQueue
        plexapp.APP.startRequest(request, context)
Ejemplo n.º 8
0
    def onRequestTimeout(self, context):
        requestID = context.request.getIdentity()

        if requestID not in self.pendingRequests:
            return

        context.request.cancel()

        util.WARN_LOG("Request to {0} timed out after {1} sec".format(util.cleanToken(context.request.url), context.timeout))

        if context.callback:
            context.callback(None, context)
    def evaluateMediaMusic(self, item, media):
        # Resolve indirects before doing anything else.
        if media.isIndirect():
            util.LOG("Resolve indirect media for {0}".format(item))
            media = media.resolveIndirect()

        choice = mediachoice.MediaChoice(media)
        if media is None:
            return choice

        # Verify the server supports audio transcoding, otherwise force direct play
        if not item.getServer().supportsAudioTranscoding:
            util.LOG(
                "MDE: force direct play because the server does not support audio transcoding"
            )
            choice.isDirectPlayable = True
            return choice

        # See if this part has a server decision to transcode and obey it
        if choice.part and choice.part.get(
                "decision", serverdecision.ServerDecision.DECISION_DIRECT_PLAY
        ) != serverdecision.ServerDecision.DECISION_DIRECT_PLAY:
            util.WARN_LOG("MDE: Server has decided this cannot direct play")
            return choice

        # Verify the codec and container are compatible
        codec = media.audioCodec
        container = media.get('container')
        canPlayCodec = item.settings.supportsAudioStream(
            codec, media.audioChannels.asInt())
        canPlayContainer = (
            codec == container) or True  # (container in ("mp4", "mka", "mkv"))

        choice.isDirectPlayable = (canPlayCodec and canPlayContainer)
        if choice.isDirectPlayable:
            # Inspect the audio stream attributes if the codec/container can direct
            # play. For now we only need to verify the sample rate.

            if choice.audioStream is not None and choice.audioStream.samplingRate.asInt(
            ) >= 192000:
                util.LOG("MDE: sampling rate is not compatible")
                choice.isDirectPlayable = False
        else:
            util.LOG("MDE: container or codec is incompatible")

        return choice
Ejemplo n.º 10
0
    def getDecision(self, requireDecision=True):
        if not self.item:
            # Return no decision. The player will either continue with the original
            # or terminate if a valid decision was required.

            if requireDecision:
                # Terminate the player by default if there was no decision returned.
                code = self.decisionsCodes["generalDecision"]
                reason = ' '.join([self.decisionsTexts["transcodeDecision"], self.decisionsTexts["generalDecision"]])
                raise DecisionFailure(code, reason)

            return None

        # Rebuild the original item with the new item.
        util.WARN_LOG("Server requested new playback decision: {0}".format(self))
        self.original.rebuild(self.item, self)
        return self.original
Ejemplo n.º 11
0
    def getPostWithTimeout(self, seconds=DEFAULT_TIMEOUT, body=None):
        if self._cancel:
            return

        self.logRequest(body, seconds, False)
        try:
            if self.method == 'PUT':
                res = self.session.put(self.url, timeout=seconds, stream=True)
            elif self.method == 'DELETE':
                res = self.session.delete(self.url,
                                          timeout=seconds,
                                          stream=True)
            elif self.method == 'HEAD':
                res = self.session.head(self.url, timeout=seconds, stream=True)
            elif self.method == 'POST' or body is not None:
                res = self.session.post(self.url,
                                        data=body,
                                        timeout=seconds,
                                        stream=True)
            else:
                res = self.session.get(self.url, timeout=seconds, stream=True)

            self.currentResponse = res

            if self._cancel:
                return None

            util.LOG("Got a {0} from {1}".format(res.status_code,
                                                 util.cleanToken(self.url)))
            # self.event = msg
            return res
        except Exception, e:
            info = traceback.extract_tb(sys.exc_info()[2])[-1]
            util.WARN_LOG(
                "Request errored out - URL: {0} File: {1} Line: {2} Msg: {3}".
                format(util.cleanToken(self.url), os.path.basename(info[0]),
                       info[1], e.message))
Ejemplo n.º 12
0
    def onAccountResponse(self, request, response, context):
        oldId = self.ID

        if response.isSuccess():
            data = response.getBodyXml()

            # The user is signed in
            self.isSignedIn = True
            self.isOffline = False
            self.ID = data.attrib.get('id')
            self.title = data.attrib.get('title')
            self.username = data.attrib.get('username')
            self.email = data.attrib.get('email')
            self.thumb = data.attrib.get('thumb')
            self.authToken = data.attrib.get('authenticationToken')
            self.isPlexPass = (
                data.find('subscription') is not None
                and data.find('subscription').attrib.get('active') == '1')
            self.isManaged = data.attrib.get('restricted') == '1'
            self.isSecure = data.attrib.get('secure') == '1'
            self.hasQueue = bool(data.attrib.get('queueEmail'))

            # PIN
            if data.attrib.get('pin'):
                self.pin = data.attrib.get('pin')
            else:
                self.pin = None
            self.isProtected = bool(self.pin)

            # update the list of users in the home
            self.updateHomeUsers()

            # set admin attribute for the user
            self.isAdmin = False
            if self.homeUsers:
                for user in self.homeUsers:
                    if self.ID == user.id:
                        self.isAdmin = str(user.admin) == "1"
                        break

            if self.isAdmin and self.isPlexPass:
                self.adminHasPlexPass = True

            # consider a single, unprotected user authenticated
            if not self.isAuthenticated and not self.isProtected and len(
                    self.homeUsers) <= 1:
                self.isAuthenticated = True

            self.logState()

            self.saveState()
            plexapp.MANAGER.publish()
            plexapp.refreshResources()
        elif response.getStatus() >= 400 and response.getStatus() < 500:
            # The user is specifically unauthorized, clear everything
            util.WARN_LOG("Sign Out: User is unauthorized")
            self.signOut(True)
        else:
            # Unexpected error, keep using whatever we read from the registry
            util.WARN_LOG(
                "Unexpected response from plex.tv ({0}), switching to OFFLINE mode"
                .format(response.getStatus()))
            self.logState()
            self.isOffline = True
            # consider a single, unprotected user authenticated
            if not self.isAuthenticated and not self.isProtected:
                self.isAuthenticated = True

        plexapp.APP.clearInitializer("myplex")
        # Logger().UpdateSyslogHeader()  # TODO: ------------------------------------------------------------------------------------------------------IMPLEMENT

        if oldId != self.ID or self.switchUser:
            self.switchUser = None
            plexapp.APP.trigger("change:user",
                                account=self,
                                reallyChanged=oldId != self.ID)

        plexapp.APP.trigger("account:response")
Ejemplo n.º 13
0
 def buildUrl(self, path, includeToken=False):
     if self.activeConnection:
         return self.activeConnection.buildUrl(self, path, includeToken)
     else:
         util.WARN_LOG("Server connection is None, returning an empty url")
         return ""