diff --git a/resources/lib/companion.py b/resources/lib/companion.py index 7608c920..feda49a6 100644 --- a/resources/lib/companion.py +++ b/resources/lib/companion.py @@ -1,94 +1,50 @@ -# -*- coding: utf-8 -*- -import logging +""" +Processes Plex companion inputs from the plexbmchelper to Kodi commands +""" +from logging import getLogger from xbmc import Player -from utils import JSONRPC from variables import ALEXA_TO_COMPANION from playqueue import Playqueue from PlexFunctions import GetPlexKeyNumber +import json_rpc as js ############################################################################### -log = logging.getLogger("PLEX."+__name__) +LOG = getLogger("PLEX." + __name__) ############################################################################### -def getPlayers(): - info = JSONRPC("Player.GetActivePlayers").execute()['result'] or [] - ret = {} - for player in info: - player['playerid'] = int(player['playerid']) - ret[player['type']] = player - return ret - - -def getPlayerIds(): - ret = [] - for player in getPlayers().values(): - ret.append(player['playerid']) - return ret - - -def getPlaylistId(typus): +def skip_to(params): """ - typus: one of the Kodi types, e.g. audio or video + Skip to a specific playlist position. - Returns None if nothing was found + Does not seem to be implemented yet by Plex! """ - for playlist in getPlaylists(): - if playlist.get('type') == typus: - return playlist.get('playlistid') - - -def getPlaylists(): - """ - Returns a list, e.g. - [ - {u'playlistid': 0, u'type': u'audio'}, - {u'playlistid': 1, u'type': u'video'}, - {u'playlistid': 2, u'type': u'picture'} - ] - """ - return JSONRPC('Playlist.GetPlaylists').execute() - - -def millisToTime(t): - millis = int(t) - seconds = millis / 1000 - minutes = seconds / 60 - hours = minutes / 60 - seconds = seconds % 60 - minutes = minutes % 60 - millis = millis % 1000 - return {'hours': hours, - 'minutes': minutes, - 'seconds': seconds, - 'milliseconds': millis} - - -def skipTo(params): - # Does not seem to be implemented yet - playQueueItemID = params.get('playQueueItemID', 'not available') - library, plex_id = GetPlexKeyNumber(params.get('key')) - log.debug('Skipping to playQueueItemID %s, plex_id %s' - % (playQueueItemID, plex_id)) + playqueue_item_id = params.get('playQueueItemID', 'not available') + _, plex_id = GetPlexKeyNumber(params.get('key')) + LOG.debug('Skipping to playQueueItemID %s, plex_id %s', + playqueue_item_id, plex_id) found = True playqueues = Playqueue() - for (player, ID) in getPlayers().iteritems(): + for (player, _) in js.get_players().iteritems(): playqueue = playqueues.get_playqueue_from_type(player) for i, item in enumerate(playqueue.items): - if item.ID == playQueueItemID or item.plex_id == plex_id: + if item.ID == playqueue_item_id or item.plex_id == plex_id: break else: - log.debug('Item not found to skip to') + LOG.debug('Item not found to skip to') found = False if found: Player().play(playqueue.kodi_pl, None, False, i) def convert_alexa_to_companion(dictionary): + """ + The params passed by Alexa must first be converted to Companion talk + """ for key in dictionary: if key in ALEXA_TO_COMPANION: dictionary[ALEXA_TO_COMPANION[key]] = dictionary[key] @@ -101,7 +57,7 @@ def process_command(request_path, params, queue=None): """ if params.get('deviceName') == 'Alexa': convert_alexa_to_companion(params) - log.debug('Received request_path: %s, params: %s' % (request_path, params)) + LOG.debug('Received request_path: %s, params: %s', request_path, params) if "/playMedia" in request_path: # We need to tell service.py action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist' @@ -109,84 +65,47 @@ def process_command(request_path, params, queue=None): 'action': action, 'data': params }) - elif request_path == 'player/playback/refreshPlayQueue': queue.put({ 'action': 'refreshPlayQueue', 'data': params }) - elif request_path == "player/playback/setParameters": if 'volume' in params: - volume = int(params['volume']) - log.debug("Adjusting the volume to %s" % volume) - JSONRPC('Application.SetVolume').execute({"volume": volume}) + js.set_volume(int(params['volume'])) else: - log.error('Unknown parameters: %s' % params) - + LOG.error('Unknown parameters: %s', params) elif request_path == "player/playback/play": - for playerid in getPlayerIds(): - JSONRPC("Player.PlayPause").execute({"playerid": playerid, - "play": True}) - + js.play() elif request_path == "player/playback/pause": - for playerid in getPlayerIds(): - JSONRPC("Player.PlayPause").execute({"playerid": playerid, - "play": False}) - + js.pause() elif request_path == "player/playback/stop": - for playerid in getPlayerIds(): - JSONRPC("Player.Stop").execute({"playerid": playerid}) - + js.stop() elif request_path == "player/playback/seekTo": - for playerid in getPlayerIds(): - JSONRPC("Player.Seek").execute( - {"playerid": playerid, - "value": millisToTime(params.get('offset', 0))}) - + js.seek_to(int(params.get('offset', 0))) elif request_path == "player/playback/stepForward": - for playerid in getPlayerIds(): - JSONRPC("Player.Seek").execute({"playerid": playerid, - "value": "smallforward"}) - + js.smallforward() elif request_path == "player/playback/stepBack": - for playerid in getPlayerIds(): - JSONRPC("Player.Seek").execute({"playerid": playerid, - "value": "smallbackward"}) - + js.smallbackward() elif request_path == "player/playback/skipNext": - for playerid in getPlayerIds(): - JSONRPC("Player.GoTo").execute({"playerid": playerid, - "to": "next"}) - + js.skipnext() elif request_path == "player/playback/skipPrevious": - for playerid in getPlayerIds(): - JSONRPC("Player.GoTo").execute({"playerid": playerid, - "to": "previous"}) - + js.skipprevious() elif request_path == "player/playback/skipTo": - skipTo(params) - + skip_to(params) elif request_path == "player/navigation/moveUp": - JSONRPC("Input.Up").execute() - + js.input_up() elif request_path == "player/navigation/moveDown": - JSONRPC("Input.Down").execute() - + js.input_down() elif request_path == "player/navigation/moveLeft": - JSONRPC("Input.Left").execute() - + js.input_left() elif request_path == "player/navigation/moveRight": - JSONRPC("Input.Right").execute() - + js.input_right() elif request_path == "player/navigation/select": - JSONRPC("Input.Select").execute() - + js.input_select() elif request_path == "player/navigation/home": - JSONRPC("Input.Home").execute() - + js.input_home() elif request_path == "player/navigation/back": - JSONRPC("Input.Back").execute() - + js.input_back() else: - log.error('Unknown request path: %s' % request_path) + LOG.error('Unknown request path: %s', request_path) diff --git a/resources/lib/json_rpc.py b/resources/lib/json_rpc.py new file mode 100644 index 00000000..971a5679 --- /dev/null +++ b/resources/lib/json_rpc.py @@ -0,0 +1,185 @@ +""" +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 + + +def get_players(): + """ + Returns all the active Kodi players (usually 3) in a dict: + { + 'video': {'playerid': int, 'type': 'video'} + 'audio': ... + 'picture': ... + } + """ + info = JSONRPC("Player.GetActivePlayers").execute()['result'] or [] + ret = {} + for player in info: + player['playerid'] = int(player['playerid']) + ret[player['type']] = player + return ret + + +def get_player_ids(): + """ + Returns a list of all the active Kodi player ids (usually 3) as int + """ + ret = [] + for player in get_players().values(): + ret.append(player['playerid']) + return ret + + +def get_playlist_id(typus): + """ + Returns the corresponding Kodi playlist id as an int + typus: Kodi playlist types: 'video', 'audio' or 'picture' + + Returns None if nothing was found + """ + for playlist in get_playlists(): + if playlist.get('type') == typus: + return playlist.get('playlistid') + + +def get_playlists(): + """ + Returns a list of all the Kodi playlists, e.g. + [ + {u'playlistid': 0, u'type': u'audio'}, + {u'playlistid': 1, u'type': u'video'}, + {u'playlistid': 2, u'type': u'picture'} + ] + """ + return JSONRPC('Playlist.GetPlaylists').execute() + + +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}) + + +def play(): + """ + Toggles all Kodi players to play + """ + for playerid in get_player_ids(): + JSONRPC("Player.PlayPause").execute({"playerid": playerid, + "play": True}) + + +def pause(): + """ + Pauses playback for all Kodi players + """ + for playerid in get_player_ids(): + JSONRPC("Player.PlayPause").execute({"playerid": playerid, + "play": False}) + + +def stop(): + """ + Stops playback for all Kodi players + """ + for playerid in get_player_ids(): + JSONRPC("Player.Stop").execute({"playerid": playerid}) + + +def seek_to(offset): + """ + Seeks all Kodi players to offset [int] + """ + for playerid in get_player_ids(): + JSONRPC("Player.Seek").execute( + {"playerid": playerid, + "value": milliseconds_to_kodi_time(offset)}) + + +def smallforward(): + """ + Small step forward for all Kodi players + """ + for playerid in get_player_ids(): + JSONRPC("Player.Seek").execute({"playerid": playerid, + "value": "smallforward"}) + + +def smallbackward(): + """ + Small step backward for all Kodi players + """ + for playerid in get_player_ids(): + JSONRPC("Player.Seek").execute({"playerid": playerid, + "value": "smallbackward"}) + + +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, + "to": "next"}) + + +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, + "to": "previous"}) + + +def input_up(): + """ + Tells Kodi the users pushed up + """ + JSONRPC("Input.Up").execute() + + +def input_down(): + """ + Tells Kodi the users pushed down + """ + JSONRPC("Input.Down").execute() + + +def input_left(): + """ + Tells Kodi the users pushed left + """ + JSONRPC("Input.Left").execute() + + +def input_right(): + """ + Tells Kodi the users pushed left + """ + JSONRPC("Input.Right").execute() + + +def input_select(): + """ + Tells Kodi the users pushed select + """ + JSONRPC("Input.Select").execute() + + +def input_home(): + """ + Tells Kodi the users pushed home + """ + JSONRPC("Input.Home").execute() + + +def input_back(): + """ + Tells Kodi the users pushed back + """ + JSONRPC("Input.Back").execute() diff --git a/resources/lib/utils.py b/resources/lib/utils.py index eddc8e7f..3b5f9f8f 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -180,6 +180,23 @@ def dialog(typus, *args, **kwargs): return types[typus](*args, **kwargs) +def milliseconds_to_kodi_time(milliseconds): + """ + Converts time in milliseconds to the time dict used by the Kodi JSON RPC + Pass in the time in milliseconds as an int + """ + seconds = milliseconds / 1000 + minutes = seconds / 60 + hours = minutes / 60 + seconds = seconds % 60 + minutes = minutes % 60 + milliseconds = milliseconds % 1000 + return {'hours': hours, + 'minutes': minutes, + 'seconds': seconds, + 'milliseconds': milliseconds} + + def tryEncode(uniString, encoding='utf-8'): """ Will try to encode uniString (in unicode) to encoding. This possibly