PlexKodiConnect/resources/lib/playlist.py

338 lines
11 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2016-02-20 06:03:06 +11:00
###############################################################################
2016-09-02 03:53:04 +10:00
import logging
import json
2016-02-11 20:30:29 +11:00
from urllib import urlencode
2016-08-07 23:33:36 +10:00
from threading import Lock
from functools import wraps
import xbmc
import embydb_functions as embydb
2016-09-02 03:53:04 +10:00
from utils import window, tryEncode
import playbackutils
2016-02-11 20:30:29 +11:00
import PlexFunctions
import PlexAPI
2016-02-20 06:03:06 +11:00
###############################################################################
2016-09-02 03:53:04 +10:00
log = logging.getLogger("PLEX."+__name__)
###############################################################################
2016-08-07 23:33:36 +10:00
class lockMethod:
2016-08-09 02:40:35 +10:00
"""
Decorator for class methods to lock hem completely. Same lock is used for
every single decorator and instance used!
Here only used for Playlist()
"""
2016-08-07 23:33:36 +10:00
lock = Lock()
@classmethod
def decorate(cls, func):
@wraps(func)
def wrapper(*args, **kwargs):
with cls.lock:
result = func(*args, **kwargs)
return result
return wrapper
class Playlist():
2016-06-27 00:10:32 +10:00
"""
Initiate with Playlist(typus='video' or 'music')
"""
2016-08-07 23:33:36 +10:00
# Borg - multiple instances, shared state
_shared_state = {}
2016-08-12 06:11:00 +10:00
typus = None
queueId = None
playQueueVersion = None
guid = None
playlistId = None
player = xbmc.Player()
# "interal" PKC playlist
items = []
2016-08-07 23:33:36 +10:00
@lockMethod.decorate
def __init__(self, typus=None):
# Borg
self.__dict__ = self._shared_state
2016-09-02 03:53:04 +10:00
self.userid = window('currUserId')
self.server = window('pms_server')
2016-07-21 02:36:31 +10:00
# Construct the Kodi playlist instance
2016-08-12 06:11:00 +10:00
if self.typus == typus:
return
2016-06-27 00:10:32 +10:00
if typus == 'video':
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
2016-07-21 02:36:31 +10:00
self.typus = 'video'
2016-09-02 03:53:04 +10:00
log.info('Initiated video playlist')
2016-06-27 00:10:32 +10:00
elif typus == 'music':
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
2016-07-21 02:36:31 +10:00
self.typus = 'music'
2016-09-02 03:53:04 +10:00
log.info('Initiated music playlist')
2016-06-27 00:10:32 +10:00
else:
self.playlist = None
2016-07-21 02:36:31 +10:00
self.typus = None
2016-09-02 03:53:04 +10:00
log.info('Empty playlist initiated')
2016-07-20 02:22:34 +10:00
if self.playlist is not None:
self.playlistId = self.playlist.getPlayListId()
2016-08-12 06:11:00 +10:00
@lockMethod.decorate
def getQueueIdFromPosition(self, playlistPosition):
return self.items[playlistPosition]['playQueueItemID']
@lockMethod.decorate
def Typus(self, value=None):
if value:
self.typus = value
else:
return self.typus
@lockMethod.decorate
def PlayQueueVersion(self, value=None):
if value:
self.playQueueVersion = value
else:
return self.playQueueVersion
@lockMethod.decorate
def QueueId(self, value=None):
if value:
self.queueId = value
else:
return self.queueId
@lockMethod.decorate
def Guid(self, value=None):
if value:
self.guid = value
else:
return self.guid
2016-08-07 23:33:36 +10:00
@lockMethod.decorate
2016-07-21 02:36:31 +10:00
def clear(self):
"""
2016-08-12 06:11:00 +10:00
Empties current Kodi playlist and associated variables
2016-07-21 02:36:31 +10:00
"""
2016-09-02 03:53:04 +10:00
log.info('Clearing playlist')
2016-07-21 02:36:31 +10:00
self.playlist.clear()
self.items = []
2016-08-12 06:11:00 +10:00
self.queueId = None
self.playQueueVersion = None
self.guid = None
2016-07-21 02:36:31 +10:00
def _initiatePlaylist(self):
2016-09-02 03:53:04 +10:00
log.info('Initiating playlist')
2016-06-27 00:10:32 +10:00
playlist = None
with embydb.GetEmbyDB() as emby_db:
2016-07-21 02:36:31 +10:00
for item in self.items:
itemid = item['plexId']
2016-06-27 00:10:32 +10:00
embydb_item = emby_db.getItem_byId(itemid)
try:
mediatype = embydb_item[4]
except TypeError:
2016-09-02 03:53:04 +10:00
log.info('Couldnt find item %s in Kodi db' % itemid)
2016-06-27 00:10:32 +10:00
item = PlexFunctions.GetPlexMetadata(itemid)
if item in (None, 401):
2016-09-02 03:53:04 +10:00
log.info('Couldnt find item %s on PMS, trying next'
% itemid)
2016-06-27 00:10:32 +10:00
continue
if PlexAPI.API(item[0]).getType() == 'track':
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
2016-09-02 03:53:04 +10:00
log.info('Music playlist initiated')
2016-07-21 02:36:31 +10:00
self.typus = 'music'
2016-06-27 00:10:32 +10:00
else:
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
2016-09-02 03:53:04 +10:00
log.info('Video playlist initiated')
2016-07-21 02:36:31 +10:00
self.typus = 'video'
2016-06-27 00:10:32 +10:00
else:
if mediatype == 'song':
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
2016-09-02 03:53:04 +10:00
log.info('Music playlist initiated')
2016-07-21 02:36:31 +10:00
self.typus = 'music'
2016-06-27 00:10:32 +10:00
else:
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
2016-09-02 03:53:04 +10:00
log.info('Video playlist initiated')
2016-07-21 02:36:31 +10:00
self.typus = 'video'
2016-06-27 00:10:32 +10:00
break
self.playlist = playlist
2016-07-20 02:22:34 +10:00
if self.playlist is not None:
self.playlistId = self.playlist.getPlayListId()
2016-06-27 00:10:32 +10:00
2016-08-07 23:33:36 +10:00
def _processItems(self, startitem, startPlayer=False):
startpos = None
with embydb.GetEmbyDB() as emby_db:
2016-07-21 02:36:31 +10:00
for pos, item in enumerate(self.items):
kodiId = None
plexId = item['plexId']
embydb_item = emby_db.getItem_byId(plexId)
try:
2016-07-21 02:36:31 +10:00
kodiId = embydb_item[0]
mediatype = embydb_item[4]
except TypeError:
2016-09-02 03:53:04 +10:00
log.info('Couldnt find item %s in Kodi db' % plexId)
2016-07-21 02:36:31 +10:00
xml = PlexFunctions.GetPlexMetadata(plexId)
if xml in (None, 401):
2016-09-02 03:53:04 +10:00
log.error('Could not download plexId %s' % plexId)
else:
2016-09-02 03:53:04 +10:00
log.debug('Downloaded xml metadata, adding now')
2016-07-21 02:36:31 +10:00
self._addtoPlaylist_xbmc(xml[0])
else:
# Add to playlist
2016-09-02 03:53:04 +10:00
log.debug("Adding %s PlexId %s, KodiId %s to playlist."
% (mediatype, plexId, kodiId))
2016-08-07 23:33:36 +10:00
self._addtoPlaylist(kodiId, mediatype)
2016-07-21 02:36:31 +10:00
# Add the kodiId
if kodiId is not None:
item['kodiId'] = str(kodiId)
if (startpos is None and startitem[1] == item[startitem[0]]):
startpos = pos
if startPlayer is True and len(self.playlist) > 0:
if startpos is not None:
self.player.play(self.playlist, startpos=startpos)
else:
2016-09-02 03:53:04 +10:00
log.info('Never received a starting item for playlist, '
'starting with the first entry')
self.player.play(self.playlist)
2016-07-21 02:36:31 +10:00
2016-08-07 23:33:36 +10:00
@lockMethod.decorate
2016-07-21 02:36:31 +10:00
def playAll(self, items, startitem, offset):
"""
items: list of dicts of the form
{
2016-08-12 06:11:00 +10:00
'playQueueItemID': Plex playQueueItemID, e.g. '29175'
2016-07-21 02:36:31 +10:00
'plexId': Plex ratingKey, e.g. '125'
'kodiId': Kodi's db id of the same item
}
2016-08-12 06:11:00 +10:00
startitem: tuple (typus, id), where typus is either
'playQueueItemID' or 'plexId' and id is the corresponding
id as a string
2016-07-21 02:36:31 +10:00
offset: First item's time offset to play in Kodi time (an int)
"""
2016-09-02 03:53:04 +10:00
log.info("---*** PLAY ALL ***---")
log.debug('Startitem: %s, offset: %s, items: %s'
% (startitem, offset, items))
2016-07-21 02:36:31 +10:00
self.items = items
2016-06-27 00:10:32 +10:00
if self.playlist is None:
2016-07-21 02:36:31 +10:00
self._initiatePlaylist()
2016-06-27 00:10:32 +10:00
if self.playlist is None:
2016-09-02 03:53:04 +10:00
log.error('Could not create playlist, abort')
2016-06-27 00:10:32 +10:00
return
2016-09-02 03:53:04 +10:00
window('plex_customplaylist', value="true")
2016-07-21 02:36:31 +10:00
if offset != 0:
2016-06-27 00:10:32 +10:00
# Seek to the starting position
2016-09-02 03:53:04 +10:00
window('plex_customplaylist.seektime', str(offset))
2016-08-07 23:33:36 +10:00
self._processItems(startitem, startPlayer=True)
2016-07-21 02:36:31 +10:00
# Log playlist
2016-08-07 23:33:36 +10:00
self._verifyPlaylist()
2016-09-02 03:53:04 +10:00
log.debug('Internal playlist: %s' % self.items)
2016-08-07 23:33:36 +10:00
@lockMethod.decorate
def modifyPlaylist(self, itemids):
2016-09-02 03:53:04 +10:00
log.info("---*** MODIFY PLAYLIST ***---")
log.debug("Items: %s" % itemids)
2016-07-20 02:22:34 +10:00
self._initiatePlaylist(itemids)
2016-08-07 23:33:36 +10:00
self._processItems(itemids, startPlayer=True)
2016-08-07 23:33:36 +10:00
self._verifyPlaylist()
2016-04-26 22:41:58 +10:00
2016-08-07 23:33:36 +10:00
@lockMethod.decorate
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
2016-06-27 00:10:32 +10:00
"""
mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist',
'album', 'song', 'genre'
"""
self._addtoPlaylist(dbid, mediatype, url)
2016-08-12 06:11:00 +10:00
def _addtoPlaylist(self, dbid=None, mediatype=None, url=None):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Add",
'params': {
2016-07-20 02:22:34 +10:00
'playlistid': self.playlistId
}
}
if dbid is not None:
2016-09-02 03:53:04 +10:00
pl['params']['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
else:
pl['params']['item'] = {'file': url}
2016-09-02 03:53:04 +10:00
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
2016-07-20 02:22:34 +10:00
def _addtoPlaylist_xbmc(self, item):
API = PlexAPI.API(item)
2016-02-11 20:30:29 +11:00
params = {
'mode': "play",
'dbid': 999999999,
'id': API.getRatingKey(),
'filename': API.getKey()
2016-02-11 20:30:29 +11:00
}
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
% urlencode(params)
listitem = API.CreateListItemFromPlexItem()
2016-07-20 02:22:34 +10:00
playbackutils.PlaybackUtils(item).setArtwork(listitem)
2016-06-27 00:10:32 +10:00
self.playlist.add(playurl, listitem)
2016-08-07 23:33:36 +10:00
@lockMethod.decorate
2016-09-02 03:53:04 +10:00
def insertintoPlaylist(self,
position,
dbid=None,
mediatype=None,
url=None):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Insert",
'params': {
2016-07-20 02:22:34 +10:00
'playlistid': self.playlistId,
'position': position
}
}
if dbid is not None:
2016-09-02 03:53:04 +10:00
pl['params']['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
else:
pl['params']['item'] = {'file': url}
2016-09-02 03:53:04 +10:00
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
2016-08-07 23:33:36 +10:00
@lockMethod.decorate
def verifyPlaylist(self):
2016-08-07 23:33:36 +10:00
self._verifyPlaylist()
2016-08-07 23:33:36 +10:00
def _verifyPlaylist(self):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.GetItems",
'params': {
2016-07-20 02:22:34 +10:00
'playlistid': self.playlistId,
2016-02-07 23:26:28 +11:00
'properties': ['title', 'file']
}
}
2016-09-02 03:53:04 +10:00
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
2016-08-07 23:33:36 +10:00
@lockMethod.decorate
def removefromPlaylist(self, position):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Remove",
'params': {
2016-07-20 02:22:34 +10:00
'playlistid': self.playlistId,
'position': position
}
}
2016-09-02 03:53:04 +10:00
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))