diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 88c4dbdb..e2792b56 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -813,3 +813,27 @@ msgstr "" msgctxt "#33165" msgid "Failed to create backup" msgstr "" + +msgctxt "#33166" +msgid "(dynamic)" +msgstr "" + +msgctxt "#33167" +msgid "Recently added" +msgstr "" + +msgctxt "#33168" +msgid "Favourites" +msgstr "" + +msgctxt "#33169" +msgid "In Progress" +msgstr "" + +msgctxt "#33170" +msgid "Unwatched" +msgstr "" + +msgctxt "#33171" +msgid "A-Z" +msgstr "" diff --git a/resources/lib/emby/core/api.py b/resources/lib/emby/core/api.py index 4f7eb3e7..14686430 100644 --- a/resources/lib/emby/core/api.py +++ b/resources/lib/emby/core/api.py @@ -80,7 +80,7 @@ def items(handler="", action="GET", params=None, json=None): return _get("Items%s" % handler, params) def user_items(handler="", params=None): - return users("/Items%s" % handler, params) + return users("/Items%s" % handler, params=params) def shows(handler, params): return _get("Shows%s" % handler, params) @@ -143,11 +143,13 @@ def get_suggestion(media="Movie,Episode", limit=1): 'Limit': limit }) -def get_recently_added(media=None, limit=20): +def get_recently_added(media=None, parent_id=None, limit=20): return user_items("/Latest", { 'Limit': limit, 'UserId': "{UserId}", - 'IncludeItemTypes': media + 'IncludeItemTypes': media, + 'ParentId': parent_id, + 'Fields': info() }) def get_next(index=None, limit=1): @@ -157,6 +159,30 @@ def get_next(index=None, limit=1): 'StartIndex': None if index is None else int(index) }) +def get_genres(parent_id=None): + return _get("Genres", { + 'ParentId': parent_id, + 'UserId': "{UserId}", + 'Fields': info() + }) + +def get_recommendation(parent_id=None, limit=20): + return _get("Movies/Recommendations", { + 'ParentId': parent_id, + 'UserId': "{UserId}", + 'Fields': info(), + 'Limit': limit + }) + +def get_items_by_letter(parent_id=None, media=None, letter=None): + return user_items(params={ + 'ParentId': parent_id, + 'NameStartsWith': letter, + 'Fields': info(), + 'Recursive': True, + 'IncludeItemTypes': media + }) + def get_intros(item_id): return user_items("/%s/Intros" % item_id) @@ -201,7 +227,8 @@ def get_plugins(): def get_seasons(show_id): return shows("/%s/Seasons" % show_id, params={ 'UserId': "{UserId}", - 'EnableImages': True + 'EnableImages': True, + 'Fields': info() }) def get_date_modified(date, parent_id, media=None): diff --git a/resources/lib/entrypoint/default.py b/resources/lib/entrypoint/default.py index 4e5ed16b..6462c30c 100644 --- a/resources/lib/entrypoint/default.py +++ b/resources/lib/entrypoint/default.py @@ -145,6 +145,7 @@ def listing(): context = [] if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id not in whitelist: + label = "%s %s" % (label, _(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: @@ -228,6 +229,14 @@ def browse(media, view_id=None, folder=None, server_id=None): return + folder = folder.lower() if folder else None + + if folder is None and media in ('homevideos'): + 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() @@ -239,29 +248,54 @@ def browse(media, view_id=None, folder=None, server_id=None): content_type = media elif media in ('homevideos', 'photos'): content_type = "images" + elif media in ('books', 'audiobooks'): + content_type = "videos" - if folder == 'FavEpisodes': - listing = TheVoid('Browse', {'Media': "Episode", 'ServerId': server_id, 'Limit': 25, 'Filters': ["IsFavorite"]}).get() + + 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 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(content_type), '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('NameStartsWith', {'Id': view_id, 'ServerId': server_id, 'Media': get_media_type(content_type), 'Filters': 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': "Video,Folder,PhotoAlbum,Photo", 'ServerId': server_id, 'Recursive': False}).get() + 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': "Movie,Boxset", 'ServerId': server_id, 'Recursive': True}).get() + 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': "Episode", 'ServerId': server_id, 'Recursive': True}).get() + 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() else: listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False}).get() - if listing and listing.get('Items'): + if listing: actions = Actions(server_id) list_li = [] + listing = listing if type(listing) == list else listing.get('Items', []) - for item in listing['Items']: + for item in listing: li = xbmcgui.ListItem() li.setProperty('embyid', item['Id']) @@ -277,7 +311,7 @@ def browse(media, view_id=None, folder=None, server_id=None): 'folder': item['Id'], 'server': server_id } - path = "%s?%s" % ("plugin://plugin.video.emby", urllib.urlencode(params)) + path = "%s?%s" % ("plugin://plugin.video.emby/", urllib.urlencode(params)) context = [] if item['Type'] in ('Series', 'Season', 'Playlist'): @@ -297,7 +331,7 @@ def browse(media, view_id=None, folder=None, server_id=None): 'mode': "play", 'server': server_id } - path = "%s?%s" % ("plugin://plugin.video.emby", urllib.urlencode(params)) + path = "%s?%s" % ("plugin://plugin.video.emby/", urllib.urlencode(params)) li.setProperty('path', path) context = [(_(13412), "RunPlugin(plugin://plugin.video.emby/?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))] @@ -321,6 +355,55 @@ def browse(media, view_id=None, folder=None, server_id=None): xbmcplugin.setContent(int(sys.argv[1]), content_type) xbmcplugin.endOfDirectory(int(sys.argv[1])) +def browse_subfolders(media, view_id, server_id=None): + + ''' Display submenus for emby views. + ''' + from views import DYNNODES + + view = TheVoid('GetItem', {'ServerId': server_id, 'Id': view_id}).get() + xbmcplugin.setPluginCategory(int(sys.argv[1]), view['Name']) + nodes = DYNNODES[media] + + for node in nodes: + + params = { + 'id': view_id, + 'mode': "browse", + 'type': media, + 'folder': view_id if node[0] == 'all' else node[0], + 'server': server_id + } + path = "%s?%s" % ("plugin://plugin.video.emby/", urllib.urlencode(params)) + directory(node[1] or view['Name'], path) + + xbmcplugin.setContent(int(sys.argv[1]), 'files') + xbmcplugin.endOfDirectory(int(sys.argv[1])) + +def browse_letters(media, view_id, server_id=None): + + ''' Display letters as options. + ''' + letters = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + view = TheVoid('GetItem', {'ServerId': server_id, 'Id': view_id}).get() + xbmcplugin.setPluginCategory(int(sys.argv[1]), view['Name']) + + for node in letters: + + params = { + 'id': view_id, + 'mode': "browse", + 'type': media, + 'folder': 'firstletter-%s' % node, + 'server': server_id + } + path = "%s?%s" % ("plugin://plugin.video.emby/", urllib.urlencode(params)) + directory(node, path) + + xbmcplugin.setContent(int(sys.argv[1]), 'files') + xbmcplugin.endOfDirectory(int(sys.argv[1])) + def get_folder_type(item): media = item['Type'] @@ -338,6 +421,19 @@ def get_folder_type(item): elif media == 'CollectionFolder': return item.get('CollectionType', 'library') +def get_media_type(media): + + if media == 'movies': + return "Movie,BoxSet" + elif media == 'homevideos': + return "Video,Folder,PhotoAlbum,Photo" + elif media == 'episodes': + return "Episode" + elif media == 'boxsets': + return "BoxSet" + elif media == 'tvshows': + return "Series" + def get_fanart(item_id, path, server_id=None): ''' Get extra fanart for listitems. This is called by skinhelper. diff --git a/resources/lib/monitor.py b/resources/lib/monitor.py index 57bc034d..51d07ee8 100644 --- a/resources/lib/monitor.py +++ b/resources/lib/monitor.py @@ -51,11 +51,12 @@ class Monitor(xbmc.Monitor): if sender == 'plugin.video.emby': method = method.split('.')[1] - if method not in ('GetItem', 'ReportProgressRequested', 'LoadServer', + if method not in ('GetItem', 'ReportProgressRequested', 'LoadServer', 'RandomItems', 'Recommended', 'GetServerAddress', 'GetPlaybackInfo', 'Browse', 'GetImages', 'GetToken', - 'PlayPlaylist', 'Play', 'GetIntros', 'GetAdditionalParts', 'RefreshItem', + 'PlayPlaylist', 'Play', 'GetIntros', 'GetAdditionalParts', 'RefreshItem', 'Genres', 'FavoriteItem', 'DeleteItem', 'AddUser', 'GetSession', 'GetUsers', 'GetThemes', - 'GetTheme', 'Playstate', 'GeneralCommand', 'GetTranscodeOptions'): + 'GetTheme', 'Playstate', 'GeneralCommand', 'GetTranscodeOptions', 'RecentlyAdded', + 'NameStartsWith', 'BrowseSeason'): return data = json.loads(data)[0] @@ -82,68 +83,57 @@ class Monitor(xbmc.Monitor): if method == 'GetItem': item = server['api'].get_item(data['Id']) - window('emby_%s.json' % data['VoidName'], item) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, item) elif method == 'GetAdditionalParts': item = server['api'].get_additional_parts(data['Id']) - window('emby_%s.json' % data['VoidName'], item) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, item) elif method == 'GetIntros': item = server['api'].get_intros(data['Id']) - window('emby_%s.json' % data['VoidName'], item) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, item) elif method == 'GetImages': item = server['api'].get_images(data['Id']) - window('emby_%s.json' % data['VoidName'], item) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, item) elif method == 'GetServerAddress': server_address = server['auth/server-address'] - window('emby_%s.json' % data['VoidName'], server_address) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, server_address) elif method == 'GetPlaybackInfo': sources = server['api'].get_play_info(data['Id'], data['Profile']) - window('emby_%s.json' % data['VoidName'], sources) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, sources) elif method == 'GetLiveStream': sources = server['api'].get_play_info(data['Id'], data['PlaySessionId'], data['Token'], data['Profile']) - window('emby_%s.json' % data['VoidName'], sources) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, sources) elif method == 'GetToken': token = server['auth/token'] - window('emby_%s.json' % data['VoidName'], token) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, token) elif method == 'GetSession': session = server['api'].get_device(self.device_id) - window('emby_%s.json' % data['VoidName'], session) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, session) elif method == 'GetUsers': users = server['api'].get_users(data.get('IsDisabled', True), data.get('IsHidden', True)) - window('emby_%s.json' % data['VoidName'], users) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, users) elif method == 'GetTranscodeOptions': result = server['api'].get_transcode_settings() - window('emby_%s.json' % data['VoidName'], result) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, result) elif method == 'GetThemes': @@ -152,23 +142,44 @@ class Monitor(xbmc.Monitor): else: theme = server['api'].get_items_theme_song(data['Id']) - window('emby_%s.json' % data['VoidName'], theme) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, theme) elif method == 'GetTheme': theme = server['api'].get_themes(data['Id']) - window('emby_%s.json' % data['VoidName'], theme) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, theme) elif method == 'Browse': result = downloader.get_filtered_section(data.get('Id'), data.get('Media'), data.get('Limit'), data.get('Recursive'), data.get('Sort'), data.get('SortOrder'), data.get('Filters'), data.get('ServerId')) - window('emby_%s.json' % data['VoidName'], result) - LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + self.void_responder(data, result) + elif method == 'BrowseSeason': + + result = server['api'].get_seasons(data['Id']) + self.void_responder(data, result) + + elif method == 'RecentlyAdded': + + result = server['api'].get_recently_added(data.get('Media'), data.get('Id'), data.get('Limit')) + self.void_responder(data, result) + + elif method == 'Genres': + + result = server['api'].get_genres(data.get('Id')) + self.void_responder(data, result) + + elif method == 'NameStartsWith': + + result = server['api'].get_items_by_letter(data.get('Id'), data.get('Media'), data.get('Filters')) + self.void_responder(data, result) + + elif method == 'Recommended': + + result = server['api'].get_recommendation(data.get('Id'), data.get('Limit')) + self.void_responder(data, result) elif method == 'RefreshItem': server['api'].refresh_item(data['Id']) @@ -221,6 +232,11 @@ class Monitor(xbmc.Monitor): elif method == 'VideoLibrary.OnUpdate': on_update(data, server) + def void_responder(self, data, result): + + window('emby_%s.json' % data['VoidName'], result) + LOG.debug("--->[ beacon/emby_%s.json ] sent", data['VoidName']) + def server_instance(self, server_id=None): server = Emby(server_id) diff --git a/resources/lib/views.py b/resources/lib/views.py index cbeee2d2..6d790b90 100644 --- a/resources/lib/views.py +++ b/resources/lib/views.py @@ -47,17 +47,54 @@ NODES = { ('recent', _(30256)), ('inprogress', _(30257)), ('unwatched', _(30258)) + ] +} +DYNNODES = { + 'tvshows': [ + ('all', None), + ('RecentlyAdded', _(30170)), + ('recentepisodes', _(30175)), + ('InProgress', _(30171)), + ('inprogressepisodes', _(30178)), + ('nextepisodes', _(30179)), + ('Genres', _(135)), + ('Random', _(30229)), + ('recommended', _(30230)) + ], + 'movies': [ + ('all', None), + ('RecentlyAdded', _(30174)), + ('InProgress', _(30177)), + ('Boxsets', _(20434)), + ('Favorite', _(33168)), + ('FirstLetter', _(33171)), + ('Genres', _(135)), + ('Random', _(30229)), + #('Recommended', _(30230)) + ], + 'musicvideos': [ + ('all', None), + ('RecentlyAdded', _(30256)), + ('InProgress', _(30257)), + ('Unwatched', _(30258)) ], 'homevideos': [ ('all', None), - ('recent', _(30251)), - ('recommended', _(30253)) + ('RecentlyAdded', _(33167)), + ('InProgress', _(33169)), + ('Favorite', _(33168)) ], - 'photos': [ + 'books': [ ('all', None), - ('recent', _(30252)), - ('sets', _(30255)), - ('recommended', _(30254)) + ('RecentlyAdded', _(33167)), + ('InProgress', _(33169)), + ('Favorite', _(33168)) + ], + 'audiobooks': [ + ('all', None), + ('RecentlyAdded', _(33167)), + ('InProgress', _(33169)), + ('Favorite', _(33168)) ] }