Playqueues major haul-over
This commit is contained in:
parent
95c87065ed
commit
0c2d4984ab
14 changed files with 673 additions and 678 deletions
|
@ -11,7 +11,6 @@ from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
||||||
httppersist, plexsettings
|
httppersist, plexsettings
|
||||||
from PlexFunctions import ParseContainerKey, GetPlayQueue, \
|
from PlexFunctions import ParseContainerKey, GetPlayQueue, \
|
||||||
ConvertPlexToKodiTime
|
ConvertPlexToKodiTime
|
||||||
import playlist
|
|
||||||
import player
|
import player
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -25,18 +24,18 @@ log = logging.getLogger("PLEX."+__name__)
|
||||||
@ThreadMethods
|
@ThreadMethods
|
||||||
class PlexCompanion(Thread):
|
class PlexCompanion(Thread):
|
||||||
"""
|
"""
|
||||||
Initialize with a Queue for callbacks
|
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, callback=None):
|
||||||
log.info("----===## Starting PlexCompanion ##===----")
|
log.info("----===## Starting PlexCompanion ##===----")
|
||||||
|
if callback is not None:
|
||||||
|
self.mgr = callback
|
||||||
|
self.playqueue = self.mgr.playqueue
|
||||||
self.settings = plexsettings.getSettings()
|
self.settings = plexsettings.getSettings()
|
||||||
# Start GDM for server/client discovery
|
# Start GDM for server/client discovery
|
||||||
self.client = plexgdm.plexgdm()
|
self.client = plexgdm.plexgdm()
|
||||||
self.client.clientDetails(self.settings)
|
self.client.clientDetails(self.settings)
|
||||||
log.debug("Registration string is: %s "
|
log.debug("Registration string is: %s "
|
||||||
% self.client.getClientDetails())
|
% self.client.getClientDetails())
|
||||||
# Initialize playlist/queue stuff
|
|
||||||
self.playlist = playlist.Playlist('video')
|
|
||||||
# kodi player instance
|
# kodi player instance
|
||||||
self.player = player.Player()
|
self.player = player.Player()
|
||||||
|
|
||||||
|
@ -72,49 +71,44 @@ class PlexCompanion(Thread):
|
||||||
data = task['data']
|
data = task['data']
|
||||||
|
|
||||||
if task['action'] == 'playlist':
|
if task['action'] == 'playlist':
|
||||||
|
# Get the playqueue ID
|
||||||
try:
|
try:
|
||||||
_, queueId, query = ParseContainerKey(data['containerKey'])
|
_, ID, query = ParseContainerKey(data['containerKey'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error('Exception while processing: %s' % e)
|
log.error('Exception while processing: %s' % e)
|
||||||
import traceback
|
import traceback
|
||||||
log.error("Traceback:\n%s" % traceback.format_exc())
|
log.error("Traceback:\n%s" % traceback.format_exc())
|
||||||
return
|
return
|
||||||
if self.playlist is not None:
|
self.mgr.playqueue.update_playqueue_with_companion(data)
|
||||||
if self.playlist.Typus() != data.get('type'):
|
|
||||||
log.debug('Switching to Kodi playlist of type %s'
|
self.playqueue = self.mgr.playqueue.get_playqueue_from_plextype(
|
||||||
% data.get('type'))
|
data.get('type'))
|
||||||
self.playlist = None
|
if queueId != self.playqueue.ID:
|
||||||
if self.playlist is None:
|
|
||||||
if data.get('type') == 'music':
|
|
||||||
self.playlist = playlist.Playlist('music')
|
|
||||||
else:
|
|
||||||
self.playlist = playlist.Playlist('video')
|
|
||||||
if queueId != self.playlist.QueueId():
|
|
||||||
log.info('New playlist received, updating!')
|
log.info('New playlist received, updating!')
|
||||||
xml = GetPlayQueue(queueId)
|
xml = GetPlayQueue(queueId)
|
||||||
if xml in (None, 401):
|
if xml in (None, 401):
|
||||||
log.error('Could not download Plex playlist.')
|
log.error('Could not download Plex playlist.')
|
||||||
return
|
return
|
||||||
# Clear existing playlist on the Kodi side
|
# Clear existing playlist on the Kodi side
|
||||||
self.playlist.clear()
|
self.playqueue.clear()
|
||||||
# Set new values
|
# Set new values
|
||||||
self.playlist.QueueId(queueId)
|
self.playqueue.QueueId(queueId)
|
||||||
self.playlist.PlayQueueVersion(int(
|
self.playqueue.PlayQueueVersion(int(
|
||||||
xml.attrib.get('playQueueVersion')))
|
xml.attrib.get('playQueueVersion')))
|
||||||
self.playlist.Guid(xml.attrib.get('guid'))
|
self.playqueue.Guid(xml.attrib.get('guid'))
|
||||||
items = []
|
items = []
|
||||||
for item in xml:
|
for item in xml:
|
||||||
items.append({
|
items.append({
|
||||||
'playQueueItemID': item.get('playQueueItemID'),
|
'playQueueItemID': item.get('playQueueItemID'),
|
||||||
'plexId': item.get('ratingKey'),
|
'plexId': item.get('ratingKey'),
|
||||||
'kodiId': None})
|
'kodiId': None})
|
||||||
self.playlist.playAll(
|
self.playqueue.playAll(
|
||||||
items,
|
items,
|
||||||
startitem=self._getStartItem(data.get('key', '')),
|
startitem=self._getStartItem(data.get('key', '')),
|
||||||
offset=ConvertPlexToKodiTime(data.get('offset', 0)))
|
offset=ConvertPlexToKodiTime(data.get('offset', 0)))
|
||||||
log.info('Initiated playlist no %s with version %s'
|
log.info('Initiated playlist no %s with version %s'
|
||||||
% (self.playlist.QueueId(),
|
% (self.playqueue.QueueId(),
|
||||||
self.playlist.PlayQueueVersion()))
|
self.playqueue.PlayQueueVersion()))
|
||||||
else:
|
else:
|
||||||
log.error('This has never happened before!')
|
log.error('This has never happened before!')
|
||||||
|
|
||||||
|
@ -129,7 +123,7 @@ class PlexCompanion(Thread):
|
||||||
requestMgr = httppersist.RequestMgr()
|
requestMgr = httppersist.RequestMgr()
|
||||||
jsonClass = functions.jsonClass(requestMgr, self.settings)
|
jsonClass = functions.jsonClass(requestMgr, self.settings)
|
||||||
subscriptionManager = subscribers.SubscriptionManager(
|
subscriptionManager = subscribers.SubscriptionManager(
|
||||||
jsonClass, requestMgr, self.player, self.playlist)
|
jsonClass, requestMgr, self.player, self.playqueue)
|
||||||
|
|
||||||
queue = Queue.Queue(maxsize=100)
|
queue = Queue.Queue(maxsize=100)
|
||||||
|
|
||||||
|
|
|
@ -60,11 +60,12 @@ KODITYPE_FROM_PLEXTYPE = {
|
||||||
'XXXXXXX': 'genre'
|
'XXXXXXX': 'genre'
|
||||||
}
|
}
|
||||||
|
|
||||||
KODIAUDIOVIDEO_FROM_MEDIA_TYPE = {
|
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE = {
|
||||||
'movie': 'video',
|
'movie': 'video',
|
||||||
'episode': 'video',
|
'episode': 'video',
|
||||||
'season': 'video',
|
'season': 'video',
|
||||||
'tvshow': 'video',
|
'tvshow': 'video',
|
||||||
|
'clip': 'video',
|
||||||
'artist': 'audio',
|
'artist': 'audio',
|
||||||
'album': 'audio',
|
'album': 'audio',
|
||||||
'track': 'audio',
|
'track': 'audio',
|
||||||
|
|
|
@ -19,7 +19,6 @@ import clientinfo
|
||||||
import downloadutils
|
import downloadutils
|
||||||
import embydb_functions as embydb
|
import embydb_functions as embydb
|
||||||
import playbackutils as pbutils
|
import playbackutils as pbutils
|
||||||
import playlist
|
|
||||||
|
|
||||||
import PlexFunctions
|
import PlexFunctions
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
|
|
|
@ -9,7 +9,7 @@ import xbmcgui
|
||||||
from utils import settings, window, language as lang
|
from utils import settings, window, language as lang
|
||||||
import clientinfo
|
import clientinfo
|
||||||
import downloadutils
|
import downloadutils
|
||||||
import userclient
|
from userclient import UserClient
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
|
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
|
||||||
|
@ -30,11 +30,10 @@ class InitialSetup():
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
self.addonId = self.clientInfo.getAddonId()
|
self.addonId = self.clientInfo.getAddonId()
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||||
self.userClient = userclient.UserClient()
|
|
||||||
self.plx = PlexAPI.PlexAPI()
|
self.plx = PlexAPI.PlexAPI()
|
||||||
self.dialog = xbmcgui.Dialog()
|
self.dialog = xbmcgui.Dialog()
|
||||||
|
|
||||||
self.server = self.userClient.getServer()
|
self.server = UserClient().getServer()
|
||||||
self.serverid = settings('plex_machineIdentifier')
|
self.serverid = settings('plex_machineIdentifier')
|
||||||
# Get Plex credentials from settings file, if they exist
|
# Get Plex credentials from settings file, if they exist
|
||||||
plexdict = self.plx.GetPlexLoginFromSettings()
|
plexdict = self.plx.GetPlexLoginFromSettings()
|
||||||
|
|
|
@ -14,7 +14,6 @@ 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, REMAP_TYPE_FROM_PLEXTYPE
|
from PlexFunctions import scrobble, REMAP_TYPE_FROM_PLEXTYPE
|
||||||
from playlist import Playlist
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -25,11 +24,11 @@ log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
class KodiMonitor(xbmc.Monitor):
|
class KodiMonitor(xbmc.Monitor):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, callback):
|
||||||
|
self.mgr = callback
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||||
self.xbmcplayer = xbmc.Player()
|
self.xbmcplayer = xbmc.Player()
|
||||||
self.playlist = Playlist('video')
|
self.playqueue = self.mgr.playqueue
|
||||||
xbmc.Monitor.__init__(self)
|
xbmc.Monitor.__init__(self)
|
||||||
log.info("Kodi monitor started.")
|
log.info("Kodi monitor started.")
|
||||||
|
|
||||||
|
@ -173,7 +172,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
# Data : {u'item': {u'type': u'movie', u'id': 3}, u'playlistid': 1,
|
# Data : {u'item': {u'type': u'movie', u'id': 3}, u'playlistid': 1,
|
||||||
# u'position': 0}
|
# u'position': 0}
|
||||||
self.playlist.kodi_onadd(data)
|
self.playlist.kodi_onadd(data)
|
||||||
Playlist()
|
|
||||||
|
|
||||||
def PlayBackStart(self, data):
|
def PlayBackStart(self, data):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -356,19 +356,10 @@ class ProcessFanartThread(Thread):
|
||||||
@ThreadMethods
|
@ThreadMethods
|
||||||
class LibrarySync(Thread):
|
class LibrarySync(Thread):
|
||||||
"""
|
"""
|
||||||
librarysync.LibrarySync(queue)
|
|
||||||
|
|
||||||
where (communication with websockets)
|
|
||||||
queue: Queue object for background sync
|
|
||||||
"""
|
"""
|
||||||
# Borg, even though it's planned to only have 1 instance up and running!
|
def __init__(self, callback=None):
|
||||||
_shared_state = {}
|
self.mgr = callback
|
||||||
|
|
||||||
def __init__(self, queue):
|
|
||||||
self.__dict__ = self._shared_state
|
|
||||||
|
|
||||||
# Communication with websockets
|
|
||||||
self.queue = queue
|
|
||||||
self.itemsToProcess = []
|
self.itemsToProcess = []
|
||||||
self.sessionKeys = []
|
self.sessionKeys = []
|
||||||
self.fanartqueue = Queue.Queue()
|
self.fanartqueue = Queue.Queue()
|
||||||
|
@ -1720,7 +1711,8 @@ class LibrarySync(Thread):
|
||||||
|
|
||||||
xbmcplayer = xbmc.Player()
|
xbmcplayer = xbmc.Player()
|
||||||
|
|
||||||
queue = self.queue
|
# Link to Websocket queue
|
||||||
|
queue = self.mgr.ws.queue
|
||||||
|
|
||||||
startupComplete = False
|
startupComplete = False
|
||||||
self.views = []
|
self.views = []
|
||||||
|
|
|
@ -11,7 +11,7 @@ import xbmcgui
|
||||||
import xbmcplugin
|
import xbmcplugin
|
||||||
|
|
||||||
import playutils as putils
|
import playutils as putils
|
||||||
import playlist
|
from playqueue import Playqueue
|
||||||
from utils import window, settings, tryEncode, tryDecode
|
from utils import window, settings, tryEncode, tryDecode
|
||||||
import downloadutils
|
import downloadutils
|
||||||
|
|
||||||
|
@ -37,10 +37,7 @@ class PlaybackUtils():
|
||||||
self.userid = window('currUserId')
|
self.userid = window('currUserId')
|
||||||
self.server = window('pms_server')
|
self.server = window('pms_server')
|
||||||
|
|
||||||
if self.API.getType() == 'track':
|
self.pl = Playqueue().get_playqueue_from_plextype(self.API.getType())
|
||||||
self.pl = playlist.Playlist(typus='music')
|
|
||||||
else:
|
|
||||||
self.pl = playlist.Playlist(typus='video')
|
|
||||||
|
|
||||||
def play(self, itemid, dbid=None):
|
def play(self, itemid, dbid=None):
|
||||||
|
|
||||||
|
@ -89,7 +86,7 @@ class PlaybackUtils():
|
||||||
contextmenu_play = window('plex_contextplay') == 'true'
|
contextmenu_play = window('plex_contextplay') == 'true'
|
||||||
window('plex_contextplay', clear=True)
|
window('plex_contextplay', clear=True)
|
||||||
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
||||||
kodiPl = self.pl.playlist
|
kodiPl = self.pl.kodi_pl
|
||||||
sizePlaylist = kodiPl.size()
|
sizePlaylist = kodiPl.size()
|
||||||
if contextmenu_play:
|
if contextmenu_play:
|
||||||
# Need to start with the items we're inserting here
|
# Need to start with the items we're inserting here
|
||||||
|
|
|
@ -1,565 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
from urllib import urlencode
|
|
||||||
from threading import Lock
|
|
||||||
from functools import wraps
|
|
||||||
from urllib import quote, urlencode
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
|
|
||||||
import embydb_functions as embydb
|
|
||||||
import kodidb_functions as kodidb
|
|
||||||
from utils import window, tryEncode, JSONRPC
|
|
||||||
import playbackutils
|
|
||||||
import PlexFunctions as PF
|
|
||||||
import PlexAPI
|
|
||||||
from downloadutils import DownloadUtils
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
log = logging.getLogger("PLEX."+__name__)
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
PLEX_PLAYQUEUE_ARGS = (
|
|
||||||
'playQueueID',
|
|
||||||
'playQueueVersion',
|
|
||||||
'playQueueSelectedItemID',
|
|
||||||
'playQueueSelectedItemOffset'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class lockMethod:
|
|
||||||
"""
|
|
||||||
Decorator for class methods to lock hem completely. Same lock is used for
|
|
||||||
every single decorator and instance used!
|
|
||||||
|
|
||||||
Here only used for Playlist()
|
|
||||||
"""
|
|
||||||
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():
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
_shared_state = {}
|
|
||||||
|
|
||||||
player = xbmc.Player()
|
|
||||||
|
|
||||||
playlists = None
|
|
||||||
|
|
||||||
@lockMethod.decorate
|
|
||||||
def __init__(self, typus=None):
|
|
||||||
# Borg
|
|
||||||
self.__dict__ = self._shared_state
|
|
||||||
|
|
||||||
# If already initiated, return
|
|
||||||
if self.playlists is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.doUtils = DownloadUtils().downloadUrl
|
|
||||||
# Get all playlists from Kodi
|
|
||||||
self.playlists = JSONRPC('Playlist.GetPlaylists').execute()
|
|
||||||
try:
|
|
||||||
self.playlists = self.playlists['result']
|
|
||||||
except KeyError:
|
|
||||||
log.error('Could not get Kodi playlists. JSON Result was: %s'
|
|
||||||
% self.playlists)
|
|
||||||
self.playlists = None
|
|
||||||
return
|
|
||||||
# Example return: [{u'playlistid': 0, u'type': u'audio'},
|
|
||||||
# {u'playlistid': 1, u'type': u'video'},
|
|
||||||
# {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
|
|
||||||
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
|
|
||||||
|
|
||||||
@lockMethod.decorate
|
|
||||||
def clear(self):
|
|
||||||
"""
|
|
||||||
Empties current Kodi playlist and associated variables
|
|
||||||
"""
|
|
||||||
log.info('Clearing playlist')
|
|
||||||
self.playlist.clear()
|
|
||||||
self.items = []
|
|
||||||
self.queueId = None
|
|
||||||
self.playQueueVersion = None
|
|
||||||
self.guid = None
|
|
||||||
|
|
||||||
def _initiatePlaylist(self):
|
|
||||||
log.info('Initiating playlist')
|
|
||||||
playlist = None
|
|
||||||
with embydb.GetEmbyDB() as emby_db:
|
|
||||||
for item in self.items:
|
|
||||||
itemid = item['plexId']
|
|
||||||
embydb_item = emby_db.getItem_byId(itemid)
|
|
||||||
try:
|
|
||||||
mediatype = embydb_item[4]
|
|
||||||
except TypeError:
|
|
||||||
log.info('Couldnt find item %s in Kodi db' % itemid)
|
|
||||||
item = PF.GetPlexMetadata(itemid)
|
|
||||||
if item in (None, 401):
|
|
||||||
log.info('Couldnt find item %s on PMS, trying next'
|
|
||||||
% itemid)
|
|
||||||
continue
|
|
||||||
if PlexAPI.API(item[0]).getType() == 'track':
|
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
|
||||||
log.info('Music playlist initiated')
|
|
||||||
self.typus = 'music'
|
|
||||||
else:
|
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
||||||
log.info('Video playlist initiated')
|
|
||||||
self.typus = 'video'
|
|
||||||
else:
|
|
||||||
if mediatype == 'song':
|
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
|
||||||
log.info('Music playlist initiated')
|
|
||||||
self.typus = 'music'
|
|
||||||
else:
|
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
||||||
log.info('Video playlist initiated')
|
|
||||||
self.typus = 'video'
|
|
||||||
break
|
|
||||||
self.playlist = playlist
|
|
||||||
if self.playlist is not None:
|
|
||||||
self.playlistId = self.playlist.getPlayListId()
|
|
||||||
|
|
||||||
def _processItems(self, startitem, startPlayer=False):
|
|
||||||
startpos = None
|
|
||||||
with embydb.GetEmbyDB() as emby_db:
|
|
||||||
for pos, item in enumerate(self.items):
|
|
||||||
kodiId = None
|
|
||||||
plexId = item['plexId']
|
|
||||||
embydb_item = emby_db.getItem_byId(plexId)
|
|
||||||
try:
|
|
||||||
kodiId = embydb_item[0]
|
|
||||||
mediatype = embydb_item[4]
|
|
||||||
except TypeError:
|
|
||||||
log.info('Couldnt find item %s in Kodi db' % plexId)
|
|
||||||
xml = PF.GetPlexMetadata(plexId)
|
|
||||||
if xml in (None, 401):
|
|
||||||
log.error('Could not download plexId %s' % plexId)
|
|
||||||
else:
|
|
||||||
log.debug('Downloaded xml metadata, adding now')
|
|
||||||
self._addtoPlaylist_xbmc(xml[0])
|
|
||||||
else:
|
|
||||||
# Add to playlist
|
|
||||||
log.debug("Adding %s PlexId %s, KodiId %s to playlist."
|
|
||||||
% (mediatype, plexId, kodiId))
|
|
||||||
self._addtoPlaylist(kodiId, mediatype)
|
|
||||||
# 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:
|
|
||||||
log.info('Never received a starting item for playlist, '
|
|
||||||
'starting with the first entry')
|
|
||||||
self.player.play(self.playlist)
|
|
||||||
|
|
||||||
@lockMethod.decorate
|
|
||||||
def playAll(self, items, startitem, offset):
|
|
||||||
"""
|
|
||||||
items: list of dicts of the form
|
|
||||||
{
|
|
||||||
'playQueueItemID': Plex playQueueItemID, e.g. '29175'
|
|
||||||
'plexId': Plex ratingKey, e.g. '125'
|
|
||||||
'kodiId': Kodi's db id of the same item
|
|
||||||
}
|
|
||||||
|
|
||||||
startitem: tuple (typus, id), where typus is either
|
|
||||||
'playQueueItemID' or 'plexId' and id is the corresponding
|
|
||||||
id as a string
|
|
||||||
offset: First item's time offset to play in Kodi time (an int)
|
|
||||||
"""
|
|
||||||
log.info("---*** PLAY ALL ***---")
|
|
||||||
log.debug('Startitem: %s, offset: %s, items: %s'
|
|
||||||
% (startitem, offset, items))
|
|
||||||
self.items = items
|
|
||||||
if self.playlist is None:
|
|
||||||
self._initiatePlaylist()
|
|
||||||
if self.playlist is None:
|
|
||||||
log.error('Could not create playlist, abort')
|
|
||||||
return
|
|
||||||
|
|
||||||
window('plex_customplaylist', value="true")
|
|
||||||
if offset != 0:
|
|
||||||
# Seek to the starting position
|
|
||||||
window('plex_customplaylist.seektime', str(offset))
|
|
||||||
self._processItems(startitem, startPlayer=True)
|
|
||||||
# Log playlist
|
|
||||||
self._verifyPlaylist()
|
|
||||||
log.debug('Internal playlist: %s' % self.items)
|
|
||||||
|
|
||||||
@lockMethod.decorate
|
|
||||||
def modifyPlaylist(self, itemids):
|
|
||||||
log.info("---*** MODIFY PLAYLIST ***---")
|
|
||||||
log.debug("Items: %s" % itemids)
|
|
||||||
|
|
||||||
self._initiatePlaylist(itemids)
|
|
||||||
self._processItems(itemids, startPlayer=True)
|
|
||||||
|
|
||||||
self._verifyPlaylist()
|
|
||||||
|
|
||||||
@lockMethod.decorate
|
|
||||||
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
|
||||||
"""
|
|
||||||
mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist',
|
|
||||||
'album', 'song', 'genre'
|
|
||||||
"""
|
|
||||||
self._addtoPlaylist(dbid, mediatype, url)
|
|
||||||
|
|
||||||
def _addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
|
||||||
pl = {
|
|
||||||
'jsonrpc': "2.0",
|
|
||||||
'id': 1,
|
|
||||||
'method': "Playlist.Add",
|
|
||||||
'params': {
|
|
||||||
'playlistid': self.playlistId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dbid is not None:
|
|
||||||
pl['params']['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
|
||||||
else:
|
|
||||||
pl['params']['item'] = {'file': url}
|
|
||||||
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
|
|
||||||
|
|
||||||
def _addtoPlaylist_xbmc(self, item):
|
|
||||||
API = PlexAPI.API(item)
|
|
||||||
params = {
|
|
||||||
'mode': "play",
|
|
||||||
'dbid': 'plextrailer',
|
|
||||||
'id': API.getRatingKey(),
|
|
||||||
'filename': API.getKey()
|
|
||||||
}
|
|
||||||
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
|
|
||||||
% urlencode(params)
|
|
||||||
|
|
||||||
listitem = API.CreateListItemFromPlexItem()
|
|
||||||
playbackutils.PlaybackUtils(item).setArtwork(listitem)
|
|
||||||
|
|
||||||
self.playlist.add(playurl, listitem)
|
|
||||||
|
|
||||||
@lockMethod.decorate
|
|
||||||
def insertintoPlaylist(self,
|
|
||||||
position,
|
|
||||||
dbid=None,
|
|
||||||
mediatype=None,
|
|
||||||
url=None):
|
|
||||||
pl = {
|
|
||||||
'jsonrpc': "2.0",
|
|
||||||
'id': 1,
|
|
||||||
'method': "Playlist.Insert",
|
|
||||||
'params': {
|
|
||||||
'playlistid': self.playlistId,
|
|
||||||
'position': position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dbid is not None:
|
|
||||||
pl['params']['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
|
||||||
else:
|
|
||||||
pl['params']['item'] = {'file': url}
|
|
||||||
|
|
||||||
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
|
|
||||||
|
|
||||||
@lockMethod.decorate
|
|
||||||
def verifyPlaylist(self):
|
|
||||||
self._verifyPlaylist()
|
|
||||||
|
|
||||||
def _verifyPlaylist(self):
|
|
||||||
pl = {
|
|
||||||
'jsonrpc': "2.0",
|
|
||||||
'id': 1,
|
|
||||||
'method': "Playlist.GetItems",
|
|
||||||
'params': {
|
|
||||||
'playlistid': self.playlistId,
|
|
||||||
'properties': ['title', 'file']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug(xbmc.executeJSONRPC(json.dumps(pl)))
|
|
||||||
|
|
||||||
@lockMethod.decorate
|
|
||||||
def removefromPlaylist(self, position):
|
|
||||||
pl = {
|
|
||||||
'jsonrpc': "2.0",
|
|
||||||
'id': 1,
|
|
||||||
'method': "Playlist.Remove",
|
|
||||||
'params': {
|
|
||||||
'playlistid': self.playlistId,
|
|
||||||
'position': position
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
394
resources/lib/playlist_func.py
Normal file
394
resources/lib/playlist_func.py
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
import logging
|
||||||
|
from urllib import quote
|
||||||
|
|
||||||
|
import embydb_functions as embydb
|
||||||
|
from downloadutils import DownloadUtils as DU
|
||||||
|
from utils import JSONRPC, tryEncode
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class Playlist_Object_Baseclase(object):
|
||||||
|
playlistid = None # Kodi playlist ID, [int]
|
||||||
|
type = None # Kodi type: 'audio', 'video', 'picture'
|
||||||
|
kodi_pl = None # Kodi xbmc.PlayList object
|
||||||
|
items = [] # list of PLAYLIST_ITEMS
|
||||||
|
old_kodi_pl = [] # to store old Kodi JSON result with all pl items
|
||||||
|
ID = None # Plex id, e.g. playQueueID
|
||||||
|
version = None # Plex version, [int]
|
||||||
|
selectedItemID = None
|
||||||
|
selectedItemOffset = None
|
||||||
|
shuffled = 0 # [int], 0: not shuffled, 1: ??? 2: ???
|
||||||
|
repeat = 0 # [int], 0: not repeated, 1: ??? 2: ???
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
answ = "<%s object: " % (self.__class__.__name__)
|
||||||
|
for key in self.__dict__:
|
||||||
|
answ += '%s: %s, ' % (key, getattr(self, key))
|
||||||
|
return answ[:-2] + ">"
|
||||||
|
|
||||||
|
|
||||||
|
class Playlist_Object(Playlist_Object_Baseclase):
|
||||||
|
kind = 'playList'
|
||||||
|
|
||||||
|
|
||||||
|
class Playqueue_Object(Playlist_Object_Baseclase):
|
||||||
|
kind = 'playQueue'
|
||||||
|
|
||||||
|
|
||||||
|
class Playlist_Item(object):
|
||||||
|
ID = None # Plex playlist/playqueue id, e.g. playQueueItemID
|
||||||
|
plex_id = None # Plex unique item id, "ratingKey"
|
||||||
|
plex_UUID = None # Plex librarySectionUUID
|
||||||
|
kodi_id = None # Kodi unique kodi id (unique only within type!)
|
||||||
|
kodi_type = None # Kodi type: 'movie'
|
||||||
|
file = None # Path to the item's file
|
||||||
|
uri = None # Weird Plex uri path involving plex_UUID
|
||||||
|
|
||||||
|
|
||||||
|
def playlist_item_from_kodi_item(kodi_item):
|
||||||
|
"""
|
||||||
|
Turns the JSON answer from Kodi into a playlist element
|
||||||
|
|
||||||
|
Supply with data['item'] as returned from Kodi JSON-RPC interface.
|
||||||
|
kodi_item dict contains keys 'id', 'type', 'file' (if applicable)
|
||||||
|
"""
|
||||||
|
item = Playlist_Item()
|
||||||
|
if kodi_item.get('id'):
|
||||||
|
item.kodi_id = kodi_item['id']
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
emby_dbitem = emby_db.getItem_byKodiId(kodi_item['id'],
|
||||||
|
kodi_item['type'])
|
||||||
|
try:
|
||||||
|
item.plex_id = emby_dbitem[0]
|
||||||
|
item.plex_UUID = emby_dbitem[0]
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
item.file = kodi_item.get('file') if kodi_item.get('file') else None
|
||||||
|
item.kodi_type = kodi_item.get('type') if kodi_item.get('type') else None
|
||||||
|
if item.plex_id is None:
|
||||||
|
item.uri = 'library://whatever/item/%s' % quote(item.file, safe='')
|
||||||
|
else:
|
||||||
|
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
||||||
|
(item.plex_UUID, item.plex_id))
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def playlist_item_from_plex(plex_id):
|
||||||
|
"""
|
||||||
|
Returns a playlist element providing the plex_id ("ratingKey")
|
||||||
|
"""
|
||||||
|
item = Playlist_Item()
|
||||||
|
item.plex_id = plex_id
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
emby_dbitem = emby_db.getItem_byId(plex_id)
|
||||||
|
try:
|
||||||
|
item.kodi_id = emby_dbitem[0]
|
||||||
|
item.kodi_type = emby_dbitem[4]
|
||||||
|
except:
|
||||||
|
raise KeyError('Could not find plex_id %s in database' % plex_id)
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def _log_xml(xml):
|
||||||
|
try:
|
||||||
|
xml.attrib
|
||||||
|
except AttributeError:
|
||||||
|
log.error('Did not receive an XML. Answer was: %s' % xml)
|
||||||
|
else:
|
||||||
|
from xml.etree.ElementTree import dump
|
||||||
|
log.error('XML received from the PMS: %s' % dump(xml))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_playListVersion_from_xml(playlist, xml):
|
||||||
|
"""
|
||||||
|
Takes a PMS xml as input to overwrite the playlist version (e.g. Plex
|
||||||
|
playQueueVersion). Returns True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
playlist.version = int(xml.attrib['%sVersion' % playlist.kind])
|
||||||
|
except (TypeError, AttributeError, KeyError):
|
||||||
|
log.error('Could not get new playlist Version for playlist %s'
|
||||||
|
% playlist)
|
||||||
|
_log_xml(xml)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _get_playlist_details_from_xml(playlist, xml):
|
||||||
|
"""
|
||||||
|
Takes a PMS xml as input and overwrites all the playlist's details, e.g.
|
||||||
|
playlist.ID with the XML's playQueueID
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
playlist.ID = xml.attrib['%sID' % playlist.kind]
|
||||||
|
playlist.version = xml.attrib['%sVersion' % playlist.kind]
|
||||||
|
playlist.selectedItemID = xml.attrib['%sSelectedItemID' % playlist.kind]
|
||||||
|
playlist.selectedItemOffset = xml.attrib['%sSelectedItemOffset' % playlist.kind]
|
||||||
|
playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind]
|
||||||
|
except:
|
||||||
|
log.error('Could not parse xml answer from PMS for playlist %s'
|
||||||
|
% playlist)
|
||||||
|
import traceback
|
||||||
|
log.error(traceback.format_exc())
|
||||||
|
_log_xml(xml)
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
|
||||||
|
def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
|
||||||
|
"""
|
||||||
|
Supply either plex_id or the data supplied by Kodi JSON-RPC
|
||||||
|
"""
|
||||||
|
if plex_id is not None:
|
||||||
|
item = playlist_item_from_plex(plex_id)
|
||||||
|
else:
|
||||||
|
item = playlist_item_from_kodi_item(kodi_item)
|
||||||
|
params = {
|
||||||
|
'next': 0,
|
||||||
|
'type': playlist.type,
|
||||||
|
'uri': item.uri
|
||||||
|
}
|
||||||
|
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
|
||||||
|
action_type="POST",
|
||||||
|
parameters=params)
|
||||||
|
_get_playlist_details_from_xml(xml)
|
||||||
|
playlist.items.append(item)
|
||||||
|
log.debug('Initialized the playlist: %s' % playlist)
|
||||||
|
|
||||||
|
|
||||||
|
def add_playlist_item(playlist, kodi_item, after_pos):
|
||||||
|
"""
|
||||||
|
Adds the new kodi_item to playlist after item at position after_pos
|
||||||
|
[int]
|
||||||
|
"""
|
||||||
|
item = playlist_item_from_kodi_item(kodi_item)
|
||||||
|
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.ID, item.uri)
|
||||||
|
# Will always put the new item at the end of the playlist
|
||||||
|
xml = DU().downloadUrl(url, action_type="PUT")
|
||||||
|
try:
|
||||||
|
item.ID = xml.attrib['%sLastAddedItemID' % playlist.kind]
|
||||||
|
except (TypeError, AttributeError, KeyError):
|
||||||
|
log.error('Could not add item %s to playlist %s'
|
||||||
|
% (kodi_item, playlist))
|
||||||
|
_log_xml(xml)
|
||||||
|
return
|
||||||
|
playlist.items.append(item)
|
||||||
|
if after_pos == len(playlist.items) - 1:
|
||||||
|
# Item was added at the end
|
||||||
|
_get_playListVersion_from_xml(playlist, xml)
|
||||||
|
else:
|
||||||
|
# Move the new item to the correct position
|
||||||
|
move_playlist_item(playlist,
|
||||||
|
len(playlist.items) - 1,
|
||||||
|
after_pos)
|
||||||
|
|
||||||
|
|
||||||
|
def move_playlist_item(playlist, before_pos, after_pos):
|
||||||
|
"""
|
||||||
|
Moves playlist item from before_pos [int] to after_pos [int]
|
||||||
|
"""
|
||||||
|
if after_pos == 0:
|
||||||
|
url = "{server}/%ss/%s/items/%s/move?after=0" % \
|
||||||
|
(playlist.kind,
|
||||||
|
playlist.ID,
|
||||||
|
playlist.items[before_pos].ID)
|
||||||
|
else:
|
||||||
|
url = "{server}/%ss/%s/items/%s/move?after=%s" % \
|
||||||
|
(playlist.kind,
|
||||||
|
playlist.ID,
|
||||||
|
playlist.items[before_pos].ID,
|
||||||
|
playlist.items[after_pos - 1].ID)
|
||||||
|
xml = DU().downloadUrl(url, action_type="PUT")
|
||||||
|
# We need to increment the playlistVersion
|
||||||
|
_get_playListVersion_from_xml(playlist, xml)
|
||||||
|
# Move our item's position in our internal playlist
|
||||||
|
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
||||||
|
|
||||||
|
|
||||||
|
def delete_playlist_item(playlist, pos):
|
||||||
|
"""
|
||||||
|
Delete the item at position pos [int]
|
||||||
|
"""
|
||||||
|
xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" %
|
||||||
|
(playlist.kind,
|
||||||
|
playlist.ID,
|
||||||
|
playlist.items[pos].ID,
|
||||||
|
playlist.repeat),
|
||||||
|
action_type="DELETE")
|
||||||
|
_get_playListVersion_from_xml(playlist, xml)
|
||||||
|
del playlist.items[pos]
|
||||||
|
|
||||||
|
|
||||||
|
def get_kodi_playlist_items(playlist):
|
||||||
|
"""
|
||||||
|
Returns a list of the current Kodi playlist items using JSON
|
||||||
|
|
||||||
|
E.g.:
|
||||||
|
[{u'title': u'3 Idiots', u'type': u'movie', u'id': 3, u'file':
|
||||||
|
u'smb://nas/PlexMovies/3 Idiots 2009 pt1.mkv', u'label': u'3 Idiots'}]
|
||||||
|
"""
|
||||||
|
answ = JSONRPC('Playlist.GetItems').execute({
|
||||||
|
'playlistid': playlist.playlistid,
|
||||||
|
'properties': ["title", "file"]
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
answ = answ['result']['items']
|
||||||
|
except KeyError:
|
||||||
|
answ = []
|
||||||
|
return answ
|
||||||
|
|
||||||
|
|
||||||
|
def get_kodi_playqueues():
|
||||||
|
"""
|
||||||
|
Example return: [{u'playlistid': 0, u'type': u'audio'},
|
||||||
|
{u'playlistid': 1, u'type': u'video'},
|
||||||
|
{u'playlistid': 2, u'type': u'picture'}]
|
||||||
|
"""
|
||||||
|
queues = JSONRPC('Playlist.GetPlaylists').execute()
|
||||||
|
try:
|
||||||
|
queues = queues['result']
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError('Could not get Kodi playqueues. JSON Result was: %s'
|
||||||
|
% queues)
|
||||||
|
return queues
|
||||||
|
|
||||||
|
|
||||||
|
# Functions operating on the Kodi playlist objects ##########
|
||||||
|
|
||||||
|
def insertintoPlaylist(self,
|
||||||
|
position,
|
||||||
|
dbid=None,
|
||||||
|
mediatype=None,
|
||||||
|
url=None):
|
||||||
|
params = {
|
||||||
|
'playlistid': self.playlistId,
|
||||||
|
'position': position
|
||||||
|
}
|
||||||
|
if dbid is not None:
|
||||||
|
params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
||||||
|
else:
|
||||||
|
params['item'] = {'file': url}
|
||||||
|
JSONRPC('Playlist.Insert').execute(params)
|
||||||
|
|
||||||
|
|
||||||
|
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
||||||
|
params = {
|
||||||
|
'playlistid': self.playlistId
|
||||||
|
}
|
||||||
|
if dbid is not None:
|
||||||
|
params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
|
||||||
|
else:
|
||||||
|
params['item'] = {'file': url}
|
||||||
|
JSONRPC('Playlist.Add').execute(params)
|
||||||
|
|
||||||
|
|
||||||
|
def removefromPlaylist(self, position):
|
||||||
|
params = {
|
||||||
|
'playlistid': self.playlistId,
|
||||||
|
'position': position
|
||||||
|
}
|
||||||
|
JSONRPC('Playlist.Remove').execute(params)
|
||||||
|
|
||||||
|
|
||||||
|
def playAll(self, items, startitem, offset):
|
||||||
|
"""
|
||||||
|
items: list of dicts of the form
|
||||||
|
{
|
||||||
|
'playQueueItemID': Plex playQueueItemID, e.g. '29175'
|
||||||
|
'plexId': Plex ratingKey, e.g. '125'
|
||||||
|
'kodiId': Kodi's db id of the same item
|
||||||
|
}
|
||||||
|
|
||||||
|
startitem: tuple (typus, id), where typus is either
|
||||||
|
'playQueueItemID' or 'plexId' and id is the corresponding
|
||||||
|
id as a string
|
||||||
|
offset: First item's time offset to play in Kodi time (an int)
|
||||||
|
"""
|
||||||
|
log.info("---*** PLAY ALL ***---")
|
||||||
|
log.debug('Startitem: %s, offset: %s, items: %s'
|
||||||
|
% (startitem, offset, items))
|
||||||
|
self.items = items
|
||||||
|
if self.playlist is None:
|
||||||
|
self._initiatePlaylist()
|
||||||
|
if self.playlist is None:
|
||||||
|
log.error('Could not create playlist, abort')
|
||||||
|
return
|
||||||
|
|
||||||
|
window('plex_customplaylist', value="true")
|
||||||
|
if offset != 0:
|
||||||
|
# Seek to the starting position
|
||||||
|
window('plex_customplaylist.seektime', str(offset))
|
||||||
|
self._processItems(startitem, startPlayer=True)
|
||||||
|
# Log playlist
|
||||||
|
self._verifyPlaylist()
|
||||||
|
log.debug('Internal playlist: %s' % self.items)
|
||||||
|
|
||||||
|
|
||||||
|
def _processItems(self, startitem, startPlayer=False):
|
||||||
|
startpos = None
|
||||||
|
with embydb.GetEmbyDB() as emby_db:
|
||||||
|
for pos, item in enumerate(self.items):
|
||||||
|
kodiId = None
|
||||||
|
plexId = item['plexId']
|
||||||
|
embydb_item = emby_db.getItem_byId(plexId)
|
||||||
|
try:
|
||||||
|
kodiId = embydb_item[0]
|
||||||
|
mediatype = embydb_item[4]
|
||||||
|
except TypeError:
|
||||||
|
log.info('Couldnt find item %s in Kodi db' % plexId)
|
||||||
|
xml = PF.GetPlexMetadata(plexId)
|
||||||
|
if xml in (None, 401):
|
||||||
|
log.error('Could not download plexId %s' % plexId)
|
||||||
|
else:
|
||||||
|
log.debug('Downloaded xml metadata, adding now')
|
||||||
|
self._addtoPlaylist_xbmc(xml[0])
|
||||||
|
else:
|
||||||
|
# Add to playlist
|
||||||
|
log.debug("Adding %s PlexId %s, KodiId %s to playlist."
|
||||||
|
% (mediatype, plexId, kodiId))
|
||||||
|
self._addtoPlaylist(kodiId, mediatype)
|
||||||
|
# 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:
|
||||||
|
log.info('Never received a starting item for playlist, '
|
||||||
|
'starting with the first entry')
|
||||||
|
self.player.play(self.playlist)
|
||||||
|
|
||||||
|
def _addtoPlaylist_xbmc(self, item):
|
||||||
|
API = PlexAPI.API(item)
|
||||||
|
params = {
|
||||||
|
'mode': "play",
|
||||||
|
'dbid': 'plextrailer',
|
||||||
|
'id': API.getRatingKey(),
|
||||||
|
'filename': API.getKey()
|
||||||
|
}
|
||||||
|
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
|
||||||
|
% urlencode(params)
|
||||||
|
|
||||||
|
listitem = API.CreateListItemFromPlexItem()
|
||||||
|
playbackutils.PlaybackUtils(item).setArtwork(listitem)
|
||||||
|
|
||||||
|
self.playlist.add(playurl, listitem)
|
||||||
|
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Empties current Kodi playlist and associated variables
|
||||||
|
"""
|
||||||
|
self.playlist.clear()
|
||||||
|
self.items = []
|
||||||
|
self.queueId = None
|
||||||
|
self.playQueueVersion = None
|
||||||
|
self.guid = None
|
||||||
|
log.info('Playlist cleared')
|
150
resources/lib/playqueue.py
Normal file
150
resources/lib/playqueue.py
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
###############################################################################
|
||||||
|
import logging
|
||||||
|
from threading import Lock, Thread
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
|
||||||
|
from utils import ThreadMethods, ThreadMethodsAdditionalSuspend, Lock_Function
|
||||||
|
import playlist_func as PL
|
||||||
|
from PlexFunctions import KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE, GetPlayQueue, \
|
||||||
|
ParseContainerKey
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
|
# Lock used to lock methods
|
||||||
|
lock = Lock()
|
||||||
|
lockmethod = Lock_Function(lock)
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
@ThreadMethodsAdditionalSuspend('plex_serverStatus')
|
||||||
|
@ThreadMethods
|
||||||
|
class Playqueue(Thread):
|
||||||
|
"""
|
||||||
|
Monitors Kodi's playqueues for changes on the Kodi side
|
||||||
|
"""
|
||||||
|
# Borg - multiple instances, shared state
|
||||||
|
__shared_state = {}
|
||||||
|
playqueues = None
|
||||||
|
|
||||||
|
@lockmethod.lockthis
|
||||||
|
def __init__(self, callback=None):
|
||||||
|
self.__dict__ = self.__shared_state
|
||||||
|
Thread.__init__(self)
|
||||||
|
if self.playqueues is not None:
|
||||||
|
return
|
||||||
|
self.mgr = callback
|
||||||
|
|
||||||
|
# Initialize Kodi playqueues
|
||||||
|
self.playqueues = []
|
||||||
|
for queue in PL.get_kodi_playqueues():
|
||||||
|
playqueue = PL.Playqueue_Object()
|
||||||
|
playqueue.playlistid = queue['playlistid']
|
||||||
|
playqueue.type = queue['type']
|
||||||
|
# Initialize each Kodi playlist
|
||||||
|
if playqueue.type == 'audio':
|
||||||
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||||
|
elif playqueue.type == 'video':
|
||||||
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
else:
|
||||||
|
# Currently, only video or audio playqueues available
|
||||||
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
self.playqueues.append(playqueue)
|
||||||
|
log.debug('Initialized the Kodi play queues: %s' % self.playqueues)
|
||||||
|
|
||||||
|
@lockmethod.lockthis
|
||||||
|
def update_playqueue_with_companion(self, data):
|
||||||
|
"""
|
||||||
|
Feed with Plex companion data
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get the correct queue
|
||||||
|
for playqueue in self.playqueues:
|
||||||
|
if playqueue.type == KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[
|
||||||
|
data['type']]:
|
||||||
|
break
|
||||||
|
|
||||||
|
@lockmethod.lockthis
|
||||||
|
def kodi_onadd(self, data):
|
||||||
|
"""
|
||||||
|
Called if an item is added to a Kodi playqueue. Data is Kodi JSON-RPC
|
||||||
|
output, e.g.
|
||||||
|
{
|
||||||
|
u'item': {u'type': u'movie', u'id': 3},
|
||||||
|
u'playlistid': 1,
|
||||||
|
u'position': 0
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
for playqueue in self.playqueues:
|
||||||
|
if playqueue.playlistid == data['playlistid']:
|
||||||
|
break
|
||||||
|
if playqueue.ID is None:
|
||||||
|
# Need to initialize the queue for the first time
|
||||||
|
PL.init_Plex_playlist(playqueue, kodi_item=data['item'])
|
||||||
|
else:
|
||||||
|
PL.add_playlist_item(playqueue, data['item'], data['position'])
|
||||||
|
|
||||||
|
@lockmethod.lockthis
|
||||||
|
def _compare_playqueues(self, playqueue, new):
|
||||||
|
"""
|
||||||
|
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
||||||
|
"""
|
||||||
|
old = playqueue.old_kodi_pl
|
||||||
|
log.debug('Comparing new Kodi playqueue %s with our play queue %s'
|
||||||
|
% (new, playqueue))
|
||||||
|
index = list(range(0, len(old)))
|
||||||
|
for i, new_item in enumerate(new):
|
||||||
|
for j, old_item in enumerate(old):
|
||||||
|
if old_item.get('id') is None:
|
||||||
|
identical = old_item['file'] == new_item['file']
|
||||||
|
else:
|
||||||
|
identical = (old_item['id'] == new_item['id'] and
|
||||||
|
old_item['type'] == new_item['type'])
|
||||||
|
if j == 0 and identical:
|
||||||
|
del old[j], index[j]
|
||||||
|
break
|
||||||
|
elif identical:
|
||||||
|
# item now at pos i has been moved from original pos i+j
|
||||||
|
PL.move_playlist_item(playqueue, i + j, i)
|
||||||
|
# Delete the item we just found
|
||||||
|
del old[i + j], index[i + j]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Did not find element i in the old list - Kodi monitor should
|
||||||
|
# pick this up!
|
||||||
|
# PL.add_playlist_item(playqueue, new_item, i-1)
|
||||||
|
pass
|
||||||
|
for i in index:
|
||||||
|
# Still got some old items left that need deleting
|
||||||
|
PL.delete_playlist_item(playqueue, i)
|
||||||
|
log.debug('New playqueue: %s' % playqueue)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
threadStopped = self.threadStopped
|
||||||
|
threadSuspended = self.threadSuspended
|
||||||
|
log.info("----===## Starting PlayQueue client ##===----")
|
||||||
|
# Initialize the playqueues, if Kodi already got items in them
|
||||||
|
for playqueue in self.playqueues:
|
||||||
|
for i, item in enumerate(PL.get_kodi_playlist_items(playqueue)):
|
||||||
|
if i == 0:
|
||||||
|
PL.init_Plex_playlist(playqueue, kodi_item=item)
|
||||||
|
else:
|
||||||
|
PL.add_playlist_item(playqueue, item, i)
|
||||||
|
while not threadStopped():
|
||||||
|
while threadSuspended():
|
||||||
|
if threadStopped():
|
||||||
|
break
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
for playqueue in self.playqueues:
|
||||||
|
if not playqueue.items:
|
||||||
|
# Skip empty playqueues as items can't be modified
|
||||||
|
continue
|
||||||
|
kodi_playqueue = PL.get_kodi_playlist_items(playqueue)
|
||||||
|
if playqueue.old_kodi_pl != kodi_playqueue:
|
||||||
|
# compare old and new playqueue
|
||||||
|
self._compare_playqueues(playqueue, kodi_playqueue)
|
||||||
|
playqueue.old_kodi_pl = list(kodi_playqueue)
|
||||||
|
xbmc.sleep(1000)
|
||||||
|
log.info("----===## PlayQueue client stopped ##===----")
|
|
@ -32,8 +32,10 @@ class UserClient(threading.Thread):
|
||||||
# Borg - multiple instances, shared state
|
# Borg - multiple instances, shared state
|
||||||
__shared_state = {}
|
__shared_state = {}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, callback=None):
|
||||||
self.__dict__ = self.__shared_state
|
self.__dict__ = self.__shared_state
|
||||||
|
if callback is not None:
|
||||||
|
self.mgr = callback
|
||||||
|
|
||||||
self.auth = True
|
self.auth = True
|
||||||
self.retry = 0
|
self.retry = 0
|
||||||
|
|
|
@ -133,21 +133,21 @@ def tryDecode(string, encoding='utf-8'):
|
||||||
|
|
||||||
|
|
||||||
def DateToKodi(stamp):
|
def DateToKodi(stamp):
|
||||||
"""
|
"""
|
||||||
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
|
converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
|
||||||
propper, human-readable time stamp used by Kodi
|
propper, human-readable time stamp used by Kodi
|
||||||
|
|
||||||
Output: Y-m-d h:m:s = 2009-04-05 23:16:04
|
Output: Y-m-d h:m:s = 2009-04-05 23:16:04
|
||||||
|
|
||||||
None if an error was encountered
|
None if an error was encountered
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
stamp = float(stamp) + float(window('kodiplextimeoffset'))
|
stamp = float(stamp) + float(window('kodiplextimeoffset'))
|
||||||
date_time = time.localtime(stamp)
|
date_time = time.localtime(stamp)
|
||||||
localdate = time.strftime('%Y-%m-%d %H:%M:%S', date_time)
|
localdate = time.strftime('%Y-%m-%d %H:%M:%S', date_time)
|
||||||
except:
|
except:
|
||||||
localdate = None
|
localdate = None
|
||||||
return localdate
|
return localdate
|
||||||
|
|
||||||
|
|
||||||
def IfExists(path):
|
def IfExists(path):
|
||||||
|
@ -938,9 +938,33 @@ def ThreadMethods(cls):
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
class Lock_Function:
|
||||||
|
"""
|
||||||
|
Decorator for class methods and functions to lock them with lock.
|
||||||
|
|
||||||
|
Initialize this class first
|
||||||
|
lockfunction = Lock_Function(lock), where lock is a threading.Lock() object
|
||||||
|
|
||||||
|
To then lock a function or method:
|
||||||
|
|
||||||
|
@lockfunction.lockthis
|
||||||
|
def some_function(args, kwargs)
|
||||||
|
"""
|
||||||
|
def __init__(self, lock):
|
||||||
|
self.lock = lock
|
||||||
|
|
||||||
|
def lockthis(self, func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with self.lock:
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# UNUSED METHODS
|
# UNUSED METHODS
|
||||||
|
|
||||||
|
|
||||||
def changePlayState(itemType, kodiId, playCount, lastplayed):
|
def changePlayState(itemType, kodiId, playCount, lastplayed):
|
||||||
"""
|
"""
|
||||||
YET UNUSED
|
YET UNUSED
|
||||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
||||||
import websocket
|
import websocket
|
||||||
from json import loads
|
from json import loads
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from Queue import Queue
|
||||||
from ssl import CERT_NONE
|
from ssl import CERT_NONE
|
||||||
|
|
||||||
from xbmc import sleep
|
from xbmc import sleep
|
||||||
|
@ -24,10 +25,12 @@ log = logging.getLogger("PLEX."+__name__)
|
||||||
class WebSocket(Thread):
|
class WebSocket(Thread):
|
||||||
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
||||||
|
|
||||||
def __init__(self, queue):
|
def __init__(self, callback=None):
|
||||||
|
if callback is not None:
|
||||||
|
self.mgr = callback
|
||||||
self.ws = None
|
self.ws = None
|
||||||
# Communication with librarysync
|
# Communication with librarysync
|
||||||
self.queue = queue
|
self.queue = Queue()
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
|
|
||||||
def process(self, opcode, message):
|
def process(self, opcode, message):
|
||||||
|
|
95
service.py
95
service.py
|
@ -5,7 +5,6 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import Queue
|
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
|
@ -33,17 +32,18 @@ sys.path.append(_base_resource)
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
from utils import settings, window, language as lang
|
from utils import settings, window, language as lang
|
||||||
import userclient
|
from userclient import UserClient
|
||||||
import clientinfo
|
import clientinfo
|
||||||
import initialsetup
|
import initialsetup
|
||||||
import kodimonitor
|
from kodimonitor import KodiMonitor
|
||||||
import librarysync
|
from librarysync import LibrarySync
|
||||||
import videonodes
|
import videonodes
|
||||||
import websocket_client as wsc
|
from websocket_client import WebSocket
|
||||||
import downloadutils
|
import downloadutils
|
||||||
|
from playqueue import Playqueue
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
import PlexCompanion
|
from PlexCompanion import PlexCompanion
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -61,11 +61,18 @@ class Service():
|
||||||
server_online = True
|
server_online = True
|
||||||
warn_auth = True
|
warn_auth = True
|
||||||
|
|
||||||
userclient_running = False
|
user = None
|
||||||
websocket_running = False
|
ws = None
|
||||||
|
library = None
|
||||||
|
plexCompanion = None
|
||||||
|
playqueue = None
|
||||||
|
|
||||||
|
user_running = False
|
||||||
|
ws_running = False
|
||||||
library_running = False
|
library_running = False
|
||||||
kodimonitor_running = False
|
|
||||||
plexCompanion_running = False
|
plexCompanion_running = False
|
||||||
|
playqueue_running = False
|
||||||
|
kodimonitor_running = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
|
@ -96,7 +103,7 @@ class Service():
|
||||||
"plex_online", "plex_serverStatus", "plex_onWake",
|
"plex_online", "plex_serverStatus", "plex_onWake",
|
||||||
"plex_dbCheck", "plex_kodiScan",
|
"plex_dbCheck", "plex_kodiScan",
|
||||||
"plex_shouldStop", "currUserId", "plex_dbScan",
|
"plex_shouldStop", "currUserId", "plex_dbScan",
|
||||||
"plex_initialScan", "plex_customplaylist", "plex_playbackProps",
|
"plex_initialScan", "plex_customplayqueue", "plex_playbackProps",
|
||||||
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
"plex_runLibScan", "plex_username", "pms_token", "plex_token",
|
||||||
"pms_server", "plex_machineIdentifier", "plex_servername",
|
"pms_server", "plex_machineIdentifier", "plex_servername",
|
||||||
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
"plex_authenticated", "PlexUserImage", "useDirectPaths",
|
||||||
|
@ -129,13 +136,13 @@ class Service():
|
||||||
# Server auto-detect
|
# Server auto-detect
|
||||||
initialsetup.InitialSetup().setup()
|
initialsetup.InitialSetup().setup()
|
||||||
|
|
||||||
# Queue for background sync
|
# Initialize important threads, handing over self for callback purposes
|
||||||
queue = Queue.Queue()
|
self.user = UserClient(self)
|
||||||
|
self.ws = WebSocket(self)
|
||||||
|
self.library = LibrarySync(self)
|
||||||
|
self.plexCompanion = PlexCompanion(self)
|
||||||
|
self.playqueue = Playqueue(self)
|
||||||
|
|
||||||
# Initialize important threads
|
|
||||||
user = userclient.UserClient()
|
|
||||||
ws = wsc.WebSocket(queue)
|
|
||||||
library = librarysync.LibrarySync(queue)
|
|
||||||
plx = PlexAPI.PlexAPI()
|
plx = PlexAPI.PlexAPI()
|
||||||
|
|
||||||
welcome_msg = True
|
welcome_msg = True
|
||||||
|
@ -157,7 +164,7 @@ class Service():
|
||||||
if window('plex_online') == "true":
|
if window('plex_online') == "true":
|
||||||
# Plex server is online
|
# Plex server is online
|
||||||
# Verify if user is set and has access to the server
|
# Verify if user is set and has access to the server
|
||||||
if (user.currUser is not None) and user.HasAccess:
|
if (self.user.currUser is not None) and self.user.HasAccess:
|
||||||
if not self.kodimonitor_running:
|
if not self.kodimonitor_running:
|
||||||
# Start up events
|
# Start up events
|
||||||
self.warn_auth = True
|
self.warn_auth = True
|
||||||
|
@ -166,38 +173,43 @@ class Service():
|
||||||
welcome_msg = False
|
welcome_msg = False
|
||||||
xbmcgui.Dialog().notification(
|
xbmcgui.Dialog().notification(
|
||||||
heading=addonName,
|
heading=addonName,
|
||||||
message="%s %s" % (lang(33000), user.currUser),
|
message="%s %s" % (lang(33000),
|
||||||
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
|
self.user.currUser),
|
||||||
|
icon="special://home/addons/plugin."
|
||||||
|
"video.plexkodiconnect/icon.png",
|
||||||
time=2000,
|
time=2000,
|
||||||
sound=False)
|
sound=False)
|
||||||
# Start monitoring kodi events
|
# Start monitoring kodi events
|
||||||
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
self.kodimonitor_running = KodiMonitor(self)
|
||||||
|
# Start playqueue client
|
||||||
|
if not self.playqueue_running:
|
||||||
|
self.playqueue_running = True
|
||||||
|
self.playqueue.start()
|
||||||
# Start the Websocket Client
|
# Start the Websocket Client
|
||||||
if not self.websocket_running:
|
if not self.ws_running:
|
||||||
self.websocket_running = True
|
self.ws_running = True
|
||||||
ws.start()
|
self.ws.start()
|
||||||
# Start the syncing thread
|
# Start the syncing thread
|
||||||
if not self.library_running:
|
if not self.library_running:
|
||||||
self.library_running = True
|
self.library_running = True
|
||||||
library.start()
|
self.library.start()
|
||||||
# Start the Plex Companion thread
|
# Start the Plex Companion thread
|
||||||
if not self.plexCompanion_running:
|
if not self.plexCompanion_running:
|
||||||
self.plexCompanion_running = True
|
self.plexCompanion_running = True
|
||||||
plexCompanion = PlexCompanion.PlexCompanion()
|
self.plexCompanion.start()
|
||||||
plexCompanion.start()
|
|
||||||
else:
|
else:
|
||||||
if (user.currUser is None) and self.warn_auth:
|
if (self.user.currUser is None) and self.warn_auth:
|
||||||
# Alert user is not authenticated and suppress future warning
|
# Alert user is not authenticated and suppress future
|
||||||
|
# warning
|
||||||
self.warn_auth = False
|
self.warn_auth = False
|
||||||
log.warn("Not authenticated yet.")
|
log.warn("Not authenticated yet.")
|
||||||
|
|
||||||
# User access is restricted.
|
# User access is restricted.
|
||||||
# Keep verifying until access is granted
|
# Keep verifying until access is granted
|
||||||
# unless server goes offline or Kodi is shut down.
|
# unless server goes offline or Kodi is shut down.
|
||||||
while user.HasAccess == False:
|
while self.user.HasAccess is False:
|
||||||
# Verify access with an API call
|
# Verify access with an API call
|
||||||
user.hasAccess()
|
self.user.hasAccess()
|
||||||
|
|
||||||
if window('plex_online') != "true":
|
if window('plex_online') != "true":
|
||||||
# Server went offline
|
# Server went offline
|
||||||
|
@ -211,7 +223,7 @@ class Service():
|
||||||
# Wait until Plex server is online
|
# Wait until Plex server is online
|
||||||
# or Kodi is shut down.
|
# or Kodi is shut down.
|
||||||
while not monitor.abortRequested():
|
while not monitor.abortRequested():
|
||||||
server = user.getServer()
|
server = self.user.getServer()
|
||||||
if server is False:
|
if server is False:
|
||||||
# No server info set in add-on settings
|
# No server info set in add-on settings
|
||||||
pass
|
pass
|
||||||
|
@ -268,9 +280,9 @@ class Service():
|
||||||
window('suspend_LibraryThread', clear=True)
|
window('suspend_LibraryThread', clear=True)
|
||||||
|
|
||||||
# Start the userclient thread
|
# Start the userclient thread
|
||||||
if not self.userclient_running:
|
if not self.user_running:
|
||||||
self.userclient_running = True
|
self.user_running = True
|
||||||
user.start()
|
self.user.start()
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -286,27 +298,22 @@ class Service():
|
||||||
|
|
||||||
# Tell all threads to terminate (e.g. several lib sync threads)
|
# Tell all threads to terminate (e.g. several lib sync threads)
|
||||||
window('plex_terminateNow', value='true')
|
window('plex_terminateNow', value='true')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plexCompanion.stopThread()
|
self.plexCompanion.stopThread()
|
||||||
except:
|
except:
|
||||||
log.warn('plexCompanion already shut down')
|
log.warn('plexCompanion already shut down')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
library.stopThread()
|
self.library.stopThread()
|
||||||
except:
|
except:
|
||||||
log.warn('Library sync already shut down')
|
log.warn('Library sync already shut down')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ws.stopThread()
|
self.ws.stopThread()
|
||||||
except:
|
except:
|
||||||
log.warn('Websocket client already shut down')
|
log.warn('Websocket client already shut down')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user.stopThread()
|
self.user.stopThread()
|
||||||
except:
|
except:
|
||||||
log.warn('User client already shut down')
|
log.warn('User client already shut down')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
downloadutils.DownloadUtils().stopSession()
|
downloadutils.DownloadUtils().stopSession()
|
||||||
except:
|
except:
|
||||||
|
|
Loading…
Add table
Reference in a new issue