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 -*-
###############################################################################
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,))

View file

@ -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)

View file

@ -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>
}
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 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,

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 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

View file

@ -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')})

View file

@ -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 <MediaContainer>
"""
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')

View file

@ -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 ##===----")

View file

@ -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"