def shutdown(self): if self.timers: util.DEBUG_LOG('Waiting for {0} App() timers: Started'.format(len(self.timers))) self.cancelAllTimers() for timer in self.timers: timer.join() util.DEBUG_LOG('Waiting for App() timers: Finished')
def discover(self, device=None): import netif ifaces = netif.getInterfaces() sockets = [] for i in ifaces: if not i.broadcast: continue # if i.ip.startswith('127.'): continue s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(0.01) # 10ms s.bind((i.ip, 0)) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sockets.append((s, i)) packet = struct.pack('>4s', 'BnGr') util.DEBUG_LOG(' o-> Broadcast Packet({0})'.format( binascii.hexlify(packet))) for attempt in (0, 1): for s, i in sockets: util.DEBUG_LOG(' o-> Broadcasting to {0}: {1}'.format( i.name, i.broadcast)) try: s.sendto(packet, (i.broadcast, DEVICE_DISCOVERY_PORT)) except: util.ERROR() end = time.time() + 0.25 # 250ms # Create reply socket rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) rs.settimeout(0.01) # 10ms rs.bind(('', DEVICE_REPLY_PORT)) rs.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) while time.time() < end: try: message, address = rs.recvfrom(8096) added = self.add(message, address) if added: util.DEBUG_LOG('<-o Response Packet({0})'.format( binascii.hexlify(message))) elif added is False: util.DEBUG_LOG('<-o Response Packet(Duplicate)') elif added is None: util.DEBUG_LOG('<-o INVALID RESPONSE({0})'.format( binascii.hexlify(message))) except socket.timeout: pass except: traceback.print_exc() for d in self.tablos: d.check()
def shutdown(self): self.abort() if self._task: self._task.cancel() if self._thread and self._thread.isAlive(): util.DEBUG_LOG('BGThreader: thread ({0}): Waiting...'.format( self.name)) self._thread.join() util.DEBUG_LOG('BGThreader: thread ({0}): Done'.format(self.name))
def reDiscover(self): self._discoveryTimestamp = time.time() self.tablos = [] self.discover() if self.tablos: util.DEBUG_LOG('Device(s) found via local discovery') else: util.DEBUG_LOG( 'No devices found via local discovery - trying association server' ) self.associationServerDiscover()
def mergeServer(self, server): if server.uuid in self.serversByUuid: existing = self.serversByUuid[server.uuid] existing.merge(server) util.DEBUG_LOG("Merged {0}".format(repr(server.name))) return existing else: self.serversByUuid[server.uuid] = server util.DEBUG_LOG("Added new server {0}".format(repr(server.name))) self.trigger("new:server", server=server) return server
def _next(self): util.DEBUG_LOG('ImageQueue: Requesting next...') images = self._handler.next(self) if not images: util.DEBUG_LOG('ImageQueue: No next images') return None util.DEBUG_LOG('ImageQueue: {0} returned'.format(len(images))) self.queue += images self.pos += 1 return self.current()
def __call__(self, caller, sItem): self.caller = caller util.DEBUG_LOG('[{0}] {1}'.format(sItem.typeChar, sItem.display())) playables = self.handlers[sItem.vtype](sItem) if playables: if sItem.vtype == 'dir': util.DEBUG_LOG(' - {0}'.format(' x {0}'.format(sItem.count) or '')) else: util.DEBUG_LOG(' - {0}'.format('NOT SHOWING')) return playables
def _queueLoop(self): if self._queue.empty(): return util.DEBUG_LOG('BGThreader: ({0}): Active'.format(self.name)) try: while not self.aborted(): self._task = self._queue.get_nowait() self._runTask(self._task) self._queue.task_done() self._task = None except Queue.Empty: util.DEBUG_LOG('BGThreader ({0}): Idle'.format(self.name))
def preShutdown(self): import http http.HttpRequest._cancel = True if self.pendingRequests: util.DEBUG_LOG('Closing down {0} App() requests'.format( len(self.pendingRequests))) for p in self.pendingRequests.values(): if p: p.request.cancel() if self.timers: util.DEBUG_LOG('Canceling App() timers') self.cancelAllTimers()
def waitForThreads(): util.DEBUG_LOG('Main: Checking for any remaining threads') while len(threading.enumerate()) > 1: for t in threading.enumerate(): if t != threading.currentThread(): if t.isAlive(): util.DEBUG_LOG('Main: Waiting on: {0}...'.format(t.name)) if isinstance(t, threading._Timer): t.cancel() t.join() elif isinstance(t, threadutils.KillableThread): t.kill(force_and_wait=True) else: t.join()
def run(self): util.DEBUG_LOG('Timer {0}: {1}'.format( repr(self.function), self._reset and 'RESET' or 'STARTED')) while not self.event.isSet() and not self.shouldAbort(): while not self.event.wait(self.timeout) and not self.shouldAbort(): if self._reset: self._reset = False return self.function(*self.args, **self.kwargs) if not self.repeat: return util.DEBUG_LOG('Timer {0}: FINISHED'.format(repr(self.function)))
def _queueLoop(self): util.DEBUG_LOG('Background queue: Active') try: while not self.aborted(): self._task = self._queue.get_nowait() self._runTask(self._task) self._queue.task_done() self._task = None except Queue.Empty: util.DEBUG_LOG('Background queue: Empty') self._queue = Queue.LifoQueue() util.DEBUG_LOG('Background queue: Finished')
def defaultHandler(self, sItem): is3D = self.caller.nextQueuedFeature.is3D and sItem.play3D if sItem.random: util.DEBUG_LOG(' - Random') bumpers = [ x for x in DB.VideoBumpers.select().where( (DB.VideoBumpers.type == sItem.vtype) & (DB.VideoBumpers.is3D == is3D)) ] bumpers = random.sample(bumpers, min(sItem.count, len(bumpers))) bumpers = [ Video(bumper.path, volume=sItem.getLive('volume')).fromModule(sItem) for bumper in bumpers ] if not bumpers and is3D and util.getSettingDefault( 'bumper.fallback2D'): util.DEBUG_LOG(' - Falling back to 2D bumper') bumpers = [ x for x in DB.VideoBumpers.select().where(( DB.VideoBumpers.type == sItem.vtype)) ] bumpers = random.sample(bumpers, min(sItem.count, len(bumpers))) bumpers = [ Video(bumper.path, volume=sItem.getLive('volume')).fromModule(sItem) for bumper in bumpers ] if not bumpers: util.DEBUG_LOG(' - No matches!') return bumpers else: util.DEBUG_LOG(' - Via source') if sItem.source: return [ Video(sItem.source, volume=sItem.getLive('volume')).fromModule(sItem) ] else: util.DEBUG_LOG(' - Empty path!') return []
def updateGuide(self): newLinup = False if self.devices.isOld(): #1 hour if self.updateLineup(quiet=True): newLinup = True else: util.DEBUG_LOG('Discovery failed!') self.resetNextGuideUpdate(300) #Discovery failed, try again in 5 mins return False err = None guide = None try: guide = hdhr.guide.Guide(self.lineUp) except hdhr.errors.NoDeviceAuthException: err = util.T(32030) except hdhr.errors.NoGuideDataException: err = util.T(32031) except: err = util.ERROR() if err: if not self.guideFetchPreviouslyFailed: #Only show notification the first time. Don't need this every 5 mins if internet is down util.showNotification(err,header=util.T(32013)) self.resetNextGuideUpdate(300) #Could not get guide data. Check again in 5 minutes self.guideFetchPreviouslyFailed = True self.setWinProperties() if self.lineUp.hasGuideData: util.DEBUG_LOG('Next guide update: {0} minutes'.format(int((self.nextGuideUpdate - time.time())/60))) return False guide = guide or hdhr.guide.Guide() self.guideFetchPreviouslyFailed = False #Set guide data for each channel for channel in self.lineUp.channels.values(): guideChan = guide.getChannel(channel.number) channel.setGuide(guideChan) if newLinup: self.fillChannelList(update=True) self.lineUp.hasGuideData = True self.setWinProperties() util.DEBUG_LOG('Next guide update: {0} minutes'.format(int((self.nextGuideUpdate - time.time())/60))) return True
def _addDirectory(self, current, tree): if not util.vfs.exists(current): util.DEBUG_LOG('Creating: {0}'.format(util.strRepr(current))) util.vfs.mkdirs(current) for branch in tree: if isinstance(branch, tuple): new = util.pathJoin(current, branch[0]) self._addDirectory(new, branch[1]) else: sub = util.pathJoin(current, branch) if util.vfs.exists(sub): continue util.DEBUG_LOG('Creating: {0}'.format(util.strRepr(sub))) util.vfs.mkdirs(sub)
def onReachabilityResult(self, connection): connection.lastTestedAt = time.time() connection.hasPendingRequest = None self.pendingReachabilityRequests -= 1 if connection.isSecure: self.pendingSecureRequests -= 1 util.DEBUG_LOG("Reachability result for {0}: {1} is {2}".format( repr(self.name), connection.address, connection.state)) # Noneate active connection if the state is unreachable if self.activeConnection and self.activeConnection.state != plexresource.ResourceConnection.STATE_REACHABLE: self.activeConnection = None # Pick a best connection. If we already had an active connection and # it's still reachable, stick with it. (replace with local if # available) best = self.activeConnection for i in range(len(self.connections) - 1, -1, -1): conn = self.connections[i] if not best or conn.getScore() > best.getScore(): best = conn if best and best.state == best.STATE_REACHABLE: if best.isSecure or self.pendingSecureRequests <= 0: self.activeConnection = best else: util.DEBUG_LOG( "Found a good connection for {0}, but holding out for better" .format(repr(self.name))) if self.pendingReachabilityRequests <= 0: # Retest the server with fallback enabled. hasFallback will only # be True if there are available insecure connections and fallback # is allowed. if self.hasFallback: self.updateReachability(False, True) else: self.trigger("completed:reachability") util.LOG("Active connection for {0} is {1}".format( repr(self.name), self.activeConnection)) import plexservermanager plexservermanager.MANAGER.updateReachabilityResult( self, bool(self.activeConnection))
def createBumpers(self, basePath, model, type_name, sub_name, pct_start, sub_default=''): paths = util.vfs.listdir(basePath) total = float(len(paths)) for ct, sub in enumerate(paths): pct = pct_start + int((ct / total) * 20) if not self._callback(pct=pct): break path = util.pathJoin(basePath, sub) if not util.isDir(path): continue if sub.startswith('_Exclude'): util.DEBUG_LOG('SKIPPING EXCLUDED DIR: {0}'.format( util.strRepr(sub))) continue if util.vfs.exists(util.pathJoin(path, 'system.xml')): # For ratings self.loadRatingSystem(util.pathJoin(path, 'system.xml')) type_ = sub.replace(' Bumpers', '') self.addBumper(model, sub, path, type_name, sub_name, type_, sub_default)
def switchHomeUser(self, userId, pin=''): if userId == self.ID and self.isAuthenticated: return True # Offline support if self.isOffline: hashed = 'NONE' if pin and self.authToken: hashed = hashlib.sha256(pin + self.authToken).digest() if not self.isProtected or self.isAuthenticated or hashed == ( self.pin or ""): util.DEBUG_LOG("OFFLINE access granted") self.isAuthenticated = True self.validateToken(self.authToken, True) return True else: # build path and post to myplex to swith the user path = '/api/home/users/{0}/switch'.format(userId) req = myplexrequest.MyPlexRequest(path) xml = req.postToStringWithTimeout({'pin': pin}) data = ElementTree.fromstring(xml) if data.attrib.get('authenticationToken'): self.isAuthenticated = True # validate the token (trigger change:user) on user change or channel startup if userId != self.ID or not locks.LOCKS.isLocked("idleLock"): self.validateToken(data.attrib.get('authenticationToken'), True) return True return False
def updateHomeUsers(self): # Ignore request and clear any home users we are not signed in if not self.isSignedIn: self.homeUsers = [] if self.isOffline: self.homeUsers.append(MyPlexAccount()) self.lastHomeUserUpdate = None return # Cache home users for 60 seconds, mainly to stop back to back tests epoch = time.time() if not self.lastHomeUserUpdate: self.lastHomeUserUpdate = epoch elif self.lastHomeUserUpdate + 60 > epoch: util.DEBUG_LOG( "Skipping home user update (updated {0} seconds ago)".format( epoch - self.lastHomeUserUpdate)) return req = myplexrequest.MyPlexRequest("/api/home/users") xml = req.getToStringWithTimeout() data = ElementTree.fromstring(xml) if data.attrib.get('size') and data.find('User') is not None: self.homeUsers = [] for user in data.findall('User'): homeUser = HomeUser(user.attrib) homeUser.isAdmin = homeUser.admin == "1" homeUser.isManaged = homeUser.restricted == "1" homeUser.isProtected = homeUser.protected == "1" self.homeUsers.append(homeUser) self.lastHomeUserUpdate = epoch util.LOG("home users: {0}".format(self.homeUsers))
def buildDirectPlay(self, obj, partIndex): util.DEBUG_LOG('buildDirectPlay()') part = self.media.parts[partIndex] server = self.item.getServer() # Check if we should include our token or not for this request obj.isRequestToServer = server.isRequestToServer( server.buildUrl(part.getAbsolutePath("key"))) obj.streamUrls = [ server.buildUrl(part.getAbsolutePath("key"), obj.isRequestToServer) ] obj.token = obj.isRequestToServer and server.getToken() or None if self.media.protocol == "hls": obj.streamFormat = "hls" obj.switchingStrategy = "full-adaptation" obj.live = self.isLiveHLS(obj.streamUrls[0], self.media.indirectHeaders) else: obj.streamFormat = self.media.get('container', 'mp4') if obj.streamFormat == "mov" or obj.streamFormat == "m4v": obj.streamFormat = "mp4" obj.streamBitrates = [self.media.bitrate.asInt()] obj.isTranscoded = False if self.choice.audioStream is not None: obj.audioLanguageSelected = self.choice.audioStream.languageCode return obj
def buildTranscodeHls(self, obj): util.DEBUG_LOG('buildTranscodeHls()') obj.streamFormat = "hls" obj.streamBitrates = [0] obj.switchingStrategy = "no-adaptation" obj.transcodeEndpoint = "/video/:/transcode/universal/start.m3u8" builder = http.HttpRequest( obj.transcodeServer.buildUrl(obj.transcodeEndpoint, True)) builder.extras = [] builder.addParam("protocol", "hls") if self.choice.subtitleDecision == self.choice.SUBTITLES_SOFT_ANY: builder.addParam("skipSubtitles", "1") else: # elif self.choice.hasBurnedInSubtitles is True: # Must burn transcoded because we can't set offset captionSize = captions.CAPTIONS.getBurnedSize() if captionSize is not None: builder.addParam("subtitleSize", captionSize) # Augment the server's profile for things that depend on the Roku's configuration. if self.item.settings.supportsAudioStream("ac3", 6): builder.extras.append( "append-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=hls&audioCodec=ac3)" ) builder.extras.append( "add-direct-play-profile(type=videoProfile&container=matroska&videoCodec=*&audioCodec=ac3)" ) return builder
def log(self, msg): util.DEBUG_LOG(msg) if not self._callback: return self._callback(msg)
def addItemToPlayQueue(item, addNext=False): # See if we have an active play queue for this self.dia type or if we need to # create one. if item.isMusicOrDirectoryItem(): player = AudioPlayer() elif item.isVideoOrDirectoryItem(): player = VideoPlayer() elif item.isPhotoOrDirectoryItem(): player = PhotoPlayer() else: player = None if not player: util.ERROR_LOG("Don't know how to add item to play queue: " + str(item)) return None elif not player.allowAddToQueue(): util.DEBUG_LOG("Not allowed to add to this player") return None if player.playQueue: playQueue = player.playQueue playQueue.addItem(item, addNext) else: options = PlayOptions() options.context = options.CONTEXT_SELF playQueue = createPlayQueueForItem(item, None, options) if playQueue: player.setPlayQueue(playQueue, False) return playQueue
def preShutdown(self): import http http.HttpRequest._cancel = True if self.pendingRequests: util.DEBUG_LOG('Closing down {0} App() requests...'.format(len(self.pendingRequests))) for p in self.pendingRequests.values(): if p: p.request.cancel() if self.timers: util.DEBUG_LOG('Canceling App() timers...') self.cancelAllTimers() if SERVERMANAGER.selectedServer: util.DEBUG_LOG('Closing server...') SERVERMANAGER.selectedServer.close()
def onManualConnectionsResponse(self, request, response, context): if not response.isSuccess(): return data = response.getBodyXml() if data is not None: serverAddress = context.serverAddress util.DEBUG_LOG( "Received manual connection response for {0}".format( serverAddress)) machineID = data.attrib.get('machineIdentifier') name = context.address if not name or not machineID: return # TODO(rob): Do we NOT want to consider manual connections local? conn = plexconnection.PlexConnection( plexresource.ResourceConnection.SOURCE_MANUAL, serverAddress, True, None) server = plexserver.createPlexServerForConnection(conn) server.uuid = machineID server.name = name server.sourceType = plexresource.ResourceConnection.SOURCE_MANUAL self.updateFromConnectionType( [server], plexresource.ResourceConnection.SOURCE_MANUAL)
def buildTranscodeMkv(self, obj): util.DEBUG_LOG('buildTranscodeMkv()') obj.streamFormat = "mkv" obj.streamBitrates = [0] obj.transcodeEndpoint = "/video/:/transcode/universal/start.mkv" builder = http.HttpRequest(obj.transcodeServer.buildUrl(obj.transcodeEndpoint, True)) builder.extras = [] # builder.addParam("protocol", "http") builder.addParam("copyts", "1") obj.subtitleUrl = None if True: # if self.choice.subtitleDecision == self.choice.SUBTITLES_BURN: # Must burn transcoded because we can't set offset builder.addParam("subtitles", "burn") captionSize = captions.CAPTIONS.getBurnedSize() if captionSize is not None: builder.addParam("subtitleSize", captionSize) else: # TODO(rob): can we safely assume the id will also be 3 (one based index). # If not, we will have to get tricky and select the subtitle stream after # video playback starts via roCaptionRenderer: GetSubtitleTracks() and # ChangeSubtitleTrack() obj.subtitleConfig = {'TrackName': "mkv/3"} # Allow text conversion of subtitles if we only burn image formats if self.item.settings.getPreference("burn_subtitles") == "image": builder.addParam("advancedSubtitles", "text") builder.addParam("subtitles", "auto") # Augment the server's profile for things that depend on the Roku's configuration. if self.item.settings.supportsSurroundSound(): if self.choice.audioStream is not None: numChannels = self.choice.audioStream.channels.asInt(6) else: numChannels = 6 for codec in ("ac3", "eac3", "dca"): if self.item.settings.supportsAudioStream(codec, numChannels): builder.extras.append("append-transcode-target-audio-codec(type=videoProfile&context=streaming&audioCodec=" + codec + ")") builder.extras.append("add-direct-play-profile(type=videoProfile&container=matroska&videoCodec=*&audioCodec=" + codec + ")") if codec == "dca": builder.extras.append( "add-limitation(scope=videoAudioCodec&scopeName=dca&type=upperBound&name=audio.channels&value=6&isRequired=false)" ) # AAC sample rate cannot be less than 22050hz (HLS is capable). if self.choice.audioStream is not None and self.choice.audioStream.samplingRate.asInt(22050) < 22050: builder.extras.append("add-limitation(scope=videoAudioCodec&scopeName=aac&type=lowerBound&name=audio.samplingRate&value=22050&isRequired=false)") # HEVC and VP9 support! if self.item.settings.getGlobal("hevcSupport"): builder.extras.append("append-transcode-target-codec(type=videoProfile&context=streaming&videoCodec=hevc)") if self.item.settings.getGlobal("vp9Support"): builder.extras.append("append-transcode-target-codec(type=videoProfile&context=streaming&videoCodec=vp9)") return builder
def __call__(self, caller, sItem): duration = sItem.getLive('duration') util.DEBUG_LOG('[{0}] {1}m'.format(sItem.typeChar, duration)) durationLimit = duration * 60 queue = ImageQueue(self, sItem).fromModule(sItem) queue.transition = sItem.getLive('transition') queue.transitionDuration = sItem.getLive('transitionDuration') vqueue = VideoQueue(self, sItem).fromModule(sItem) for slides in self.getTriviaImages(sItem): if isinstance(slides, Video): vqueue.append(slides) else: queue += slides if queue.duration + vqueue.duration >= durationLimit: break ret = [] if queue.duration: ret.append(queue) queue.maxDuration -= vqueue.duration if vqueue.duration: ret.append(vqueue) self.addMusic(sItem, queue) return ret
def updateChannels(self): util.DEBUG_LOG('Updating channels') self.nextChannelUpdate = MAX_TIME_INT for mli in self.channelList: self.updateListItem(mli.dataSource['channel'],mli)
def getItemsFromString(dstring): try: data = json.loads(dstring) return [Item.fromDict(ddict) for ddict in data['items']] except ValueError: if dstring and dstring.startswith('{'): util.DEBUG_LOG(repr(dstring)) util.ERROR() else: try: return getItemsFromXMLString(dstring) except: util.DEBUG_LOG(repr(dstring[:100])) util.ERROR() return None
def collectDataFromRoot(self, data): # Make sure we're processing data for our server, and not some other # server that happened to be at the same IP. if self.uuid != data.attrib.get('machineIdentifier'): util.LOG( "Got a reachability response, but from a different server") return False self.serverClass = data.attrib.get('serverClass') self.supportsAudioTranscoding = data.attrib.get( 'transcoderAudio') == '1' self.supportsVideoTranscoding = data.attrib.get( 'transcoderVideo') == '1' or data.attrib.get( 'transcoderVideoQualities') self.supportsVideoRemuxOnly = data.attrib.get( 'transcoderVideoRemuxOnly') == '1' self.supportsPhotoTranscoding = data.attrib.get( 'transcoderPhoto') == '1' or ( not data.attrib.get('transcoderPhoto') and not self.synced and not self.isSecondary()) self.allowChannelAccess = data.attrib.get( 'allowChannelAccess') == '1' or ( not data.attrib.get('allowChannelAccess') and self.owned and not self.synced and not self.isSecondary()) self.supportsScrobble = not self.isSecondary() or self.synced self.allowsMediaDeletion = not self.synced and self.owned and data.attrib.get( 'allowMediaDeletion') == '1' self.multiuser = data.attrib.get('multiuser') == '1' self.name = data.attrib.get('friendlyName') or self.name self.platform = data.attrib.get('platform') # TODO(schuyler): Process transcoder qualities self.rawVersion = data.attrib.get('version') if self.rawVersion: self.versionNorm = util.normalizedVersion(self.rawVersion) features = { 'mkvTranscode': '0.9.11.11', 'themeTranscode': '0.9.14.0', 'allPartsStreamSelection': '0.9.12.5', 'claimServer': '0.9.14.2', 'streamingBrain': '1.2.0' } for f, v in features.items(): if verlib.suggest_normalized_version(v) <= self.versionNorm: self.features[f] = True appMinVer = plexapp.INTERFACE.getGlobal('minServerVersionArr', '0.0.0.0') self.isSupported = self.isSecondary( ) or verlib.suggest_normalized_version(appMinVer) <= self.versionNorm util.DEBUG_LOG( "Server information updated from reachability check: {0}".format( self)) return True