Centralize Kodi json rpc
This commit is contained in:
parent
f6b666e892
commit
f2bc95813a
9 changed files with 472 additions and 500 deletions
|
@ -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,))
|
||||||
|
|
|
@ -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,68 +283,50 @@ 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 settings('ignoreSpecialsNextEpisodes') == "true":
|
||||||
# If we found any, find the oldest unwatched show for each one.
|
params = {
|
||||||
try:
|
'tvshowid': item['tvshowid'],
|
||||||
items = result['result']['tvshows']
|
'sort': {'method': "episode"},
|
||||||
except (KeyError, TypeError):
|
'filter': {
|
||||||
pass
|
'and': [
|
||||||
else:
|
{'operator': "lessthan",
|
||||||
for item in items:
|
'field': "playcount",
|
||||||
if settings('ignoreSpecialsNextEpisodes') == "true":
|
'value': "1"},
|
||||||
params = {
|
{'operator': "greaterthan",
|
||||||
'tvshowid': item['tvshowid'],
|
'field': "season",
|
||||||
'sort': {'method': "episode"},
|
'value': "0"}]},
|
||||||
'filter': {
|
'properties': [
|
||||||
'and': [
|
"title", "playcount", "season", "episode", "showtitle",
|
||||||
{'operator': "lessthan",
|
"plot", "file", "rating", "resume", "tvshowid", "art",
|
||||||
'field': "playcount",
|
"streamdetails", "firstaired", "runtime", "writer",
|
||||||
'value': "1"},
|
"dateadded", "lastplayed"
|
||||||
{'operator': "greaterthan",
|
],
|
||||||
'field': "season",
|
'limits': {"end": 1}
|
||||||
'value': "0"}]},
|
}
|
||||||
'properties': [
|
else:
|
||||||
"title", "playcount", "season", "episode", "showtitle",
|
params = {
|
||||||
"plot", "file", "rating", "resume", "tvshowid", "art",
|
'tvshowid': item['tvshowid'],
|
||||||
"streamdetails", "firstaired", "runtime", "writer",
|
'sort': {'method': "episode"},
|
||||||
"dateadded", "lastplayed"
|
'filter': {
|
||||||
],
|
'operator': "lessthan",
|
||||||
'limits': {"end": 1}
|
'field': "playcount",
|
||||||
}
|
'value': "1"},
|
||||||
else:
|
'properties': [
|
||||||
params = {
|
"title", "playcount", "season", "episode", "showtitle",
|
||||||
'tvshowid': item['tvshowid'],
|
"plot", "file", "rating", "resume", "tvshowid", "art",
|
||||||
'sort': {'method': "episode"},
|
"streamdetails", "firstaired", "runtime", "writer",
|
||||||
'filter': {
|
"dateadded", "lastplayed"
|
||||||
'operator': "lessthan",
|
],
|
||||||
'field': "playcount",
|
'limits': {"end": 1}
|
||||||
'value': "1"},
|
}
|
||||||
'properties': [
|
for episode in js.get_episodes(params):
|
||||||
"title", "playcount", "season", "episode", "showtitle",
|
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||||
"plot", "file", "rating", "resume", "tvshowid", "art",
|
url=episode['file'],
|
||||||
"streamdetails", "firstaired", "runtime", "writer",
|
listitem=createListItem(episode))
|
||||||
"dateadded", "lastplayed"
|
count += 1
|
||||||
],
|
if count == limit:
|
||||||
'limits': {"end": 1}
|
break
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(handle=HANDLE)
|
xbmcplugin.endOfDirectory(handle=HANDLE)
|
||||||
|
|
||||||
|
|
||||||
|
@ -364,42 +346,26 @@ 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.
|
params = {
|
||||||
try:
|
'tvshowid': item['tvshowid'],
|
||||||
items = result['result']['tvshows']
|
'sort': {'method': "episode"},
|
||||||
except (KeyError, TypeError):
|
'filter': {
|
||||||
pass
|
'operator': "true",
|
||||||
else:
|
'field': "inprogress",
|
||||||
for item in items:
|
'value': ""},
|
||||||
params = {
|
'properties': ["title", "playcount", "season", "episode",
|
||||||
'tvshowid': item['tvshowid'],
|
"showtitle", "plot", "file", "rating", "resume",
|
||||||
'sort': {'method': "episode"},
|
"tvshowid", "art", "cast", "streamdetails", "firstaired",
|
||||||
'filter': {
|
"runtime", "writer", "dateadded", "lastplayed"]
|
||||||
'operator': "true",
|
}
|
||||||
'field': "inprogress",
|
for episode in js.get_episodes(params):
|
||||||
'value': ""},
|
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||||
'properties': ["title", "playcount", "season", "episode",
|
url=episode['file'],
|
||||||
"showtitle", "plot", "file", "rating", "resume",
|
listitem=createListItem(episode))
|
||||||
"tvshowid", "art", "cast", "streamdetails", "firstaired",
|
count += 1
|
||||||
"runtime", "writer", "dateadded", "lastplayed"]
|
if count == limit:
|
||||||
}
|
break
|
||||||
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
|
|
||||||
|
|
||||||
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:
|
if episode['tvshowid'] in allshowsIds:
|
||||||
episodes = result['result']['episodes']
|
listitem = createListItem(episode,
|
||||||
except (KeyError, TypeError):
|
appendShowTitle=appendShowTitle,
|
||||||
pass
|
appendSxxExx=appendSxxExx)
|
||||||
else:
|
xbmcplugin.addDirectoryItem(
|
||||||
for episode in episodes:
|
handle=HANDLE,
|
||||||
if episode['tvshowid'] in allshowsIds:
|
url=episode['file'],
|
||||||
li = createListItem(episode,
|
listitem=listitem)
|
||||||
appendShowTitle=appendShowTitle,
|
count += 1
|
||||||
appendSxxExx=appendSxxExx)
|
if count == limit:
|
||||||
xbmcplugin.addDirectoryItem(
|
break
|
||||||
handle=HANDLE,
|
|
||||||
url=episode['file'],
|
|
||||||
listitem=li)
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
if count == limit:
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')})
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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 ##===----")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue