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
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)
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
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
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)
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))
def isSuccess(self): code = self.decisionsCodes["mdeDecision"] util.TEST("Code: " + str(code)) util.TEST(self.isSupported) return not self.isSupported or 1000 <= code < 2000
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
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()