Centralize Kodi json rpc

This commit is contained in:
croneter 2017-12-08 19:43:06 +01:00
parent f6b666e892
commit f2bc95813a
9 changed files with 472 additions and 500 deletions

View file

@ -1,15 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
import logging from logging import getLogger
from json import dumps, loads
import requests import requests
from shutil import rmtree from shutil import rmtree
from urllib import quote_plus, unquote from urllib import quote_plus, unquote
from threading import Thread from threading import Thread
from Queue import Queue, Empty 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 xbmcvfs import exists
from utils import window, settings, language as lang, kodiSQL, tryEncode, \ from utils import window, settings, language as lang, kodiSQL, tryEncode, \
@ -20,7 +20,7 @@ import requests.packages.urllib3
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@ -34,87 +34,16 @@ def setKodiWebServerDetails():
xbmc_port = None xbmc_port = None
xbmc_username = None xbmc_username = None
xbmc_password = None xbmc_password = None
web_query = { if js.get_setting('services.webserver') in (None, False):
"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:
# Enable the webserver, it is disabled # 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 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" xbmc_username = "kodi"
js.set_setting('services.webserverport', xbmc_port)
js.set_setting('services.webserver', True)
# Webserver already enabled # Webserver already enabled
web_port = { xbmc_port = js.get_setting('services.webserverport')
"jsonrpc": "2.0", xbmc_username = js.get_setting('services.webserverusername')
"id": 1, xbmc_password = js.get_setting('services.webserverpassword')
"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
return (xbmc_port, xbmc_username, xbmc_password) return (xbmc_port, xbmc_username, xbmc_password)
@ -152,7 +81,7 @@ class Image_Cache_Thread(Thread):
# Set in service.py # Set in service.py
if thread_stopped(): if thread_stopped():
# Abort was requested while waiting. We should exit # Abort was requested while waiting. We should exit
log.info("---===### Stopped Image_Cache_Thread ###===---") LOG.info("---===### Stopped Image_Cache_Thread ###===---")
return return
sleep(1000) sleep(1000)
try: try:
@ -179,10 +108,10 @@ class Image_Cache_Thread(Thread):
# Server thinks its a DOS attack, ('error 10053') # Server thinks its a DOS attack, ('error 10053')
# Wait before trying again # Wait before trying again
if sleeptime > 5: if sleeptime > 5:
log.error('Repeatedly got ConnectionError for url %s' LOG.error('Repeatedly got ConnectionError for url %s'
% double_urldecode(url)) % double_urldecode(url))
break 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 ' 'over-loaded. Sleep %s seconds before trying '
'again to download %s' 'again to download %s'
% (2**sleeptime, double_urldecode(url))) % (2**sleeptime, double_urldecode(url)))
@ -190,18 +119,18 @@ class Image_Cache_Thread(Thread):
sleeptime += 1 sleeptime += 1
continue continue
except Exception as e: except Exception as e:
log.error('Unknown exception for url %s: %s' LOG.error('Unknown exception for url %s: %s'
% (double_urldecode(url), e)) % (double_urldecode(url), e))
import traceback import traceback
log.error("Traceback:\n%s" % traceback.format_exc()) LOG.error("Traceback:\n%s" % traceback.format_exc())
break break
# We did not even get a timeout # We did not even get a timeout
break break
queue.task_done() 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 for a bit to reduce CPU strain
sleep(sleep_between) sleep(sleep_between)
log.info("---===### Stopped Image_Cache_Thread ###===---") LOG.info("---===### Stopped Image_Cache_Thread ###===---")
class Artwork(): class Artwork():
@ -217,11 +146,11 @@ class Artwork():
if not dialog('yesno', "Image Texture Cache", lang(39250)): if not dialog('yesno', "Image Texture Cache", lang(39250)):
return return
log.info("Doing Image Cache Sync") LOG.info("Doing Image Cache Sync")
# ask to rest all existing or not # ask to rest all existing or not
if dialog('yesno', "Image Texture Cache", lang(39251)): 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 # Remove all existing textures first
path = tryDecode(translatePath("special://thumbnails/")) path = tryDecode(translatePath("special://thumbnails/"))
if exists_dir(path): if exists_dir(path):
@ -248,7 +177,7 @@ class Artwork():
cursor.execute(query, ('actor', )) cursor.execute(query, ('actor', ))
result = cursor.fetchall() result = cursor.fetchall()
total = len(result) 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() connection.close()
for url in result: for url in result:
@ -259,7 +188,7 @@ class Artwork():
cursor.execute("SELECT url FROM art") cursor.execute("SELECT url FROM art")
result = cursor.fetchall() result = cursor.fetchall()
total = len(result) 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() connection.close()
for url in result: for url in result:
self.cacheTexture(url[0]) self.cacheTexture(url[0])
@ -364,7 +293,7 @@ class Artwork():
url = cursor.fetchone()[0] url = cursor.fetchone()[0]
except TypeError: except TypeError:
# Add the artwork # Add the artwork
log.debug("Adding Art Link for kodiId: %s (%s)" LOG.debug("Adding Art Link for kodiId: %s (%s)"
% (kodiId, imageUrl)) % (kodiId, imageUrl))
query = ( query = (
''' '''
@ -382,7 +311,7 @@ class Artwork():
imageType in ("fanart", "poster")): imageType in ("fanart", "poster")):
# Delete current entry before updating with the new one # Delete current entry before updating with the new one
self.deleteCachedArtwork(url) 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)) % (imageType, kodiId, url, imageUrl))
query = ' '.join(( query = ' '.join((
"UPDATE art", "UPDATE art",
@ -418,11 +347,11 @@ class Artwork():
(url,)) (url,))
cachedurl = cursor.fetchone()[0] cachedurl = cursor.fetchone()[0]
except TypeError: except TypeError:
log.info("Could not find cached url.") LOG.info("Could not find cached url.")
else: else:
# Delete thumbnail as well as the entry # Delete thumbnail as well as the entry
path = translatePath("special://thumbnails/%s" % cachedurl) path = translatePath("special://thumbnails/%s" % cachedurl)
log.debug("Deleting cached thumbnail: %s" % path) LOG.debug("Deleting cached thumbnail: %s" % path)
if exists(path): if exists(path):
rmtree(tryDecode(path), ignore_errors=True) rmtree(tryDecode(path), ignore_errors=True)
cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) cursor.execute("DELETE FROM texture WHERE url = ?", (url,))

View file

@ -12,12 +12,13 @@ from xbmc import sleep, executebuiltin, translatePath
from xbmcgui import ListItem from xbmcgui import ListItem
from utils import window, settings, language as lang, dialog, tryEncode, \ 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 import downloadutils
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \ from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
GetMachineIdentifier GetMachineIdentifier
from PlexAPI import API from PlexAPI import API
import json_rpc as js
import variables as v import variables as v
############################################################################### ###############################################################################
@ -268,7 +269,6 @@ def createListItem(item, appendShowTitle=False, appendSxxExx=False):
##### GET NEXTUP EPISODES FOR TAGNAME ##### ##### GET NEXTUP EPISODES FOR TAGNAME #####
def getNextUpEpisodes(tagname, limit): def getNextUpEpisodes(tagname, limit):
count = 0 count = 0
# if the addon is called with nextup parameter, # if the addon is called with nextup parameter,
# we return the nextepisodes list of the given tagname # we return the nextepisodes list of the given tagname
@ -283,15 +283,7 @@ def getNextUpEpisodes(tagname, limit):
]}, ]},
'properties': ['title', 'studio', 'mpaa', 'file', 'art'] 'properties': ['title', 'studio', 'mpaa', 'file', 'art']
} }
result = JSONRPC('VideoLibrary.GetTVShows').execute(params) for item in js.get_tv_shows(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": if settings('ignoreSpecialsNextEpisodes') == "true":
params = { params = {
'tvshowid': item['tvshowid'], 'tvshowid': item['tvshowid'],
@ -328,23 +320,13 @@ def getNextUpEpisodes(tagname, limit):
], ],
'limits': {"end": 1} 'limits': {"end": 1}
} }
for episode in js.get_episodes(params):
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, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'], url=episode['file'],
listitem=li) listitem=createListItem(episode))
count += 1 count += 1
if count == limit: if count == limit:
break break
xbmcplugin.endOfDirectory(handle=HANDLE) xbmcplugin.endOfDirectory(handle=HANDLE)
@ -364,14 +346,7 @@ def getInProgressEpisodes(tagname, limit):
]}, ]},
'properties': ['title', 'studio', 'mpaa', 'file', 'art'] 'properties': ['title', 'studio', 'mpaa', 'file', 'art']
} }
result = JSONRPC('VideoLibrary.GetTVShows').execute(params) for item in js.get_tv_shows(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 = { params = {
'tvshowid': item['tvshowid'], 'tvshowid': item['tvshowid'],
'sort': {'method': "episode"}, 'sort': {'method': "episode"},
@ -384,22 +359,13 @@ def getInProgressEpisodes(tagname, limit):
"tvshowid", "art", "cast", "streamdetails", "firstaired", "tvshowid", "art", "cast", "streamdetails", "firstaired",
"runtime", "writer", "dateadded", "lastplayed"] "runtime", "writer", "dateadded", "lastplayed"]
} }
result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) for episode in js.get_episodes(params):
try:
episodes = result['result']['episodes']
except (KeyError, TypeError):
pass
else:
for episode in episodes:
li = createListItem(episode)
xbmcplugin.addDirectoryItem(handle=HANDLE, xbmcplugin.addDirectoryItem(handle=HANDLE,
url=episode['file'], url=episode['file'],
listitem=li) listitem=createListItem(episode))
count += 1 count += 1
if count == limit: if count == limit:
break break
xbmcplugin.endOfDirectory(handle=HANDLE) xbmcplugin.endOfDirectory(handle=HANDLE)
##### GET RECENT EPISODES FOR TAGNAME ##### ##### GET RECENT EPISODES FOR TAGNAME #####
@ -412,22 +378,13 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit):
appendShowTitle = settings('RecentTvAppendShow') == 'true' appendShowTitle = settings('RecentTvAppendShow') == 'true'
appendSxxExx = settings('RecentTvAppendSeason') == 'true' appendSxxExx = settings('RecentTvAppendSeason') == 'true'
# First we get a list of all the TV shows - filtered by tag # First we get a list of all the TV shows - filtered by tag
allshowsIds = set()
params = { params = {
'sort': {'order': "descending", 'method': "dateadded"}, 'sort': {'order': "descending", 'method': "dateadded"},
'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname}, 'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname},
} }
result = JSONRPC('VideoLibrary.GetTVShows').execute(params) for tv_show in js.get_tv_shows(params):
# If we found any, find the oldest unwatched show for each one. allshowsIds.add(tv_show['tvshowid'])
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'])
params = { params = {
'sort': {'order': "descending", 'method': "dateadded"}, 'sort': {'order': "descending", 'method': "dateadded"},
'properties': ["title", "playcount", "season", "episode", "showtitle", 'properties': ["title", "playcount", "season", "episode", "showtitle",
@ -442,26 +399,18 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit):
'field': "playcount", 'field': "playcount",
'value': "1" 'value': "1"
} }
result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) for episode in js.get_episodes(params):
try:
episodes = result['result']['episodes']
except (KeyError, TypeError):
pass
else:
for episode in episodes:
if episode['tvshowid'] in allshowsIds: if episode['tvshowid'] in allshowsIds:
li = createListItem(episode, listitem = createListItem(episode,
appendShowTitle=appendShowTitle, appendShowTitle=appendShowTitle,
appendSxxExx=appendSxxExx) appendSxxExx=appendSxxExx)
xbmcplugin.addDirectoryItem( xbmcplugin.addDirectoryItem(
handle=HANDLE, handle=HANDLE,
url=episode['file'], url=episode['file'],
listitem=li) listitem=listitem)
count += 1 count += 1
if count == limit: if count == limit:
break break
xbmcplugin.endOfDirectory(handle=HANDLE) xbmcplugin.endOfDirectory(handle=HANDLE)
@ -644,15 +593,6 @@ def getOnDeck(viewid, mediatype, tagname, limit):
{'operator': "is", 'field': "tag", 'value': "%s" % tagname} {'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 = { params = {
'sort': {'method': "episode"}, 'sort': {'method': "episode"},
'limits': {"end": 1}, 'limits': {"end": 1},
@ -677,7 +617,6 @@ def getOnDeck(viewid, mediatype, tagname, limit):
{'operator': "true", 'field': "inprogress", 'value': ""} {'operator': "true", 'field': "inprogress", 'value': ""}
] ]
} }
# Are there any episodes still in progress/not yet finished watching?!? # Are there any episodes still in progress/not yet finished watching?!?
# Then we should show this episode, NOT the "next up" # Then we should show this episode, NOT the "next up"
inprog_params = { inprog_params = {
@ -687,35 +626,26 @@ def getOnDeck(viewid, mediatype, tagname, limit):
} }
count = 0 count = 0
for item in items: for item in js.get_tv_shows(params):
inprog_params['tvshowid'] = item['tvshowid'] inprog_params['tvshowid'] = item['tvshowid']
result = JSONRPC('VideoLibrary.GetEpisodes').execute(inprog_params) episodes = js.get_episodes(inprog_params)
try: if not episodes:
episodes = result['result']['episodes']
except (KeyError, TypeError):
# No, there are no episodes not yet finished. Get "next up" # No, there are no episodes not yet finished. Get "next up"
params['tvshowid'] = item['tvshowid'] params['tvshowid'] = item['tvshowid']
result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) episodes = js.get_episodes(params)
try:
episodes = result['result']['episodes']
except (KeyError, TypeError):
# Also no episodes currently coming up
continue
for episode in episodes: for episode in episodes:
# There will always be only 1 episode ('limit=1') # There will always be only 1 episode ('limit=1')
li = createListItem(episode, listitem = createListItem(episode,
appendShowTitle=appendShowTitle, appendShowTitle=appendShowTitle,
appendSxxExx=appendSxxExx) appendSxxExx=appendSxxExx)
xbmcplugin.addDirectoryItem( xbmcplugin.addDirectoryItem(
handle=HANDLE, handle=HANDLE,
url=episode['file'], url=episode['file'],
listitem=li, listitem=listitem,
isFolder=False) isFolder=False)
count += 1 count += 1
if count >= limit: if count >= limit:
break break
xbmcplugin.endOfDirectory(handle=HANDLE) xbmcplugin.endOfDirectory(handle=HANDLE)

View file

@ -2,7 +2,7 @@
Collection of functions using the Kodi JSON RPC interface. Collection of functions using the Kodi JSON RPC interface.
See http://kodi.wiki/view/JSON-RPC_API 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(): def get_players():
@ -14,7 +14,7 @@ def get_players():
'picture': ... 'picture': ...
} }
""" """
info = JSONRPC("Player.GetActivePlayers").execute()['result'] or [] info = jsonrpc("Player.GetActivePlayers").execute()['result'] or []
ret = {} ret = {}
for player in info: for player in info:
player['playerid'] = int(player['playerid']) player['playerid'] = int(player['playerid'])
@ -53,7 +53,19 @@ def get_playlists():
{u'playlistid': 2, u'type': u'picture'} {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): def set_volume(volume):
@ -61,7 +73,15 @@ def set_volume(volume):
Set's the volume (for Kodi overall, not only a player). Set's the volume (for Kodi overall, not only a player).
Feed with an int 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(): def play():
@ -69,7 +89,7 @@ def play():
Toggles all Kodi players to play Toggles all Kodi players to play
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JSONRPC("Player.PlayPause").execute({"playerid": playerid, jsonrpc("Player.PlayPause").execute({"playerid": playerid,
"play": True}) "play": True})
@ -78,7 +98,7 @@ def pause():
Pauses playback for all Kodi players Pauses playback for all Kodi players
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JSONRPC("Player.PlayPause").execute({"playerid": playerid, jsonrpc("Player.PlayPause").execute({"playerid": playerid,
"play": False}) "play": False})
@ -87,7 +107,7 @@ def stop():
Stops playback for all Kodi players Stops playback for all Kodi players
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JSONRPC("Player.Stop").execute({"playerid": playerid}) jsonrpc("Player.Stop").execute({"playerid": playerid})
def seek_to(offset): def seek_to(offset):
@ -95,7 +115,7 @@ def seek_to(offset):
Seeks all Kodi players to offset [int] Seeks all Kodi players to offset [int]
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JSONRPC("Player.Seek").execute( jsonrpc("Player.Seek").execute(
{"playerid": playerid, {"playerid": playerid,
"value": milliseconds_to_kodi_time(offset)}) "value": milliseconds_to_kodi_time(offset)})
@ -105,7 +125,7 @@ def smallforward():
Small step forward for all Kodi players Small step forward for all Kodi players
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JSONRPC("Player.Seek").execute({"playerid": playerid, jsonrpc("Player.Seek").execute({"playerid": playerid,
"value": "smallforward"}) "value": "smallforward"})
@ -114,7 +134,7 @@ def smallbackward():
Small step backward for all Kodi players Small step backward for all Kodi players
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JSONRPC("Player.Seek").execute({"playerid": playerid, jsonrpc("Player.Seek").execute({"playerid": playerid,
"value": "smallbackward"}) "value": "smallbackward"})
@ -123,7 +143,7 @@ def skipnext():
Skips to the next item to play for all Kodi players Skips to the next item to play for all Kodi players
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JSONRPC("Player.GoTo").execute({"playerid": playerid, jsonrpc("Player.GoTo").execute({"playerid": playerid,
"to": "next"}) "to": "next"})
@ -132,7 +152,7 @@ def skipprevious():
Skips to the previous item to play for all Kodi players Skips to the previous item to play for all Kodi players
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JSONRPC("Player.GoTo").execute({"playerid": playerid, jsonrpc("Player.GoTo").execute({"playerid": playerid,
"to": "previous"}) "to": "previous"})
@ -140,46 +160,209 @@ def input_up():
""" """
Tells Kodi the users pushed up Tells Kodi the users pushed up
""" """
JSONRPC("Input.Up").execute() jsonrpc("Input.Up").execute()
def input_down(): def input_down():
""" """
Tells Kodi the users pushed down Tells Kodi the users pushed down
""" """
JSONRPC("Input.Down").execute() jsonrpc("Input.Down").execute()
def input_left(): def input_left():
""" """
Tells Kodi the users pushed left Tells Kodi the users pushed left
""" """
JSONRPC("Input.Left").execute() jsonrpc("Input.Left").execute()
def input_right(): def input_right():
""" """
Tells Kodi the users pushed left Tells Kodi the users pushed left
""" """
JSONRPC("Input.Right").execute() jsonrpc("Input.Right").execute()
def input_select(): def input_select():
""" """
Tells Kodi the users pushed select Tells Kodi the users pushed select
""" """
JSONRPC("Input.Select").execute() jsonrpc("Input.Select").execute()
def input_home(): def input_home():
""" """
Tells Kodi the users pushed home Tells Kodi the users pushed home
""" """
JSONRPC("Input.Home").execute() jsonrpc("Input.Home").execute()
def input_back(): def input_back():
""" """
Tells Kodi the users pushed 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>
}
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

View file

@ -12,7 +12,7 @@ from playbackutils import PlaybackUtils
from utils import window from utils import window
from PlexFunctions import GetPlexMetadata from PlexFunctions import GetPlexMetadata
from PlexAPI import API from PlexAPI import API
from playqueue import lock from playqueue import LOCK
import variables as v import variables as v
from downloadutils import DownloadUtils from downloadutils import DownloadUtils
from PKC_listitem import convert_PKC_to_listitem from PKC_listitem import convert_PKC_to_listitem
@ -62,7 +62,7 @@ class Playback_Starter(Thread):
# Video and Music # Video and Music
playqueue = self.playqueue.get_playqueue_from_type( playqueue = self.playqueue.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
with lock: with LOCK:
result = PlaybackUtils(xml, playqueue).play( result = PlaybackUtils(xml, playqueue).play(
plex_id, plex_id,
kodi_id, kodi_id,
@ -113,7 +113,7 @@ class Playback_Starter(Thread):
log.info('Couldnt find item %s in Kodi db' log.info('Couldnt find item %s in Kodi db'
% api.getRatingKey()) % api.getRatingKey())
playqueue = self.playqueue.get_playqueue_from_type(typus) playqueue = self.playqueue.get_playqueue_from_type(typus)
with lock: with LOCK:
result = PlaybackUtils(xml, playqueue).play( result = PlaybackUtils(xml, playqueue).play(
plex_id, plex_id,
kodi_id=kodi_id, kodi_id=kodi_id,

View file

@ -18,7 +18,7 @@ from PlexFunctions import init_plex_playqueue
from PKC_listitem import PKC_ListItem as ListItem, convert_PKC_to_listitem from PKC_listitem import PKC_ListItem as ListItem, convert_PKC_to_listitem
from playlist_func import add_item_to_kodi_playlist, \ from playlist_func import add_item_to_kodi_playlist, \
get_playlist_details_from_xml, add_listitem_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 pickler import Playback_Successful
from plexdb_functions import Get_Plex_DB from plexdb_functions import Get_Plex_DB
import variables as v import variables as v
@ -155,7 +155,7 @@ class PlaybackUtils():
playurl, playurl,
xml[0]) xml[0])
# Remove the original item from playlist # Remove the original item from playlist
remove_from_Kodi_playlist( remove_from_kodi_playlist(
playqueue, playqueue,
startPos+1) startPos+1)
# Readd the original item to playlist - via jsonrpc so we have # Readd the original item to playlist - via jsonrpc so we have

View file

@ -10,12 +10,13 @@ from utils import window, DateToKodi, getUnixTimestamp, tryDecode, tryEncode
import downloadutils import downloadutils
import plexdb_functions as plexdb import plexdb_functions as plexdb
import kodidb_functions as kodidb import kodidb_functions as kodidb
import json_rpc as js
import variables as v import variables as v
import state import state
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) LOG = logging.getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@ -29,7 +30,7 @@ class Player(xbmc.Player):
def __init__(self): def __init__(self):
self.doUtils = downloadutils.DownloadUtils self.doUtils = downloadutils.DownloadUtils
xbmc.Player.__init__(self) xbmc.Player.__init__(self)
log.info("Started playback monitor.") LOG.info("Started playback monitor.")
def onPlayBackStarted(self): def onPlayBackStarted(self):
""" """
@ -56,7 +57,7 @@ class Player(xbmc.Player):
else: else:
count += 1 count += 1
if not currentFile: if not currentFile:
log.warn('Error getting currently playing file; abort reporting') LOG.warn('Error getting currently playing file; abort reporting')
return return
# Save currentFile for cleanup later and for references # Save currentFile for cleanup later and for references
@ -69,11 +70,11 @@ class Player(xbmc.Player):
xbmc.sleep(200) xbmc.sleep(200)
itemId = window("plex_%s.itemid" % tryEncode(currentFile)) itemId = window("plex_%s.itemid" % tryEncode(currentFile))
if count == 5: if count == 5:
log.warn("Could not find itemId, cancelling playback report!") LOG.warn("Could not find itemId, cancelling playback report!")
return return
count += 1 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) plexitem = "plex_%s" % tryEncode(currentFile)
runtime = window("%s.runtime" % plexitem) runtime = window("%s.runtime" % plexitem)
@ -86,40 +87,26 @@ class Player(xbmc.Player):
playcount = 0 playcount = 0
window('plex_skipWatched%s' % itemId, value="true") 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') customseek = window('plex_customplaylist.seektime')
if customseek: if customseek:
# Start at, when using custom playlist (play to Kodi from # Start at, when using custom playlist (play to Kodi from
# webclient) # webclient)
log.info("Seeking to: %s" % customseek) LOG.info("Seeking to: %s" % customseek)
try: try:
self.seekTime(int(customseek)) self.seekTime(int(customseek))
except: except:
log.error('Could not seek!') LOG.error('Could not seek!')
window('plex_customplaylist.seektime', clear=True) window('plex_customplaylist.seektime', clear=True)
try: try:
seekTime = self.getTime() seekTime = self.getTime()
except RuntimeError: except RuntimeError:
log.error('Could not get current seektime from xbmc player') LOG.error('Could not get current seektime from xbmc player')
seekTime = 0 seekTime = 0
volume = js.get_volume()
# Get playback volume muted = js.get_muted()
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')
# Postdata structure to send to plex server # Postdata structure to send to plex server
url = "{server}/:/timeline?" url = "{server}/:/timeline?"
@ -144,35 +131,13 @@ class Player(xbmc.Player):
% tryEncode(currentFile)) % tryEncode(currentFile))
else: else:
# Get the current kodi audio and subtitles and convert to plex equivalent # Get the current kodi audio and subtitles and convert to plex equivalent
tracks_query = { indexAudio = js.current_audiostream(1).get('index', 0)
"jsonrpc": "2.0", subsEnabled = js.subtitle_enabled(1)
"id": 1, if subsEnabled:
"method": "Player.GetProperties", indexSubs = js.current_subtitle(1).get('index', 0)
"params": { else:
"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):
indexSubs = 0 indexSubs = 0
try: # If subtitles are enabled
subsEnabled = result['subtitleenabled']
except (KeyError, TypeError):
subsEnabled = ""
# Postdata for the audio # Postdata for the audio
postdata['AudioStreamIndex'] = indexAudio + 1 postdata['AudioStreamIndex'] = indexAudio + 1
@ -185,7 +150,7 @@ class Player(xbmc.Player):
if mapping: # Set in playbackutils.py if mapping: # Set in playbackutils.py
log.debug("Mapping for external subtitles index: %s" LOG.debug("Mapping for external subtitles index: %s"
% mapping) % mapping)
externalIndex = json.loads(mapping) externalIndex = json.loads(mapping)
@ -213,9 +178,9 @@ class Player(xbmc.Player):
except ValueError: except ValueError:
try: try:
runtime = self.getTotalTime() runtime = self.getTotalTime()
log.error("Runtime is missing, Kodi runtime: %s" % runtime) LOG.error("Runtime is missing, Kodi runtime: %s" % runtime)
except: except:
log.error('Could not get kodi runtime, setting to zero') LOG.error('Could not get kodi runtime, setting to zero')
runtime = 0 runtime = 0
with plexdb.Get_Plex_DB() as plex_db: with plexdb.Get_Plex_DB() as plex_db:
@ -223,7 +188,7 @@ class Player(xbmc.Player):
try: try:
fileid = plex_dbitem[1] fileid = plex_dbitem[1]
except TypeError: except TypeError:
log.info("Could not find fileid in plex db.") LOG.info("Could not find fileid in plex db.")
fileid = None fileid = None
# Save data map for updates and position calls # Save data map for updates and position calls
data = { data = {
@ -242,7 +207,7 @@ class Player(xbmc.Player):
} }
self.played_info[currentFile] = data self.played_info[currentFile] = data
log.info("ADDING_FILE: %s" % data) LOG.info("ADDING_FILE: %s" % data)
# log some playback stats # log some playback stats
'''if(itemType != None): '''if(itemType != None):
@ -262,7 +227,7 @@ class Player(xbmc.Player):
def onPlayBackPaused(self): def onPlayBackPaused(self):
currentFile = self.currentFile currentFile = self.currentFile
log.info("PLAYBACK_PAUSED: %s" % currentFile) LOG.info("PLAYBACK_PAUSED: %s" % currentFile)
if self.played_info.get(currentFile): if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = True self.played_info[currentFile]['paused'] = True
@ -270,7 +235,7 @@ class Player(xbmc.Player):
def onPlayBackResumed(self): def onPlayBackResumed(self):
currentFile = self.currentFile currentFile = self.currentFile
log.info("PLAYBACK_RESUMED: %s" % currentFile) LOG.info("PLAYBACK_RESUMED: %s" % currentFile)
if self.played_info.get(currentFile): if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = False self.played_info[currentFile]['paused'] = False
@ -278,7 +243,7 @@ class Player(xbmc.Player):
def onPlayBackSeek(self, time, seekOffset): def onPlayBackSeek(self, time, seekOffset):
# Make position when seeking a bit more accurate # Make position when seeking a bit more accurate
currentFile = self.currentFile currentFile = self.currentFile
log.info("PLAYBACK_SEEK: %s" % currentFile) LOG.info("PLAYBACK_SEEK: %s" % currentFile)
if self.played_info.get(currentFile): if self.played_info.get(currentFile):
try: try:
@ -290,7 +255,7 @@ class Player(xbmc.Player):
def onPlayBackStopped(self): def onPlayBackStopped(self):
# Will be called when user stops xbmc playing a file # Will be called when user stops xbmc playing a file
log.info("ONPLAYBACK_STOPPED") LOG.info("ONPLAYBACK_STOPPED")
self.stopAll() self.stopAll()
@ -303,24 +268,24 @@ class Player(xbmc.Player):
# We might have saved a transient token from a user flinging media via # 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) # Companion (if we could not use the playqueue to store the token)
state.PLEX_TRANSIENT_TOKEN = None state.PLEX_TRANSIENT_TOKEN = None
log.debug("Cleared playlist properties.") LOG.debug("Cleared playlist properties.")
def onPlayBackEnded(self): def onPlayBackEnded(self):
# Will be called when xbmc stops playing a file, because the file ended # Will be called when xbmc stops playing a file, because the file ended
log.info("ONPLAYBACK_ENDED") LOG.info("ONPLAYBACK_ENDED")
self.onPlayBackStopped() self.onPlayBackStopped()
def stopAll(self): def stopAll(self):
if not self.played_info: if not self.played_info:
return return
log.info("Played_information: %s" % self.played_info) LOG.info("Played_information: %s" % self.played_info)
# Process each items # Process each items
for item in self.played_info: for item in self.played_info:
data = self.played_info.get(item) data = self.played_info.get(item)
if not data: if not data:
continue continue
log.debug("Item path: %s" % item) LOG.debug("Item path: %s" % item)
log.debug("Item data: %s" % data) LOG.debug("Item data: %s" % data)
runtime = data['runtime'] runtime = data['runtime']
currentPosition = data['currentPosition'] currentPosition = data['currentPosition']
@ -340,7 +305,7 @@ class Player(xbmc.Player):
except ZeroDivisionError: except ZeroDivisionError:
# Runtime is 0. # Runtime is 0.
percentComplete = 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)) % (percentComplete, v.MARK_PLAYED_AT))
if percentComplete >= v.MARK_PLAYED_AT: if percentComplete >= v.MARK_PLAYED_AT:
# Tell Kodi that we've finished watching (Plex knows) # Tell Kodi that we've finished watching (Plex knows)
@ -374,7 +339,7 @@ class Player(xbmc.Player):
# Stop transcoding # Stop transcoding
if playMethod == "Transcode": if playMethod == "Transcode":
log.info("Transcoding for %s terminating" % itemid) LOG.info("Transcoding for %s terminating" % itemid)
self.doUtils().downloadUrl( self.doUtils().downloadUrl(
"{server}/video/:/transcode/universal/stop", "{server}/video/:/transcode/universal/stop",
parameters={'session': window('plex_client_Id')}) parameters={'session': window('plex_client_Id')})

View file

@ -1,3 +1,6 @@
"""
Collection of functions associated with Kodi and Plex playlists and playqueues
"""
import logging import logging
from urllib import quote from urllib import quote
from urlparse import parse_qsl, urlsplit from urlparse import parse_qsl, urlsplit
@ -5,13 +8,14 @@ from re import compile as re_compile
import plexdb_functions as plexdb import plexdb_functions as plexdb
from downloadutils import DownloadUtils as DU from downloadutils import DownloadUtils as DU
from utils import JSONRPC, tryEncode, escape_html from utils import tryEncode, escape_html
from PlexAPI import API from PlexAPI import API
from PlexFunctions import GetPlexMetadata 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+)''') REGEX = re_compile(r'''metadata%2F(\d+)''')
############################################################################### ###############################################################################
@ -29,7 +33,7 @@ class Playlist_Object_Baseclase(object):
kodi_pl = None kodi_pl = None
items = [] items = []
old_kodi_pl = [] old_kodi_pl = []
ID = None id = None
version = None version = None
selectedItemID = None selectedItemID = None
selectedItemOffset = None selectedItemOffset = None
@ -43,10 +47,10 @@ class Playlist_Object_Baseclase(object):
""" """
answ = "<%s: " % (self.__class__.__name__) answ = "<%s: " % (self.__class__.__name__)
# For some reason, can't use dir directly # For some reason, can't use dir directly
answ += "ID: %s, " % self.ID answ += "id: %s, " % self.id
answ += "items: %s, " % self.items answ += "items: %s, " % self.items
for key in self.__dict__: 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): if type(getattr(self, key)) in (str, unicode):
answ += '%s: %s, ' % (key, tryEncode(getattr(self, key))) answ += '%s: %s, ' % (key, tryEncode(getattr(self, key)))
else: else:
@ -61,14 +65,14 @@ class Playlist_Object_Baseclase(object):
self.kodi_pl.clear() # Clear Kodi playlist object self.kodi_pl.clear() # Clear Kodi playlist object
self.items = [] self.items = []
self.old_kodi_pl = [] self.old_kodi_pl = []
self.ID = None self.id = None
self.version = None self.version = None
self.selectedItemID = None self.selectedItemID = None
self.selectedItemOffset = None self.selectedItemOffset = None
self.shuffled = 0 self.shuffled = 0
self.repeat = 0 self.repeat = 0
self.plex_transient_token = None self.plex_transient_token = None
log.debug('Playlist cleared: %s' % self) LOG.debug('Playlist cleared: %s', self)
class Playlist_Object(Playlist_Object_Baseclase): 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 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' type = None [str] Kodi type: 'audio', 'video', 'picture'
kodi_pl = None Kodi xbmc.PlayList object kodi_pl = None Kodi xbmc.PlayList object
items = [] [list] of Playlist_Items items = [] [list] of Playlist_Items
old_kodi_pl = [] [list] store old Kodi JSON result with all pl 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 version = None [int] Plex version of the playQueue
selectedItemID = None selectedItemID = None
[str] Plex selectedItemID, playing element in queue [str] Plex selectedItemID, playing element in queue
@ -106,7 +110,7 @@ class Playlist_Item(object):
""" """
Object to fill our playqueues and playlists with. 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_id = None [str] Plex unique item id, "ratingKey"
plex_type = None [str] Plex type, e.g. 'movie', 'clip' plex_type = None [str] Plex type, e.g. 'movie', 'clip'
plex_UUID = None [str] Plex librarySectionUUID plex_UUID = None [str] Plex librarySectionUUID
@ -117,7 +121,7 @@ class Playlist_Item(object):
guid = None [str] Weird Plex guid guid = None [str] Weird Plex guid
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer> xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
""" """
ID = None id = None
plex_id = None plex_id = None
plex_type = None plex_type = None
plex_UUID = 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 # TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' % item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
(item.plex_UUID, item.plex_id)) (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 return item
@ -199,7 +203,7 @@ def playlist_item_from_plex(plex_id):
item.plex_UUID = plex_id item.plex_UUID = plex_id
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' % item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
(item.plex_UUID, plex_id)) (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 return item
@ -213,7 +217,7 @@ def playlist_item_from_xml(playlist, xml_video_element):
api = API(xml_video_element) api = API(xml_video_element)
item.plex_id = api.getRatingKey() item.plex_id = api.getRatingKey()
item.plex_type = api.getType() 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') item.guid = xml_video_element.attrib.get('guid')
if item.guid is not None: if item.guid is not None:
item.guid = escape_html(item.guid) item.guid = escape_html(item.guid)
@ -225,7 +229,7 @@ def playlist_item_from_xml(playlist, xml_video_element):
except TypeError: except TypeError:
pass pass
item.xml = xml_video_element 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 return item
@ -237,8 +241,8 @@ def _get_playListVersion_from_xml(playlist, xml):
try: try:
playlist.version = int(xml.attrib['%sVersion' % playlist.kind]) playlist.version = int(xml.attrib['%sVersion' % playlist.kind])
except (TypeError, AttributeError, KeyError): except (TypeError, AttributeError, KeyError):
log.error('Could not get new playlist Version for playlist %s' LOG.error('Could not get new playlist Version for playlist %s',
% playlist) playlist)
return False return False
return True return True
@ -249,7 +253,7 @@ def get_playlist_details_from_xml(playlist, xml):
playlist.ID with the XML's playQueueID playlist.ID with the XML's playQueueID
""" """
try: try:
playlist.ID = xml.attrib['%sID' % playlist.kind] playlist.id = xml.attrib['%sID' % playlist.kind]
playlist.version = xml.attrib['%sVersion' % playlist.kind] playlist.version = xml.attrib['%sVersion' % playlist.kind]
playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind] playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind]
playlist.selectedItemID = xml.attrib.get( playlist.selectedItemID = xml.attrib.get(
@ -257,12 +261,12 @@ def get_playlist_details_from_xml(playlist, xml):
playlist.selectedItemOffset = xml.attrib.get( playlist.selectedItemOffset = xml.attrib.get(
'%sSelectedItemOffset' % playlist.kind) '%sSelectedItemOffset' % playlist.kind)
except: except:
log.error('Could not parse xml answer from PMS for playlist %s' LOG.error('Could not parse xml answer from PMS for playlist %s',
% playlist) playlist)
import traceback import traceback
log.error(traceback.format_exc()) LOG.error(traceback.format_exc())
raise KeyError 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): 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: try:
get_playlist_details_from_xml(playlist, xml) get_playlist_details_from_xml(playlist, xml)
except KeyError: except KeyError:
log.error('Could not update playlist from PMS') LOG.error('Could not update playlist from PMS')
return return
for plex_item in xml: for plex_item in xml:
playlist_item = add_to_Kodi_playlist(playlist, plex_item) 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 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: try:
if plex_id: if plex_id:
item = playlist_item_from_plex(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) get_playlist_details_from_xml(playlist, xml)
item.xml = xml[0] item.xml = xml[0]
except (KeyError, IndexError, TypeError): 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) 'kodi_item %s', plex_id, kodi_item)
return False return False
playlist.items.append(item) 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 return True
@ -329,10 +333,10 @@ def add_listitem_to_playlist(playlist, pos, listitem, kodi_id=None,
file: str!! file: str!!
""" """
log.debug('add_listitem_to_playlist at position %s. Playlist before add: ' LOG.debug('add_listitem_to_playlist at position %s. Playlist before add: '
'%s' % (pos, playlist)) '%s', pos, playlist)
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file} 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) init_Plex_playlist(playlist, plex_id, kodi_item)
else: else:
add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item) 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! 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} 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) success = init_Plex_playlist(playlist, plex_id, kodi_item)
else: else:
success = add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item) 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)} params['item'] = {'%sid' % item.kodi_type: int(item.kodi_id)}
else: else:
params['item'] = {'file': item.file} params['item'] = {'file': item.file}
reply = JSONRPC('Playlist.Insert').execute(params) reply = js.playlist_insert(params)
if reply.get('error') is not None: 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 False
return True return True
@ -395,25 +399,24 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
try: try:
item = playlist_item_from_plex(plex_id) item = playlist_item_from_plex(plex_id)
except KeyError: 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 return False
else: else:
item = playlist_item_from_kodi(kodi_item) 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 # Will always put the new item at the end of the Plex playlist
xml = DU().downloadUrl(url, action_type="PUT") xml = DU().downloadUrl(url, action_type="PUT")
try: try:
item.xml = xml[-1] item.xml = xml[-1]
item.ID = xml[-1].attrib['%sItemID' % playlist.kind] item.ID = xml[-1].attrib['%sItemID' % playlist.kind]
except IndexError: 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): except (TypeError, AttributeError, KeyError):
log.error('Could not add item %s to playlist %s' LOG.error('Could not add item %s to playlist %s', kodi_item, playlist)
% (kodi_item, playlist))
return False return False
# Get the guid for this item # Get the guid for this item
for plex_item in xml: 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']) item.guid = escape_html(plex_item.attrib['guid'])
playlist.items.append(item) playlist.items.append(item)
if pos == len(playlist.items) - 1: 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, move_playlist_item(playlist,
len(playlist.items) - 1, len(playlist.items) - 1,
pos) 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 return True
@ -437,9 +440,9 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
file: str! file: str!
""" """
log.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi ' LOG.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi '
'only at position %s for %s' 'only at position %s for %s',
% (kodi_id, kodi_type, file, pos, playlist)) kodi_id, kodi_type, file, pos, playlist)
params = { params = {
'playlistid': playlist.playlistid, 'playlistid': playlist.playlistid,
'position': pos '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)} params['item'] = {'%sid' % kodi_type: int(kodi_id)}
else: else:
params['item'] = {'file': file} params['item'] = {'file': file}
reply = JSONRPC('Playlist.Insert').execute(params) reply = js.playlist_insert(params)
if reply.get('error') is not None: 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 False
item = playlist_item_from_kodi( 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: if item.plex_id is not None:
xml = GetPlexMetadata(item.plex_id) xml = GetPlexMetadata(item.plex_id)
try: try:
item.xml = xml[-1] item.xml = xml[-1]
except (TypeError, IndexError): 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) playlist.items.insert(pos, item)
return True return True
@ -470,26 +473,26 @@ def move_playlist_item(playlist, before_pos, after_pos):
WILL ALSO CHANGE OUR PLAYLISTS. Returns True if successful WILL ALSO CHANGE OUR PLAYLISTS. Returns True if successful
""" """
log.debug('Moving item from %s to %s on the Plex side for %s' LOG.debug('Moving item from %s to %s on the Plex side for %s',
% (before_pos, after_pos, playlist)) before_pos, after_pos, playlist)
if after_pos == 0: if after_pos == 0:
url = "{server}/%ss/%s/items/%s/move?after=0" % \ url = "{server}/%ss/%s/items/%s/move?after=0" % \
(playlist.kind, (playlist.kind,
playlist.ID, playlist.id,
playlist.items[before_pos].ID) playlist.items[before_pos].id)
else: else:
url = "{server}/%ss/%s/items/%s/move?after=%s" % \ url = "{server}/%ss/%s/items/%s/move?after=%s" % \
(playlist.kind, (playlist.kind,
playlist.ID, playlist.id,
playlist.items[before_pos].ID, playlist.items[before_pos].id,
playlist.items[after_pos - 1].ID) playlist.items[after_pos - 1].id)
# We need to increment the playlistVersion # We need to increment the playlistVersion
if _get_playListVersion_from_xml( if _get_playListVersion_from_xml(
playlist, DU().downloadUrl(url, action_type="PUT")) is False: playlist, DU().downloadUrl(url, action_type="PUT")) is False:
return False return False
# Move our item's position in our internal playlist # Move our item's position in our internal playlist
playlist.items.insert(after_pos, playlist.items.pop(before_pos)) 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 return True
@ -500,7 +503,7 @@ def get_PMS_playlist(playlist, playlist_id=None):
Returns None if something went wrong 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( xml = DU().downloadUrl(
"{server}/%ss/%s" % (playlist.kind, playlist_id), "{server}/%ss/%s" % (playlist.kind, playlist_id),
headerOptions={'Accept': 'application/xml'}) headerOptions={'Accept': 'application/xml'})
@ -520,59 +523,24 @@ def refresh_playlist_from_PMS(playlist):
try: try:
get_playlist_details_from_xml(playlist, xml) get_playlist_details_from_xml(playlist, xml)
except KeyError: 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): def delete_playlist_item_from_PMS(playlist, pos):
""" """
Delete the item at position pos [int] on the Plex side and our playlists 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" % xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" %
(playlist.kind, (playlist.kind,
playlist.ID, playlist.id,
playlist.items[pos].ID, playlist.items[pos].id,
playlist.repeat), playlist.repeat),
action_type="DELETE") action_type="DELETE")
_get_playListVersion_from_xml(playlist, xml) _get_playListVersion_from_xml(playlist, xml)
del playlist.items[pos] 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 ########## # Functions operating on the Kodi playlist objects ##########
def add_to_Kodi_playlist(playlist, xml_video_element): 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 Returns a Playlist_Item or None if it did not work
""" """
item = playlist_item_from_xml(playlist, xml_video_element) item = playlist_item_from_xml(playlist, xml_video_element)
params = {
'playlistid': playlist.playlistid
}
if item.kodi_id: if item.kodi_id:
params['item'] = {'%sid' % item.kodi_type: item.kodi_id} json_item = {'%sid' % item.kodi_type: item.kodi_id}
else: else:
params['item'] = {'file': item.file} json_item = {'file': item.file}
reply = JSONRPC('Playlist.Add').execute(params) reply = js.playlist_add(playlist.playlistid, json_item)
if reply.get('error') is not None: if reply.get('error') is not None:
log.error('Could not add item %s to Kodi playlist. Error: %s' LOG.error('Could not add item %s to Kodi playlist. Error: %s',
% (xml_video_element, reply)) xml_video_element, reply)
return None return None
return item return item
@ -607,8 +572,8 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
file: string! file: string!
""" """
log.debug('Insert listitem at position %s for Kodi only for %s' LOG.debug('Insert listitem at position %s for Kodi only for %s',
% (pos, playlist)) pos, playlist)
# Add the item into Kodi playlist # Add the item into Kodi playlist
playlist.kodi_pl.add(file, listitem, index=pos) playlist.kodi_pl.add(file, listitem, index=pos)
# We need to add this to our internal queue as well # 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: if file is not None:
item.file = file item.file = file
playlist.items.insert(pos, item) 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. Removes the item at position pos from the Kodi playlist using JSON.
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
""" """
log.debug('Removing position %s from Kodi only from %s' % (pos, playlist)) LOG.debug('Removing position %s from Kodi only from %s', pos, playlist)
reply = JSONRPC('Playlist.Remove').execute({ reply = js.playlist_remove(playlist.playlistid, pos)
'playlistid': playlist.playlistid,
'position': pos
})
if reply.get('error') is not None: if reply.get('error') is not None:
log.error('Could not delete the item from the playlist. Error: %s' LOG.error('Could not delete the item from the playlist. Error: %s',
% reply) reply)
return return
else:
try: try:
del playlist.items[pos] del playlist.items[pos]
except IndexError: except IndexError:
log.error('Cannot delete position %s for %s' % (pos, playlist)) LOG.error('Cannot delete position %s for %s', pos, playlist)
def get_pms_playqueue(playqueue_id): def get_pms_playqueue(playqueue_id):
@ -654,7 +615,7 @@ def get_pms_playqueue(playqueue_id):
try: try:
xml.attrib xml.attrib
except AttributeError: except AttributeError:
log.error('Could not download Plex playqueue %s' % playqueue_id) LOG.error('Could not download Plex playqueue %s', playqueue_id)
xml = None xml = None
return xml return xml
@ -669,12 +630,12 @@ def get_plextype_from_xml(xml):
try: try:
plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0] plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0]
except IndexError: 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 return
new_xml = GetPlexMetadata(plex_id) new_xml = GetPlexMetadata(plex_id)
try: try:
new_xml[0].attrib new_xml[0].attrib
except (TypeError, IndexError, AttributeError): 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
return new_xml[0].attrib.get('type') return new_xml[0].attrib.get('type')

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- """
############################################################################### Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
"""
import logging import logging
from threading import RLock, Thread from threading import RLock, Thread
@ -10,13 +11,14 @@ import playlist_func as PL
from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren
from PlexAPI import API from PlexAPI import API
from playbackutils import PlaybackUtils from playbackutils import PlaybackUtils
import json_rpc as js
import variables as v import variables as v
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) LOG = logging.getLogger("PLEX." + __name__)
# Lock used for playqueue manipulations # lock used for playqueue manipulations
lock = RLock() LOCK = RLock()
PLUGIN = 'plugin://%s' % v.ADDON_ID PLUGIN = 'plugin://%s' % v.ADDON_ID
############################################################################### ###############################################################################
@ -33,15 +35,15 @@ class Playqueue(Thread):
def __init__(self, callback=None): def __init__(self, callback=None):
self.__dict__ = self.__shared_state self.__dict__ = self.__shared_state
if self.playqueues is not None: 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) Thread.__init__(self)
return return
self.mgr = callback self.mgr = callback
# Initialize Kodi playqueues # Initialize Kodi playqueues
with lock: with LOCK:
self.playqueues = [] self.playqueues = []
for queue in PL.get_kodi_playqueues(): for queue in js.get_playlists():
playqueue = PL.Playqueue_Object() playqueue = PL.Playqueue_Object()
playqueue.playlistid = queue['playlistid'] playqueue.playlistid = queue['playlistid']
playqueue.type = queue['type'] playqueue.type = queue['type']
@ -59,7 +61,7 @@ class Playqueue(Thread):
# sort the list by their playlistid, just in case # sort the list by their playlistid, just in case
self.playqueues = sorted( self.playqueues = sorted(
self.playqueues, key=lambda i: i.playlistid) 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) Thread.__init__(self)
def get_playqueue_from_type(self, typus): def get_playqueue_from_type(self, typus):
@ -67,7 +69,7 @@ class Playqueue(Thread):
Returns the playqueue according to the typus ('video', 'audio', Returns the playqueue according to the typus ('video', 'audio',
'picture') passed in 'picture') passed in
""" """
with lock: with LOCK:
for playqueue in self.playqueues: for playqueue in self.playqueues:
if playqueue.type == typus: if playqueue.type == typus:
break break
@ -85,7 +87,7 @@ class Playqueue(Thread):
try: try:
xml[0].attrib xml[0].attrib
except (TypeError, IndexError, AttributeError): 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 return
playqueue = self.get_playqueue_from_type( playqueue = self.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['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): for i, child in enumerate(xml):
api = API(child) api = API(child)
PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey()) 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) Player().play(playqueue.kodi_pl, None, False, 0)
return playqueue return playqueue
@ -109,15 +111,15 @@ class Playqueue(Thread):
repeat = 0, 1, 2 repeat = 0, 1, 2
offset = time offset in Plextime (milliseconds) 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)) '%s, repeat %s' % (playqueue_id, offset, repeat))
with lock: with LOCK:
xml = PL.get_PMS_playlist(playqueue, playqueue_id) xml = PL.get_PMS_playlist(playqueue, playqueue_id)
playqueue.clear() playqueue.clear()
try: try:
PL.get_playlist_details_from_xml(playqueue, xml) PL.get_playlist_details_from_xml(playqueue, xml)
except KeyError: except KeyError:
log.error('Could not get playqueue ID %s' % playqueue_id) LOG.error('Could not get playqueue ID %s' % playqueue_id)
return return
PlaybackUtils(xml, playqueue).play_all() PlaybackUtils(xml, playqueue).play_all()
playqueue.repeat = 0 if not repeat else int(repeat) playqueue.repeat = 0 if not repeat else int(repeat)
@ -126,12 +128,12 @@ class Playqueue(Thread):
window('plex_customplaylist.seektime', window('plex_customplaylist.seektime',
str(ConvertPlexToKodiTime(offset))) str(ConvertPlexToKodiTime(offset)))
for startpos, item in enumerate(playqueue.items): for startpos, item in enumerate(playqueue.items):
if item.ID == playqueue.selectedItemID: if item.id == playqueue.selectedItemID:
break break
else: else:
startpos = 0 startpos = 0
# Start playback. Player does not return in time # 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) % self.playqueues)
thread = Thread(target=Player().play, thread = Thread(target=Player().play,
args=(playqueue.kodi_pl, args=(playqueue.kodi_pl,
@ -147,7 +149,7 @@ class Playqueue(Thread):
""" """
old = list(playqueue.items) old = list(playqueue.items)
index = list(range(0, len(old))) 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)) % (new, old))
if self.thread_stopped(): if self.thread_stopped():
# Chances are that we got an empty Kodi playlist due to # Chances are that we got an empty Kodi playlist due to
@ -176,15 +178,15 @@ class Playqueue(Thread):
del old[j], index[j] del old[j], index[j]
break break
elif identical: 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)) % (i+j, i))
PL.move_playlist_item(playqueue, i + j, i) PL.move_playlist_item(playqueue, i + j, i)
del old[j], index[j] del old[j], index[j]
break break
else: else:
log.debug('Detected new Kodi element at position %s: %s ' LOG.debug('Detected new Kodi element at position %s: %s '
% (i, new_item)) % (i, new_item))
if playqueue.ID is None: if playqueue.id is None:
PL.init_Plex_playlist(playqueue, PL.init_Plex_playlist(playqueue,
kodi_item=new_item) kodi_item=new_item)
else: else:
@ -194,17 +196,18 @@ class Playqueue(Thread):
for j in range(i, len(index)): for j in range(i, len(index)):
index[j] += 1 index[j] += 1
for i in reversed(index): 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) PL.delete_playlist_item_from_PMS(playqueue, i)
log.debug('Done comparing playqueues') LOG.debug('Done comparing playqueues')
def run(self): def run(self):
thread_stopped = self.thread_stopped thread_stopped = self.thread_stopped
thread_suspended = self.thread_suspended 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 # Initialize the playqueues, if Kodi already got items in them
for playqueue in self.playqueues: 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: if i == 0:
PL.init_Plex_playlist(playqueue, kodi_item=item) PL.init_Plex_playlist(playqueue, kodi_item=item)
else: else:
@ -214,9 +217,10 @@ class Playqueue(Thread):
if thread_stopped(): if thread_stopped():
break break
sleep(1000) sleep(1000)
with lock: with LOCK:
for playqueue in self.playqueues: 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: if playqueue.old_kodi_pl != kodi_playqueue:
# compare old and new playqueue # compare old and new playqueue
self._compare_playqueues(playqueue, kodi_playqueue) self._compare_playqueues(playqueue, kodi_playqueue)
@ -226,4 +230,4 @@ class Playqueue(Thread):
sleep(10) sleep(10)
continue continue
sleep(200) sleep(200)
log.info("----===## PlayQueue client stopped ##===----") LOG.info("----===## PlayQueue client stopped ##===----")

View file

@ -333,14 +333,14 @@ def create_actor_db_index():
def getScreensaver(): def getScreensaver():
# Get the current screensaver value # Get the current screensaver value
params = {'setting': "screensaver.mode"} params = {'setting': "screensaver.mode"}
return JSONRPC('Settings.getSettingValue').execute(params)['result']['value'] return jsonrpc('Settings.getSettingValue').execute(params)['result']['value']
def setScreensaver(value): def setScreensaver(value):
# Toggle the screensaver # Toggle the screensaver
params = {'setting': "screensaver.mode", 'value': value} params = {'setting': "screensaver.mode", 'value': value}
log.debug('Toggling screensaver to "%s": %s' log.debug('Toggling screensaver to "%s": %s'
% (value, JSONRPC('Settings.setSettingValue').execute(params))) % (value, jsonrpc('Settings.setSettingValue').execute(params)))
def reset(): def reset():
@ -1141,7 +1141,7 @@ def changePlayState(itemType, kodiId, playCount, lastplayed):
log.debug("JSON result was: %s" % result) log.debug("JSON result was: %s" % result)
class JSONRPC(object): class jsonrpc(object):
id_ = 1 id_ = 1
jsonrpc = "2.0" jsonrpc = "2.0"