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
def delete(self): if not self.ratingKey: return req = plexrequest.PlexRequest(self.server, '/library/metadata/{0}'.format(self.ratingKey), method='DELETE') req.getToStringWithTimeout(10) self.deleted = req.wasOK() return self.deleted
def removeItem(self, item): request = plexrequest.PlexRequest( self.server, "/playQueues/" + str(self.id) + "/items/" + item.get("playQueueItemID", "-1"), "DELETE") self.addRequestOptions(request) context = request.createRequestContext( "delete", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context)
def addItem(self, item, addNext=False, excludeSeedItem=False): request = plexrequest.PlexRequest(self.server, "/playQueues/" + str(self.id), "PUT") request.addParam("uri", item.getItemUri()) request.addParam("next", addNext and "1" or "0") request.addParam("excludeSeedItem", excludeSeedItem and "1" or "0") self.addRequestOptions(request) context = request.createRequestContext( "add", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context)
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)
def createPlayQueueForId(id, server=None, contentType=None): obj = PlayQueue(server, contentType) obj.id = id request = plexrequest.PlexRequest(server, "/playQueues/" + str(id)) request.addParam("own", "1") obj.addRequestOptions(request) context = request.createRequestContext("own", callback.Callable(obj.onResponse)) plexapp.APP.startRequest(request, context) return obj
def moveItem(self, item, after): if after: query = "?after=" + after.get("playQueueItemID", "-1") else: query = "" request = plexrequest.PlexRequest( self.server, "/playQueues/" + str(self.id) + "/items/" + item.get("playQueueItemID", "-1") + "/move" + query, "PUT") self.addRequestOptions(request) context = request.createRequestContext( "move", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context)
def setShuffle(self, shuffle=None): if shuffle is None: shuffle = not self.isShuffled if self.isShuffled == shuffle: return if shuffle: command = "/shuffle" else: command = "/unshuffle" # Don't change self.isShuffled, it'll be set in OnResponse if all goes well request = plexrequest.PlexRequest( self.server, "/playQueues/" + str(self.id) + command, "PUT") self.addRequestOptions(request) context = request.createRequestContext( "shuffle", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context)
def setSelectedStream(self, streamType, streamId, async): if streamType == plexstream.PlexStream.TYPE_AUDIO: typeString = "audio" elif streamType == plexstream.PlexStream.TYPE_SUBTITLE: typeString = "subtitle" elif streamType == plexstream.PlexStream.TYPE_VIDEO: typeString = "video" else: return None path = "/library/parts/{0}?{1}StreamID={2}".format( self.id(''), typeString, streamId) if self.getServer().supportsFeature("allPartsStreamSelection"): path = path + "&allParts=1" request = plexrequest.PlexRequest(self.getServer(), path, "PUT") if async: context = request.createRequestContext("ignored") import plexapp plexapp.APP.startRequest(request, context, "") else: request.postToStringWithTimeout() matching = plexstream.NoneStream() # Update any affected streams for stream in self.streams: if stream.streamType.asInt() == streamType: if stream.id == streamId: stream.setSelected(True) matching = stream elif stream.isSelected(): stream.setSelected(False) return matching
def refresh(self, force=True, delay=False, wait=False): # Ignore refreshing local PQs if self.isLocal(): return if wait: self.responded = False self.initialized = False # We refresh our play queue if the caller insists or if we only have a # portion of our play queue loaded. In particular, this means that we don't # refresh the play queue if we're asked to refresh because a new track is # being played but we have the entire album loaded already. if force or self.isWindowed(): if delay: # We occasionally want to refresh the PQ in response to moving to a # new item and starting playback, but if we refresh immediately: # we probably end up refreshing before PMS realizes we've moved on. # There's no great solution, but delaying our refresh by just a few # seconds makes us much more likely to get an accurate window (and # accurate selected IDs) from PMS. if not self.refreshTimer: self.refreshTimer = plexapp.createTimer( 5000, self.onRefreshTimer) plexapp.APP.addTimer(self.refreshTimer) else: request = plexrequest.PlexRequest( self.server, "/playQueues/" + str(self.id)) self.addRequestOptions(request) context = request.createRequestContext( "refresh", callback.Callable(self.onResponse)) plexapp.APP.startRequest(request, context) if wait: return self.waitForInitialization()
def resolveIndirect(self): if not self.isIndirect() or locks.LOCKS.isLocked("resolve_indirect"): return self part = self.parts[0] if part is None: util.DEBUG("Failed to resolve indirect media: missing valid part") return None postBody = None postUrl = part.postURL request = plexrequest.PlexRequest( self.getServer(), part.key, postUrl is not None and "POST" or "GET") if postUrl is not None: util.DEBUG( "Fetching content for indirect media POST URL: {0}".format( postUrl)) # Force setting the certificate to handle following https redirects postRequest = http.HttpRequest(postUrl, None, True) postResponse = postRequest.getToStringWithTimeout(30) if len(postResponse) > 0 and type( postRequest.event) == "roUrlEvent": util.DEBUG( "Retrieved data from postURL, posting to resolve container" ) crlf = chr(13) + chr(10) postBody = "" for header in postRequest.event.getResponseHeadersArray(): for name in header: postBody = postBody + name + ": " + header[name] + crlf postBody = postBody + crlf + postResponse else: util.DEBUG("Failed to resolve indirect media postUrl") self.Set("indirect", "-1") return self request.addParam("postURL", postUrl) response = request.doRequestWithTimeout(30, postBody) item = response.items[0] if item is None or item.mediaItems[0] is None: util.DEBUG("Failed to resolve indirect media: no media items") self.indirect = -1 return self media = item.mediaItems[0] # Add indirect headers to the media item media.indirectHeaders = util.AttributeDict() for header in (item.container.httpHeaders or '').split("&"): arr = header.split("=") if len(arr) == 2: media.indirectHeaders[arr[0]] = arr[1] # Reset the fallback media id if applicable if self.id.asInt() < 0: media.id = self.id return media.resolveIndirect()
def createRemotePlayQueue(item, contentType, options, args): util.DEBUG_LOG('Creating remote playQueue request...') obj = PlayQueue(item.getServer(), contentType, options) # The item's URI is made up of the library section UUID, a descriptor of # the item type (item or directory), and the item's path, URL-encoded. uri = "library://" + item.getLibrarySectionUuid() + "/" itemType = item.isDirectory() and "directory" or "item" path = None if not options.key: # if item.onDeck and len(item.onDeck) > 0: # options.key = item.onDeck[0].getAbsolutePath("key") # el if not item.isDirectory(): options.key = item.get("key") # If we're asked to play unwatched, ignore the option unless we are unwatched. options.unwatched = options.unwatched and item.isUnwatched() # TODO(schuyler): Until we build postplay, we're not allowed to queue containers for episodes. if item.type == "episode": options.context = options.CONTEXT_SELF elif item.type == "movie": if not options.extrasPrefixCount and not options.resume: options.extrasPrefixCount = plexapp.INTERFACE.getPreference( "cinema_trailers", 0) # How exactly to construct the item URI depends on the metadata type, though # whenever possible we simply use /library/metadata/:id. if item.isLibraryItem() and not item.isLibraryPQ: path = "/library/metadata/" + item.ratingKey else: path = item.getAbsolutePath("key") if options.context == options.CONTEXT_SELF: # If the context is specifically for just this item,: just use the # item's key and get out. pass elif item.type == "playlist": path = None uri = item.get("ratingKey") options.isPlaylist = True elif item.type == "track": # TODO(rob): Is there ever a time the container address is wrong? If we # expect to play a single track,: use options.CONTEXT_SELF. path = item.container.address or "/library/metadata/" + item.get( "parentRatingKey", "") itemType = "directory" elif item.isPhotoOrDirectoryItem(): if item.type == "photoalbum" or item.parentKey: path = item.getParentPath(item.type == "photoalbum" and "key" or "parentKey") itemType = "item" elif item.isDirectory(): path = item.getAbsolutePath("key") else: path = item.container.address itemType = "directory" options.key = item.getAbsolutePath("key") elif item.type == "episode": path = "/library/metadata/" + item.get("grandparentRatingKey", "") itemType = "directory" options.key = item.getAbsolutePath("key") # elif item.type == "show": # path = "/library/metadata/" + item.get("ratingKey", "") if path: if args: path += util.joinArgs(args) util.DEBUG_LOG("playQueue path: " + str(path)) if "/search" not in path: # Convert a few params to the PQ spec convert = {'type': "sourceType", 'unwatchedLeaves': "unwatched"} for key in convert: regex = re.compile("(?i)([?&])" + key + "=") path = regex.sub("\1" + convert[key] + "=", path) util.DEBUG_LOG("playQueue path: " + str(path)) uri = uri + itemType + "/" + urllib.quote_plus(path) util.DEBUG_LOG("playQueue uri: " + str(uri)) # Create the PQ request request = plexrequest.PlexRequest(obj.server, "/playQueues") request.addParam(not options.isPlaylist and "uri" or "playlistID", uri) request.addParam("type", contentType) # request.addParam('X-Plex-Client-Identifier', plexapp.INTERFACE.getGlobal('clientIdentifier')) # Add options we pass once during PQ creation if options.shuffle: request.addParam("shuffle", "1") options.key = None else: request.addParam("shuffle", "0") if options.key: request.addParam("key", options.key) # Add options we pass every time querying PQs obj.addRequestOptions(request) util.DEBUG_LOG('Initial playQueue request started...') context = request.createRequestContext("create", callback.Callable(obj.onResponse)) plexapp.APP.startRequest(request, context, body='') return obj