def window_nodes(self): ''' Just read from the database and populate based on SortedViews Setup the window properties that reflect the jellyfin server views and more. ''' self.window_clear() self.window_clear('Jellyfin.wnodes') with Database('jellyfin') as jellyfindb: libraries = jellyfin_db.JellyfinDatabase( jellyfindb.cursor).get_views() libraries = self.order_media_folders(libraries or []) index = 0 windex = 0 try: self.media_folders = self.get_libraries() except IndexError as error: LOG.exception(error) for library in (libraries or []): view = { 'Id': library[0], 'Name': library[1], 'Tag': library[1], 'Media': library[2] } if library[0] in [ x.replace('Mixed:', "") for x in self.sync['Whitelist'] ]: # Synced libraries if view['Media'] in ('movies', 'tvshows', 'musicvideos', 'mixed'): if view['Media'] == 'mixed': for media in ('movies', 'tvshows'): for node in NODES[media]: temp_view = dict(view) temp_view['Media'] = media temp_view['Name'] = "%s (%s)" % (view['Name'], _(media)) self.window_node(index, temp_view, *node) self.window_wnode(windex, temp_view, *node) else: # Add one to compensate for the duplicate. index += 1 windex += 1 else: for node in NODES[view['Media']]: self.window_node(index, view, *node) if view['Media'] in ('movies', 'tvshows'): self.window_wnode(windex, view, *node) if view['Media'] in ('movies', 'tvshows'): windex += 1 elif view['Media'] == 'music': self.window_node(index, view, 'music') else: # Dynamic entry if view['Media'] in ('homevideos', 'books', 'playlists'): self.window_wnode(windex, view, 'browse') windex += 1 self.window_node(index, view, 'browse') index += 1 for single in [{ 'Name': _('fav_movies'), 'Tag': "Favorite movies", 'Media': "movies" }, { 'Name': _('fav_tvshows'), 'Tag': "Favorite tvshows", 'Media': "tvshows" }, { 'Name': _('fav_episodes'), 'Tag': "Favorite episodes", 'Media': "episodes" }]: self.window_single_node(index, "favorites", single) index += 1 window('Jellyfin.nodes.total', str(index)) window('Jellyfin.wnodes.total', str(windex))
import xml.etree.ElementTree as etree import xbmc import xbmcvfs import downloader as server from database import Database, jellyfin_db, get_sync, save_sync from objects.kodi import kodi from helper import _, api, indent, write_xml, window, event from jellyfin import Jellyfin ################################################################################################# LOG = logging.getLogger("JELLYFIN." + __name__) NODES = { 'tvshows': [('all', None), ('recent', _(30170)), ('recentepisodes', _(30175)), ('inprogress', _(30171)), ('inprogressepisodes', _(30178)), ('nextepisodes', _(30179)), ('genres', 135), ('random', _(30229)), ('recommended', _(30230))], 'movies': [('all', None), ('recent', _(30174)), ('inprogress', _(30177)), ('unwatched', _(30189)), ('sets', 20434), ('genres', 135), ('random', _(30229)), ('recommended', _(30230))], 'musicvideos': [('all', None), ('recent', _(30256)), ('inprogress', _(30257)), ('unwatched', _(30258))] } DYNNODES = { 'tvshows': [('all', None), ('RecentlyAdded', _(30170)), ('recentepisodes', _(30175)), ('InProgress', _(30171)), ('inprogressepisodes', _(30178)), ('nextepisodes', _(30179)), ('Genres', _(135)), ('Random', _(30229)),
def get_nodes(self): ''' Set up playlists, video nodes, window prop. ''' node_path = xbmc.translatePath( "special://profile/library/video").decode('utf-8') playlist_path = xbmc.translatePath( "special://profile/playlists/video").decode('utf-8') index = 0 with Database('jellyfin') as jellyfindb: db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) for library in self.sync['Whitelist']: library = library.replace('Mixed:', "") view = db.get_view(library) if view: view = { 'Id': library, 'Name': view[0], 'Tag': view[0], 'Media': view[1] } if view['Media'] == 'mixed': for media in ('movies', 'tvshows'): temp_view = dict(view) temp_view['Media'] = media self.add_playlist(playlist_path, temp_view, True) self.add_nodes(node_path, temp_view, True) else: # Compensate for the duplicate. index += 1 else: if view['Media'] in ('movies', 'tvshows', 'musicvideos'): self.add_playlist(playlist_path, view) if view['Media'] not in ('music', ): self.add_nodes(node_path, view) index += 1 for single in [{ 'Name': _('fav_movies'), 'Tag': "Favorite movies", 'Media': "movies" }, { 'Name': _('fav_tvshows'), 'Tag': "Favorite tvshows", 'Media': "tvshows" }, { 'Name': _('fav_episodes'), 'Tag': "Favorite episodes", 'Media': "episodes" }]: self.add_single_node(node_path, index, "favorites", single) index += 1 self.window_nodes()
def fast_sync(self): ''' Movie and userdata not provided by server yet. ''' last_sync = settings('LastIncrementalSync') filters = ["tvshows", "boxsets", "musicvideos", "music", "movies"] sync = get_sync() LOG.info("--[ retrieve changes ] %s", last_sync) """ for library in sync['Whitelist']: data = self.server.jellyfin.get_date_modified(last_sync, library.replace('Mixed:', ""), "Series,Episode,BoxSet,Movie,MusicVideo,MusicArtist,MusicAlbum,Audio") [self.updated_output[query['Type']].put(query) for query in data['Items']] """ try: updated = [] userdata = [] removed = [] for media in filters: result = self.server.jellyfin.get_sync_queue( last_sync, ",".join([x for x in filters if x != media])) updated.extend(result['ItemsAdded']) updated.extend(result['ItemsUpdated']) userdata.extend(result['UserDataChanged']) removed.extend(result['ItemsRemoved']) total = len(updated) + len(userdata) if total > int(settings('syncIndicator') or 99): ''' Inverse yes no, in case the dialog is forced closed by Kodi. ''' if dialog("yesno", heading="{jellyfin}", line1=_(33172).replace('{number}', str(total)), nolabel=_(107), yeslabel=_(106)): LOG.warn("Large updates skipped.") return True self.updated(updated) self.userdata(userdata) self.removed(removed) """ result = self.server.jellyfin.get_sync_queue(last_sync) self.userdata(result['UserDataChanged']) self.removed(result['ItemsRemoved']) filters.extend(["tvshows", "boxsets", "musicvideos", "music"]) # Get only movies. result = self.server.jellyfin.get_sync_queue(last_sync, ",".join(filters)) self.updated(result['ItemsAdded']) self.updated(result['ItemsUpdated']) self.userdata(result['UserDataChanged']) self.removed(result['ItemsRemoved']) """ except Exception as error: LOG.exception(error) return False return True
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.emby', 'xbmc'): return if sender == 'plugin.video.emby': method = method.split('.')[1] if method not in ('ServerUnreachable', 'ServerShuttingDown', 'UserDataChanged', 'ServerConnect', 'LibraryChanged', 'ServerOnline', 'SyncLibrary', 'RepairLibrary', 'RemoveLibrary', 'EmbyConnect', 'SyncLibrarySelection', 'RepairLibrarySelection', 'AddServer', 'Unauthorized', 'UpdateServer', 'UserConfigurationUpdated', 'ServerRestarting', 'RemoveServer', 'AddLibrarySelection', 'CheckUpdate', 'RemoveLibrarySelection', 'PatchMusic', 'WebSocketRestarting', 'ResetUpdate', 'UserPolicyUpdated', 'SetServerSSL'): return data = json.loads(data)[0] else: if method not in ('System.OnQuit', 'System.OnSleep', 'System.OnWake', 'GUI.OnScreensaverDeactivated'): return data = json.loads(data) LOG.info("[ onNotification/%s/%s ]", sender, method) LOG.debug("[ %s: %s ] %s", sender, method, json.dumps(data, indent=4)) if method == 'ServerOnline': if data.get('ServerId') is None: window('emby_online.bool', True) self['auth_check'] = True self['warn'] = True if settings('connectMsg.bool'): users = Emby()['api'].get_device( window('emby_deviceId'))[0]['AdditionalUsers'] users = [user['UserName'] for user in users] users.insert(0, settings('username').decode('utf-8')) dialog("notification", heading="{emby}", message="%s %s" % (_(33000), ", ".join(users)), icon="{emby}", time=1500, sound=False) elif method in ('ServerUnreachable', 'ServerShuttingDown'): if self['warn'] or data.get('ServerId'): self['warn'] = data.get('ServerId') is not None dialog("notification", heading="{emby}", message=_(33146) if data.get('ServerId') is None else _(33149), icon=xbmcgui.NOTIFICATION_ERROR) if data.get('ServerId') is None: self._server(20) elif method == 'Unauthorized': dialog("notification", heading="{emby}", message=_(33147) if data['ServerId'] is None else _(33148), icon=xbmcgui.NOTIFICATION_ERROR) if data.get('ServerId') is None and self['auth_check']: self['auth_check'] = False self._server(120) elif method == 'ServerRestarting': if data.get('ServerId'): return if settings('restartMsg.bool'): dialog("notification", heading="{emby}", message=_(33006), icon="{emby}") self._server(20) elif method == 'ServerConnect': self['connect'].register(data['Id']) xbmc.executebuiltin("Container.Refresh") elif method == 'EmbyConnect': self['connect'].setup_login_connect() 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="{emby}", line1=_(33151)) self['connect'].setup_manual_server() elif method == 'UserDataChanged': if not self['library'] and data.get( 'ServerId') or not self['library'].started: return if data.get('UserId') != Emby()['auth/user-id']: return LOG.info("[ UserDataChanged ] %s", data) self['library'].userdata(data['UserDataList']) elif method == 'LibraryChanged' and self['library'].started: if data.get('ServerId') or not self['library'].started: return LOG.info("[ LibraryChanged ] %s", data) self['library'].updated(data['ItemsUpdated'] + data['ItemsAdded']) self['library'].removed(data['ItemsRemoved']) self['library'].delay_verify(data.get('ItemsVerify', [])) elif method == 'WebSocketRestarting': if self['library']: try: self['library'].get_fast_sync() except Exception as error: LOG.error(error) elif method == 'System.OnQuit': window('emby_should_stop.bool', True) self.running = False elif method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'): self['library'].select_libraries(method) elif method == 'SyncLibrary': if not data.get('Id'): return self['library'].add_library(data['Id'], data.get('Update', False)) elif method == 'RepairLibrary': if not data.get('Id'): return libraries = data['Id'].split(',') for lib in libraries: self['library'].remove_library(lib) self['library'].add_library(data['Id']) elif method == 'RemoveLibrary': libraries = data['Id'].split(',') for lib in libraries: self['library'].remove_library(lib) elif method == 'System.OnSleep': LOG.info("-->[ sleep ]") window('emby_should_stop.bool', True) self._server(close=True) Emby.close_all() self['monitor'].server = [] self['monitor'].sleep = True elif method == 'System.OnWake': if not self['monitor'].sleep: LOG.warn("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('emby_should_stop', clear=True) self._server() elif method == 'GUI.OnScreensaverDeactivated': LOG.info("--<[ screensaver ]") xbmc.sleep(5000) if self['library'] is not None: self['library'].get_fast_sync() elif method in ('UserConfigurationUpdated', 'UserPolicyUpdated'): if data.get('ServerId') is None: Views().get_views() elif method == 'CheckUpdate': if not PATCH.check_update(True): dialog("notification", heading="{emby}", message=_(21341), icon="{emby}", sound=False) else: dialog("notification", heading="{emby}", message=_(33181), icon="{emby}", sound=False) window('emby.restart.bool', True) elif method == 'ResetUpdate': PATCH.reset() elif method == 'PatchMusic': self['library'].run_library_task(method, data.get('Notification', True)) elif method == 'SetServerSSL': self['connect'].set_ssl(data['Id'])
def service(self): ''' If error is encountered, it will rerun this function. Start new "daemon threads" to process library updates. (actual daemon thread is not supported in Kodi) ''' for threads in (self.download_threads, self.writer_threads['updated'], self.writer_threads['userdata'], self.writer_threads['removed']): for thread in threads: if thread.is_done: threads.remove(thread) if not self.player.isPlayingVideo() or settings( 'syncDuringPlay.bool') or xbmc.getCondVisibility( 'VideoPlayer.Content(livetv)'): self.worker_downloads() self.worker_sort() self.worker_updates() self.worker_userdata() self.worker_remove() self.worker_notify() if self.pending_refresh: window('jellyfin_sync.bool', True) if self.total_updates > self.progress_display: queue_size = self.worker_queue_size() if self.progress_updates is None: self.progress_updates = xbmcgui.DialogProgressBG() self.progress_updates.create(_('addon_name'), _(33178)) self.progress_updates.update( int((float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message="%s: %s" % (_(33178), queue_size)) elif queue_size: self.progress_updates.update( int((float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message="%s: %s" % (_(33178), queue_size)) else: self.progress_updates.update(int( (float(self.total_updates - queue_size) / float(self.total_updates)) * 100), message=_(33178)) if not settings( 'dbSyncScreensaver.bool') and self.screensaver is None: xbmc.executebuiltin('InhibitIdleShutdown(true)') self.screensaver = get_screensaver() set_screensaver(value="") if (self.pending_refresh and not self.download_threads and not self.writer_threads['updated'] and not self.writer_threads['userdata'] and not self.writer_threads['removed']): self.pending_refresh = False self.save_last_sync() self.total_updates = 0 window('jellyfin_sync', clear=True) if self.progress_updates: self.progress_updates.close() self.progress_updates = None if not settings( 'dbSyncScreensaver.bool') and self.screensaver is not None: xbmc.executebuiltin('InhibitIdleShutdown(false)') set_screensaver(value=self.screensaver) self.screensaver = None if xbmc.getCondVisibility('Container.Content(musicvideos)' ): # Prevent cursor from moving xbmc.executebuiltin('Container.Refresh') else: # Update widgets xbmc.executebuiltin('UpdateLibrary(video)') if xbmc.getCondVisibility('Window.IsMedia'): xbmc.executebuiltin('Container.Refresh')
def startup(self): ''' Run at startup. Check databases. Check for the server plugin. ''' self.test_databases() Views().get_views() Views().get_nodes() try: if get_sync()['Libraries']: try: with FullSync(self, self.server) as sync: sync.libraries() Views().get_nodes() except Exception as error: LOG.exception(error) elif not settings('SyncInstallRunDone.bool'): with FullSync(self, self.server) as sync: sync.libraries() Views().get_nodes() return True if settings('SyncInstallRunDone.bool'): if settings('kodiCompanion.bool'): for plugin in self.server.jellyfin.get_plugins(): if plugin['Name'] in ("Jellyfin.Kodi Sync Queue", "Kodi companion", "Kodi Sync Queue"): if not self.fast_sync(): dialog("ok", heading="{jellyfin}", line1=_(33128)) raise Exception( "Failed to retrieve latest updates") LOG.info("--<[ retrieve changes ]") break else: raise LibraryException('CompanionMissing') return True except LibraryException as error: LOG.error(error.status) if error.status in 'SyncLibraryLater': dialog("ok", heading="{jellyfin}", line1=_(33129)) settings('SyncInstallRunDone.bool', True) sync = get_sync() sync['Libraries'] = [] save_sync(sync) return True elif error.status == 'CompanionMissing': dialog("ok", heading="{jellyfin}", line1=_(33099)) settings('kodiCompanion.bool', False) return True except Exception as error: LOG.exception(error) return False
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 "" ).decode('utf-8').split(',') if user ] users.insert(0, settings('username').decode('utf-8')) dialog("notification", heading="{jellyfin}", message="%s %s" % (_(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=_(33146) if data.get('ServerId') is None else _(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=_(33147) if data['ServerId'] is None else _(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=_(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=_(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 stop_playback(self): ''' Stop all playback. Check for external player for positionticks. ''' if not self.played: return LOG.info("Played info: %s", self.played) for file in self.played: item = self.get_file_info(file) window('jellyfin.skip.%s.bool' % item['Id'], True) if window('jellyfin.external.bool'): window('jellyfin.external', clear=True) if int(item['CurrentPosition']) == 1: item['CurrentPosition'] = int(item['Runtime']) data = { 'ItemId': item['Id'], 'MediaSourceId': item['MediaSourceId'], 'PositionTicks': int(item['CurrentPosition'] * 10000000), 'PlaySessionId': item['PlaySessionId'] } item['Server']['api'].session_stop(data) if item.get('LiveStreamId'): LOG.info("<[ livestream/%s ]", item['LiveStreamId']) item['Server']['api'].close_live_stream(item['LiveStreamId']) elif item['PlayMethod'] == 'Transcode': LOG.info("<[ transcode/%s ]", item['Id']) item['Server']['api'].close_transcode(item['DeviceId']) path = xbmc.translatePath( "special://profile/addon_data/plugin.video.jellyfin/temp/" ).decode('utf-8') if xbmcvfs.exists(path): dirs, files = xbmcvfs.listdir(path) for file in files: xbmcvfs.delete(os.path.join(path, file.decode('utf-8'))) result = item['Server']['api'].get_item(item['Id']) or {} if 'UserData' in result and result['UserData']['Played']: delete = False if result['Type'] == 'Episode' and settings('deleteTV.bool'): delete = True elif result['Type'] == 'Movie' and settings( 'deleteMovies.bool'): delete = True if not settings('offerDelete.bool'): delete = False if delete: LOG.info("Offer delete option") if dialog("yesno", heading=_(30091), line1=_(33015), autoclose=120000): item['Server']['api'].delete_item(item['Id']) window('jellyfin.external_check', clear=True) self.played.clear()
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.jellyfin/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="{jellyfin}", line1=_(33152)) return with Database('jellyfin') as jellyfindb: all_views = jellyfin_db.JellyfinDatabase(jellyfindb.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="{jellyfin}", message=_(33153), icon="{jellyfin}", time=1000, sound=False)
def browse(media, view_id=None, folder=None, server_id=None): ''' Browse content dynamically. ''' LOG.info("--[ v:%s/%s ] %s", view_id, media, folder) if not window('jellyfin_online.bool') and server_id is None: monitor = xbmc.Monitor() for i in range(300): if window('jellyfin_online.bool'): break elif monitor.waitForAbort(0.1): return else: LOG.error("Default server is not online.") return folder = folder.lower() if folder else None if folder is None and media in ('homevideos', 'movies', 'books', 'audiobooks'): return browse_subfolders(media, view_id, server_id) if folder and folder == 'firstletter': return browse_letters(media, view_id, server_id) if view_id: view = TheVoid('GetItem', {'ServerId': server_id, 'Id': view_id}).get() xbmcplugin.setPluginCategory(int(sys.argv[1]), view['Name']) content_type = "files" if media in ('tvshows', 'seasons', 'episodes', 'movies', 'musicvideos', 'songs', 'albums'): content_type = media elif media in ('homevideos', 'photos'): content_type = "images" elif media in ('books', 'audiobooks'): content_type = "videos" elif media == 'music': content_type = "artists" if folder == 'recentlyadded': listing = TheVoid('RecentlyAdded', {'Id': view_id, 'ServerId': server_id}).get() elif folder == 'genres': listing = TheVoid('Genres', {'Id': view_id, 'ServerId': server_id}).get() elif media == 'livetv': listing = TheVoid('LiveTV', {'Id': view_id, 'ServerId': server_id}).get() elif folder == 'unwatched': listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Filters': ['IsUnplayed']}).get() elif folder == 'favorite': listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Filters': ['IsFavorite']}).get() elif folder == 'inprogress': listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Filters': ['IsResumable']}).get() elif folder == 'boxsets': listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type('boxsets'), 'Recursive': True}).get() elif folder == 'random': listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type(content_type), 'Sort': "Random", 'Limit': 25, 'Recursive': True}).get() elif (folder or "").startswith('firstletter-'): listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type(content_type), 'Params': {'NameStartsWith': folder.split('-')[1]}}).get() elif (folder or "").startswith('genres-'): listing = TheVoid('Browse', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type(content_type), 'Params': {'GenreIds': folder.split('-')[1]}}).get() elif folder == 'favepisodes': listing = TheVoid('Browse', {'Media': get_media_type(content_type), 'ServerId': server_id, 'Limit': 25, 'Filters': ['IsFavorite']}).get() elif media == 'homevideos': listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': get_media_type(content_type), 'ServerId': server_id, 'Recursive': False}).get() elif media == 'movies': listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': get_media_type(content_type), 'ServerId': server_id, 'Recursive': True}).get() elif media in ('boxset', 'library'): listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': True}).get() elif media == 'episodes': listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': get_media_type(content_type), 'ServerId': server_id, 'Recursive': True}).get() elif media == 'boxsets': listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False, 'Filters': ["Boxsets"]}).get() elif media == 'tvshows': listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': True, 'Media': get_media_type(content_type)}).get() elif media == 'seasons': listing = TheVoid('BrowseSeason', {'Id': folder, 'ServerId': server_id}).get() elif media != 'files': listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False, 'Media': get_media_type(content_type)}).get() else: listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False}).get() if listing: actions = Actions(server_id) list_li = [] listing = listing if type(listing) == list else listing.get('Items', []) for item in listing: li = xbmcgui.ListItem() li.setProperty('jellyfinid', item['Id']) li.setProperty('jellyfinserver', server_id) actions.set_listitem(item, li) if item.get('IsFolder'): params = { 'id': view_id or item['Id'], 'mode': "browse", 'type': get_folder_type(item, media) or media, 'folder': item['Id'], 'server': server_id } path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) context = [] if item['Type'] in ('Series', 'Season', 'Playlist'): context.append(("Play", "RunPlugin(plugin://plugin.video.jellyfin/?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))) if item['UserData']['Played']: context.append((_(16104), "RunPlugin(plugin://plugin.video.jellyfin/?mode=unwatched&id=%s&server=%s)" % (item['Id'], server_id))) else: context.append((_(16103), "RunPlugin(plugin://plugin.video.jellyfin/?mode=watched&id=%s&server=%s)" % (item['Id'], server_id))) li.addContextMenuItems(context) list_li.append((path, li, True)) elif item['Type'] == 'Genre': params = { 'id': view_id or item['Id'], 'mode': "browse", 'type': get_folder_type(item, media) or media, 'folder': 'genres-%s' % item['Id'], 'server': server_id } path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) list_li.append((path, li, True)) else: if item['Type'] not in ('Photo', 'PhotoAlbum'): params = { 'id': item['Id'], 'mode': "play", 'server': server_id } path = "%s?%s" % ("plugin://plugin.video.jellyfin/", urllib.urlencode(params)) li.setProperty('path', path) context = [(_(13412), "RunPlugin(plugin://plugin.video.jellyfin/?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))] if item['UserData']['Played']: context.append((_(16104), "RunPlugin(plugin://plugin.video.jellyfin/?mode=unwatched&id=%s&server=%s)" % (item['Id'], server_id))) else: context.append((_(16103), "RunPlugin(plugin://plugin.video.jellyfin/?mode=watched&id=%s&server=%s)" % (item['Id'], server_id))) li.addContextMenuItems(context) list_li.append((li.getProperty('path'), li, False)) xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li)) if content_type == 'images': xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME) xbmcplugin.setContent(int(sys.argv[1]), content_type) 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.decode('utf-8'), _(33166)) context.append((_(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((_(33136), "RunPlugin(plugin://plugin.video.jellyfin/?mode=updatelib&id=%s)" % view_id)) context.append((_(33132), "RunPlugin(plugin://plugin.video.jellyfin/?mode=repairlib&id=%s)" % view_id)) context.append((_(33133), "RunPlugin(plugin://plugin.video.jellyfin/?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.jellyfin/?mode=removeserver&server=%s)" % server['Id'])) if 'AccessToken' not in server: directory("%s (%s)" % (server['Name'], _(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(_(33194), "plugin://plugin.video.jellyfin/?mode=managelibs", True) directory(_(33134), "plugin://plugin.video.jellyfin/?mode=addserver", False) directory(_(33054), "plugin://plugin.video.jellyfin/?mode=adduser", False) directory(_(5), "plugin://plugin.video.jellyfin/?mode=settings", False) directory(_(33058), "plugin://plugin.video.jellyfin/?mode=reset", False) directory(_(33192), "plugin://plugin.video.jellyfin/?mode=restartservice", False) if settings('backupPath'): directory(_(33092), "plugin://plugin.video.jellyfin/?mode=backup", False) xbmcplugin.setContent(int(sys.argv[1]), 'files') xbmcplugin.endOfDirectory(int(sys.argv[1]))