From f2bc95813a324d0a4ea62dc1cf071f6a7b1892b5 Mon Sep 17 00:00:00 2001 From: croneter Date: Fri, 8 Dec 2017 19:43:06 +0100 Subject: [PATCH] Centralize Kodi json rpc --- resources/lib/artwork.py | 121 +++------------ resources/lib/entrypoint.py | 248 +++++++++++------------------- resources/lib/json_rpc.py | 221 +++++++++++++++++++++++--- resources/lib/playback_starter.py | 6 +- resources/lib/playbackutils.py | 4 +- resources/lib/player.py | 101 ++++-------- resources/lib/playlist_func.py | 205 ++++++++++-------------- resources/lib/playqueue.py | 60 ++++---- resources/lib/utils.py | 6 +- 9 files changed, 472 insertions(+), 500 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index ce2edc34..b6306c70 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- ############################################################################### -import logging -from json import dumps, loads +from logging import getLogger import requests from shutil import rmtree from urllib import quote_plus, unquote from threading import Thread from Queue import Queue, Empty +import json_rpc as js -from xbmc import executeJSONRPC, sleep, translatePath +from xbmc import sleep, translatePath from xbmcvfs import exists from utils import window, settings, language as lang, kodiSQL, tryEncode, \ @@ -20,7 +20,7 @@ import requests.packages.urllib3 requests.packages.urllib3.disable_warnings() ############################################################################### -log = logging.getLogger("PLEX."+__name__) +LOG = getLogger("PLEX." + __name__) ############################################################################### @@ -34,87 +34,16 @@ def setKodiWebServerDetails(): xbmc_port = None xbmc_username = None xbmc_password = None - web_query = { - "jsonrpc": "2.0", - "id": 1, - "method": "Settings.GetSettingValue", - "params": { - "setting": "services.webserver" - } - } - result = executeJSONRPC(dumps(web_query)) - result = loads(result) - try: - xbmc_webserver_enabled = result['result']['value'] - except (KeyError, TypeError): - xbmc_webserver_enabled = False - if not xbmc_webserver_enabled: + if js.get_setting('services.webserver') in (None, False): # Enable the webserver, it is disabled - web_port = { - "jsonrpc": "2.0", - "id": 1, - "method": "Settings.SetSettingValue", - "params": { - "setting": "services.webserverport", - "value": 8080 - } - } - result = executeJSONRPC(dumps(web_port)) xbmc_port = 8080 - web_user = { - "jsonrpc": "2.0", - "id": 1, - "method": "Settings.SetSettingValue", - "params": { - "setting": "services.webserver", - "value": True - } - } - result = executeJSONRPC(dumps(web_user)) xbmc_username = "kodi" + js.set_setting('services.webserverport', xbmc_port) + js.set_setting('services.webserver', True) # Webserver already enabled - web_port = { - "jsonrpc": "2.0", - "id": 1, - "method": "Settings.GetSettingValue", - "params": { - "setting": "services.webserverport" - } - } - result = executeJSONRPC(dumps(web_port)) - result = loads(result) - try: - xbmc_port = result['result']['value'] - except (TypeError, KeyError): - pass - web_user = { - "jsonrpc": "2.0", - "id": 1, - "method": "Settings.GetSettingValue", - "params": { - "setting": "services.webserverusername" - } - } - result = executeJSONRPC(dumps(web_user)) - result = loads(result) - try: - xbmc_username = result['result']['value'] - except (TypeError, KeyError): - pass - web_pass = { - "jsonrpc": "2.0", - "id": 1, - "method": "Settings.GetSettingValue", - "params": { - "setting": "services.webserverpassword" - } - } - result = executeJSONRPC(dumps(web_pass)) - result = loads(result) - try: - xbmc_password = result['result']['value'] - except TypeError: - pass + xbmc_port = js.get_setting('services.webserverport') + xbmc_username = js.get_setting('services.webserverusername') + xbmc_password = js.get_setting('services.webserverpassword') return (xbmc_port, xbmc_username, xbmc_password) @@ -152,7 +81,7 @@ class Image_Cache_Thread(Thread): # Set in service.py if thread_stopped(): # Abort was requested while waiting. We should exit - log.info("---===### Stopped Image_Cache_Thread ###===---") + LOG.info("---===### Stopped Image_Cache_Thread ###===---") return sleep(1000) try: @@ -179,10 +108,10 @@ class Image_Cache_Thread(Thread): # Server thinks its a DOS attack, ('error 10053') # Wait before trying again if sleeptime > 5: - log.error('Repeatedly got ConnectionError for url %s' + LOG.error('Repeatedly got ConnectionError for url %s' % double_urldecode(url)) break - log.debug('Were trying too hard to download art, server ' + LOG.debug('Were trying too hard to download art, server ' 'over-loaded. Sleep %s seconds before trying ' 'again to download %s' % (2**sleeptime, double_urldecode(url))) @@ -190,18 +119,18 @@ class Image_Cache_Thread(Thread): sleeptime += 1 continue except Exception as e: - log.error('Unknown exception for url %s: %s' + LOG.error('Unknown exception for url %s: %s' % (double_urldecode(url), e)) import traceback - log.error("Traceback:\n%s" % traceback.format_exc()) + LOG.error("Traceback:\n%s" % traceback.format_exc()) break # We did not even get a timeout break queue.task_done() - log.debug('Cached art: %s' % double_urldecode(url)) + LOG.debug('Cached art: %s' % double_urldecode(url)) # Sleep for a bit to reduce CPU strain sleep(sleep_between) - log.info("---===### Stopped Image_Cache_Thread ###===---") + LOG.info("---===### Stopped Image_Cache_Thread ###===---") class Artwork(): @@ -217,11 +146,11 @@ class Artwork(): if not dialog('yesno', "Image Texture Cache", lang(39250)): return - log.info("Doing Image Cache Sync") + LOG.info("Doing Image Cache Sync") # ask to rest all existing or not if dialog('yesno', "Image Texture Cache", lang(39251)): - log.info("Resetting all cache data first") + LOG.info("Resetting all cache data first") # Remove all existing textures first path = tryDecode(translatePath("special://thumbnails/")) if exists_dir(path): @@ -248,7 +177,7 @@ class Artwork(): cursor.execute(query, ('actor', )) result = cursor.fetchall() total = len(result) - log.info("Image cache sync about to process %s video images" % total) + LOG.info("Image cache sync about to process %s video images" % total) connection.close() for url in result: @@ -259,7 +188,7 @@ class Artwork(): cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) - log.info("Image cache sync about to process %s music images" % total) + LOG.info("Image cache sync about to process %s music images" % total) connection.close() for url in result: self.cacheTexture(url[0]) @@ -364,7 +293,7 @@ class Artwork(): url = cursor.fetchone()[0] except TypeError: # Add the artwork - log.debug("Adding Art Link for kodiId: %s (%s)" + LOG.debug("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl)) query = ( ''' @@ -382,7 +311,7 @@ class Artwork(): imageType in ("fanart", "poster")): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) - log.debug("Updating Art url for %s kodiId %s %s -> (%s)" + LOG.debug("Updating Art url for %s kodiId %s %s -> (%s)" % (imageType, kodiId, url, imageUrl)) query = ' '.join(( "UPDATE art", @@ -418,11 +347,11 @@ class Artwork(): (url,)) cachedurl = cursor.fetchone()[0] except TypeError: - log.info("Could not find cached url.") + LOG.info("Could not find cached url.") else: # Delete thumbnail as well as the entry path = translatePath("special://thumbnails/%s" % cachedurl) - log.debug("Deleting cached thumbnail: %s" % path) + LOG.debug("Deleting cached thumbnail: %s" % path) if exists(path): rmtree(tryDecode(path), ignore_errors=True) cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 6ac3366d..c96b9935 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -12,12 +12,13 @@ from xbmc import sleep, executebuiltin, translatePath from xbmcgui import ListItem from utils import window, settings, language as lang, dialog, tryEncode, \ - CatchExceptions, JSONRPC, exists_dir, plex_command, tryDecode + CatchExceptions, exists_dir, plex_command, tryDecode import downloadutils from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \ GetMachineIdentifier from PlexAPI import API +import json_rpc as js import variables as v ############################################################################### @@ -268,7 +269,6 @@ def createListItem(item, appendShowTitle=False, appendSxxExx=False): ##### GET NEXTUP EPISODES FOR TAGNAME ##### def getNextUpEpisodes(tagname, limit): - count = 0 # if the addon is called with nextup parameter, # we return the nextepisodes list of the given tagname @@ -283,68 +283,50 @@ def getNextUpEpisodes(tagname, limit): ]}, 'properties': ['title', 'studio', 'mpaa', 'file', 'art'] } - result = JSONRPC('VideoLibrary.GetTVShows').execute(params) - - # If we found any, find the oldest unwatched show for each one. - try: - items = result['result']['tvshows'] - except (KeyError, TypeError): - pass - else: - for item in items: - if settings('ignoreSpecialsNextEpisodes') == "true": - 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 = createListItem(episode) - xbmcplugin.addDirectoryItem(handle=HANDLE, - url=episode['file'], - listitem=li) - count += 1 - - if count == limit: - break - + for item in js.get_tv_shows(params): + if settings('ignoreSpecialsNextEpisodes') == "true": + 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} + } + for episode in js.get_episodes(params): + xbmcplugin.addDirectoryItem(handle=HANDLE, + url=episode['file'], + listitem=createListItem(episode)) + count += 1 + if count == limit: + break xbmcplugin.endOfDirectory(handle=HANDLE) @@ -364,42 +346,26 @@ def getInProgressEpisodes(tagname, limit): ]}, 'properties': ['title', 'studio', 'mpaa', 'file', 'art'] } - result = JSONRPC('VideoLibrary.GetTVShows').execute(params) - # If we found any, find the oldest unwatched show for each one. - try: - items = result['result']['tvshows'] - except (KeyError, TypeError): - pass - else: - for item in items: - params = { - 'tvshowid': item['tvshowid'], - 'sort': {'method': "episode"}, - 'filter': { - 'operator': "true", - 'field': "inprogress", - 'value': ""}, - 'properties': ["title", "playcount", "season", "episode", - "showtitle", "plot", "file", "rating", "resume", - "tvshowid", "art", "cast", "streamdetails", "firstaired", - "runtime", "writer", "dateadded", "lastplayed"] - } - result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) - try: - episodes = result['result']['episodes'] - except (KeyError, TypeError): - pass - else: - for episode in episodes: - li = createListItem(episode) - xbmcplugin.addDirectoryItem(handle=HANDLE, - url=episode['file'], - listitem=li) - count += 1 - - if count == limit: - break - + for item in js.get_tv_shows(params): + params = { + 'tvshowid': item['tvshowid'], + 'sort': {'method': "episode"}, + 'filter': { + 'operator': "true", + 'field': "inprogress", + 'value': ""}, + 'properties': ["title", "playcount", "season", "episode", + "showtitle", "plot", "file", "rating", "resume", + "tvshowid", "art", "cast", "streamdetails", "firstaired", + "runtime", "writer", "dateadded", "lastplayed"] + } + for episode in js.get_episodes(params): + xbmcplugin.addDirectoryItem(handle=HANDLE, + url=episode['file'], + listitem=createListItem(episode)) + count += 1 + if count == limit: + break xbmcplugin.endOfDirectory(handle=HANDLE) ##### GET RECENT EPISODES FOR TAGNAME ##### @@ -412,22 +378,13 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit): appendShowTitle = settings('RecentTvAppendShow') == 'true' appendSxxExx = settings('RecentTvAppendSeason') == 'true' # First we get a list of all the TV shows - filtered by tag + allshowsIds = set() params = { 'sort': {'order': "descending", 'method': "dateadded"}, 'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname}, } - result = JSONRPC('VideoLibrary.GetTVShows').execute(params) - # If we found any, find the oldest unwatched show for each one. - try: - items = result['result'][mediatype] - except (KeyError, TypeError): - # No items, empty folder - xbmcplugin.endOfDirectory(handle=HANDLE) - return - - allshowsIds = set() - for item in items: - allshowsIds.add(item['tvshowid']) + for tv_show in js.get_tv_shows(params): + allshowsIds.add(tv_show['tvshowid']) params = { 'sort': {'order': "descending", 'method': "dateadded"}, 'properties': ["title", "playcount", "season", "episode", "showtitle", @@ -442,26 +399,18 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit): 'field': "playcount", 'value': "1" } - result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) - try: - episodes = result['result']['episodes'] - except (KeyError, TypeError): - pass - else: - for episode in episodes: - if episode['tvshowid'] in allshowsIds: - li = createListItem(episode, - appendShowTitle=appendShowTitle, - appendSxxExx=appendSxxExx) - xbmcplugin.addDirectoryItem( - handle=HANDLE, - url=episode['file'], - listitem=li) - count += 1 - - if count == limit: - break - + for episode in js.get_episodes(params): + if episode['tvshowid'] in allshowsIds: + listitem = createListItem(episode, + appendShowTitle=appendShowTitle, + appendSxxExx=appendSxxExx) + xbmcplugin.addDirectoryItem( + handle=HANDLE, + url=episode['file'], + listitem=listitem) + count += 1 + if count == limit: + break xbmcplugin.endOfDirectory(handle=HANDLE) @@ -644,15 +593,6 @@ def getOnDeck(viewid, mediatype, tagname, limit): {'operator': "is", 'field': "tag", 'value': "%s" % tagname} ]} } - result = JSONRPC('VideoLibrary.GetTVShows').execute(params) - # If we found any, find the oldest unwatched show for each one. - try: - items = result['result'][mediatype] - except (KeyError, TypeError): - # Now items retrieved - empty directory - xbmcplugin.endOfDirectory(handle=HANDLE) - return - params = { 'sort': {'method': "episode"}, 'limits': {"end": 1}, @@ -677,7 +617,6 @@ def getOnDeck(viewid, mediatype, tagname, limit): {'operator': "true", 'field': "inprogress", 'value': ""} ] } - # Are there any episodes still in progress/not yet finished watching?!? # Then we should show this episode, NOT the "next up" inprog_params = { @@ -687,35 +626,26 @@ def getOnDeck(viewid, mediatype, tagname, limit): } count = 0 - for item in items: + for item in js.get_tv_shows(params): inprog_params['tvshowid'] = item['tvshowid'] - result = JSONRPC('VideoLibrary.GetEpisodes').execute(inprog_params) - try: - episodes = result['result']['episodes'] - except (KeyError, TypeError): + episodes = js.get_episodes(inprog_params) + if not episodes: # No, there are no episodes not yet finished. Get "next up" params['tvshowid'] = item['tvshowid'] - result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) - try: - episodes = result['result']['episodes'] - except (KeyError, TypeError): - # Also no episodes currently coming up - continue + episodes = js.get_episodes(params) for episode in episodes: # There will always be only 1 episode ('limit=1') - li = createListItem(episode, - appendShowTitle=appendShowTitle, - appendSxxExx=appendSxxExx) + listitem = createListItem(episode, + appendShowTitle=appendShowTitle, + appendSxxExx=appendSxxExx) xbmcplugin.addDirectoryItem( handle=HANDLE, url=episode['file'], - listitem=li, + listitem=listitem, isFolder=False) - count += 1 if count >= limit: break - xbmcplugin.endOfDirectory(handle=HANDLE) diff --git a/resources/lib/json_rpc.py b/resources/lib/json_rpc.py index 971a5679..f42499f1 100644 --- a/resources/lib/json_rpc.py +++ b/resources/lib/json_rpc.py @@ -2,7 +2,7 @@ Collection of functions using the Kodi JSON RPC interface. See http://kodi.wiki/view/JSON-RPC_API """ -from utils import JSONRPC, milliseconds_to_kodi_time +from utils import jsonrpc, milliseconds_to_kodi_time def get_players(): @@ -14,7 +14,7 @@ def get_players(): 'picture': ... } """ - info = JSONRPC("Player.GetActivePlayers").execute()['result'] or [] + info = jsonrpc("Player.GetActivePlayers").execute()['result'] or [] ret = {} for player in info: player['playerid'] = int(player['playerid']) @@ -53,7 +53,19 @@ def get_playlists(): {u'playlistid': 2, u'type': u'picture'} ] """ - return JSONRPC('Playlist.GetPlaylists').execute() + try: + ret = jsonrpc('Playlist.GetPlaylists').execute()['result'] + except KeyError: + ret = [] + return ret + + +def get_volume(): + """ + Returns the Kodi volume as an int between 0 (min) and 100 (max) + """ + return jsonrpc('Application.GetProperties').execute( + {"properties": ['volume']})['result']['volume'] def set_volume(volume): @@ -61,7 +73,15 @@ def set_volume(volume): Set's the volume (for Kodi overall, not only a player). Feed with an int """ - return JSONRPC('Application.SetVolume').execute({"volume": volume}) + return jsonrpc('Application.SetVolume').execute({"volume": volume}) + + +def get_muted(): + """ + Returns True if Kodi is muted, False otherwise + """ + return jsonrpc('Application.GetProperties').execute( + {"properties": ['muted']})['result']['muted'] def play(): @@ -69,7 +89,7 @@ def play(): Toggles all Kodi players to play """ for playerid in get_player_ids(): - JSONRPC("Player.PlayPause").execute({"playerid": playerid, + jsonrpc("Player.PlayPause").execute({"playerid": playerid, "play": True}) @@ -78,7 +98,7 @@ def pause(): Pauses playback for all Kodi players """ for playerid in get_player_ids(): - JSONRPC("Player.PlayPause").execute({"playerid": playerid, + jsonrpc("Player.PlayPause").execute({"playerid": playerid, "play": False}) @@ -87,7 +107,7 @@ def stop(): Stops playback for all Kodi players """ for playerid in get_player_ids(): - JSONRPC("Player.Stop").execute({"playerid": playerid}) + jsonrpc("Player.Stop").execute({"playerid": playerid}) def seek_to(offset): @@ -95,7 +115,7 @@ def seek_to(offset): Seeks all Kodi players to offset [int] """ for playerid in get_player_ids(): - JSONRPC("Player.Seek").execute( + jsonrpc("Player.Seek").execute( {"playerid": playerid, "value": milliseconds_to_kodi_time(offset)}) @@ -105,7 +125,7 @@ def smallforward(): Small step forward for all Kodi players """ for playerid in get_player_ids(): - JSONRPC("Player.Seek").execute({"playerid": playerid, + jsonrpc("Player.Seek").execute({"playerid": playerid, "value": "smallforward"}) @@ -114,7 +134,7 @@ def smallbackward(): Small step backward for all Kodi players """ for playerid in get_player_ids(): - JSONRPC("Player.Seek").execute({"playerid": playerid, + jsonrpc("Player.Seek").execute({"playerid": playerid, "value": "smallbackward"}) @@ -123,7 +143,7 @@ def skipnext(): Skips to the next item to play for all Kodi players """ for playerid in get_player_ids(): - JSONRPC("Player.GoTo").execute({"playerid": playerid, + jsonrpc("Player.GoTo").execute({"playerid": playerid, "to": "next"}) @@ -132,7 +152,7 @@ def skipprevious(): Skips to the previous item to play for all Kodi players """ for playerid in get_player_ids(): - JSONRPC("Player.GoTo").execute({"playerid": playerid, + jsonrpc("Player.GoTo").execute({"playerid": playerid, "to": "previous"}) @@ -140,46 +160,209 @@ def input_up(): """ Tells Kodi the users pushed up """ - JSONRPC("Input.Up").execute() + jsonrpc("Input.Up").execute() def input_down(): """ Tells Kodi the users pushed down """ - JSONRPC("Input.Down").execute() + jsonrpc("Input.Down").execute() def input_left(): """ Tells Kodi the users pushed left """ - JSONRPC("Input.Left").execute() + jsonrpc("Input.Left").execute() def input_right(): """ Tells Kodi the users pushed left """ - JSONRPC("Input.Right").execute() + jsonrpc("Input.Right").execute() def input_select(): """ Tells Kodi the users pushed select """ - JSONRPC("Input.Select").execute() + jsonrpc("Input.Select").execute() def input_home(): """ Tells Kodi the users pushed home """ - JSONRPC("Input.Home").execute() + jsonrpc("Input.Home").execute() def input_back(): """ Tells Kodi the users pushed back """ - JSONRPC("Input.Back").execute() + jsonrpc("Input.Back").execute() + + +def playlist_get_items(playlistid, properties): + """ + playlistid: [int] id of the Kodi playlist + properties: [list] of strings for the properties to return + e.g. 'title', 'file' + + Returns a list of Kodi playlist items as dicts with the keys specified in + properties. Or an empty list if unsuccessful. Example: + [{u'title': u'3 Idiots', u'type': u'movie', u'id': 3, u'file': + u'smb://nas/PlexMovies/3 Idiots 2009 pt1.mkv', u'label': u'3 Idiots'}] + """ + reply = jsonrpc('Playlist.GetItems').execute({ + 'playlistid': playlistid, + 'properties': properties + }) + try: + reply = reply['result']['items'] + except KeyError: + reply = [] + return reply + + +def playlist_add(playlistid, item): + """ + Adds an item to the Kodi playlist with id playlistid. item is either the + dict + {'file': filepath as string} + or + {kodi_type: kodi_id} + + Returns a dict with the key 'error' if unsuccessful. + """ + return jsonrpc('Playlist.Add').execute({'playlistid': playlistid, + 'item': item}) + + +def playlist_insert(params): + """ + Insert item(s) into playlist. Does not work for picture playlists (aka + slideshows). params is the dict + { + 'playlistid': [int] + 'position': [int] + 'item': + } + item is either the dict + {'file': filepath as string} + or + {kodi_type: kodi_id} + Returns a dict with the key 'error' if something went wrong. + """ + return jsonrpc('Playlist.Insert').execute(params) + + +def playlist_remove(playlistid, position): + """ + Removes the playlist item at position from the playlist + position: [int] + + Returns a dict with the key 'error' if something went wrong. + """ + return jsonrpc('Playlist.Remove').execute({'playlistid': playlistid, + 'position': position}) + + +def get_setting(setting): + """ + Returns the Kodi setting, a [str], or None if not possible + """ + try: + ret = jsonrpc('Settings.GetSettingValue').execute( + {'setting': setting})['result']['value'] + except (KeyError, TypeError): + ret = None + return ret + + +def set_setting(setting, value): + """ + Sets the Kodi setting, a [str], to value + """ + return jsonrpc('Settings.SetSettingValue').execute( + {'setting': setting, 'value': value}) + + +def get_tv_shows(params): + """ + Returns a list of tv shows for params (check the Kodi wiki) + """ + ret = jsonrpc('VideoLibrary.GetTVShows').execute(params) + try: + ret['result']['tvshows'] + except (KeyError, TypeError): + ret = [] + return ret + + +def get_episodes(params): + """ + Returns a list of tv show episodes for params (check the Kodi wiki) + """ + ret = jsonrpc('VideoLibrary.GetEpisodes').execute(params) + try: + ret['result']['episodes'] + except (KeyError, TypeError): + ret = [] + return ret + + +def current_audiostream(playerid): + """ + Returns a dict of the active audiostream for playerid [int]: + { + 'index': [int], audiostream index + 'language': [str] + 'name': [str] + 'codec': [str] + 'bitrate': [int] + 'channels': [int] + } + or an empty dict if unsuccessful + """ + ret = jsonrpc('Player.GetProperties').execute( + {'properties': ['currentaudiostream'], 'playerid': playerid}) + try: + ret = ret['result']['currentaudiostream'] + except (KeyError, TypeError): + ret = {} + return ret + + +def current_subtitle(playerid): + """ + Returns a dict of the active subtitle for playerid [int]: + { + 'index': [int], subtitle index + 'language': [str] + 'name': [str] + } + or an empty dict if unsuccessful + """ + ret = jsonrpc('Player.GetProperties').execute( + {'properties': ['currentsubtitle'], 'playerid': playerid}) + try: + ret = ret['result']['currentsubtitle'] + except (KeyError, TypeError): + ret = {} + return ret + + +def subtitle_enabled(playerid): + """ + Returns True if a subtitle is enabled, False otherwise + """ + ret = jsonrpc('Player.GetProperties').execute( + {'properties': ['subtitleenabled'], 'playerid': playerid}) + try: + ret = ret['result']['subtitleenabled'] + except (KeyError, TypeError): + ret = False + return ret diff --git a/resources/lib/playback_starter.py b/resources/lib/playback_starter.py index aabfc3ac..5abba986 100644 --- a/resources/lib/playback_starter.py +++ b/resources/lib/playback_starter.py @@ -12,7 +12,7 @@ from playbackutils import PlaybackUtils from utils import window from PlexFunctions import GetPlexMetadata from PlexAPI import API -from playqueue import lock +from playqueue import LOCK import variables as v from downloadutils import DownloadUtils from PKC_listitem import convert_PKC_to_listitem @@ -62,7 +62,7 @@ class Playback_Starter(Thread): # Video and Music playqueue = self.playqueue.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]) - with lock: + with LOCK: result = PlaybackUtils(xml, playqueue).play( plex_id, kodi_id, @@ -113,7 +113,7 @@ class Playback_Starter(Thread): log.info('Couldnt find item %s in Kodi db' % api.getRatingKey()) playqueue = self.playqueue.get_playqueue_from_type(typus) - with lock: + with LOCK: result = PlaybackUtils(xml, playqueue).play( plex_id, kodi_id=kodi_id, diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 388b0dc5..b302a4a2 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -18,7 +18,7 @@ from PlexFunctions import init_plex_playqueue from PKC_listitem import PKC_ListItem as ListItem, convert_PKC_to_listitem from playlist_func import add_item_to_kodi_playlist, \ get_playlist_details_from_xml, add_listitem_to_Kodi_playlist, \ - add_listitem_to_playlist, remove_from_Kodi_playlist + add_listitem_to_playlist, remove_from_kodi_playlist from pickler import Playback_Successful from plexdb_functions import Get_Plex_DB import variables as v @@ -155,7 +155,7 @@ class PlaybackUtils(): playurl, xml[0]) # Remove the original item from playlist - remove_from_Kodi_playlist( + remove_from_kodi_playlist( playqueue, startPos+1) # Readd the original item to playlist - via jsonrpc so we have diff --git a/resources/lib/player.py b/resources/lib/player.py index 3d6944e9..4bb51a83 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -10,12 +10,13 @@ from utils import window, DateToKodi, getUnixTimestamp, tryDecode, tryEncode import downloadutils import plexdb_functions as plexdb import kodidb_functions as kodidb +import json_rpc as js import variables as v import state ############################################################################### -log = logging.getLogger("PLEX."+__name__) +LOG = logging.getLogger("PLEX." + __name__) ############################################################################### @@ -29,7 +30,7 @@ class Player(xbmc.Player): def __init__(self): self.doUtils = downloadutils.DownloadUtils xbmc.Player.__init__(self) - log.info("Started playback monitor.") + LOG.info("Started playback monitor.") def onPlayBackStarted(self): """ @@ -56,7 +57,7 @@ class Player(xbmc.Player): else: count += 1 if not currentFile: - log.warn('Error getting currently playing file; abort reporting') + LOG.warn('Error getting currently playing file; abort reporting') return # Save currentFile for cleanup later and for references @@ -69,11 +70,11 @@ class Player(xbmc.Player): xbmc.sleep(200) itemId = window("plex_%s.itemid" % tryEncode(currentFile)) if count == 5: - log.warn("Could not find itemId, cancelling playback report!") + LOG.warn("Could not find itemId, cancelling playback report!") return count += 1 - log.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId)) + LOG.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId)) plexitem = "plex_%s" % tryEncode(currentFile) runtime = window("%s.runtime" % plexitem) @@ -86,40 +87,26 @@ class Player(xbmc.Player): playcount = 0 window('plex_skipWatched%s' % itemId, value="true") - log.debug("Playing itemtype is: %s" % itemType) + LOG.debug("Playing itemtype is: %s" % itemType) customseek = window('plex_customplaylist.seektime') if customseek: # Start at, when using custom playlist (play to Kodi from # webclient) - log.info("Seeking to: %s" % customseek) + LOG.info("Seeking to: %s" % customseek) try: self.seekTime(int(customseek)) except: - log.error('Could not seek!') + LOG.error('Could not seek!') window('plex_customplaylist.seektime', clear=True) try: seekTime = self.getTime() except RuntimeError: - log.error('Could not get current seektime from xbmc player') + LOG.error('Could not get current seektime from xbmc player') seekTime = 0 - - # Get playback volume - volume_query = { - "jsonrpc": "2.0", - "id": 1, - "method": "Application.GetProperties", - "params": { - "properties": ["volume", "muted"] - } - } - result = xbmc.executeJSONRPC(json.dumps(volume_query)) - result = json.loads(result) - result = result.get('result') - - volume = result.get('volume') - muted = result.get('muted') + volume = js.get_volume() + muted = js.get_muted() # Postdata structure to send to plex server url = "{server}/:/timeline?" @@ -144,35 +131,13 @@ class Player(xbmc.Player): % tryEncode(currentFile)) else: # Get the current kodi audio and subtitles and convert to plex equivalent - tracks_query = { - "jsonrpc": "2.0", - "id": 1, - "method": "Player.GetProperties", - "params": { - - "playerid": 1, - "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"] - } - } - result = xbmc.executeJSONRPC(json.dumps(tracks_query)) - result = json.loads(result) - result = result.get('result') - - try: # Audio tracks - indexAudio = result['currentaudiostream']['index'] - except (KeyError, TypeError): - indexAudio = 0 - - try: # Subtitles tracks - indexSubs = result['currentsubtitle']['index'] - except (KeyError, TypeError): + indexAudio = js.current_audiostream(1).get('index', 0) + subsEnabled = js.subtitle_enabled(1) + if subsEnabled: + indexSubs = js.current_subtitle(1).get('index', 0) + else: indexSubs = 0 - try: # If subtitles are enabled - subsEnabled = result['subtitleenabled'] - except (KeyError, TypeError): - subsEnabled = "" - # Postdata for the audio postdata['AudioStreamIndex'] = indexAudio + 1 @@ -185,7 +150,7 @@ class Player(xbmc.Player): if mapping: # Set in playbackutils.py - log.debug("Mapping for external subtitles index: %s" + LOG.debug("Mapping for external subtitles index: %s" % mapping) externalIndex = json.loads(mapping) @@ -213,9 +178,9 @@ class Player(xbmc.Player): except ValueError: try: runtime = self.getTotalTime() - log.error("Runtime is missing, Kodi runtime: %s" % runtime) + LOG.error("Runtime is missing, Kodi runtime: %s" % runtime) except: - log.error('Could not get kodi runtime, setting to zero') + LOG.error('Could not get kodi runtime, setting to zero') runtime = 0 with plexdb.Get_Plex_DB() as plex_db: @@ -223,7 +188,7 @@ class Player(xbmc.Player): try: fileid = plex_dbitem[1] except TypeError: - log.info("Could not find fileid in plex db.") + LOG.info("Could not find fileid in plex db.") fileid = None # Save data map for updates and position calls data = { @@ -242,7 +207,7 @@ class Player(xbmc.Player): } self.played_info[currentFile] = data - log.info("ADDING_FILE: %s" % data) + LOG.info("ADDING_FILE: %s" % data) # log some playback stats '''if(itemType != None): @@ -262,7 +227,7 @@ class Player(xbmc.Player): def onPlayBackPaused(self): currentFile = self.currentFile - log.info("PLAYBACK_PAUSED: %s" % currentFile) + LOG.info("PLAYBACK_PAUSED: %s" % currentFile) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = True @@ -270,7 +235,7 @@ class Player(xbmc.Player): def onPlayBackResumed(self): currentFile = self.currentFile - log.info("PLAYBACK_RESUMED: %s" % currentFile) + LOG.info("PLAYBACK_RESUMED: %s" % currentFile) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = False @@ -278,7 +243,7 @@ class Player(xbmc.Player): def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate currentFile = self.currentFile - log.info("PLAYBACK_SEEK: %s" % currentFile) + LOG.info("PLAYBACK_SEEK: %s" % currentFile) if self.played_info.get(currentFile): try: @@ -290,7 +255,7 @@ class Player(xbmc.Player): def onPlayBackStopped(self): # Will be called when user stops xbmc playing a file - log.info("ONPLAYBACK_STOPPED") + LOG.info("ONPLAYBACK_STOPPED") self.stopAll() @@ -303,24 +268,24 @@ class Player(xbmc.Player): # We might have saved a transient token from a user flinging media via # Companion (if we could not use the playqueue to store the token) state.PLEX_TRANSIENT_TOKEN = None - log.debug("Cleared playlist properties.") + LOG.debug("Cleared playlist properties.") def onPlayBackEnded(self): # Will be called when xbmc stops playing a file, because the file ended - log.info("ONPLAYBACK_ENDED") + LOG.info("ONPLAYBACK_ENDED") self.onPlayBackStopped() def stopAll(self): if not self.played_info: return - log.info("Played_information: %s" % self.played_info) + LOG.info("Played_information: %s" % self.played_info) # Process each items for item in self.played_info: data = self.played_info.get(item) if not data: continue - log.debug("Item path: %s" % item) - log.debug("Item data: %s" % data) + LOG.debug("Item path: %s" % item) + LOG.debug("Item data: %s" % data) runtime = data['runtime'] currentPosition = data['currentPosition'] @@ -340,7 +305,7 @@ class Player(xbmc.Player): except ZeroDivisionError: # Runtime is 0. percentComplete = 0 - log.info("Percent complete: %s Mark played at: %s" + LOG.info("Percent complete: %s Mark played at: %s" % (percentComplete, v.MARK_PLAYED_AT)) if percentComplete >= v.MARK_PLAYED_AT: # Tell Kodi that we've finished watching (Plex knows) @@ -374,7 +339,7 @@ class Player(xbmc.Player): # Stop transcoding if playMethod == "Transcode": - log.info("Transcoding for %s terminating" % itemid) + LOG.info("Transcoding for %s terminating" % itemid) self.doUtils().downloadUrl( "{server}/video/:/transcode/universal/stop", parameters={'session': window('plex_client_Id')}) diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py index 054f7bfd..cb4a1883 100644 --- a/resources/lib/playlist_func.py +++ b/resources/lib/playlist_func.py @@ -1,3 +1,6 @@ +""" +Collection of functions associated with Kodi and Plex playlists and playqueues +""" import logging from urllib import quote from urlparse import parse_qsl, urlsplit @@ -5,13 +8,14 @@ from re import compile as re_compile import plexdb_functions as plexdb from downloadutils import DownloadUtils as DU -from utils import JSONRPC, tryEncode, escape_html +from utils import tryEncode, escape_html from PlexAPI import API from PlexFunctions import GetPlexMetadata +import json_rpc as js ############################################################################### -log = logging.getLogger("PLEX."+__name__) +LOG = logging.getLogger("PLEX." + __name__) REGEX = re_compile(r'''metadata%2F(\d+)''') ############################################################################### @@ -29,7 +33,7 @@ class Playlist_Object_Baseclase(object): kodi_pl = None items = [] old_kodi_pl = [] - ID = None + id = None version = None selectedItemID = None selectedItemOffset = None @@ -43,10 +47,10 @@ class Playlist_Object_Baseclase(object): """ answ = "<%s: " % (self.__class__.__name__) # For some reason, can't use dir directly - answ += "ID: %s, " % self.ID + answ += "id: %s, " % self.id answ += "items: %s, " % self.items for key in self.__dict__: - if key not in ("ID", 'items'): + if key not in ("id", 'items'): if type(getattr(self, key)) in (str, unicode): answ += '%s: %s, ' % (key, tryEncode(getattr(self, key))) else: @@ -61,14 +65,14 @@ class Playlist_Object_Baseclase(object): self.kodi_pl.clear() # Clear Kodi playlist object self.items = [] self.old_kodi_pl = [] - self.ID = None + self.id = None self.version = None self.selectedItemID = None self.selectedItemOffset = None self.shuffled = 0 self.repeat = 0 self.plex_transient_token = None - log.debug('Playlist cleared: %s' % self) + LOG.debug('Playlist cleared: %s', self) class Playlist_Object(Playlist_Object_Baseclase): @@ -82,12 +86,12 @@ class Playqueue_Object(Playlist_Object_Baseclase): """ PKC object to represent PMS playQueues and Kodi playlist for queueing - playlistid = None [int] Kodi playlist ID (0, 1, 2) + playlistid = None [int] Kodi playlist id (0, 1, 2) type = None [str] Kodi type: 'audio', 'video', 'picture' kodi_pl = None Kodi xbmc.PlayList object items = [] [list] of Playlist_Items old_kodi_pl = [] [list] store old Kodi JSON result with all pl items - ID = None [str] Plex playQueueID, unique Plex identifier + id = None [str] Plex playQueueID, unique Plex identifier version = None [int] Plex version of the playQueue selectedItemID = None [str] Plex selectedItemID, playing element in queue @@ -106,7 +110,7 @@ class Playlist_Item(object): """ Object to fill our playqueues and playlists with. - ID = None [str] Plex playlist/playqueue id, e.g. playQueueItemID + id = None [str] Plex playlist/playqueue id, e.g. playQueueItemID plex_id = None [str] Plex unique item id, "ratingKey" plex_type = None [str] Plex type, e.g. 'movie', 'clip' plex_UUID = None [str] Plex librarySectionUUID @@ -117,7 +121,7 @@ class Playlist_Item(object): guid = None [str] Weird Plex guid xml = None [etree] XML from PMS, 1 lvl below """ - ID = None + id = None plex_id = None plex_type = None plex_UUID = None @@ -176,7 +180,7 @@ def playlist_item_from_kodi(kodi_item): # TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' % (item.plex_UUID, item.plex_id)) - log.debug('Made playlist item from Kodi: %s' % item) + LOG.debug('Made playlist item from Kodi: %s', item) return item @@ -199,7 +203,7 @@ def playlist_item_from_plex(plex_id): item.plex_UUID = plex_id item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' % (item.plex_UUID, plex_id)) - log.debug('Made playlist item from plex: %s' % item) + LOG.debug('Made playlist item from plex: %s', item) return item @@ -213,7 +217,7 @@ def playlist_item_from_xml(playlist, xml_video_element): api = API(xml_video_element) item.plex_id = api.getRatingKey() item.plex_type = api.getType() - item.ID = xml_video_element.attrib['%sItemID' % playlist.kind] + item.id = xml_video_element.attrib['%sItemID' % playlist.kind] item.guid = xml_video_element.attrib.get('guid') if item.guid is not None: item.guid = escape_html(item.guid) @@ -225,7 +229,7 @@ def playlist_item_from_xml(playlist, xml_video_element): except TypeError: pass item.xml = xml_video_element - log.debug('Created new playlist item from xml: %s' % item) + LOG.debug('Created new playlist item from xml: %s', item) return item @@ -237,8 +241,8 @@ def _get_playListVersion_from_xml(playlist, xml): try: playlist.version = int(xml.attrib['%sVersion' % playlist.kind]) except (TypeError, AttributeError, KeyError): - log.error('Could not get new playlist Version for playlist %s' - % playlist) + LOG.error('Could not get new playlist Version for playlist %s', + playlist) return False return True @@ -249,7 +253,7 @@ def get_playlist_details_from_xml(playlist, xml): playlist.ID with the XML's playQueueID """ try: - playlist.ID = xml.attrib['%sID' % playlist.kind] + playlist.id = xml.attrib['%sID' % playlist.kind] playlist.version = xml.attrib['%sVersion' % playlist.kind] playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind] playlist.selectedItemID = xml.attrib.get( @@ -257,12 +261,12 @@ def get_playlist_details_from_xml(playlist, xml): playlist.selectedItemOffset = xml.attrib.get( '%sSelectedItemOffset' % playlist.kind) except: - log.error('Could not parse xml answer from PMS for playlist %s' - % playlist) + LOG.error('Could not parse xml answer from PMS for playlist %s', + playlist) import traceback - log.error(traceback.format_exc()) + LOG.error(traceback.format_exc()) raise KeyError - log.debug('Updated playlist from xml: %s' % playlist) + LOG.debug('Updated playlist from xml: %s', playlist) def update_playlist_from_PMS(playlist, playlist_id=None, xml=None): @@ -280,7 +284,7 @@ def update_playlist_from_PMS(playlist, playlist_id=None, xml=None): try: get_playlist_details_from_xml(playlist, xml) except KeyError: - log.error('Could not update playlist from PMS') + LOG.error('Could not update playlist from PMS') return for plex_item in xml: playlist_item = add_to_Kodi_playlist(playlist, plex_item) @@ -295,7 +299,7 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None): Returns True if successful, False otherwise """ - log.debug('Initializing the playlist %s on the Plex side' % playlist) + LOG.debug('Initializing the playlist %s on the Plex side', playlist) try: if plex_id: item = playlist_item_from_plex(plex_id) @@ -312,11 +316,11 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None): get_playlist_details_from_xml(playlist, xml) item.xml = xml[0] except (KeyError, IndexError, TypeError): - log.error('Could not init Plex playlist with plex_id %s and ' + LOG.error('Could not init Plex playlist with plex_id %s and ' 'kodi_item %s', plex_id, kodi_item) return False playlist.items.append(item) - log.debug('Initialized the playlist on the Plex side: %s' % playlist) + LOG.debug('Initialized the playlist on the Plex side: %s' % playlist) return True @@ -329,10 +333,10 @@ def add_listitem_to_playlist(playlist, pos, listitem, kodi_id=None, file: str!! """ - log.debug('add_listitem_to_playlist at position %s. Playlist before add: ' - '%s' % (pos, playlist)) + LOG.debug('add_listitem_to_playlist at position %s. Playlist before add: ' + '%s', pos, playlist) kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file} - if playlist.ID is None: + if playlist.id is None: init_Plex_playlist(playlist, plex_id, kodi_item) else: add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item) @@ -358,9 +362,9 @@ def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None, file: str! """ - log.debug('add_item_to_playlist. Playlist before adding: %s' % playlist) + LOG.debug('add_item_to_playlist. Playlist before adding: %s', playlist) kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file} - if playlist.ID is None: + if playlist.id is None: success = init_Plex_playlist(playlist, plex_id, kodi_item) else: success = add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item) @@ -376,9 +380,9 @@ def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None, params['item'] = {'%sid' % item.kodi_type: int(item.kodi_id)} else: params['item'] = {'file': item.file} - reply = JSONRPC('Playlist.Insert').execute(params) + reply = js.playlist_insert(params) if reply.get('error') is not None: - log.error('Could not add item to playlist. Kodi reply. %s', reply) + LOG.error('Could not add item to playlist. Kodi reply. %s', reply) return False return True @@ -395,25 +399,24 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None): try: item = playlist_item_from_plex(plex_id) except KeyError: - log.error('Could not add new item to the PMS playlist') + LOG.error('Could not add new item to the PMS playlist') return False else: item = playlist_item_from_kodi(kodi_item) - url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.ID, item.uri) + url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.id, item.uri) # Will always put the new item at the end of the Plex playlist xml = DU().downloadUrl(url, action_type="PUT") try: item.xml = xml[-1] item.ID = xml[-1].attrib['%sItemID' % playlist.kind] except IndexError: - log.info('Could not get playlist children. Adding a dummy') + LOG.info('Could not get playlist children. Adding a dummy') except (TypeError, AttributeError, KeyError): - log.error('Could not add item %s to playlist %s' - % (kodi_item, playlist)) + LOG.error('Could not add item %s to playlist %s', kodi_item, playlist) return False # Get the guid for this item for plex_item in xml: - if plex_item.attrib['%sItemID' % playlist.kind] == item.ID: + if plex_item.attrib['%sItemID' % playlist.kind] == item.id: item.guid = escape_html(plex_item.attrib['guid']) playlist.items.append(item) if pos == len(playlist.items) - 1: @@ -424,7 +427,7 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None): move_playlist_item(playlist, len(playlist.items) - 1, pos) - log.debug('Successfully added item on the Plex side: %s' % playlist) + LOG.debug('Successfully added item on the Plex side: %s', playlist) return True @@ -437,9 +440,9 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None, file: str! """ - log.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi ' - 'only at position %s for %s' - % (kodi_id, kodi_type, file, pos, playlist)) + LOG.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi ' + 'only at position %s for %s', + kodi_id, kodi_type, file, pos, playlist) params = { 'playlistid': playlist.playlistid, 'position': pos @@ -448,18 +451,18 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None, params['item'] = {'%sid' % kodi_type: int(kodi_id)} else: params['item'] = {'file': file} - reply = JSONRPC('Playlist.Insert').execute(params) + reply = js.playlist_insert(params) if reply.get('error') is not None: - log.error('Could not add item to playlist. Kodi reply. %s' % reply) + LOG.error('Could not add item to playlist. Kodi reply. %s', reply) return False item = playlist_item_from_kodi( - {'id': kodi_id, 'type': kodi_type, 'file': file}, playlist) + {'id': kodi_id, 'type': kodi_type, 'file': file}) if item.plex_id is not None: xml = GetPlexMetadata(item.plex_id) try: item.xml = xml[-1] except (TypeError, IndexError): - log.error('Could not get metadata for playlist item %s', item) + LOG.error('Could not get metadata for playlist item %s', item) playlist.items.insert(pos, item) return True @@ -470,26 +473,26 @@ def move_playlist_item(playlist, before_pos, after_pos): WILL ALSO CHANGE OUR PLAYLISTS. Returns True if successful """ - log.debug('Moving item from %s to %s on the Plex side for %s' - % (before_pos, after_pos, playlist)) + LOG.debug('Moving item from %s to %s on the Plex side for %s', + before_pos, after_pos, playlist) if after_pos == 0: url = "{server}/%ss/%s/items/%s/move?after=0" % \ (playlist.kind, - playlist.ID, - playlist.items[before_pos].ID) + playlist.id, + playlist.items[before_pos].id) else: url = "{server}/%ss/%s/items/%s/move?after=%s" % \ (playlist.kind, - playlist.ID, - playlist.items[before_pos].ID, - playlist.items[after_pos - 1].ID) + playlist.id, + playlist.items[before_pos].id, + playlist.items[after_pos - 1].id) # We need to increment the playlistVersion if _get_playListVersion_from_xml( playlist, DU().downloadUrl(url, action_type="PUT")) is False: return False # Move our item's position in our internal playlist playlist.items.insert(after_pos, playlist.items.pop(before_pos)) - log.debug('Done moving for %s' % playlist) + LOG.debug('Done moving for %s' % playlist) return True @@ -500,7 +503,7 @@ def get_PMS_playlist(playlist, playlist_id=None): Returns None if something went wrong """ - playlist_id = playlist_id if playlist_id else playlist.ID + playlist_id = playlist_id if playlist_id else playlist.id xml = DU().downloadUrl( "{server}/%ss/%s" % (playlist.kind, playlist_id), headerOptions={'Accept': 'application/xml'}) @@ -520,59 +523,24 @@ def refresh_playlist_from_PMS(playlist): try: get_playlist_details_from_xml(playlist, xml) except KeyError: - log.error('Could not refresh playlist from PMS') + LOG.error('Could not refresh playlist from PMS') def delete_playlist_item_from_PMS(playlist, pos): """ Delete the item at position pos [int] on the Plex side and our playlists """ - log.debug('Deleting position %s for %s on the Plex side' % (pos, playlist)) + LOG.debug('Deleting position %s for %s on the Plex side', pos, playlist) xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" % (playlist.kind, - playlist.ID, - playlist.items[pos].ID, + playlist.id, + playlist.items[pos].id, playlist.repeat), action_type="DELETE") _get_playListVersion_from_xml(playlist, xml) del playlist.items[pos] -def get_kodi_playlist_items(playlist): - """ - Returns a list of the current Kodi playlist items using JSON - - E.g.: - [{u'title': u'3 Idiots', u'type': u'movie', u'id': 3, u'file': - u'smb://nas/PlexMovies/3 Idiots 2009 pt1.mkv', u'label': u'3 Idiots'}] - """ - answ = JSONRPC('Playlist.GetItems').execute({ - 'playlistid': playlist.playlistid, - 'properties': ["title", "file"] - }) - try: - answ = answ['result']['items'] - except KeyError: - answ = [] - return answ - - -def get_kodi_playqueues(): - """ - Example return: [{u'playlistid': 0, u'type': u'audio'}, - {u'playlistid': 1, u'type': u'video'}, - {u'playlistid': 2, u'type': u'picture'}] - """ - queues = JSONRPC('Playlist.GetPlaylists').execute() - try: - queues = queues['result'] - except KeyError: - log.error('Could not get Kodi playqueues. JSON Result was: %s' - % queues) - queues = [] - return queues - - # Functions operating on the Kodi playlist objects ########## def add_to_Kodi_playlist(playlist, xml_video_element): @@ -583,17 +551,14 @@ def add_to_Kodi_playlist(playlist, xml_video_element): Returns a Playlist_Item or None if it did not work """ item = playlist_item_from_xml(playlist, xml_video_element) - params = { - 'playlistid': playlist.playlistid - } if item.kodi_id: - params['item'] = {'%sid' % item.kodi_type: item.kodi_id} + json_item = {'%sid' % item.kodi_type: item.kodi_id} else: - params['item'] = {'file': item.file} - reply = JSONRPC('Playlist.Add').execute(params) + json_item = {'file': item.file} + reply = js.playlist_add(playlist.playlistid, json_item) if reply.get('error') is not None: - log.error('Could not add item %s to Kodi playlist. Error: %s' - % (xml_video_element, reply)) + LOG.error('Could not add item %s to Kodi playlist. Error: %s', + xml_video_element, reply) return None return item @@ -607,8 +572,8 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file, file: string! """ - log.debug('Insert listitem at position %s for Kodi only for %s' - % (pos, playlist)) + LOG.debug('Insert listitem at position %s for Kodi only for %s', + pos, playlist) # Add the item into Kodi playlist playlist.kodi_pl.add(file, listitem, index=pos) # We need to add this to our internal queue as well @@ -619,29 +584,25 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file, if file is not None: item.file = file playlist.items.insert(pos, item) - log.debug('Done inserting for %s' % playlist) + LOG.debug('Done inserting for %s', playlist) -def remove_from_Kodi_playlist(playlist, pos): +def remove_from_kodi_playlist(playlist, pos): """ Removes the item at position pos from the Kodi playlist using JSON. WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS """ - log.debug('Removing position %s from Kodi only from %s' % (pos, playlist)) - reply = JSONRPC('Playlist.Remove').execute({ - 'playlistid': playlist.playlistid, - 'position': pos - }) + LOG.debug('Removing position %s from Kodi only from %s', pos, playlist) + reply = js.playlist_remove(playlist.playlistid, pos) if reply.get('error') is not None: - log.error('Could not delete the item from the playlist. Error: %s' - % reply) + LOG.error('Could not delete the item from the playlist. Error: %s', + reply) return - else: - try: - del playlist.items[pos] - except IndexError: - log.error('Cannot delete position %s for %s' % (pos, playlist)) + try: + del playlist.items[pos] + except IndexError: + LOG.error('Cannot delete position %s for %s', pos, playlist) def get_pms_playqueue(playqueue_id): @@ -654,7 +615,7 @@ def get_pms_playqueue(playqueue_id): try: xml.attrib except AttributeError: - log.error('Could not download Plex playqueue %s' % playqueue_id) + LOG.error('Could not download Plex playqueue %s', playqueue_id) xml = None return xml @@ -669,12 +630,12 @@ def get_plextype_from_xml(xml): try: plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0] except IndexError: - log.error('Could not get plex_id from xml: %s' % xml.attrib) + LOG.error('Could not get plex_id from xml: %s', xml.attrib) return new_xml = GetPlexMetadata(plex_id) try: new_xml[0].attrib except (TypeError, IndexError, AttributeError): - log.error('Could not get plex metadata for plex id %s' % plex_id) + LOG.error('Could not get plex metadata for plex id %s', plex_id) return return new_xml[0].attrib.get('type') diff --git a/resources/lib/playqueue.py b/resources/lib/playqueue.py index 2420ca0a..4203349d 100644 --- a/resources/lib/playqueue.py +++ b/resources/lib/playqueue.py @@ -1,5 +1,6 @@ -# -*- coding: utf-8 -*- -############################################################################### +""" +Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly +""" import logging from threading import RLock, Thread @@ -10,13 +11,14 @@ import playlist_func as PL from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren from PlexAPI import API from playbackutils import PlaybackUtils +import json_rpc as js import variables as v ############################################################################### -log = logging.getLogger("PLEX."+__name__) +LOG = logging.getLogger("PLEX." + __name__) -# Lock used for playqueue manipulations -lock = RLock() +# lock used for playqueue manipulations +LOCK = RLock() PLUGIN = 'plugin://%s' % v.ADDON_ID ############################################################################### @@ -33,15 +35,15 @@ class Playqueue(Thread): def __init__(self, callback=None): self.__dict__ = self.__shared_state if self.playqueues is not None: - log.debug('Playqueue thread has already been initialized') + LOG.debug('Playqueue thread has already been initialized') Thread.__init__(self) return self.mgr = callback # Initialize Kodi playqueues - with lock: + with LOCK: self.playqueues = [] - for queue in PL.get_kodi_playqueues(): + for queue in js.get_playlists(): playqueue = PL.Playqueue_Object() playqueue.playlistid = queue['playlistid'] playqueue.type = queue['type'] @@ -59,7 +61,7 @@ class Playqueue(Thread): # sort the list by their playlistid, just in case self.playqueues = sorted( self.playqueues, key=lambda i: i.playlistid) - log.debug('Initialized the Kodi play queues: %s' % self.playqueues) + LOG.debug('Initialized the Kodi play queues: %s' % self.playqueues) Thread.__init__(self) def get_playqueue_from_type(self, typus): @@ -67,7 +69,7 @@ class Playqueue(Thread): Returns the playqueue according to the typus ('video', 'audio', 'picture') passed in """ - with lock: + with LOCK: for playqueue in self.playqueues: if playqueue.type == typus: break @@ -85,7 +87,7 @@ class Playqueue(Thread): try: xml[0].attrib except (TypeError, IndexError, AttributeError): - log.error('Could not download the PMS xml for %s' % plex_id) + LOG.error('Could not download the PMS xml for %s' % plex_id) return playqueue = self.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']]) @@ -93,7 +95,7 @@ class Playqueue(Thread): for i, child in enumerate(xml): api = API(child) PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey()) - log.debug('Firing up Kodi player') + LOG.debug('Firing up Kodi player') Player().play(playqueue.kodi_pl, None, False, 0) return playqueue @@ -109,15 +111,15 @@ class Playqueue(Thread): repeat = 0, 1, 2 offset = time offset in Plextime (milliseconds) """ - log.info('New playqueue %s received from Plex companion with offset ' + LOG.info('New playqueue %s received from Plex companion with offset ' '%s, repeat %s' % (playqueue_id, offset, repeat)) - with lock: + with LOCK: xml = PL.get_PMS_playlist(playqueue, playqueue_id) playqueue.clear() try: PL.get_playlist_details_from_xml(playqueue, xml) except KeyError: - log.error('Could not get playqueue ID %s' % playqueue_id) + LOG.error('Could not get playqueue ID %s' % playqueue_id) return PlaybackUtils(xml, playqueue).play_all() playqueue.repeat = 0 if not repeat else int(repeat) @@ -126,12 +128,12 @@ class Playqueue(Thread): window('plex_customplaylist.seektime', str(ConvertPlexToKodiTime(offset))) for startpos, item in enumerate(playqueue.items): - if item.ID == playqueue.selectedItemID: + if item.id == playqueue.selectedItemID: break else: startpos = 0 # Start playback. Player does not return in time - log.debug('Playqueues after Plex Companion update are now: %s' + LOG.debug('Playqueues after Plex Companion update are now: %s' % self.playqueues) thread = Thread(target=Player().play, args=(playqueue.kodi_pl, @@ -147,7 +149,7 @@ class Playqueue(Thread): """ old = list(playqueue.items) index = list(range(0, len(old))) - log.debug('Comparing new Kodi playqueue %s with our play queue %s' + LOG.debug('Comparing new Kodi playqueue %s with our play queue %s' % (new, old)) if self.thread_stopped(): # Chances are that we got an empty Kodi playlist due to @@ -176,15 +178,15 @@ class Playqueue(Thread): del old[j], index[j] break elif identical: - log.debug('Detected playqueue item %s moved to position %s' + LOG.debug('Detected playqueue item %s moved to position %s' % (i+j, i)) PL.move_playlist_item(playqueue, i + j, i) del old[j], index[j] break else: - log.debug('Detected new Kodi element at position %s: %s ' + LOG.debug('Detected new Kodi element at position %s: %s ' % (i, new_item)) - if playqueue.ID is None: + if playqueue.id is None: PL.init_Plex_playlist(playqueue, kodi_item=new_item) else: @@ -194,17 +196,18 @@ class Playqueue(Thread): for j in range(i, len(index)): index[j] += 1 for i in reversed(index): - log.debug('Detected deletion of playqueue element at pos %s' % i) + LOG.debug('Detected deletion of playqueue element at pos %s' % i) PL.delete_playlist_item_from_PMS(playqueue, i) - log.debug('Done comparing playqueues') + LOG.debug('Done comparing playqueues') def run(self): thread_stopped = self.thread_stopped thread_suspended = self.thread_suspended - log.info("----===## Starting PlayQueue client ##===----") + LOG.info("----===## Starting PlayQueue client ##===----") # Initialize the playqueues, if Kodi already got items in them for playqueue in self.playqueues: - for i, item in enumerate(PL.get_kodi_playlist_items(playqueue)): + for i, item in enumerate(js.playlist_get_items( + playqueue.id, ["title", "file"])): if i == 0: PL.init_Plex_playlist(playqueue, kodi_item=item) else: @@ -214,9 +217,10 @@ class Playqueue(Thread): if thread_stopped(): break sleep(1000) - with lock: + with LOCK: for playqueue in self.playqueues: - kodi_playqueue = PL.get_kodi_playlist_items(playqueue) + kodi_playqueue = js.playlist_get_items(playqueue.id, + ["title", "file"]) if playqueue.old_kodi_pl != kodi_playqueue: # compare old and new playqueue self._compare_playqueues(playqueue, kodi_playqueue) @@ -226,4 +230,4 @@ class Playqueue(Thread): sleep(10) continue sleep(200) - log.info("----===## PlayQueue client stopped ##===----") + LOG.info("----===## PlayQueue client stopped ##===----") diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 3b5f9f8f..123ebd0d 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -333,14 +333,14 @@ def create_actor_db_index(): def getScreensaver(): # Get the current screensaver value params = {'setting': "screensaver.mode"} - return JSONRPC('Settings.getSettingValue').execute(params)['result']['value'] + return jsonrpc('Settings.getSettingValue').execute(params)['result']['value'] def setScreensaver(value): # Toggle the screensaver params = {'setting': "screensaver.mode", 'value': value} log.debug('Toggling screensaver to "%s": %s' - % (value, JSONRPC('Settings.setSettingValue').execute(params))) + % (value, jsonrpc('Settings.setSettingValue').execute(params))) def reset(): @@ -1141,7 +1141,7 @@ def changePlayState(itemType, kodiId, playCount, lastplayed): log.debug("JSON result was: %s" % result) -class JSONRPC(object): +class jsonrpc(object): id_ = 1 jsonrpc = "2.0"