예제 #1
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)
                util.TEST(decision)

                util.TEST(decision.isDecision(True))

                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
예제 #2
0
 def isDecision(self, requireItem=False):
     # Server has provided a valid decision if there was a valid decision code
     # or if the response returned zero items (could not play).
     util.TEST("Supported: " + str(self.isSupported))
     util.TEST("decision code: " + str(self.decisionsCodes["mdeDecision"]))
     util.TEST("requiredItem: " + str(requireItem))
     util.TEST("is not item" + str(not self.item))
     return self.isSupported and not (self.decisionsCodes["mdeDecision"]
                                      == -1 or not self.item)
예제 #3
0
    def getDecisionPath(self, directPlay=False):
        if not self.item or not self.metadata:
            return None

        decisionPath = self.metadata.decisionPath
        if not decisionPath:
            server = self.metadata.transcodeServer or self.item.getServer()
            decisionPath = self.buildTranscode(server, util.AttributeDict(), self.metadata.partIndex, True, False).decisionPath

        util.TEST(decisionPath)

        # Modify the decision params based on the transcode url
        if decisionPath:
            if directPlay:
                decisionPath = decisionPath.replace("directPlay=0", "directPlay=1")

                # Clear all subtitle parameters and add the a valid subtitle type based
                # on the video player. This will let the server decide if it can supply
                # sidecar subs, burn or embed w/ an optional transcode.
                for key in ("subtitles", "advancedSubtitles"):
                    decisionPath = re.sub('([?&]{0}=)\w+'.format(key), '', decisionPath)
                subType = 'sidecar'  # AppSettings().getBoolPreference("custom_video_player"), "embedded", "sidecar")
                decisionPath = http.addUrlParam(decisionPath, "subtitles=" + subType)

            # Global variables for all decisions
            decisionPath = http.addUrlParam(decisionPath, "mediaBufferSize=20971") # Kodi default is 20971520 (20MB)
            decisionPath = http.addUrlParam(decisionPath, "hasMDE=1")
            decisionPath = http.addUrlParam(decisionPath, 'X-Plex-Platform=Chrome')

        return decisionPath
예제 #4
0
    def _startAsync(self, body=None, contentType=None, context=None):
        timeout = context and context.timeout or DEFAULT_TIMEOUT
        self.logRequest(body, timeout)
        if self._cancel:
            return
        try:
            if self.method == 'PUT':
                res = self.session.put(self.url, timeout=timeout, stream=True)
            elif self.method == 'DELETE':
                res = self.session.delete(self.url,
                                          timeout=timeout,
                                          stream=True)
            elif self.method == 'HEAD':
                res = self.session.head(self.url, timeout=timeout, stream=True)
            elif self.method == 'POST' or body is not None:
                if not contentType:
                    self.session.headers[
                        "Content-Type"] = "application/x-www-form-urlencoded"
                else:
                    self.session.headers[
                        "Content-Type"] = mimetypes.guess_type(contentType)

                res = self.session.post(self.url,
                                        data=body or None,
                                        timeout=timeout,
                                        stream=True)
            else:
                res = self.session.get(self.url, timeout=timeout, stream=True)
            util.TEST(res)
            self.currentResponse = res

            if self._cancel:
                return
        except asyncadapter.TimeoutException:
            import plexapp
            plexapp.APP.onRequestTimeout(context)
            self.removeAsPending()
            return
        except Exception, e:
            util.ERROR('Request failed {0}'.format(util.cleanToken(self.url)),
                       e)
            if not hasattr(e, 'response'):
                return
            res = e.response
예제 #5
0
    def __init__(self, response, path, server=None):
        util.TEST(response)

        self.event = response
        if self.event:
            self.event.content  # force data to be read
            self.event.close()

        data = self.getBodyXml()

        plexobjects.PlexContainer.__init__(self,
                                           data,
                                           initpath=path,
                                           server=server,
                                           address=path)
        self.container = self

        self.items = plexobjects.listItems(server,
                                           path,
                                           data=data,
                                           container=self)
예제 #6
0
    def init(self):
        self.isSupported = self.response.server.supportsFeature(
            "streamingBrain")
        for item in self.response.items:
            util.TEST(item)
            if item and item.media:
                self.item = item
                self.original.transcodeDecision = mediachoice.MediaChoice(
                    self.item.media[0])

        # Decision codes and text
        self.decisionsCodes = {}
        self.decisionsTexts = {}
        for key in [
                "directPlayDecision", "generalDecision", "mdeDecision",
                "transcodeDecision", "termination"
        ]:
            self.decisionsCodes[key] = self.response.container.get(
                key + "Code", "-1").asInt()
            self.decisionsTexts[key] = self.response.container.get(key +
                                                                   "Text")

        util.DEBUG_LOG("Decision codes: {0}".format(self.decisionsCodes))
예제 #7
0
 def isSuccess(self):
     code = self.decisionsCodes["mdeDecision"]
     util.TEST("Code: " + str(code))
     util.TEST(self.isSupported)
     return not self.isSupported or 1000 <= code < 2000
예제 #8
0
    def buildTranscode(self, server, obj, partIndex, directStream,
                       isCurrentPart):
        util.DEBUG_LOG('buildTranscode()')
        obj.transcodeServer = server
        obj.isTranscoded = True

        # if server.supportsFeature("mkvTranscode") and self.item.settings.getPreference("transcode_format", 'mkv') != "hls":
        if server.supportsFeature("mkvTranscode"):
            builder = self.buildTranscodeMkv(obj)
        else:
            builder = self.buildTranscodeHls(obj)

        if self.item.getServer().TYPE == 'MYPLEXSERVER':
            path = server.swizzleUrl(self.item.getAbsolutePath("key"))
        else:
            path = self.item.getAbsolutePath("key")

        builder.addParam("path", path)

        part = self.media.parts[partIndex]
        seekOffset = int(self.seekValue / 1000)

        # Disabled for HLS due to a Roku bug plexinc/roku-client-issues#776
        if True:  # obj.streamFormat == "mkv":
            # Trust our seekOffset for this part if it's the current part (now playing) or
            # the seekOffset is within the time frame. We have to trust the current part
            # as we may have to rebuild the transcode when seeking, and not all parts
            # have a valid duration.

            if isCurrentPart or len(self.media.parts) <= 1 or (
                    seekOffset >= obj.startOffset
                    and seekOffset <= obj.get('startOffset', 0) +
                    int(part.duration.asInt() / 1000)):
                startOffset = seekOffset - (obj.startOffset or 0)

                # Avoid a perfect storm of PMS and Roku quirks. If we pass an offset to
                # the transcoder,: it'll start transcoding from that point. But if
                # we try to start a few seconds into the video, the Roku seems to want
                # to grab the first segment. The first segment doesn't exist, so PMS
                # returns a 404 (but only if the offset is <= 12s, otherwise it returns
                # a blank segment). If the Roku gets a 404 for the first segment,:
                # it'll fail. So, if we're going to start playing from less than 12
                # seconds, don't bother telling the transcoder. It's not worth the
                # potential failure, let it transcode from the start so that the first
                # segment will always exist.

                # TODO: Probably can remove this (Rick)
                if startOffset <= 12:
                    startOffset = 0
            else:
                startOffset = 0

            builder.addParam("offset", str(startOffset))

        builder.addParam("session",
                         self.item.settings.getGlobal("clientIdentifier"))
        builder.addParam("directStream", directStream and "1" or "0")
        builder.addParam("directPlay", "0")

        qualityIndex = self.item.settings.getQualityIndex(
            self.item.getQualityType(server))
        builder.addParam(
            "videoQuality",
            self.item.settings.getGlobal("transcodeVideoQualities")
            [qualityIndex])
        builder.addParam(
            "videoResolution",
            str(
                self.item.settings.getGlobal("transcodeVideoResolutions")
                [qualityIndex]))
        builder.addParam(
            "maxVideoBitrate",
            self.item.settings.getGlobal("transcodeVideoBitrates")
            [qualityIndex])

        if self.media.mediaIndex is not None:
            builder.addParam("mediaIndex", str(self.media.mediaIndex))

        builder.addParam("partIndex", str(partIndex))

        # Augment the server's profile for things that depend on the Roku's configuration.
        if self.item.settings.getPreference("h264_level", "auto") != "auto":
            builder.extras.append(
                "add-limitation(scope=videoCodec&scopeName=h264&type=upperBound&name=video.level&value={0}&isRequired=true)"
                .format(self.item.settings.getPreference("h264_level")))

        if not self.item.settings.getGlobal(
                "supports1080p60") and self.item.settings.getGlobal(
                    "transcodeVideoResolutions")[qualityIndex][0] >= 1920:
            builder.extras.append(
                "add-limitation(scope=videoCodec&scopeName=h264&type=upperBound&name=video.frameRate&value=30&isRequired=false)"
            )

        if builder.extras:
            builder.addParam("X-Plex-Client-Profile-Extra",
                             '+'.join(builder.extras))

        if server.isLocalConnection():
            builder.addParam("location", "lan")

        obj.streamUrls = [builder.getUrl()]

        # Build the decision path now that we have build our stream url, and only if the server supports it.
        if server.supportsFeature("streamingBrain"):
            util.TEST("TEST==========================")
            decisionPath = builder.getRelativeUrl().replace(
                obj.transcodeEndpoint, self.DECISION_ENDPOINT)
            if decisionPath.startswith(self.DECISION_ENDPOINT):
                obj.decisionPath = decisionPath

        return obj
예제 #9
0
 def __init__(self, event):
     self.event = event
     if not self.event is None:
         self.event.content
         util.TEST(self.event.content)  # force data to be read
         self.event.close()