def __init__(self, queue, notify, lock, database, server=None, direct_path=None, *args): self.queue = queue self.notify_output = notify self.notify = settings('newContent.bool') self.lock = lock self.database = Database(database) self.args = args self.server = server self.direct_path = direct_path threading.Thread.__init__(self)
def libraries(self, library_id=None, update=False): ''' Map the syncing process and start the sync. Ensure only one sync is running. ''' self.direct_path = settings('useDirectPaths') == "1" self.update_library = update self.sync = get_sync() if library_id: # Look up library in local Jellyfin database library = self.get_library(library_id) if library: if library.media_type == 'mixed': self.sync['Libraries'].append("Mixed:%s" % library_id) # Include boxsets library libraries = self.get_libraries() boxsets = [ row.view_id for row in libraries if row.media_type == 'boxsets' ] if boxsets: self.sync['Libraries'].append('Boxsets:%s' % boxsets[0]) elif library.media_type == 'movies': self.sync['Libraries'].append(library_id) # Include boxsets library libraries = self.get_libraries() boxsets = [ row.view_id for row in libraries if row.media_type == 'boxsets' ] if boxsets: self.sync['Libraries'].append('Boxsets:%s' % boxsets[0]) else: # Only called if the library isn't already known about self.sync['Libraries'].append(library_id) else: self.sync['Libraries'].append(library_id) else: self.mapping() if not xmls.advanced_settings() and self.sync['Libraries']: self.start()
def delete_item(self): delete = True if not settings('skipContextMenu.bool'): if not dialog("yesno", heading="{emby}", line1=_(33015)): delete = False if delete: self.server['api'].delete_item(self.item['Id']) event( "LibraryChanged", { 'ItemsRemoved': [self.item['Id']], 'ItemsVerify': [self.item['Id']], 'ItemsUpdated': [], 'ItemsAdded': [] })
def set_progress_dialog(self): queue_size = self.worker_queue_size() try: self.progress_percent = int( (float(self.total_updates - queue_size) / float(self.total_updates)) * 100) except Exception: self.progress_percent = 0 LOG.debug("--[ pdialog (%s/%s) ]", queue_size, self.total_updates) if self.total_updates < int(settings('syncProgress') or 50): return if self.progress_updates is None: LOG.info("-->[ pdialog ]") self.progress_updates = xbmcgui.DialogProgressBG() self.progress_updates.create(_('addon_name'), _(33178))
def __init__(self, monitor): self.media = { 'Movies': Movies, 'TVShows': TVShows, 'MusicVideos': MusicVideos, 'Music': Music } self.kodi_media = { 'Movies': kMovies, 'TVShows': kTVShows, 'MusicVideos': kMusicVideos, 'Music': kMusic, 'Kodi': Kodi } self.MEDIA = MEDIA self.direct_path = settings('useDirectPaths') == "1" self.monitor = monitor self.player = monitor.monitor.player self.server = Emby().get_client() self.updated_queue = Queue.Queue() self.userdata_queue = Queue.Queue() self.removed_queue = Queue.Queue() self.updated_output = self.__new_queues__() self.userdata_output = self.__new_queues__() self.removed_output = self.__new_queues__() self.notify_output = Queue.Queue() self.add_lib_queue = Queue.Queue() self.remove_lib_queue = Queue.Queue() self.verify_queue = Queue.Queue() self.emby_threads = [] self.download_threads = [] self.notify_threads = [] self.writer_threads = {'updated': [], 'userdata': [], 'removed': []} self.database_lock = threading.Lock() self.music_database_lock = threading.Lock() self.sync = Sync threading.Thread.__init__(self) self.start()
def set_playlist(self, item, listitem, db_id=None, transcode=False, *args, **kwargs): ''' Verify seektime, set intros, set main item and set additional parts. Detect the seektime for video type content. Verify the default video action set in Kodi for accurate resume behavior. ''' seektime = window('emby.resume.bool') window('emby.resume', clear=True) if item['MediaType'] in ('Video', 'Audio'): resume = item['UserData'].get('PlaybackPositionTicks') if resume: if get_play_action() == "Resume": seektime = True if transcode and not seektime: choice = self.resume_dialog( api.API(item, self.server).adjust_resume( (resume or 0) / 10000000.0)) if choice is None: raise Exception("User backed out of resume dialog.") seektime = False if not choice else True if settings('enableCinema.bool') and not seektime: self._set_intros(item) self.set_listitem(item, listitem, db_id, seektime) playutils.set_properties(item, item['PlaybackInfo']['Method'], self.server_id) self.stack.append([item['PlaybackInfo']['Path'], listitem]) if item.get('PartCount'): self._set_additional_parts(item['Id'])
def play(self, item, db_id=None, transcode=False, playlist=False): ''' Play item based on if playback started from widget ot not. To get everything to work together, play the first item in the stack with setResolvedUrl, add the rest to the regular playlist. ''' listitem = xbmcgui.ListItem() LOG.info("[ play/%s ] %s", item['Id'], item['Name']) transcode = transcode or settings('playFromTranscode.bool') kodi_playlist = self.get_playlist(item) play = playutils.PlayUtils(item, transcode, self.server_id, self.server) source = play.select_source(play.get_sources()) play.set_external_subs(source, listitem) self.set_playlist(item, listitem, db_id, transcode) index = max(kodi_playlist.getposition(), 0) + 1 # Can return -1 force_play = False self.stack[0][1].setPath(self.stack[0][0]) try: if not playlist and self.detect_widgets(item): LOG.info(" [ play/widget ]") raise IndexError xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, self.stack[0][1]) self.stack.pop(0) except IndexError: force_play = True for stack in self.stack: kodi_playlist.add(url=stack[0], listitem=stack[1], index=index) index += 1 if force_play: if len(sys.argv) > 1: xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, self.stack[0][1]) xbmc.Player().play(kodi_playlist, windowed=False)
def setup(self): minimum = "3.0.23" if settings('MinimumSetup') == minimum: return self._is_mode() LOG.info("Add-on playback: %s", settings('useDirectPaths') == "0") self._is_artwork_caching() LOG.info("Artwork caching: %s", settings('enableTextureCache.bool')) self._is_empty_shows() LOG.info("Sync empty shows: %s", settings('syncEmptyShows.bool')) # Setup completed settings('MinimumSetup', minimum)
def _set_intros(self, item): ''' if we have any play them when the movie/show is not being resumed. ''' intros = TheVoid('GetIntros', { 'ServerId': self.server_id, 'Id': item['Id'] }).get() if intros['Items']: enabled = True if settings('askCinema') == "true": resp = dialog("yesno", heading="{jellyfin}", line1=translate(33016)) if not resp: enabled = False LOG.info("Skip trailers.") if enabled: for intro in intros['Items']: listitem = xbmcgui.ListItem() LOG.info("[ intro/%s ] %s", intro['Id'], intro['Name']) play = playutils.PlayUtils(intro, False, self.server_id, self.server) source = play.select_source(play.get_sources()) self.set_listitem(intro, listitem, intro=True) listitem.setPath(intro['PlaybackInfo']['Path']) playutils.set_properties(intro, intro['PlaybackInfo']['Method'], self.server_id) self.stack.append( [intro['PlaybackInfo']['Path'], listitem]) window('jellyfin.skip.%s' % intro['Id'], value="true")
def register(self, server_id=None, options={}): ''' Login into server. If server is None, then it will show the proper prompts to login, etc. If a server id is specified then only a login dialog will be shown for that server. ''' LOG.info("--[ server/%s ]", server_id or 'default') credentials = dict(get_credentials()) servers = credentials['Servers'] if server_id is None and credentials['Servers']: credentials['Servers'] = [credentials['Servers'][0]] elif credentials['Servers']: for server in credentials['Servers']: if server['Id'] == server_id: credentials['Servers'] = [server] server_select = True if server_id is None and not settings( 'SyncInstallRunDone.bool') else False new_credentials = self.register_client(credentials, options, server_id, server_select) for server in servers: if server['Id'] == new_credentials['Servers'][0]['Id']: server = new_credentials['Servers'][0] break else: servers = new_credentials['Servers'] credentials['Servers'] = servers save_credentials(credentials) try: Jellyfin(server_id).start(True) except ValueError as error: LOG.error(error)
def libraries(self, library_id=None, update=False, forced=False): ''' Map the syncing process and start the sync. Ensure only one sync is running. force to resume any previous sync ''' self.direct_path = settings('useDirectPaths') == "1" self.update_library = update self.sync = get_sync() if library_id: libraries = library_id.split(',') for selected in libraries: if selected not in [ x.replace('Mixed:', "") for x in self.sync['Libraries'] ]: library = self.get_libraries(selected) if library: self.sync['Libraries'].append("Mixed:%s" % selected if library[1] == 'mixed' else selected) if library[1] in ('mixed', 'movies'): self.sync['Libraries'].append('Boxsets:%s' % selected) else: self.sync['Libraries'].append(selected) else: self.mapping(forced) xmls.sources() if not xmls.advanced_settings() and self.sync['Libraries']: self.start()
def setup(self): minimum = "3.0.24" cached = settings('MinimumSetup') if cached == minimum: return if not cached: self._is_mode() LOG.info("Add-on playback: %s", settings('useDirectPaths') == "0") self._is_artwork_caching() LOG.info("Artwork caching: %s", settings('enableTextureCache.bool')) self._is_empty_shows() LOG.info("Sync empty shows: %s", settings('syncEmptyShows.bool')) self._is_multiep() LOG.info("Enable multi episode label: %s", settings('displayMultiEpLabel.bool')) # Setup completed settings('MinimumSetup', minimum)
def get_themes(): ''' Add theme media locally, via strm. This is only for tv tunes. If another script is used, adjust this code. ''' from helper.utils import normalize_string from helper.playutils import PlayUtils from helper.xmls import tvtunes_nfo library = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library").decode('utf-8') play = settings('useDirectPaths') == "1" if not xbmcvfs.exists(library + '/'): xbmcvfs.mkdir(library) if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) LOG.info("TV Tunes custom path is enabled and set.") else: dialog("ok", heading="{emby}", line1=_(33152)) return with Database('emby') as embydb: all_views = emby_db.EmbyDatabase(embydb.cursor).get_views() views = [x[0] for x in all_views if x[2] in ('movies', 'tvshows', 'mixed')] items = {} server = TheVoid('GetServerAddress', {'ServerId': None}).get() token = TheVoid('GetToken', {'ServerId': None}).get() for view in views: result = TheVoid('GetThemes', {'Type': "Video", 'Id': view}).get() for item in result['Items']: folder = normalize_string(item['Name'].encode('utf-8')) items[item['Id']] = folder result = TheVoid('GetThemes', {'Type': "Song", 'Id': view}).get() for item in result['Items']: folder = normalize_string(item['Name'].encode('utf-8')) items[item['Id']] = folder for item in items: nfo_path = os.path.join(library, items[item]) nfo_file = os.path.join(nfo_path, "tvtunes.nfo") if not xbmcvfs.exists(nfo_path): xbmcvfs.mkdir(nfo_path) themes = TheVoid('GetTheme', {'Id': item}).get() paths = [] for theme in themes['ThemeVideosResult']['Items'] + themes['ThemeSongsResult']['Items']: putils = PlayUtils(theme, False, None, server, token) if play: paths.append(putils.direct_play(theme['MediaSources'][0]).encode('utf-8')) else: paths.append(putils.direct_url(theme['MediaSources'][0]).encode('utf-8')) tvtunes_nfo(nfo_file, paths) dialog("notification", heading="{emby}", message=_(33153), icon="{emby}", time=1000, sound=False)
def get_next_episodes(item_id, limit): ''' Only for synced content. ''' with Database('emby') as embydb: db = emby_db.EmbyDatabase(embydb.cursor) library = db.get_view_name(item_id) if not library: return result = JSONRPC('VideoLibrary.GetTVShows').execute({ 'sort': {'order': "descending", 'method': "lastplayed"}, 'filter': { 'and': [ {'operator': "true", 'field': "inprogress", 'value': ""}, {'operator': "is", 'field': "tag", 'value': "%s" % library} ]}, 'properties': ['title', 'studio', 'mpaa', 'file', 'art'] }) try: items = result['result']['tvshows'] except (KeyError, TypeError): return list_li = [] for item in items: if settings('ignoreSpecialsNextEpisodes.bool'): params = { 'tvshowid': item['tvshowid'], 'sort': {'method': "episode"}, 'filter': { 'and': [ {'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "greaterthan", 'field': "season", 'value': "0"} ]}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], 'limits': {"end": 1} } else: params = { 'tvshowid': item['tvshowid'], 'sort': {'method': "episode"}, 'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], 'limits': {"end": 1} } result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) try: episodes = result['result']['episodes'] except (KeyError, TypeError): pass else: for episode in episodes: li = create_listitem(episode) list_li.append((episode['file'], li)) if len(list_li) == limit: break xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li)) xbmcplugin.setContent(int(sys.argv[1]), 'episodes') xbmcplugin.endOfDirectory(int(sys.argv[1]))
def add_user(permanent=False): ''' Add or remove users from the default server session. permanent=True from the add-on settings. ''' if not window('emby_online.bool'): return session = TheVoid('GetSession', {}).get() users = TheVoid('GetUsers', {'IsDisabled': False, 'IsHidden': False}).get() for user in users: if user['Id'] == session[0]['UserId']: users.remove(user) break while True: session = TheVoid('GetSession', {}).get() additional = current = session[0]['AdditionalUsers'] add_session = True if permanent: perm_users = settings('addUsers').split(',') if settings('addUsers') else [] current = [] for user in users: for perm_user in perm_users: if user['Id'] == perm_user: current.append({'UserName': user['Name'], 'UserId': user['Id']}) result = dialog("select", _(33061), [_(33062), _(33063)] if current else [_(33062)]) if result < 0: break if not result: # Add user eligible = [x for x in users if x['Id'] not in [current_user['UserId'] for current_user in current]] resp = dialog("select", _(33064), [x['Name'] for x in eligible]) if resp < 0: break user = eligible[resp] if permanent: perm_users.append(user['Id']) settings('addUsers', ','.join(perm_users)) if user['Id'] in [current_user['UserId'] for current_user in additional]: add_session = False if add_session: event('AddUser', {'Id': user['Id'], 'Add': True}) dialog("notification", heading="{emby}", message="%s %s" % (_(33067), user['Name']), icon="{emby}", time=1000, sound=False) else: # Remove user resp = dialog("select", _(33064), [x['UserName'] for x in current]) if resp < 0: break user = current[resp] if permanent: perm_users.remove(user['UserId']) settings('addUsers', ','.join(perm_users)) if add_session: event('AddUser', {'Id': user['UserId'], 'Add': False}) dialog("notification", heading="{emby}", message="%s %s" % (_(33066), user['UserName']), icon="{emby}", time=1000, sound=False)
def set_web_server(self): ''' Enable the webserver if not enabled. This is used to cache artwork. Will only test once, if it fails, user will be notified only once. ''' if settings('enableTextureCache.bool'): get_setting = JSONRPC('Settings.GetSettingValue') if not self.get_web_server(): set_setting = JSONRPC('Settings.SetSetingValue') set_setting.execute({ 'setting': "services.webserverport", 'value': 8080 }) set_setting.execute({ 'setting': "services.webserver", 'value': True }) if not self.get_web_server(): settings('enableTextureCache.bool', False) dialog("ok", heading="{jellyfin}", line1=_(33103)) return result = get_setting.execute({'setting': "services.webserverport"}) settings('webServerPort', str(result['result']['value'] or "")) result = get_setting.execute( {'setting': "services.webserverusername"}) settings('webServerUser', str(result['result']['value'] or "")) result = get_setting.execute( {'setting': "services.webserverpassword"}) settings('webServerPass', str(result['result']['value'] or "")) settings('useWebServer.bool', True)
def listing(): ''' Display all emby nodes and dynamic entries when appropriate. ''' total = int(window('Emby.nodes.total') or 0) sync = get_sync() whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']] servers = get_credentials()['Servers'][1:] for i in range(total): window_prop = "Emby.nodes.%s" % i path = window('%s.index' % window_prop) if not path: path = window('%s.content' % window_prop) or window('%s.path' % window_prop) label = window('%s.title' % window_prop) node = window('%s.type' % window_prop) artwork = window('%s.artwork' % window_prop) view_id = window('%s.id' % window_prop) context = [] if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed') and view_id not in whitelist: label = "%s %s" % (label.decode('utf-8'), _(33166)) context.append((_(33123), "RunPlugin(plugin://plugin.video.emby/?mode=synclib&id=%s)" % view_id)) if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id in whitelist: context.append((_(33136), "RunPlugin(plugin://plugin.video.emby/?mode=updatelib&id=%s)" % view_id)) context.append((_(33132), "RunPlugin(plugin://plugin.video.emby/?mode=repairlib&id=%s)" % view_id)) context.append((_(33133), "RunPlugin(plugin://plugin.video.emby/?mode=removelib&id=%s)" % view_id)) LOG.debug("--[ listing/%s/%s ] %s", node, label, path) if path: if xbmc.getCondVisibility('Window.IsActive(Pictures)') and node in ('photos', 'homevideos'): directory(label, path, artwork=artwork) elif xbmc.getCondVisibility('Window.IsActive(Videos)') and node not in ('photos', 'music', 'audiobooks'): directory(label, path, artwork=artwork, context=context) elif xbmc.getCondVisibility('Window.IsActive(Music)') and node in ('music'): directory(label, path, artwork=artwork, context=context) elif not xbmc.getCondVisibility('Window.IsActive(Videos) | Window.IsActive(Pictures) | Window.IsActive(Music)'): directory(label, path, artwork=artwork) for server in servers: context = [] if server.get('ManualAddress'): context.append((_(33141), "RunPlugin(plugin://plugin.video.emby/?mode=removeserver&server=%s)" % server['Id'])) if 'AccessToken' not in server: directory("%s (%s)" % (server['Name'], _(30539)), "plugin://plugin.video.emby/?mode=login&server=%s" % server['Id'], False, context=context) else: directory(server['Name'], "plugin://plugin.video.emby/?mode=browse&server=%s" % server['Id'], context=context) directory(_(33194), "plugin://plugin.video.emby/?mode=managelibs", True) directory(_(33134), "plugin://plugin.video.emby/?mode=addserver", False) directory(_(33054), "plugin://plugin.video.emby/?mode=adduser", False) directory(_(5), "plugin://plugin.video.emby/?mode=settings", False) directory(_(33059), "plugin://plugin.video.emby/?mode=texturecache", False) directory(_(33058), "plugin://plugin.video.emby/?mode=reset", False) directory(_(33192), "plugin://plugin.video.emby/?mode=restartservice", False) if settings('backupPath'): directory(_(33092), "plugin://plugin.video.emby/?mode=backup", False) directory(_(33163), None, False, artwork="special://home/addons/plugin.video.emby/donations.png") xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.endOfDirectory(int(sys.argv[1]))
def listing(): ''' Display all jellyfin nodes and dynamic entries when appropriate. ''' total = int(window('Jellyfin.nodes.total') or 0) sync = get_sync() whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']] servers = get_credentials()['Servers'][1:] for i in range(total): window_prop = "Jellyfin.nodes.%s" % i path = window('%s.index' % window_prop) if not path: path = window('%s.content' % window_prop) or window( '%s.path' % window_prop) label = window('%s.title' % window_prop) node = window('%s.type' % window_prop) artwork = window('%s.artwork' % window_prop) view_id = window('%s.id' % window_prop) context = [] if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed') and view_id not in whitelist: label = "%s %s" % (label, translate(33166)) context.append(( translate(33123), "RunPlugin(plugin://plugin.video.jellyfin/?mode=synclib&id=%s)" % view_id)) if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id in whitelist: context.append((translate( 33136 ), "RunPlugin(plugin://plugin.video.jellyfin/?mode=updatelib&id=%s)" % view_id)) context.append((translate( 33132 ), "RunPlugin(plugin://plugin.video.jellyfin/?mode=repairlib&id=%s)" % view_id)) context.append((translate( 33133 ), "RunPlugin(plugin://plugin.video.jellyfin/?mode=removelib&id=%s)" % view_id)) LOG.debug("--[ listing/%s/%s ] %s", node, label, path) if path: directory(label, path, artwork=artwork, context=context) for server in servers: context = [] if server.get('ManualAddress'): context.append((translate( 33141 ), "RunPlugin(plugin://plugin.video.jellyfin/?mode=removeserver&server=%s)" % server['Id'])) if 'AccessToken' not in server: directory("%s (%s)" % (server['Name'], translate(30539)), "plugin://plugin.video.jellyfin/?mode=login&server=%s" % server['Id'], False, context=context) else: directory(server['Name'], "plugin://plugin.video.jellyfin/?mode=browse&server=%s" % server['Id'], context=context) directory(translate(33194), "plugin://plugin.video.jellyfin/?mode=managelibs", True) directory(translate(33134), "plugin://plugin.video.jellyfin/?mode=addserver", False) directory(translate(33054), "plugin://plugin.video.jellyfin/?mode=adduser", False) directory(translate(5), "plugin://plugin.video.jellyfin/?mode=settings", False) directory(translate(33161), "plugin://plugin.video.jellyfin/?mode=updatepassword", False) directory(translate(33058), "plugin://plugin.video.jellyfin/?mode=reset", False) directory(translate(33180), "plugin://plugin.video.jellyfin/?mode=restartservice", False) if settings('backupPath'): directory(translate(33092), "plugin://plugin.video.jellyfin/?mode=backup", False) xbmcplugin.setContent(PROCESS_HANDLE, 'files') xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def _is_music(self): value = dialog("yesno", heading="{jellyfin}", line1=_(33039)) settings('enableMusic.bool', value=value)
def __init__(self): window('jellyfin_should_stop', clear=True) self.settings['addon_version'] = client.get_version() self.settings['profile'] = xbmc.translatePath('special://profile') self.settings['mode'] = settings('useDirectPaths') self.settings['log_level'] = settings('logLevel') or "1" self.settings['auth_check'] = True self.settings['enable_context'] = settings('enableContext.bool') self.settings['enable_context_transcode'] = settings( 'enableContextTranscode.bool') self.settings['kodi_companion'] = settings('kodiCompanion.bool') window('jellyfin_logLevel', value=str(self.settings['log_level'])) window('jellyfin_kodiProfile', value=self.settings['profile']) settings('platformDetected', client.get_platform()) if self.settings['enable_context']: window('jellyfin_context.bool', True) if self.settings['enable_context_transcode']: window('jellyfin_context_transcode.bool', True) LOG.info("--->>>[ %s ]", client.get_addon_name()) LOG.info("Version: %s", client.get_version()) LOG.info("KODI Version: %s", xbmc.getInfoLabel('System.BuildVersion')) LOG.info("Platform: %s", settings('platformDetected')) LOG.info("Python Version: %s", sys.version) LOG.info("Using dynamic paths: %s", settings('useDirectPaths') == "0") LOG.info("Log Level: %s", self.settings['log_level']) verify_kodi_defaults() try: Views().get_nodes() except Exception as error: LOG.exception(error) window('jellyfin.connected.bool', True) settings('groupedSets.bool', objects.utils.get_grouped_set()) xbmc.Monitor.__init__(self)
def onNotification(self, sender, method, data): ''' All notifications are sent via NotifyAll built-in or Kodi. Central hub. ''' if sender.lower() not in ('plugin.video.jellyfin', 'xbmc'): return if sender == 'plugin.video.jellyfin': method = method.split('.')[1] if method not in ('ServerUnreachable', 'ServerShuttingDown', 'UserDataChanged', 'ServerConnect', 'LibraryChanged', 'ServerOnline', 'SyncLibrary', 'RepairLibrary', 'RemoveLibrary', 'SyncLibrarySelection', 'RepairLibrarySelection', 'AddServer', 'Unauthorized', 'UpdateServer', 'UserConfigurationUpdated', 'ServerRestarting', 'RemoveServer', 'AddLibrarySelection', 'RemoveLibrarySelection'): return data = json.loads(data)[0] else: if method not in ('System.OnQuit', 'System.OnSleep', 'System.OnWake'): return data = json.loads(data) LOG.debug("[ %s: %s ] %s", sender, method, json.dumps(data, indent=4)) if method == 'ServerOnline': if data.get('ServerId') is None: window('jellyfin_online.bool', True) self.settings['auth_check'] = True self.warn = True if settings('connectMsg.bool'): users = [ user for user in ( settings('additionalUsers') or "").split(',') if user ] users.insert(0, settings('username')) dialog("notification", heading="{jellyfin}", message="%s %s" % (translate(33000), ", ".join(users)), icon="{jellyfin}", time=1500, sound=False) if self.library_thread is None: self.library_thread = library.Library(self) self.library_thread.start() elif method in ('ServerUnreachable', 'ServerShuttingDown'): if self.warn or data.get('ServerId'): self.warn = data.get('ServerId') is not None dialog("notification", heading="{jellyfin}", message=translate(33146) if data.get('ServerId') is None else translate(33149), icon=xbmcgui.NOTIFICATION_ERROR) if data.get('ServerId') is None: self.stop_default() if self.waitForAbort(120): return self.start_default() elif method == 'Unauthorized': dialog("notification", heading="{jellyfin}", message=translate(33147) if data['ServerId'] is None else translate(33148), icon=xbmcgui.NOTIFICATION_ERROR) if data.get('ServerId') is None and self.settings['auth_check']: self.settings['auth_check'] = False self.stop_default() if self.waitForAbort(5): return self.start_default() elif method == 'ServerRestarting': if data.get('ServerId'): return if settings('restartMsg.bool'): dialog("notification", heading="{jellyfin}", message=translate(33006), icon="{jellyfin}") self.stop_default() if self.waitForAbort(15): return self.start_default() elif method == 'ServerConnect': self.connect.register(data['Id']) xbmc.executebuiltin("Container.Refresh") elif method == 'AddServer': self.connect.setup_manual_server() xbmc.executebuiltin("Container.Refresh") elif method == 'RemoveServer': self.connect.remove_server(data['Id']) xbmc.executebuiltin("Container.Refresh") elif method == 'UpdateServer': dialog("ok", heading="{jellyfin}", line1=translate(33151)) self.connect.setup_manual_server() elif method == 'UserDataChanged' and self.library_thread: if data.get('ServerId') or not window('jellyfin_startup.bool'): return LOG.info("[ UserDataChanged ] %s", data) self.library_thread.userdata(data['UserDataList']) elif method == 'LibraryChanged' and self.library_thread: if data.get('ServerId') or not window('jellyfin_startup.bool'): return LOG.info("[ LibraryChanged ] %s", data) self.library_thread.updated(data['ItemsUpdated'] + data['ItemsAdded']) self.library_thread.removed(data['ItemsRemoved']) elif method == 'System.OnQuit': window('jellyfin_should_stop.bool', True) self.running = False elif method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'): self.library_thread.select_libraries(method) elif method == 'SyncLibrary': if not data.get('Id'): return self.library_thread.add_library(data['Id'], data.get('Update', False)) xbmc.executebuiltin("Container.Refresh") elif method == 'RepairLibrary': if not data.get('Id'): return libraries = data['Id'].split(',') for lib in libraries: if not self.library_thread.remove_library(lib): return self.library_thread.add_library(data['Id']) xbmc.executebuiltin("Container.Refresh") elif method == 'RemoveLibrary': libraries = data['Id'].split(',') for lib in libraries: if not self.library_thread.remove_library(lib): return xbmc.executebuiltin("Container.Refresh") elif method == 'System.OnSleep': LOG.info("-->[ sleep ]") window('jellyfin_should_stop.bool', True) if self.library_thread is not None: self.library_thread.stop_client() self.library_thread = None Jellyfin.close_all() self.monitor.server = [] self.monitor.sleep = True elif method == 'System.OnWake': if not self.monitor.sleep: LOG.warning( "System.OnSleep was never called, skip System.OnWake") return LOG.info("--<[ sleep ]") xbmc.sleep(10000) # Allow network to wake up self.monitor.sleep = False window('jellyfin_should_stop', clear=True) try: self.connect.register() except Exception as error: LOG.exception(error) elif method == 'GUI.OnScreensaverDeactivated': LOG.info("--<[ screensaver ]") xbmc.sleep(5000) if self.library_thread is not None: self.library_thread.fast_sync() elif method == 'UserConfigurationUpdated': if data.get('ServerId') is None: Views().get_views()
def movie(self, item, e_item, library): ''' If item does not exist, entry will be added. If item exists, entry will be updated. ''' server_data = self.server.auth.get_server_info( self.server.auth.server_id) server_address = self.server.auth.get_server_address( server_data, server_data['LastConnectionMode']) API = api.API(item, server_address) obj = self.objects.map(item, 'Movie') update = True try: obj['MovieId'] = e_item[0] obj['FileId'] = e_item[1] obj['PathId'] = e_item[2] except TypeError: update = False LOG.debug("MovieId %s not found", obj['Id']) obj['MovieId'] = self.create_entry() else: if self.get(*values(obj, QU.get_movie_obj)) is None: update = False LOG.info("MovieId %s missing from kodi. repairing the entry.", obj['MovieId']) if not settings('syncRottenTomatoes.bool'): obj['CriticRating'] = None obj['Path'] = API.get_file_path(obj['Path']) obj['LibraryId'] = library['Id'] obj['LibraryName'] = library['Name'] obj['Genres'] = obj['Genres'] or [] obj['Studios'] = [ API.validate_studio(studio) for studio in (obj['Studios'] or []) ] obj['People'] = obj['People'] or [] obj['Genre'] = " / ".join(obj['Genres']) obj['Writers'] = " / ".join(obj['Writers'] or []) obj['Directors'] = " / ".join(obj['Directors'] or []) obj['Plot'] = API.get_overview(obj['Plot']) obj['Mpaa'] = API.get_mpaa(obj['Mpaa']) obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) obj['People'] = API.get_people_artwork(obj['People']) obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace( 'T', " ") obj['DatePlayed'] = None if not obj['DatePlayed'] else Local( obj['DatePlayed']).split('.')[0].replace('T', " ") obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container']) obj['Audio'] = API.audio_streams(obj['Audio'] or []) obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles']) self.get_path_filename(obj) self.trailer(obj) if obj['Countries']: self.add_countries(*values(obj, QU.update_country_obj)) tags = [] tags.extend(obj['Tags'] or []) tags.append(obj['LibraryName']) if obj['Favorite']: tags.append('Favorite movies') obj['Tags'] = tags if update: self.movie_update(obj) else: self.movie_add(obj) self.update_path(*values(obj, QU.update_path_movie_obj)) self.update_file(*values(obj, QU.update_file_obj)) self.add_tags(*values(obj, QU.add_tags_movie_obj)) self.add_genres(*values(obj, QU.add_genres_movie_obj)) self.add_studios(*values(obj, QU.add_studios_movie_obj)) self.add_playstate(*values(obj, QU.add_bookmark_obj)) self.add_people(*values(obj, QU.add_people_movie_obj)) self.add_streams(*values(obj, QU.add_streams_obj)) self.artwork.add(obj['Artwork'], obj['MovieId'], "movie") self.item_ids.append(obj['Id']) return not update
def tvshow(self, item, e_item, library): ''' If item does not exist, entry will be added. If item exists, entry will be updated. If the show is empty, try to remove it. Process seasons. Apply series pooling. ''' API = api.API(item, self.server['auth/server-address']) obj = self.objects.map(item, 'Series') update = True if not settings('syncEmptyShows.bool') and not obj['RecursiveCount']: LOG.info("Skipping empty show %s: %s", obj['Title'], obj['Id']) self.remove(obj['Id']) return False try: obj['ShowId'] = e_item[0] obj['PathId'] = e_item[2] except TypeError as error: update = False LOG.debug("ShowId %s not found", obj['Id']) obj['ShowId'] = self.create_entry() else: if self.get(*values(obj, QU.get_tvshow_obj)) is None: update = False LOG.info("ShowId %s missing from kodi. repairing the entry.", obj['ShowId']) obj['Path'] = API.get_file_path(obj['Path']) obj['LibraryId'] = library['Id'] obj['LibraryName'] = library['Name'] obj['Genres'] = obj['Genres'] or [] obj['People'] = obj['People'] or [] obj['Mpaa'] = API.get_mpaa(obj['Mpaa']) obj['Studios'] = [ API.validate_studio(studio) for studio in (obj['Studios'] or []) ] obj['Genre'] = " / ".join(obj['Genres']) obj['People'] = API.get_people_artwork(obj['People']) obj['Plot'] = API.get_overview(obj['Plot']) obj['Studio'] = " / ".join(obj['Studios']) obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) self.get_path_filename(obj) if obj['Premiere']: obj['Premiere'] = str(obj['Premiere']).split('.')[0].replace( 'T', " ") tags = [] tags.extend(obj['Tags'] or []) tags.append(obj['LibraryName']) if obj['Favorite']: tags.append('Favorite tvshows') obj['Tags'] = tags if update: self.tvshow_update(obj) else: self.tvshow_add(obj) self.link(*values(obj, QU.update_tvshow_link_obj)) self.update_path(*values(obj, QU.update_path_tvshow_obj)) self.add_tags(*values(obj, QU.add_tags_tvshow_obj)) self.add_people(*values(obj, QU.add_people_tvshow_obj)) self.add_genres(*values(obj, QU.add_genres_tvshow_obj)) self.add_studios(*values(obj, QU.add_studios_tvshow_obj)) self.artwork.add(obj['Artwork'], obj['ShowId'], "tvshow") self.item_ids.append(obj['Id']) season_episodes = {} for season in self.server['api'].get_seasons(obj['Id'])['Items']: if season['SeriesId'] != obj['Id']: obj['SeriesId'] = season['SeriesId'] self.item_ids.append(season['SeriesId']) try: self.emby_db.get_item_by_id( *values(obj, QUEM.get_item_series_obj))[0] if self.update_library: season_episodes[season['Id']] = season['SeriesId'] except TypeError: self.emby_db.add_reference( *values(obj, QUEM.add_reference_pool_obj)) LOG.info("POOL %s [%s/%s]", obj['Title'], obj['Id'], obj['SeriesId']) season_episodes[season['Id']] = season['SeriesId'] try: self.emby_db.get_item_by_id(season['Id'])[0] self.item_ids.append(season['Id']) except TypeError: self.season(season, obj['ShowId']) else: season_id = self.get_season( *values(obj, QU.get_season_special_obj)) self.artwork.add(obj['Artwork'], season_id, "season") for season in season_episodes: for episodes in server.get_episode_by_season( season_episodes[season], season): for episode in episodes['Items']: self.episode(episode)
def get_themes(api_client): ''' Add theme media locally, via strm. This is only for tv tunes. If another script is used, adjust this code. ''' from helper.utils import normalize_string from helper.playutils import PlayUtils from helper.xmls import tvtunes_nfo library = xbmc.translatePath( "special://profile/addon_data/plugin.video.jellyfin/library") play = settings('useDirectPaths') == "1" if not xbmcvfs.exists(library + '/'): xbmcvfs.mkdir(library) if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) LOG.info("TV Tunes custom path is enabled and set.") else: dialog("ok", "{jellyfin}", translate(33152)) return with Database('jellyfin') as jellyfindb: all_views = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views() views = [ x.view_id for x in all_views if x.media_type in ('movies', 'tvshows', 'mixed') ] items = {} server = api_client.config.data['auth.server'] for view in views: result = api_client.get_items_theme_video(view) for item in result['Items']: folder = normalize_string(item['Name']) items[item['Id']] = folder result = api_client.get_items_theme_song(view) for item in result['Items']: folder = normalize_string(item['Name']) items[item['Id']] = folder for item in items: nfo_path = os.path.join(library, items[item]) nfo_file = os.path.join(nfo_path, "tvtunes.nfo") if not xbmcvfs.exists(nfo_path): xbmcvfs.mkdir(nfo_path) themes = api_client.get_themes(item) paths = [] for theme in themes['ThemeVideosResult']['Items'] + themes[ 'ThemeSongsResult']['Items']: putils = PlayUtils(theme, False, None, server, api_client) if play: paths.append(putils.direct_play(theme['MediaSources'][0])) else: paths.append(putils.direct_url(theme['MediaSources'][0])) tvtunes_nfo(nfo_file, paths) dialog("notification", heading="{jellyfin}", message=translate(33153), icon="{jellyfin}", time=1000, sound=False)
def onSettingsChanged(self): ''' React to setting changes that impact window values. ''' if window('jellyfin_should_stop.bool'): return if settings('logLevel') != self.settings['log_level']: log_level = settings('logLevel') window('jellyfin_logLevel', str(log_level)) self.settings['logLevel'] = log_level LOG.info("New log level: %s", log_level) if settings('enableContext.bool') != self.settings['enable_context']: window('jellyfin_context', settings('enableContext')) self.settings['enable_context'] = settings('enableContext.bool') LOG.info("New context setting: %s", self.settings['enable_context']) if settings('enableContextTranscode.bool' ) != self.settings['enable_context_transcode']: window('jellyfin_context_transcode', settings('enableContextTranscode')) self.settings['enable_context_transcode'] = settings( 'enableContextTranscode.bool') LOG.info("New context transcode setting: %s", self.settings['enable_context_transcode']) if settings('useDirectPaths' ) != self.settings['mode'] and self.library_thread.started: self.settings['mode'] = settings('useDirectPaths') LOG.info("New playback mode setting: %s", self.settings['mode']) if not self.settings.get('mode_warn'): self.settings['mode_warn'] = True dialog("yesno", heading="{jellyfin}", line1=translate(33118)) if settings('kodiCompanion.bool') != self.settings['kodi_companion']: self.settings['kodi_companion'] = settings('kodiCompanion.bool') if not self.settings['kodi_companion']: dialog("ok", heading="{jellyfin}", line1=translate(33138))
def _is_empty_shows(self): value = dialog("yesno", heading="{jellyfin}", line1=_(33100)) settings('syncEmptyShows.bool', value)
sys.path.insert(0, __cache__) sys.path.insert(0, __pcache__) sys.path.append(__base__) ################################################################################################# from entrypoint import Service from helper import settings from emby import Emby ################################################################################################# LOG = logging.getLogger("EMBY.service") DELAY = int( settings('startupDelay') if settings('SyncInstallRunDone.bool') else 4 or 0 ) ################################################################################################# class ServiceManager(threading.Thread): ''' Service thread. To allow to restart and reload modules internally. ''' exception = None def __init__(self): threading.Thread.__init__(self) def run(self):
def _is_rotten_tomatoes(self): value = dialog("yesno", heading="{jellyfin}", line1=_(33188)) settings('syncRottenTomatoes.bool', value)
def general_commands(self, data): ''' General commands from Jellyfin to control the Kodi interface. ''' command = data['Name'] args = data['Arguments'] if command in ('Mute', 'Unmute', 'SetVolume', 'SetSubtitleStreamIndex', 'SetAudioStreamIndex', 'SetRepeatMode'): if command == 'Mute': xbmc.executebuiltin('Mute') elif command == 'Unmute': xbmc.executebuiltin('Mute') elif command == 'SetVolume': xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % args['Volume']) elif command == 'SetRepeatMode': xbmc.executebuiltin('xbmc.PlayerControl(%s)' % args['RepeatMode']) elif command == 'SetAudioStreamIndex': self.player.set_audio_subs(args['Index']) elif command == 'SetSubtitleStreamIndex': self.player.set_audio_subs(None, args['Index']) self.player.report_playback() elif command == 'DisplayMessage': dialog("notification", heading=args['Header'], message=args['Text'], icon="{jellyfin}", time=int(settings('displayMessage')) * 1000) elif command == 'SendString': JSONRPC('Input.SendText').execute({ 'text': args['String'], 'done': False }) elif command == 'GoHome': JSONRPC('GUI.ActivateWindow').execute({'window': "home"}) elif command == 'Guide': JSONRPC('GUI.ActivateWindow').execute({'window': "tvguide"}) elif command in ('MoveUp', 'MoveDown', 'MoveRight', 'MoveLeft'): actions = { 'MoveUp': "Input.Up", 'MoveDown': "Input.Down", 'MoveRight': "Input.Right", 'MoveLeft': "Input.Left" } JSONRPC(actions[command]).execute() else: builtin = { 'ToggleFullscreen': 'Action(FullScreen)', 'ToggleOsdMenu': 'Action(OSD)', 'ToggleContextMenu': 'Action(ContextMenu)', 'Select': 'Action(Select)', 'Back': 'Action(back)', 'PageUp': 'Action(PageUp)', 'NextLetter': 'Action(NextLetter)', 'GoToSearch': 'VideoLibrary.Search', 'GoToSettings': 'ActivateWindow(Settings)', 'PageDown': 'Action(PageDown)', 'PreviousLetter': 'Action(PrevLetter)', 'TakeScreenshot': 'TakeScreenshot', 'ToggleMute': 'Mute', 'VolumeUp': 'Action(VolumeUp)', 'VolumeDown': 'Action(VolumeDown)', } if command in builtin: xbmc.executebuiltin(builtin[command])
def _is_artwork_caching(self): value = dialog("yesno", heading="{jellyfin}", line1=_(33117)) settings('enableTextureCache.bool', value)