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")
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 # 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=50000") decisionPath = http.addUrlParam(decisionPath, "hasMDE=1") decisionPath = http.addUrlParam(decisionPath, 'X-Plex-Platform=Chrome') return decisionPath
def __init__(self, timelineType, *args, **kwargs): util.AttributeDict.__init__(self, *args, **kwargs) self.type = timelineType self.state = "stopped" self.item = None self.choice = None self.playQueue = None self.controllable = util.AttributeDict() self.controllableStr = None self.attrs = util.AttributeDict() # Set default controllable for all content. Other controllable aspects # will be set based on the players content. # self.setControllable("playPause", True) self.setControllable("stop", True)
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 deSerialize(cls, jstring): import plexserver obj = json.loads(jstring) server = plexserver.PlexServer.deSerialize(obj['server']) server.identifier = None ad = util.AttributeDict() ad.attrib = obj['obj'] ad.find = lambda x: None po = buildItem(server, ad, ad.initpath, container=server) return po
def getManualConnections(self): manualConnections = [] jstring = plexapp.INTERFACE.getPreference('manual_connections') if jstring: connections = json.loads(jstring) if isinstance(connections, list): for conn in connections: conn = util.AttributeDict(conn) if conn.connection: manualConnections.append(conn) return manualConnections
def __init__(self): self.deviceInfo = plexapp.INTERFACE.getGlobal("deviceInfo") self.textSize = util.AttributeDict({ 'extrasmall': 15, 'small': 20, 'medium': 30, 'large': 45, 'extralarge': 65, }) self.burnedSize = util.AttributeDict({ 'extrasmall': "60", 'small': "80", 'medium': "100", 'large': "135", 'extralarge': "200" }) self.colors = util.AttributeDict({ 'white': 0xffffffff, 'black': 0x000000ff, 'red': 0xff0000ff, 'green': 0x008000ff, 'blue': 0x0000ffff, 'yellow': 0xffff00ff, 'magenta': 0xff00ffff, 'cyan': 0x00ffffff, }) self.defaults = util.AttributeDict({ 'textSize': self.textSize.medium, 'textColor': self.colors.white, 'textOpacity': 80, 'backgroundColor': self.colors.black, 'backgroundOpacity': 70, 'burnedSize': None })
def __init__(self): # Constants self.NAVIGATION = "navigation" self.FULLSCREEN_VIDEO = "fullScreenVideo" self.FULLSCREEN_MUSIC = "fullScreenMusic" self.FULLSCREEN_PHOTO = "fullScreenPhoto" self.TIMELINE_TYPES = ["video", "music", "photo"] # Members self.serverTimelines = util.AttributeDict() self.subscribers = util.AttributeDict() self.pollReplies = util.AttributeDict() self.timelines = util.AttributeDict() self.location = self.NAVIGATION self.textFieldName = None self.textFieldContent = None self.textFieldSecure = None # Initialization for timelineType in self.TIMELINE_TYPES: self.timelines[timelineType] = TimelineData(timelineType)
def build(self): if self.media.parts and self.media.parts[0]: obj = util.AttributeDict() part = self.media.parts[0] path = part.key or part.thumb server = self.item.getServer() obj.url = server.buildUrl(path, True) obj.enableBlur = server.supportsPhotoTranscoding util.DEBUG_LOG("Constructed photo item for playback: {0}".format(dict(obj))) self.metadata = obj return self.metadata
def read_model(self, filepath): self.model.read(filepath + "tf") if os.path.isfile(filepath + "vars"): try: with open(filepath + "vars", "rb") as f: data = pickle.load(f) except: with open(filepath + "vars", "rt") as f: data = json.load(f) self.step = data.get("step") + 1 history = data.get("history", None) if history is not None: self.history = util.AttributeDict(history)
def build(self, directPlay=None): directPlay = directPlay or self.choice.isDirectPlayable obj = util.AttributeDict() # TODO(schuyler): Do we want/need to add anything generic here? Title? Duration? if directPlay: obj = self.buildDirectPlay(obj) else: obj = self.buildTranscode(obj) self.metadata = obj util.LOG("Constructed audio item for playback: {0}".format(dict(obj))) return self.metadata
def __init__(self, server, contentType, options=None): signalsmixin.SignalsMixin.__init__(self) self.id = None self.selectedId = None self.version = -1 self.isShuffled = False self.isRepeat = False self.isRepeatOne = False self.isLocalPlayQueue = False self.isMixed = None self.totalSize = 0 self.windowSize = 0 self.forcedWindow = False self.container = None # Forced limitations self.allowShuffle = False self.allowSeek = True self.allowRepeat = False self.allowSkipPrev = False self.allowSkipNext = False self.allowAddToQueue = False self.refreshOnTimeline = False self.server = server self.type = contentType self._items = [] self.options = options or util.AttributeDict() self.usage = None self.refreshTimer = None self.canceled = False self.responded = False self.initialized = False self.composite = plexobjects.PlexValue('', parent=self) # Add a few default options for specific PQ types if self.type == "audio": self.options.includeRelated = True elif self.type == "photo": self.setRepeat(True)
def __init__(self, data, initpath=None, server=None, media=None): plexobjects.PlexObject.__init__(self, data, initpath, server) self.container_ = self.container self.container = media self.streams = [] # If we weren't given any data, this is a synthetic part if data is not None: self.streams = [ plexstream.PlexStream(e, initpath=self.initpath, server=self.server) for e in data if e.tag == 'Stream' ] if self.indexes: indexKeys = self.indexes('').split(",") self.indexes = util.AttributeDict() for indexKey in indexKeys: self.indexes[indexKey] = True
def __init__(self, settings): self.S = settings self.feature_extractor = FeatureExtractor( self.S.image_width, self.S.image_height, self.S.batch_size) self.model = Model(settings) self.model.add_layers() self.model.add_optimizer() self.history = util.AttributeDict() self.history.val_accuracy = [] self.history.val_iou = [] self.history.train_loss = [] self.history.train_accuracy = [] self.history.train_iou = [] self.history.val_loss_estimate = [] self.history.val_accuracy_estimate = [] self.history.val_iou_estimate = [] self.step = 0 self.model.start()
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 _build(self, directPlay=None, directStream=True, currentPartIndex=None): isForced = directPlay is not None if isForced: util.LOG( directPlay and "Forced Direct Play" or "Forced Transcode; allowDirectStream={0}".format(directStream)) directPlay = directPlay or self.choice.isDirectPlayable server = self.item.getServer() # A lot of our content metadata is independent of the direct play decision. # Add that first. obj = util.AttributeDict() obj.duration = self.media.duration.asInt() videoRes = self.media.getVideoResolution() obj.fullHD = videoRes >= 1080 obj.streamQualities = ( videoRes >= 480 and self.item.settings.getGlobal("IsHD")) and ["HD"] or ["SD"] frameRate = self.media.videoFrameRate or "24p" if frameRate == "24p": obj.frameRate = 24 elif frameRate == "NTSC": obj.frameRate = 30 # Add soft subtitle info if self.choice.subtitleDecision == self.choice.SUBTITLES_SOFT_ANY: obj.subtitleUrl = server.buildUrl( self.choice.subtitleStream.getSubtitlePath(), True) elif self.choice.subtitleDecision == self.choice.SUBTITLES_SOFT_DP: obj.subtitleConfig = { 'TrackName': "mkv/" + str(self.choice.subtitleStream.index.asInt() + 1) } # Create one content metadata object for each part and store them as a # linked list. We probably want a doubly linked list, except that it # becomes a circular reference nuisance, so we make the current item the # base object and singly link in each direction from there. baseObj = obj prevObj = None startOffset = 0 startPartIndex = currentPartIndex or 0 for partIndex in range(startPartIndex, len(self.media.parts)): isCurrentPart = (currentPartIndex is not None and partIndex == currentPartIndex) partObj = util.AttributeDict() partObj.update(baseObj) partObj.live = False partObj.partIndex = partIndex partObj.startOffset = startOffset part = self.media.parts[partIndex] partObj.partDuration = part.duration.asInt() if part.isIndexed(): partObj.sdBifPath = part.getIndexPath("sd") partObj.hdBifPath = part.getIndexPath("hd") # We have to evaluate every part before playback. Normally we'd expect # all parts to be identical, but in reality they can be different. if partIndex > 0 and (not isForced and directPlay or not isCurrentPart): choice = mediadecisionengine.MediaDecisionEngine( ).evaluateMediaVideo(self.item, self.media, partIndex) canDirectPlay = (choice.isDirectPlayable is True) else: canDirectPlay = directPlay if canDirectPlay: partObj = self.buildDirectPlay(partObj, partIndex) else: transcodeServer = self.item.getTranscodeServer(True, "video") if transcodeServer is None: return None partObj = self.buildTranscode(transcodeServer, partObj, partIndex, directStream, isCurrentPart) # Set up our linked list references. If we couldn't build an actual # object: fail fast. Otherwise, see if we're at our start offset # yet in order to decide if we need to link forwards or backwards. # We also need to account for parts missing a duration, by verifying # the prevObj is None or if the startOffset has incremented. if partObj is None: obj = None break elif prevObj is None or (startOffset > 0 and int( self.seekValue / 1000) >= startOffset): obj = partObj partObj.prevObj = prevObj elif prevObj is not None: prevObj.nextPart = partObj startOffset = startOffset + int(part.duration.asInt() / 1000) prevObj = partObj # Only set PlayStart for the initial part, and adjust for the part's offset if obj is not None: if obj.live: # Start the stream at the end. Per Roku, this can be achieved using # a number higher than the duration. Using the current time should # ensure it's definitely high enough. obj.playStart = util.now() + 1800 else: obj.playStart = int(self.seekValue / 1000) - obj.startOffset self.metadata = obj util.LOG("Constructed video item for playback: {0}".format(dict(obj))) return self.metadata
class MediaDecisionEngine(object): proxyTypes = util.AttributeDict({'NORMAL': 0, 'LOCAL': 42, 'CLOUD': 43}) def __init__(self): self.softSubLanguages = None # TODO(schuyler): Do we need to allow this to be async? We may have to request # the media again to fetch details, and we may need to make multiple requests to # resolve an indirect. We can do it all async, we can block, or we can allow # both. 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 def sortChoices(self, choices): if choices is None: return [] if len(choices) > 1: self.sort(choices, "bitrate") self.sort(choices, "audioChannels") self.sort(choices, "audioDS") self.sort(choices, "resolution") self.sort(choices, "videoDS") self.sort(choices, "directPlay") self.sort(choices, self.higherResIfCapable) self.sort(choices, self.cloudIfRemote) return choices def evaluateMediaVideo(self, item, media, partIndex=0): # 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, partIndex) server = item.getServer() if not media: return choice choice.isSelected = media.isSelected() choice.protocol = media.protocol("http") maxResolution = item.settings.getMaxResolution( item.getQualityType(), self.isSupported4k(media, choice.videoStream)) maxBitrate = item.settings.getMaxBitrate(item.getQualityType()) choice.resolution = media.getVideoResolution() if choice.resolution > maxResolution or media.bitrate.asInt( ) > maxBitrate: choice.forceTranscode = True if choice.subtitleStream: choice.subtitleDecision = self.evaluateSubtitles( choice.subtitleStream) choice.hasBurnedInSubtitles = ( choice.subtitleDecision != choice.SUBTITLES_SOFT_DP and choice.subtitleDecision != choice.SUBTITLES_SOFT_ANY) else: choice.hasBurnedInSubtitles = False # For evaluation purposes, we only care about the first part part = media.parts[partIndex] if not part: return choice # Although PMS has already told us which streams are selected, we can't # necessarily tell the video player which streams we want. So we need to # iterate over the streams and see if there are any red flags that would # prevent direct play. If there are multiple video streams, we're hosed. # For audio streams, we have a fighting chance if the selected stream can # be selected by language, but we need to be careful about guessing which # audio stream the Roku will pick for a given language. numVideoStreams = 0 problematicAudioStream = False if part.get('hasChapterVideoStream').asBool(): numVideoStreams = 1 for stream in part.streams: streamType = stream.streamType.asInt() if streamType == stream.TYPE_VIDEO: numVideoStreams = numVideoStreams + 1 if stream.codec == "h264" or ( stream.codec == "hevc" and item.settings.getPreference("allow_hevc", False) ) or (stream.codec == "vp9" and item.settings.getGlobal("vp9Support")): choice.sorts.videoDS = 1 # Special cases to force direct play forceDirectPlay = False if choice.protocol == "hls": util.LOG("MDE: Assuming HLS is direct playable") forceDirectPlay = True elif not server.supportsVideoTranscoding: # See if we can use another server to transcode, otherwise force direct play transcodeServer = item.getTranscodeServer(True, "video") if not transcodeServer or not transcodeServer.supportsVideoTranscoding: util.LOG( "MDE: force direct play because the server does not support video transcoding" ) forceDirectPlay = True # See if we found any red flags based on the streams. Otherwise, go ahead # with our codec checks. if forceDirectPlay: # Consider the choice DP, but continue to allow the # choice to have the sorts set properly. choice.isDirectPlayable = True elif choice.hasBurnedInSubtitles: util.LOG("MDE: Need to burn in subtitles") elif choice.protocol != "http": util.LOG("MDE: " + choice.protocol + " not supported") elif numVideoStreams > 1: util.LOG("MDE: Multiple video streams, won't try to direct play") elif problematicAudioStream: util.LOG( "MDE: Problematic AAC stream with more than 2 channels prevents direct play" ) elif self.canDirectPlay(item, choice): choice.isDirectPlayable = True elif item.isMediaSynthesized: util.LOG("MDE: assuming synthesized media can direct play") choice.isDirectPlayable = True # Check for a server decision. This is authority as it's the only playback type # the server will allow. This will also support forcing direct play, overriding # only our local MDE checks based on the user pref, and only if the server # agrees. decision = part.get("decision") if decision: if decision != serverdecision.ServerDecision.DECISION_DIRECT_PLAY: util.LOG("MDE: Server has decided this cannot direct play") choice.isDirectPlayable = False else: util.LOG("MDE: Server has allowed direct play") choice.isDirectPlayable = True # Setup sorts if choice.videoStream is not None: choice.sorts.bitrate = choice.videoStream.bitrate.asInt() elif choice.media is not None: choice.sorts.bitrate = choice.media.bitrate.asInt() else: choice.sorts.bitrate = 0 if choice.audioStream is not None: choice.sorts.audioChannels = choice.audioStream.channels.asInt() elif choice.media is not None: choice.sorts.audioChannels = choice.media.audioChannels.asInt() else: choice.sorts.audioChannels = 0 choice.sorts.videoDS = not ( choice.sorts.videoDS is None or choice.forceTranscode is True) and choice.sorts.videoDS or 0 choice.sorts.resolution = choice.resolution # Server properties probably don't need to be associated with each choice choice.sorts.canTranscode = server.supportsVideoTranscoding and 1 or 0 choice.sorts.canRemuxOnly = server.supportsVideoRemuxOnly and 1 or 0 choice.sorts.directPlay = (choice.isDirectPlayable is True and choice.forceTranscode is not True) and 1 or 0 choice.sorts.proxyType = choice.media.proxyType and choice.media.proxyType or self.proxyTypes.NORMAL return choice def canDirectPlay(self, item, choice): maxResolution = item.settings.getMaxResolution( item.getQualityType(), self.isSupported4k(choice.media, choice.videoStream)) height = choice.media.getVideoResolution() if height > maxResolution: util.LOG( "MDE: (DP) Video height is greater than max allowed: {0} > {1}" .format(height, maxResolution)) if height > 1088 and item.settings.getPreference("allow_4k", True): util.LOG("MDE: (DP) Unsupported 4k media") return False maxBitrate = item.settings.getMaxBitrate(item.getQualityType()) bitrate = choice.media.bitrate.asInt() if bitrate > maxBitrate: util.LOG( "MDE: (DP) Video bitrate is greater than the allowed max: {0} > {1}" .format(bitrate, maxBitrate)) return False if choice.videoStream is None: util.ERROR_LOG("MDE: (DP) No video stream") return True if not item.settings.getGlobal("supports1080p60"): videoFrameRate = choice.videoStream.asInt() if videoFrameRate > 30 and height >= 1080: util.LOG( "MDE: (DP) Frame rate is not supported for resolution: {0}@{1}" .format(height, videoFrameRate)) return False if choice.videoStream.codec == "hevc" and not item.settings.getPreference( "allow_hevc", False): util.LOG("MDE: (DP) Codec is HEVC, which is disabled") return False return True # container = choice.media.get('container') # videoCodec = choice.videoStream.codec # if choice.audioStream is None: # audioCodec = None # numChannels = 0 # else: # audioCodec = choice.audioStream.codec # numChannels = choice.audioStream.channels.asInt() # Formats: https://support.roku.com/hc/en-us/articles/208754908-Roku-Media-Player-Playing-your-personal-videos-music-photos # All Models: H.264/AVC (MKV, MP4, MOV), # Roku 4 only: H.265/HEVC (MKV, MP4, MOV); VP9 (.MKV) # if True: # container in ("mp4", "mov", "m4v", "mkv"): # util.LOG("MDE: {0} container looks OK, checking streams".format(container)) # isHEVC = videoCodec == "hevc" and item.settings.getPreference("allow_hevc", False) # isVP9 = videoCodec == "vp9" and container == "mkv" and item.settings.getGlobal("vp9Support") # if videoCodec != "h264" and videoCodec != "mpeg4" and not isHEVC and not isVP9: # util.LOG("MDE: Unsupported video codec: {0}".format(videoCodec)) # return False # # TODO(schuyler): Fix ref frames check. It's more nuanced than this. # if choice.videoStream.refFrames.asInt() > 8: # util.LOG("MDE: Too many ref frames: {0}".format(choice.videoStream.refFrames)) # return False # # HEVC supports a bitDepth of 10, otherwise 8 is the limit # if choice.videoStream.bitDepth.asInt() > (isHEVC and 10 or 8): # util.LOG("MDE: Bit depth too high: {0}".format(choice.videoStream.bitDepth)) # return False # # We shouldn't have to whitelist particular audio codecs, we can just # # check to see if the Roku can decode this codec with the number of channels. # if not item.settings.supportsAudioStream(audioCodec, numChannels): # util.LOG("MDE: Unsupported audio track: {0} ({1} channels)".format(audioCodec, numChannels)) # return False # # # TODO(schuyler): We've reported this to Roku, they may fix it. If/when # # # they do, we should move this behind a firmware version check. # # if container == "mkv" and choice.videoStream.headerStripping.asBool() and audioCodec == "ac3": # # util.ERROR_LOG("MDE: Header stripping with AC3 audio") # # return False # # Those were our problems, everything else should be OK. # return True # else: # util.LOG("MDE: Unsupported container: {0}".format(container)) # return False def evaluateSubtitles(self, stream): if plexapp.INTERFACE.getPreference("burn_subtitles") == "always": # If the user prefers them burned, always burn return mediachoice.MediaChoice.SUBTITLES_BURN # elif stream.codec != "srt": # # We only support soft subtitles for SRT. Anything else has to use the # # transcoder, and we defer to it on whether the subs will have to be # # burned or can be converted to SRT and muxed. # return mediachoice.MediaChoice.SUBTITLES_DEFAULT elif stream.key is None: # Embedded subs don't have keys and can only be direct played result = mediachoice.MediaChoice.SUBTITLES_SOFT_DP else: # Sidecar subs can be direct played or used alongside a transcode result = mediachoice.MediaChoice.SUBTITLES_SOFT_ANY # # TODO(schuyler) If Roku adds support for non-Latin characters, remove # # this hackery. To the extent that we continue using this hackery, it # # seems that the Roku requires UTF-8 subtitles but only supports characters # # from Windows-1252. This should be the full set of languages that are # # completely representable in Windows-1252. PMS should specifically be # # returning ISO 639-2/B language codes. # # Update: Roku has added support for additional characters, but still only # # Latin characters. We can now basically support anything from the various # # ISO-8859 character sets, but nothing non-Latin. # if not self.softSubLanguages: # self.softSubLanguages = frozenset(( # 'afr', # 'alb', # 'baq', # 'bre', # 'cat', # 'cze', # 'dan', # 'dut', # 'eng', # 'epo', # 'est', # 'fao', # 'fin', # 'fre', # 'ger', # 'gla', # 'gle', # 'glg', # 'hrv', # 'hun', # 'ice', # 'ita', # 'lat', # 'lav', # 'lit', # 'ltz', # 'may', # 'mlt', # 'nno', # 'nob', # 'nor', # 'oci', # 'pol', # 'por', # 'roh', # 'rum', # 'slo', # 'slv', # 'spa', # 'srd', # 'swa', # 'swe', # 'tur', # 'vie', # 'wel', # 'wln' # )) # if not (stream.languageCode or 'eng') in self.softSubLanguages: # # If the language is unsupported,: we need to force burning # result = mediachoice.MediaChoice.SUBTITLES_BURN return result 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 # Simple Quick sort function modeled after roku sdk function def sort(self, choices, key=None): if not isinstance(choices, list): return if key is None: choices.sort() elif isinstance(key, basestring): choices.sort(key=lambda x: getattr(x.media, key)) elif hasattr(key, '__call__'): choices.sort(key=key) def higherResIfCapable(self, choice): if choice.media is not None: server = choice.media.getServer() if server.supportsVideoTranscoding and not server.supportsVideoRemuxOnly and ( choice.sorts.directPlay == 1 or choice.sorts.videoDS == 1): return util.validInt(choice.sorts.resolution) return 0 def cloudIfRemote(self, choice): if choice.media is not None and choice.media.getServer( ).isLocalConnection( ) and choice.media.proxyType != self.proxyTypes.CLOUD: return 1 return 0 def isSupported4k(self, media, videoStream): if videoStream is None or not plexapp.INTERFACE.getPreference( "allow_4k", True): return False # # Roku 4 only: H.265/HEVC (MKV, MP4, MOV); VP9 (.MKV) # if media.get('container') in ("mp4", "mov", "m4v", "mkv"): # isHEVC = (videoStream.codec == "hevc" and plexapp.INTERFACE.getPreference("allow_hevc")) # isVP9 = (videoStream.codec == "vp9" and media.get('container') == "mkv" and plexapp.INTERFACE.getGlobal("vp9Support")) # return (isHEVC or isVP9) # return False return True
def main(): parser = argparse.ArgumentParser(description='Process some integers.') parser.add_argument('--units', metavar='units', type=str, help='an unit to visualize e.g. [0, 999]') parser.add_argument('--n_iters', metavar='iter', type=int, default=10, help='Number of sampling steps per each unit') parser.add_argument( '--threshold', metavar='w', type=float, default=-1.0, nargs='?', help='The probability threshold to decide whether to keep an image') parser.add_argument( '--save_every', metavar='save_iter', type=int, default=1, help='Save a sample every N iterations. 0 to disable saving') parser.add_argument('--reset_every', metavar='reset_iter', type=int, default=0, help='Reset the code every N iterations') parser.add_argument('--lr', metavar='lr', type=float, default=2.0, nargs='?', help='Learning rate') parser.add_argument('--lr_end', metavar='lr', type=float, default=-1.0, nargs='?', help='Ending Learning rate') parser.add_argument('--epsilon2', metavar='eps', type=float, default=1.0, nargs='?', help='Scalar for condition ') parser.add_argument('--epsilon1', metavar='eps', type=float, default=1.0, nargs='?', help='Scalar for prior') parser.add_argument('--epsilon3', metavar='eps', type=float, default=1.0, nargs='?', help='Scalar for noise') parser.add_argument('--mask_epsilon', metavar='eps', type=float, default=1e-6, nargs='?', help='Scalar for mask loss') parser.add_argument('--edge_epsilon', metavar='eps', type=float, default=1.0, nargs='?', help='Scalar for edge loss') parser.add_argument('--content_epsilon', metavar='eps', type=float, default=1.0, nargs='?', help='Scalar for content loss') parser.add_argument('--style_epsilon', metavar='eps', type=float, default=1.0, nargs='?', help='Scalar for style loss') parser.add_argument('--content_layer', metavar='layer', type=str, default='conv4', nargs='?', help='Layer to use for content loss') parser.add_argument('--mask_type', metavar='mask', type=str, default='', nargs='?', help='Mask type. Only square and random available') parser.add_argument('--ratio_sample', metavar='eps', type=float, default=1.0, nargs='?', help='Amount to sample for random mask') parser.add_argument('--seed', metavar='n', type=int, default=0, nargs='?', help='Random seed') parser.add_argument('--xy', metavar='n', type=int, default=0, nargs='?', help='Spatial position for conv units') parser.add_argument('--opt_layer', metavar='s', type=str, help='Layer at which we optimize a code') parser.add_argument('--act_layer', metavar='s', type=str, default="fc8", help='Layer at which we activate a neuron') parser.add_argument('--init_dir', metavar='s', type=str, default="None", help='Init image') parser.add_argument('--write_labels', action='store_true', default=False, help='Write class labels to images') parser.add_argument('--use_square', action='store_true', default=False, help='Whether or not to use the square') parser.add_argument('--output_dir', metavar='b', type=str, default=".", help='Output directory for saving results') parser.add_argument('--net_weights', metavar='b', type=str, default=settings.encoder_weights, help='Weights of the net being visualized') parser.add_argument('--net_definition', metavar='b', type=str, default=settings.encoder_definition, help='Definition of the net being visualized') args = parser.parse_args() # Default to constant learning rate if args.lr_end < 0: args.lr_end = args.lr # summary print("-------------") print(" units: %s xy: %s" % (args.units, args.xy)) print(" n_iters: %s" % args.n_iters) print(" reset_every: %s" % args.reset_every) print(" save_every: %s" % args.save_every) print(" threshold: %s" % args.threshold) print(" epsilon1: %s" % args.epsilon1) print(" epsilon2: %s" % args.epsilon2) print(" epsilon3: %s" % args.epsilon3) print(" mask_epsilon: %s" % args.mask_epsilon) print(" edge_epsilon: %s" % args.edge_epsilon) print(" content_epsilon: %s" % args.content_epsilon) print(" style_epsilon: %s" % args.style_epsilon) print(" mask_type: %s" % args.mask_type) print(" content_layer: %s" % args.content_layer) print(" start learning rate: %s" % args.lr) print(" end learning rate: %s" % args.lr_end) print(" seed: %s" % args.seed) print(" opt_layer: %s" % args.opt_layer) print(" act_layer: %s" % args.act_layer) print(" init_file: %s" % args.init_dir) print("-------------") print(" output dir: %s" % args.output_dir) print(" net weights: %s" % args.net_weights) print(" net definition: %s" % args.net_definition) print("-------------") # encoder and generator for images encoder = caffe.Net(settings.encoder_definition, settings.encoder_weights, caffe.TEST) generator = caffe.Net(settings.generator_definition, settings.generator_weights, caffe.TEST) # condition network, here an image classification net net = caffe.Classifier( args.net_definition, args.net_weights, mean=np.float32([104.0, 117.0, 123.0]), # ImageNet mean channel_swap=( 2, 1, 0)) # the reference model has channels in BGR order instead of RGB edge_detector = caffe.Net(settings.edge_definition, caffe.TEST) # make Sobel operator for edge detection laplace = np.array((0, -1, 0, -1, 4, -1, 0, -1, 0), dtype=np.float32).reshape((3, 3)) edge_detector.params['laplace'][0].data[0, 0, :, :] = laplace # horizontal # Fix the seed np.random.seed(args.seed) args = util.AttributeDict(vars(args)) # Separate the dash-separated list of units into numbers conditions = [{ "unit": int(u), "xy": args.xy } for u in args.units.split("_")] files_to_read = [ os.path.join(args.init_dir, f) for f in os.listdir(args.init_dir) if 'lena' in f ] attributes = ['content_epsilon', 'style_epsilon', 'edge_epsilon'] # attributes = ['edge_epsilon']#,'style_epsilon', 'edge_epsilon'] masks = ['random', 'laplace', 'square_random', 'square_laplace'] eps = [(0, 0, 0), (1e-4, 0, 0), (0, 1e-6, 0), (0, 0, 1e-2), (0, 1e-6, 1e-2)] output_dir = args.output_dir images_to_save = [] for image_file in files_to_read: image_name = re.split('\.|/', image_file)[-2] print('image_name', image_name) image_path = os.path.join(args.output_dir, image_name) if not os.path.exists(image_path): os.makedirs(image_path) images_col = None for i, (edge, content, style) in enumerate(eps): sampler = ClassConditionalSampler() start_image = sampler.load_image( shape=encoder.blobs["data"].data.shape, path=image_file, output_dir=output_dir, save=False) if images_col is None: images_col = [start_image.copy()] print('running', image_file, i) mask = get_mask(start_image, args.mask_type, inverse=False) start_code = sampler.get_code(encoder=encoder, data=start_image, layer=args.opt_layer, mask=mask) output_image, list_samples = sampler.sampling( condition_net=net, image_encoder=encoder, image_net=net, image_generator=generator, edge_detector=edge_detector, gen_in_layer=settings.generator_in_layer, gen_out_layer=settings.generator_out_layer, start_code=start_code, n_iters=args.n_iters, lr=args.lr, lr_end=args.lr_end, threshold=args.threshold, layer=args.act_layer, conditions=conditions, epsilon1=args.epsilon1, epsilon2=args.epsilon2, epsilon3=args.epsilon3, mask_epsilon=args.mask_epsilon, content_epsilon=content, style_epsilon=style, edge_epsilon=edge, content_layer=args.content_layer, output_dir=output_dir, mask=mask, input_image=start_image, reset_every=args.reset_every, save_every=args.save_every) print('Saving {} for {}'.format(i, image_name)) images_col.append(output_image) file_path = os.path.join(image_path, str(i) + '.jpg') util.save_image(output_image, file_path) images_to_save.append(images_col) filename = "%s/%s_%04d_%04d_%s_h_%s_%s_%s__%s.jpg" % ( args.output_dir, 'loss_survey', conditions[0]["unit"], args.n_iters, args.lr, str(args.epsilon1), str(args.epsilon2), str( args.epsilon3), args.seed) util.save_checkerboard(images_to_save, filename, labels=[ 'ground truth', 'no loss', 'edge loss', 'content loss', 'style loss', 'content + style' ])