Playlist major upgrade part 1

This commit is contained in:
tomkat83 2016-12-03 11:50:05 +01:00
parent e6b4646aa1
commit db02a001a8
4 changed files with 301 additions and 30 deletions

View file

@ -60,6 +60,17 @@ KODITYPE_FROM_PLEXTYPE = {
'XXXXXXX': 'genre' 'XXXXXXX': 'genre'
} }
KODIAUDIOVIDEO_FROM_MEDIA_TYPE = {
'movie': 'video',
'episode': 'video',
'season': 'video',
'tvshow': 'video',
'artist': 'audio',
'album': 'audio',
'track': 'audio',
'song': 'audio'
}
def ConvertPlexToKodiTime(plexTime): def ConvertPlexToKodiTime(plexTime):
""" """

View file

@ -14,6 +14,7 @@ import kodidb_functions as kodidb
import playbackutils as pbutils import playbackutils as pbutils
from utils import window, settings, CatchExceptions, tryDecode, tryEncode from utils import window, settings, CatchExceptions, tryDecode, tryEncode
from PlexFunctions import scrobble from PlexFunctions import scrobble
from playlist import Playlist
############################################################################### ###############################################################################
@ -28,6 +29,7 @@ class KodiMonitor(xbmc.Monitor):
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.xbmcplayer = xbmc.Player() self.xbmcplayer = xbmc.Player()
self.playlist = Playlist('video')
xbmc.Monitor.__init__(self) xbmc.Monitor.__init__(self)
log.info("Kodi monitor started.") log.info("Kodi monitor started.")
@ -157,6 +159,13 @@ class KodiMonitor(xbmc.Monitor):
elif method == "Playlist.OnClear": elif method == "Playlist.OnClear":
pass pass
elif method == "Playlist.OnAdd":
# User manipulated Kodi playlist
# Data : {u'item': {u'type': u'movie', u'id': 3}, u'playlistid': 1,
# u'position': 0}
self.playlist.kodi_onadd(data)
Playlist()
def PlayBackStart(self, data): def PlayBackStart(self, data):
""" """
Called whenever a playback is started Called whenever a playback is started

View file

@ -7,14 +7,17 @@ import json
from urllib import urlencode from urllib import urlencode
from threading import Lock from threading import Lock
from functools import wraps from functools import wraps
from urllib import quote, urlencode
import xbmc import xbmc
import embydb_functions as embydb import embydb_functions as embydb
from utils import window, tryEncode import kodidb_functions as kodidb
from utils import window, tryEncode, JSONRPC
import playbackutils import playbackutils
import PlexFunctions import PlexFunctions as PF
import PlexAPI import PlexAPI
from downloadutils import DownloadUtils
############################################################################### ###############################################################################
@ -22,6 +25,13 @@ log = logging.getLogger("PLEX."+__name__)
############################################################################### ###############################################################################
PLEX_PLAYQUEUE_ARGS = (
'playQueueID',
'playQueueVersion',
'playQueueSelectedItemID',
'playQueueSelectedItemOffset'
)
class lockMethod: class lockMethod:
""" """
@ -45,43 +55,115 @@ class lockMethod:
class Playlist(): class Playlist():
""" """
Initiate with Playlist(typus='video' or 'music') Initiate with Playlist(typus='video' or 'music')
ATTRIBUTES:
id: integer
position: integer, default -1
type: string, default "unknown"
"unknown",
"video",
"audio",
"picture",
"mixed"
size: integer
""" """
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
_shared_state = {} _shared_state = {}
typus = None
queueId = None
playQueueVersion = None
guid = None
playlistId = None
player = xbmc.Player() player = xbmc.Player()
# "interal" PKC playlist
items = [] playlists = None
@lockMethod.decorate @lockMethod.decorate
def __init__(self, typus=None): def __init__(self, typus=None):
# Borg # Borg
self.__dict__ = self._shared_state self.__dict__ = self._shared_state
self.userid = window('currUserId') # If already initiated, return
self.server = window('pms_server') if self.playlists is not None:
# Construct the Kodi playlist instance
if self.typus == typus:
return return
if typus == 'video':
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) self.doUtils = DownloadUtils().downloadUrl
self.typus = 'video' # Get all playlists from Kodi
log.info('Initiated video playlist') self.playlists = JSONRPC('Playlist.GetPlaylists').execute()
elif typus == 'music': try:
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) self.playlists = self.playlists['result']
self.typus = 'music' except KeyError:
log.info('Initiated music playlist') log.error('Could not get Kodi playlists. JSON Result was: %s'
else: % self.playlists)
self.playlist = None self.playlists = None
self.typus = None return
log.info('Empty playlist initiated') # Example return: [{u'playlistid': 0, u'type': u'audio'},
if self.playlist is not None: # {u'playlistid': 1, u'type': u'video'},
self.playlistId = self.playlist.getPlayListId() # {u'playlistid': 2, u'type': u'picture'}]
# Initiate the Kodi playlists
for playlist in self.playlists:
# Initialize each Kodi playlist
if playlist['type'] == 'audio':
playlist['kodi_pl'] = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
elif playlist['type'] == 'video':
playlist['kodi_pl'] = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
else:
# Currently, only video or audio playlists available
playlist['kodi_pl'] = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
# Initialize Plex info on the playQueue
for arg in PLEX_PLAYQUEUE_ARGS:
playlist[arg] = None
# Build a list of all items within each playlist
playlist['items'] = []
for item in self._get_kodi_items(playlist['playlistid']):
playlist['items'].append({
'kodi_id': item.get('id'),
'type': item['type'],
'file': item['file'],
'playQueueItemID': None,
'plex_id': self._get_plexid(item)
})
log.debug('self.playlist: %s' % playlist)
def _init_pl_item(self):
return {
'plex_id': None,
'kodi_id': None,
'file': None,
'type': None, # 'audio' or 'video'
'playQueueItemID': None,
'uri': None,
# To be able to drag Kodi JSON data along:
'playlistid': None,
'position': None,
'item': None,
}
def _get_plexid(self, item):
"""
Supply with data['item'] as returned from Kodi JSON-RPC interface
"""
with embydb.GetEmbyDB() as emby_db:
emby_dbitem = emby_db.getItem_byKodiId(item.get('id'),
item.get('type'))
try:
plex_id = emby_dbitem[0]
except TypeError:
plex_id = None
return plex_id
def _get_kodi_items(self, playlistid):
params = {
'playlistid': playlistid,
'properties': ["title", "file"]
}
answ = JSONRPC('Playlist.GetItems').execute(params)
# returns 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'}]
try:
answ = answ['result']['items']
except KeyError:
answ = []
return answ
@lockMethod.decorate @lockMethod.decorate
def getQueueIdFromPosition(self, playlistPosition): def getQueueIdFromPosition(self, playlistPosition):
@ -138,7 +220,7 @@ class Playlist():
mediatype = embydb_item[4] mediatype = embydb_item[4]
except TypeError: except TypeError:
log.info('Couldnt find item %s in Kodi db' % itemid) log.info('Couldnt find item %s in Kodi db' % itemid)
item = PlexFunctions.GetPlexMetadata(itemid) item = PF.GetPlexMetadata(itemid)
if item in (None, 401): if item in (None, 401):
log.info('Couldnt find item %s on PMS, trying next' log.info('Couldnt find item %s on PMS, trying next'
% itemid) % itemid)
@ -177,7 +259,7 @@ class Playlist():
mediatype = embydb_item[4] mediatype = embydb_item[4]
except TypeError: except TypeError:
log.info('Couldnt find item %s in Kodi db' % plexId) log.info('Couldnt find item %s in Kodi db' % plexId)
xml = PlexFunctions.GetPlexMetadata(plexId) xml = PF.GetPlexMetadata(plexId)
if xml in (None, 401): if xml in (None, 401):
log.error('Could not download plexId %s' % plexId) log.error('Could not download plexId %s' % plexId)
else: else:
@ -335,3 +417,149 @@ class Playlist():
} }
} }
log.debug(xbmc.executeJSONRPC(json.dumps(pl))) log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
def _get_uri(self, plex_id=None, item=None):
"""
Supply with either plex_id or data['item'] as received from Kodi JSON-
RPC
"""
uri = None
if plex_id is None:
plex_id = self._get_plexid(item)
self._cur_item['plex_id'] = plex_id
if plex_id is not None:
xml = PF.GetPlexMetadata(plex_id)
try:
uri = ('library://%s/item/%s%s' %
(xml.attrib.get('librarySectionUUID'),
quote('library/metadata/', safe=''), plex_id))
except:
pass
if uri is None:
try:
uri = 'library://whatever/item/%s' % quote(item['file'],
safe='')
except:
raise KeyError('Could not get file/url with item: %s' % item)
self._cur_item['uri'] = uri
return uri
def _init_plex_playQueue(self, plex_id=None, data=None):
"""
Supply either plex_id or the data supplied by Kodi JSON-RPC
"""
if plex_id is None:
plex_id = self._get_plexid(data['item'])
self._cur_item['plex_id'] = plex_id
if data is not None:
playlistid = data['playlistid']
plex_type = self.playlists[playlistid]['type']
else:
with embydb.GetEmbyDB() as emby_db:
plex_type = emby_db.getItem_byId(plex_id)
try:
plex_type = PF.KODIAUDIOVIDEO_FROM_MEDIA_TYPE[plex_type[4]]
except TypeError:
raise KeyError('Unknown plex_type %s' % plex_type)
for playlist in self.playlists:
if playlist['type'] == plex_type:
playlistid = playlist['playlistid']
self._cur_item['playlistid'] = playlistid
self._cur_item['type'] = plex_type
params = {
'next': 0,
'type': plex_type,
'uri': self._get_uri(plex_id=plex_id, item=data['item'])
}
log.debug('params: %s' % urlencode(params))
xml = self.doUtils(url="{server}/playQueues",
action_type="POST",
parameters=params)
try:
xml.attrib
except (TypeError, AttributeError):
raise KeyError('Could not post to PMS, received: %s' % xml)
self._Plex_item_updated(xml)
def _Plex_item_updated(self, xml):
"""
Called if a new item has just been added/updated @ Plex playQueue
Call with the PMS' xml reply
"""
# Update the ITEM
log.debug('xml.attrib: %s' % xml.attrib)
args = {
'playQueueItemID': 'playQueueLastAddedItemID', # for playlist PUT
'playQueueItemID': 'playQueueSelectedItemID' # for playlist INIT
}
for old, new in args.items():
if new in xml.attrib:
self._cur_item[old] = xml.attrib[new]
# Update the PLAYLIST
for arg in PLEX_PLAYQUEUE_ARGS:
if arg in xml.attrib:
self.playlists[self._cur_item['playlistid']][arg] = xml.attrib[arg]
def _init_Kodi_item(self, item):
"""
Call with Kodi's JSON-RPC data['item']
"""
self._cur_item['kodi_id'] = item.get('id')
try:
self._cur_item['type'] = PF.KODIAUDIOVIDEO_FROM_MEDIA_TYPE[
item.get('type')]
except KeyError:
log.error('Could not get media_type for %s' % item)
def _add_curr_item(self):
self.playlists[self._cur_item['playlistid']]['items'].insert(
self._cur_item['position'],
self._cur_item)
@lockMethod.decorate
def kodi_onadd(self, data):
"""
Called if Kodi playlist is modified. Data is Kodi JSON-RPC output, e.g.
{
u'item': {u'type': u'movie', u'id': 3},
u'playlistid': 1,
u'position': 0
}
"""
self._cur_item = self._init_pl_item()
self._cur_item.update(data)
self._init_Kodi_item(data['item'])
pl = self.playlists[data['playlistid']]
if pl['playQueueID'] is None:
# Playlist needs to be initialized!
try:
self._init_plex_playQueue(data=data)
except KeyError as e:
log.error('Error encountered while init playQueue: %s' % e)
return
else:
next_item = data['position']
if next_item != 0:
next_item = pl['items'][data['position']-1]['playQueueItemID']
params = {
'next': next_item,
'type': pl['type'],
'uri': self._get_uri(item=data['item'])
}
xml = self.doUtils(url="{server}/playQueues/%s"
% pl['playQueueID'],
action_type="PUT",
parameters=params)
try:
xml.attrib
except AttributeError:
log.error('Could not add item %s to playQueue' % data)
return
self._Plex_item_updated(xml)
# Add the new item to our playlist
self._add_curr_item()
log.debug('self.playlists are now: %s' % self.playlists)

View file

@ -15,7 +15,6 @@ from functools import wraps
from calendar import timegm from calendar import timegm
import os import os
import xbmc import xbmc
import xbmcaddon import xbmcaddon
import xbmcgui import xbmcgui
@ -964,3 +963,27 @@ def changePlayState(itemType, kodiId, playCount, lastplayed):
result = json.loads(result) result = json.loads(result)
result = result.get('result') result = result.get('result')
log.debug("JSON result was: %s" % result) log.debug("JSON result was: %s" % result)
class JSONRPC(object):
id_ = 1
jsonrpc = "2.0"
def __init__(self, method, **kwargs):
self.method = method
for arg in kwargs: # id_(int), jsonrpc(str)
self.arg = arg
def _query(self):
query = {
'jsonrpc': self.jsonrpc,
'id': self.id_,
'method': self.method,
}
if self.params is not None:
query['params'] = self.params
return json.dumps(query)
def execute(self, params=None):
self.params = params
return json.loads(xbmc.executeJSONRPC(self._query()))