def get_libraries(self, library_id=None): with Database('jellyfin') as jellyfindb: if library_id is None: return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views() else: return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_view(library_id)
def get_nodes(self): ''' Set up playlists, video nodes, window prop. ''' node_path = xbmc.translatePath("special://profile/library/video") playlist_path = xbmc.translatePath("special://profile/playlists/video") 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': translate('fav_movies'), 'Tag': "Favorite movies", 'Media': "movies" }, { 'Name': translate('fav_tvshows'), 'Tag': "Favorite tvshows", 'Media': "tvshows" }, { 'Name': translate('fav_episodes'), 'Tag': "Favorite episodes", 'Media': "episodes" }]: self.add_single_node(node_path, index, "favorites", single) index += 1 self.window_nodes()
def run(self): with Database('jellyfin') as jellyfindb: database = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) while True: try: item_id = self.queue.get(timeout=1) except Queue.Empty: break try: media = database.get_media_by_id(item_id) self.output[media].put({'Id': item_id, 'Type': media}) except Exception as error: LOG.exception(error) items = database.get_media_by_parent_id(item_id) if not items: LOG.info("Could not find media %s in the jellyfin database.", item_id) else: for item in items: self.output[item[1]].put({'Id': item[0], 'Type': item[1]}) self.queue.task_done() if window('jellyfin_should_stop.bool'): break LOG.info("--<[ q:sort/%s ]", id(self)) self.is_done = True
def select_libraries(self, mode=None): ''' Select from libraries synced. Either update or repair libraries. Send event back to service.py ''' modes = { 'SyncLibrarySelection': 'SyncLibrary', 'RepairLibrarySelection': 'RepairLibrary', 'AddLibrarySelection': 'SyncLibrary', 'RemoveLibrarySelection': 'RemoveLibrary' } sync = get_sync() whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']] libraries = [] with Database('jellyfin') as jellyfindb: db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) if mode in ('SyncLibrarySelection', 'RepairLibrarySelection', 'RemoveLibrarySelection'): for library in sync['Whitelist']: name = db.get_view_name(library.replace('Mixed:', "")) libraries.append({'Id': library, 'Name': name}) else: available = [x for x in sync['SortedViews'] if x not in whitelist] for library in available: name, media = db.get_view(library) if media in ('movies', 'tvshows', 'musicvideos', 'mixed', 'music'): libraries.append({'Id': library, 'Name': name}) choices = [x['Name'] for x in libraries] choices.insert(0, translate(33121)) titles = { "RepairLibrarySelection": 33199, "SyncLibrarySelection": 33198, "RemoveLibrarySelection": 33200, "AddLibrarySelection": 33120 } title = titles.get(mode, "Failed to get title {}".format(mode)) selection = dialog("multi", translate(title), choices) if selection is None: return if 0 in selection: selection = list(range(1, len(libraries) + 1)) selected_libraries = [] for x in selection: library = libraries[x - 1] selected_libraries.append(library['Id']) event(modes[mode], {'Id': ','.join([libraries[x - 1]['Id'] for x in selection]), 'Update': mode == 'SyncLibrarySelection'})
def remove_library(self, view_id): ''' Remove entry from view table in jellyfin database. ''' with Database('jellyfin') as jellyfindb: jellyfin_db.JellyfinDatabase( jellyfindb.cursor).remove_view(view_id) self.delete_playlist_by_id(view_id) self.delete_node_by_id(view_id)
def musicvideos_compare(self, library, obj, jellyfindb): ''' Compare entries from library to what's in the jellyfindb. Remove surplus ''' db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) items = db.get_item_by_media_folder(library['Id']) current = obj.item_ids for x in items: if x[0] not in current and x[1] == 'MusicVideo': obj.remove(x[0])
def check_version(self): ''' Checks database version and triggers any required data migrations ''' with Database('jellyfin') as jellyfindb: db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) db_version = db.get_version() if not db_version: # Make sure we always have a version in the database db.add_version((TARGET_DB_VERSION))
def __init__(self, server, jellyfindb, videodb, direct_path): self.server = server self.jellyfin = jellyfindb self.video = videodb self.direct_path = direct_path self.jellyfin_db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) self.objects = Objects() self.item_ids = [] KodiDb.__init__(self, videodb.cursor)
def __init__(self, server, jellyfindb, musicdb, direct_path, library=None): self.server = server self.jellyfin = jellyfindb self.music = musicdb self.direct_path = direct_path self.jellyfin_db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) self.objects = Objects() self.item_ids = [] self.library = library KodiDb.__init__(self, musicdb.cursor)
def get_item(kodi_id, media): ''' Get jellyfin item based on kodi id and media. ''' with Database('jellyfin') as jellyfindb: item = jellyfin_db.JellyfinDatabase( jellyfindb.cursor).get_full_item_by_kodi_id(kodi_id, media) if not item: LOG.debug("Not an jellyfin item") return return item
def music_compare(self, library, obj, jellyfindb): ''' Compare entries from library to what's in the jellyfindb. Remove surplus ''' db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) items = db.get_item_by_media_folder(library['Id']) for x in list(items): items.extend(obj.get_child(x[0])) current = obj.item_ids for x in items: if x[0] not in current and x[1] == 'MusicArtist': obj.remove(x[0])
def __init__(self, server, jellyfindb, videodb, direct_path, library=None, update_library=False): self.server = server self.jellyfin = jellyfindb self.video = videodb self.direct_path = direct_path self.update_library = update_library self.jellyfin_db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) self.objects = Objects() self.item_ids = [] self.library = library KodiDb.__init__(self, videodb.cursor)
def get_views(self): ''' Get the media folders. Add or remove them. Do not proceed if issue getting libraries. ''' media = { 'movies': "Movie", 'tvshows': "Series", 'musicvideos': "MusicVideo" } try: libraries = self.get_libraries() except IndexError as error: LOG.exception(error) return self.sync['SortedViews'] = [x['Id'] for x in libraries] for library in libraries: if library['Type'] == 'Channel': library['Media'] = "channels" else: library['Media'] = library.get( 'OriginalCollectionType', library.get('CollectionType', "mixed")) self.add_library(library) with Database('jellyfin') as jellyfindb: views = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views() removed = [] for view in views: if view[0] not in self.sync['SortedViews']: removed.append(view[0]) if removed: event('RemoveLibrary', {'Id': ','.join(removed)}) save_sync(self.sync)
def check_version(self): ''' Check the database version to ensure we do not need to do a reset. ''' with Database('jellyfin') as jellyfindb: version = jellyfin_db.JellyfinDatabase( jellyfindb.cursor).get_version() LOG.info("---[ db/%s ]", version) if version and compare_version(version, "3.1.0") < 0: resp = dialog("yesno", heading=_('addon_name'), line1=_(33022)) if not resp: LOG.warn("Database version is out of date! USER IGNORED!") dialog("ok", heading=_('addon_name'), line1=_(33023)) raise Exception("User backed out of a required database reset") else: reset() raise Exception("Completed database reset")
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 get_next_episodes(item_id, limit): ''' Only for synced content. ''' with Database('jellyfin') as jellyfindb: db = jellyfin_db.JellyfinDatabase(jellyfindb.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(PROCESS_HANDLE, list_li, len(list_li)) xbmcplugin.setContent(PROCESS_HANDLE, 'episodes') xbmcplugin.endOfDirectory(PROCESS_HANDLE)
def get_libraries(self): with Database('jellyfin') as jellyfindb: return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
def remove_library(self, library_id, dialog): ''' Remove library by their id from the Kodi database. ''' direct_path = self.library.direct_path with Database('jellyfin') as jellyfindb: db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) library = db.get_view(library_id.replace('Mixed:', "")) items = db.get_item_by_media_folder( library_id.replace('Mixed:', "")) media = 'music' if library[1] == 'music' else 'video' if media == 'music': settings('MusicRescan.bool', False) if items: count = 0 with self.library.music_database_lock if media == 'music' else self.library.database_lock: with Database(media) as kodidb: if library[1] == 'mixed': movies = [x for x in items if x[1] == 'Movie'] tvshows = [x for x in items if x[1] == 'Series'] obj = Movies(self.server, jellyfindb, kodidb, direct_path).remove for item in movies: obj(item[0]) dialog.update( int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library[0])) count += 1 obj = TVShows(self.server, jellyfindb, kodidb, direct_path).remove for item in tvshows: obj(item[0]) dialog.update( int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library[0])) count += 1 else: default_args = (self.server, jellyfindb, kodidb, direct_path) for item in items: if item[1] in ('Series', 'Season', 'Episode'): TVShows(*default_args).remove(item[0]) elif item[1] in ('Movie', 'BoxSet'): Movies(*default_args).remove(item[0]) elif item[1] in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'): Music(*default_args).remove(item[0]) elif item[1] == 'MusicVideo': MusicVideos(*default_args).remove(item[0]) dialog.update( int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library[0])) count += 1 self.sync = get_sync() if library_id in self.sync['Whitelist']: self.sync['Whitelist'].remove(library_id) elif 'Mixed:%s' % library_id in self.sync['Whitelist']: self.sync['Whitelist'].remove('Mixed:%s' % library_id) save_sync(self.sync)
def get_library(self, library_id): with Database('jellyfin') as jellyfindb: return jellyfin_db.JellyfinDatabase( jellyfindb.cursor).get_view(library_id)
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'], translate(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': translate('fav_movies'), 'Tag': "Favorite movies", 'Media': "movies" }, { 'Name': translate('fav_tvshows'), 'Tag': "Favorite tvshows", 'Media': "tvshows" }, { 'Name': translate('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))
def remove_library(self, library_id, dialog): ''' Remove library by their id from the Kodi database. ''' MEDIA = self.library.MEDIA direct_path = self.library.direct_path with Database('jellyfin') as jellyfindb: db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) library = db.get_view(library_id.replace('Mixed:', "")) items = db.get_item_by_media_folder( library_id.replace('Mixed:', "")) media = 'music' if library[1] == 'music' else 'video' if media == 'music': settings('MusicRescan.bool', False) if items: count = 0 with self.library.music_database_lock if media == 'music' else self.library.database_lock: with Database(media) as kodidb: if library[1] == 'mixed': movies = [x for x in items if x[1] == 'Movie'] tvshows = [x for x in items if x[1] == 'Series'] obj = Movies(self.server, jellyfindb, kodidb, direct_path).remove for item in movies: obj(item[0]) dialog.update(int( (float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0])) count += 1 obj = TVShows(self.server, jellyfindb, kodidb, direct_path).remove for item in tvshows: obj(item[0]) dialog.update(int( (float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0])) count += 1 else: # from mcarlton: I'm not sure what triggers this. # I've added and removed every media type except # for music videos (because i don't have any) and # can't find it, but I'm not comfortable # removing it right now LOG.info('Triggered the mystery function') LOG.debug('Mystery function item type: {}'.format( items[0][1])) obj = MEDIA[items[0][1]](self.server, jellyfindb, kodidb, direct_path).remove for item in items: obj(item[0]) dialog.update(int( (float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0])) count += 1 self.sync = get_sync() if library_id in self.sync['Whitelist']: self.sync['Whitelist'].remove(library_id) elif 'Mixed:%s' % library_id in self.sync['Whitelist']: self.sync['Whitelist'].remove('Mixed:%s' % library_id) save_sync(self.sync)
def remove_library(self, library_id, dialog): ''' Remove library by their id from the Kodi database. ''' MEDIA = self.library.MEDIA direct_path = self.library.direct_path with Database('jellyfin') as jellyfindb: db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) library = db.get_view(library_id.replace('Mixed:', "")) items = db.get_item_by_media_folder( library_id.replace('Mixed:', "")) media = 'music' if library[1] == 'music' else 'video' if media == 'music': settings('MusicRescan.bool', False) if items: count = 0 with self.library.music_database_lock if media == 'music' else self.library.database_lock: with Database(media) as kodidb: if library[1] == 'mixed': movies = [x for x in items if x[1] == 'Movie'] tvshows = [x for x in items if x[1] == 'Series'] obj = MEDIA['Movie'](self.server, jellyfindb, kodidb, direct_path)['Remove'] for item in movies: obj(item[0]) dialog.update(int( (float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0])) count += 1 obj = MEDIA['Series'](self.server, jellyfindb, kodidb, direct_path)['Remove'] for item in tvshows: obj(item[0]) dialog.update(int( (float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0])) count += 1 else: obj = MEDIA[items[0][1]](self.server, jellyfindb, kodidb, direct_path)['Remove'] for item in items: obj(item[0]) dialog.update(int( (float(count) / float(len(items)) * 100)), heading="%s: %s" % (_('addon_name'), library[0])) count += 1 self.sync = get_sync() if library_id in self.sync['Whitelist']: self.sync['Whitelist'].remove(library_id) elif 'Mixed:%s' % library_id in self.sync['Whitelist']: self.sync['Whitelist'].remove('Mixed:%s' % library_id) save_sync(self.sync)
def add_library(self, view): ''' Add entry to view table in jellyfin database. ''' with Database('jellyfin') as jellyfindb: jellyfin_db.JellyfinDatabase(jellyfindb.cursor).add_view( view['Id'], view['Name'], view['Media'])