2018-03-07 06:40:30 +11:00
|
|
|
# -*- coding: utf-8 -*-
|
2017-12-09 05:43:06 +11:00
|
|
|
"""
|
|
|
|
Collection of functions associated with Kodi and Plex playlists and playqueues
|
|
|
|
"""
|
2017-12-10 00:35:08 +11:00
|
|
|
from logging import getLogger
|
2018-05-01 23:21:29 +10:00
|
|
|
import os
|
2018-05-02 17:33:37 +10:00
|
|
|
import urllib
|
2017-03-26 23:09:43 +11:00
|
|
|
from urlparse import parse_qsl, urlsplit
|
2017-05-31 21:44:04 +10:00
|
|
|
from re import compile as re_compile
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-01-05 06:57:16 +11:00
|
|
|
import plexdb_functions as plexdb
|
2016-12-28 03:33:52 +11:00
|
|
|
from downloadutils import DownloadUtils as DU
|
2018-03-07 06:40:30 +11:00
|
|
|
from utils import try_decode, try_encode
|
2016-12-28 23:14:21 +11:00
|
|
|
from PlexAPI import API
|
2017-05-31 21:44:04 +10:00
|
|
|
from PlexFunctions import GetPlexMetadata
|
2018-02-15 18:09:57 +11:00
|
|
|
from kodidb_functions import kodiid_from_filename
|
2017-12-09 05:43:06 +11:00
|
|
|
import json_rpc as js
|
2017-12-14 20:22:48 +11:00
|
|
|
import variables as v
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
|
2017-12-10 00:35:08 +11:00
|
|
|
LOG = getLogger("PLEX." + __name__)
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-05-31 21:44:04 +10:00
|
|
|
REGEX = re_compile(r'''metadata%2F(\d+)''')
|
2016-12-28 03:33:52 +11:00
|
|
|
###############################################################################
|
|
|
|
|
2018-01-30 17:50:44 +11:00
|
|
|
|
|
|
|
class PlaylistError(Exception):
|
|
|
|
"""
|
|
|
|
Exception for our playlist constructs
|
|
|
|
"""
|
|
|
|
pass
|
2017-01-03 00:07:24 +11:00
|
|
|
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-12-21 19:28:06 +11:00
|
|
|
class PlaylistObjectBaseclase(object):
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
|
|
|
Base class
|
|
|
|
"""
|
2017-12-21 19:28:06 +11:00
|
|
|
def __init__(self):
|
|
|
|
self.id = None
|
2018-05-04 01:30:29 +10:00
|
|
|
self.type = None
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
def __repr__(self):
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
2018-03-07 06:40:30 +11:00
|
|
|
Print the playlist, e.g. to log. Returns utf-8 encoded string
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
2018-03-07 06:40:30 +11:00
|
|
|
answ = u'{\'%s\': {\'id\': %s, ' % (self.__class__.__name__, self.id)
|
2016-12-29 00:48:23 +11:00
|
|
|
# For some reason, can't use dir directly
|
2016-12-28 03:33:52 +11:00
|
|
|
for key in self.__dict__:
|
2018-05-04 01:30:29 +10:00
|
|
|
if key in ('id', 'kodi_pl'):
|
2017-12-29 03:32:58 +11:00
|
|
|
continue
|
2018-03-07 06:40:30 +11:00
|
|
|
if isinstance(getattr(self, key), str):
|
2017-12-29 03:32:58 +11:00
|
|
|
answ += '\'%s\': \'%s\', ' % (key,
|
2018-03-07 06:40:30 +11:00
|
|
|
try_decode(getattr(self, key)))
|
2018-04-08 20:37:25 +10:00
|
|
|
elif isinstance(getattr(self, key), unicode):
|
|
|
|
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
|
2017-12-29 03:32:58 +11:00
|
|
|
else:
|
|
|
|
# e.g. int
|
2018-03-07 06:40:30 +11:00
|
|
|
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
|
2018-05-04 01:30:29 +10:00
|
|
|
return try_encode(answ + '}}')
|
2016-12-28 23:14:21 +11:00
|
|
|
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-12-21 19:28:06 +11:00
|
|
|
class Playlist_Object(PlaylistObjectBaseclase):
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
|
|
|
To be done for synching Plex playlists to Kodi
|
|
|
|
"""
|
2016-12-28 03:33:52 +11:00
|
|
|
kind = 'playList'
|
|
|
|
|
2018-04-28 17:12:29 +10:00
|
|
|
def __init__(self):
|
|
|
|
self.plex_name = None
|
|
|
|
self.plex_updatedat = None
|
|
|
|
self._kodi_path = None
|
|
|
|
self.kodi_filename = None
|
|
|
|
self.kodi_extension = None
|
|
|
|
self.kodi_hash = None
|
2018-05-01 22:56:07 +10:00
|
|
|
PlaylistObjectBaseclase.__init__(self)
|
2018-04-28 17:12:29 +10:00
|
|
|
|
|
|
|
@property
|
|
|
|
def kodi_path(self):
|
|
|
|
return self._kodi_path
|
|
|
|
|
|
|
|
@kodi_path.setter
|
|
|
|
def kodi_path(self, path):
|
2018-05-02 00:20:57 +10:00
|
|
|
file = os.path.basename(path)
|
2018-04-28 17:12:29 +10:00
|
|
|
try:
|
2018-05-01 23:23:39 +10:00
|
|
|
self.kodi_filename, self.kodi_extension = file.split('.', 1)
|
2018-04-28 17:12:29 +10:00
|
|
|
except ValueError:
|
2018-05-02 00:18:06 +10:00
|
|
|
LOG.error('Trying to set invalid path: %s', path)
|
2018-04-28 17:12:29 +10:00
|
|
|
raise PlaylistError('Invalid path: %s' % path)
|
|
|
|
if path.startswith(v.PLAYLIST_PATH_VIDEO):
|
2018-05-01 22:48:49 +10:00
|
|
|
self.type = v.KODI_TYPE_VIDEO_PLAYLIST
|
2018-04-28 17:12:29 +10:00
|
|
|
elif path.startswith(v.PLAYLIST_PATH_MUSIC):
|
2018-05-01 22:48:49 +10:00
|
|
|
self.type = v.KODI_TYPE_AUDIO_PLAYLIST
|
2018-04-28 17:12:29 +10:00
|
|
|
else:
|
2018-05-02 00:18:06 +10:00
|
|
|
LOG.error('Playlist type not supported for %s', path)
|
2018-04-28 17:12:29 +10:00
|
|
|
raise PlaylistError('Playlist type not supported: %s' % path)
|
|
|
|
if not self.plex_name:
|
|
|
|
self.plex_name = self.kodi_filename
|
|
|
|
self._kodi_path = path
|
|
|
|
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-12-21 19:28:06 +11:00
|
|
|
class Playqueue_Object(PlaylistObjectBaseclase):
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
|
|
|
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
|
|
|
|
2017-12-09 05:43:06 +11:00
|
|
|
playlistid = None [int] Kodi playlist id (0, 1, 2)
|
2017-12-06 21:40:27 +11:00
|
|
|
type = None [str] Kodi type: 'audio', 'video', 'picture'
|
|
|
|
kodi_pl = None Kodi xbmc.PlayList object
|
|
|
|
items = [] [list] of Playlist_Items
|
2017-12-09 05:43:06 +11:00
|
|
|
id = None [str] Plex playQueueID, unique Plex identifier
|
2017-12-06 21:40:27 +11:00
|
|
|
version = None [int] Plex version of the playQueue
|
|
|
|
selectedItemID = None
|
|
|
|
[str] Plex selectedItemID, playing element in queue
|
|
|
|
selectedItemOffset = None
|
|
|
|
[str] Offset of the playing element in queue
|
|
|
|
shuffled = 0 [int] 0: not shuffled, 1: ??? 2: ???
|
|
|
|
repeat = 0 [int] 0: not repeated, 1: ??? 2: ???
|
|
|
|
|
|
|
|
If Companion playback is initiated by another user:
|
|
|
|
plex_transient_token = None
|
|
|
|
"""
|
2016-12-28 03:33:52 +11:00
|
|
|
kind = 'playQueue'
|
|
|
|
|
2018-05-04 01:30:29 +10:00
|
|
|
def __init__(self):
|
|
|
|
self.playlistid = None
|
|
|
|
self.kodi_pl = None
|
|
|
|
self.items = []
|
|
|
|
self.version = None
|
|
|
|
self.selectedItemID = None
|
|
|
|
self.selectedItemOffset = None
|
|
|
|
self.shuffled = 0
|
|
|
|
self.repeat = 0
|
|
|
|
self.plex_transient_token = None
|
|
|
|
# Need a hack for detecting swaps of elements
|
|
|
|
self.old_kodi_pl = []
|
|
|
|
# Did PKC itself just change the playqueue so the PKC playqueue monitor
|
|
|
|
# should not pick up any changes?
|
|
|
|
self.pkc_edit = False
|
|
|
|
# Workaround to avoid endless loops of detecting PL clears
|
|
|
|
self._clear_list = []
|
|
|
|
PlaylistObjectBaseclase.__init__(self)
|
|
|
|
|
|
|
|
def is_pkc_clear(self):
|
|
|
|
"""
|
|
|
|
Returns True if PKC has cleared the Kodi playqueue just recently.
|
|
|
|
Then this clear will be ignored from now on
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
self._clear_list.pop()
|
|
|
|
except IndexError:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def clear(self, kodi=True):
|
|
|
|
"""
|
|
|
|
Resets the playlist object to an empty playlist.
|
|
|
|
|
|
|
|
Pass kodi=False in order to NOT clear the Kodi playqueue
|
|
|
|
"""
|
|
|
|
# kodi monitor's on_clear method will only be called if there were some
|
|
|
|
# items to begin with
|
|
|
|
if kodi and self.kodi_pl.size() != 0:
|
|
|
|
self._clear_list.append(None)
|
|
|
|
self.kodi_pl.clear() # Clear Kodi playlist object
|
|
|
|
self.items = []
|
|
|
|
self.id = None
|
|
|
|
self.version = None
|
|
|
|
self.selectedItemID = None
|
|
|
|
self.selectedItemOffset = None
|
|
|
|
self.shuffled = 0
|
|
|
|
self.repeat = 0
|
|
|
|
self.plex_transient_token = None
|
|
|
|
self.old_kodi_pl = []
|
|
|
|
LOG.debug('Playlist cleared: %s', self)
|
|
|
|
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
class Playlist_Item(object):
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
|
|
|
Object to fill our playqueues and playlists with.
|
|
|
|
|
2017-12-09 05:43:06 +11:00
|
|
|
id = None [str] Plex playlist/playqueue id, e.g. playQueueItemID
|
2017-12-07 04:05:01 +11:00
|
|
|
plex_id = None [str] Plex unique item id, "ratingKey"
|
|
|
|
plex_type = None [str] Plex type, e.g. 'movie', 'clip'
|
2017-12-21 19:28:06 +11:00
|
|
|
plex_uuid = None [str] Plex librarySectionUUID
|
2018-01-03 01:12:01 +11:00
|
|
|
kodi_id = None Kodi unique kodi id (unique only within type!)
|
2017-12-07 04:05:01 +11:00
|
|
|
kodi_type = None [str] Kodi type: 'movie'
|
|
|
|
file = None [str] Path to the item's file. STRING!!
|
2017-12-21 19:28:06 +11:00
|
|
|
uri = None [str] Weird Plex uri path involving plex_uuid. STRING!
|
2017-12-07 04:05:01 +11:00
|
|
|
guid = None [str] Weird Plex guid
|
|
|
|
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
|
2018-01-08 03:50:30 +11:00
|
|
|
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
|
2018-01-22 04:31:49 +11:00
|
|
|
playcount = None [int] how many times the item has already been played
|
|
|
|
offset = None [int] the item's view offset UPON START in Plex time
|
2018-01-11 06:14:05 +11:00
|
|
|
part = 0 [int] part number if Plex video consists of mult. parts
|
2018-02-03 22:45:48 +11:00
|
|
|
force_transcode [bool] defaults to False
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
2017-12-21 19:28:06 +11:00
|
|
|
def __init__(self):
|
|
|
|
self.id = None
|
|
|
|
self.plex_id = None
|
|
|
|
self.plex_type = None
|
|
|
|
self.plex_uuid = None
|
|
|
|
self.kodi_id = None
|
|
|
|
self.kodi_type = None
|
|
|
|
self.file = None
|
|
|
|
self.uri = None
|
|
|
|
self.guid = None
|
|
|
|
self.xml = None
|
2018-01-08 03:50:30 +11:00
|
|
|
self.playmethod = None
|
2018-01-22 04:31:49 +11:00
|
|
|
self.playcount = None
|
|
|
|
self.offset = None
|
2018-01-11 06:14:05 +11:00
|
|
|
# If Plex video consists of several parts; part number
|
2017-12-21 19:28:06 +11:00
|
|
|
self.part = 0
|
2018-02-03 22:45:48 +11:00
|
|
|
self.force_transcode = False
|
2017-12-08 03:25:24 +11:00
|
|
|
|
2016-12-28 23:14:21 +11:00
|
|
|
def __repr__(self):
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
2018-03-07 06:40:30 +11:00
|
|
|
Print the playlist item, e.g. to log. Returns utf-8 encoded string
|
2017-12-06 21:40:27 +11:00
|
|
|
"""
|
2018-03-07 06:40:30 +11:00
|
|
|
answ = (u'{\'%s\': {\'id\': \'%s\', \'plex_id\': \'%s\', '
|
|
|
|
% (self.__class__.__name__, self.id, self.plex_id))
|
2016-12-28 23:14:21 +11:00
|
|
|
for key in self.__dict__:
|
2017-12-29 03:32:58 +11:00
|
|
|
if key in ('id', 'plex_id', 'xml'):
|
|
|
|
continue
|
2018-03-07 06:40:30 +11:00
|
|
|
if isinstance(getattr(self, key), str):
|
2017-12-29 03:32:58 +11:00
|
|
|
answ += '\'%s\': \'%s\', ' % (key,
|
2018-03-07 06:40:30 +11:00
|
|
|
try_decode(getattr(self, key)))
|
2018-04-08 20:37:25 +10:00
|
|
|
elif isinstance(getattr(self, key), unicode):
|
|
|
|
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
|
2017-03-06 01:06:54 +11:00
|
|
|
else:
|
|
|
|
# e.g. int
|
2018-03-07 06:40:30 +11:00
|
|
|
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
|
2017-12-29 03:32:58 +11:00
|
|
|
if self.xml is None:
|
|
|
|
answ += '\'xml\': None}}'
|
|
|
|
else:
|
|
|
|
answ += '\'xml\': \'%s\'}}' % self.xml.tag
|
2018-03-07 06:40:30 +11:00
|
|
|
return try_encode(answ)
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-12-14 20:22:48 +11:00
|
|
|
def plex_stream_index(self, kodi_stream_index, stream_type):
|
|
|
|
"""
|
|
|
|
Pass in the kodi_stream_index [int] in order to receive the Plex stream
|
|
|
|
index.
|
|
|
|
|
|
|
|
stream_type: 'video', 'audio', 'subtitle'
|
|
|
|
|
|
|
|
Returns None if unsuccessful
|
|
|
|
"""
|
|
|
|
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
|
|
|
count = 0
|
2018-02-24 01:24:26 +11:00
|
|
|
# Kodi indexes differently than Plex
|
2017-12-14 20:22:48 +11:00
|
|
|
for stream in self.xml[0][self.part]:
|
2018-03-07 17:52:13 +11:00
|
|
|
if (stream.attrib['streamType'] == stream_type and
|
2018-02-24 01:24:26 +11:00
|
|
|
'key' in stream.attrib):
|
|
|
|
if count == kodi_stream_index:
|
|
|
|
return stream.attrib['id']
|
|
|
|
count += 1
|
|
|
|
for stream in self.xml[0][self.part]:
|
2018-03-07 17:52:13 +11:00
|
|
|
if (stream.attrib['streamType'] == stream_type and
|
2018-02-24 01:24:26 +11:00
|
|
|
'key' not in stream.attrib):
|
2017-12-14 20:22:48 +11:00
|
|
|
if count == kodi_stream_index:
|
|
|
|
return stream.attrib['id']
|
|
|
|
count += 1
|
|
|
|
|
2017-12-16 02:11:19 +11:00
|
|
|
def kodi_stream_index(self, plex_stream_index, stream_type):
|
|
|
|
"""
|
|
|
|
Pass in the kodi_stream_index [int] in order to receive the Plex stream
|
|
|
|
index.
|
|
|
|
|
|
|
|
stream_type: 'video', 'audio', 'subtitle'
|
|
|
|
|
|
|
|
Returns None if unsuccessful
|
|
|
|
"""
|
|
|
|
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
|
|
|
count = 0
|
|
|
|
for stream in self.xml[0][self.part]:
|
2018-02-24 01:24:26 +11:00
|
|
|
if (stream.attrib['streamType'] == stream_type and
|
|
|
|
'key' in stream.attrib):
|
|
|
|
if stream.attrib['id'] == plex_stream_index:
|
|
|
|
return count
|
|
|
|
count += 1
|
|
|
|
for stream in self.xml[0][self.part]:
|
|
|
|
if (stream.attrib['streamType'] == stream_type and
|
|
|
|
'key' not in stream.attrib):
|
2017-12-16 02:11:19 +11:00
|
|
|
if stream.attrib['id'] == plex_stream_index:
|
|
|
|
return count
|
|
|
|
count += 1
|
|
|
|
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-01-03 00:07:24 +11:00
|
|
|
def playlist_item_from_kodi(kodi_item):
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
|
|
|
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()
|
2016-12-29 00:48:23 +11:00
|
|
|
item.kodi_id = kodi_item.get('id')
|
2017-05-07 23:02:45 +10:00
|
|
|
item.kodi_type = kodi_item.get('type')
|
2016-12-29 00:48:23 +11:00
|
|
|
if item.kodi_id:
|
2017-01-05 06:57:16 +11:00
|
|
|
with plexdb.Get_Plex_DB() as plex_db:
|
|
|
|
plex_dbitem = plex_db.getItem_byKodiId(kodi_item['id'],
|
2016-12-28 03:33:52 +11:00
|
|
|
kodi_item['type'])
|
|
|
|
try:
|
2018-01-03 01:12:01 +11:00
|
|
|
item.plex_id = plex_dbitem[0]
|
2017-05-07 23:02:45 +10:00
|
|
|
item.plex_type = plex_dbitem[2]
|
2018-01-03 01:12:01 +11:00
|
|
|
item.plex_uuid = plex_dbitem[0] # we dont need the uuid yet :-)
|
2016-12-28 03:33:52 +11:00
|
|
|
except TypeError:
|
|
|
|
pass
|
2017-01-03 00:07:24 +11:00
|
|
|
item.file = kodi_item.get('file')
|
2017-05-07 23:02:45 +10:00
|
|
|
if item.plex_id is None and item.file is not None:
|
|
|
|
query = dict(parse_qsl(urlsplit(item.file).query))
|
|
|
|
item.plex_id = query.get('plex_id')
|
|
|
|
item.plex_type = query.get('itemType')
|
2017-12-29 01:45:48 +11:00
|
|
|
if item.plex_id is None and item.file is not None:
|
2018-05-02 17:33:37 +10:00
|
|
|
item.uri = ('library://whatever/item/%s'
|
|
|
|
% urllib.quote(item.file, safe=''))
|
2016-12-28 03:33:52 +11:00
|
|
|
else:
|
2017-01-03 00:07:24 +11:00
|
|
|
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
|
2016-12-28 03:33:52 +11:00
|
|
|
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
2017-12-21 19:28:06 +11:00
|
|
|
(item.plex_uuid, item.plex_id))
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Made playlist item from Kodi: %s', item)
|
2016-12-28 03:33:52 +11:00
|
|
|
return item
|
|
|
|
|
|
|
|
|
2018-02-15 18:09:57 +11:00
|
|
|
def verify_kodi_item(plex_id, kodi_item):
|
|
|
|
"""
|
|
|
|
Tries to lookup kodi_id and kodi_type for kodi_item (with kodi_item['file']
|
|
|
|
supplied) - if and only if plex_id is None.
|
|
|
|
|
|
|
|
Returns the kodi_item with kodi_item['id'] and kodi_item['type'] possibly
|
|
|
|
set to None if unsuccessful.
|
|
|
|
|
|
|
|
Will raise a PlaylistError if plex_id is None and kodi_item['file'] starts
|
|
|
|
with either 'plugin' or 'http'
|
|
|
|
"""
|
|
|
|
if plex_id is not None or kodi_item.get('id') is not None:
|
|
|
|
# Got all the info we need
|
|
|
|
return kodi_item
|
|
|
|
# Need more info since we don't have kodi_id nor type. Use file path.
|
|
|
|
if (kodi_item['file'].startswith('plugin') or
|
|
|
|
kodi_item['file'].startswith('http')):
|
2018-02-15 18:15:50 +11:00
|
|
|
raise PlaylistError('kodi_item cannot be used for Plex playback')
|
2018-02-15 18:09:57 +11:00
|
|
|
LOG.debug('Starting research for Kodi id since we didnt get one: %s',
|
|
|
|
kodi_item)
|
2018-04-28 17:12:29 +10:00
|
|
|
kodi_id, _ = kodiid_from_filename(kodi_item['file'], v.KODI_TYPE_MOVIE)
|
2018-02-15 18:09:57 +11:00
|
|
|
kodi_item['type'] = v.KODI_TYPE_MOVIE
|
|
|
|
if kodi_id is None:
|
2018-04-28 17:12:29 +10:00
|
|
|
kodi_id, _ = kodiid_from_filename(kodi_item['file'],
|
|
|
|
v.KODI_TYPE_EPISODE)
|
2018-02-15 18:09:57 +11:00
|
|
|
kodi_item['type'] = v.KODI_TYPE_EPISODE
|
|
|
|
if kodi_id is None:
|
2018-04-28 17:12:29 +10:00
|
|
|
kodi_id, _ = kodiid_from_filename(kodi_item['file'], v.KODI_TYPE_SONG)
|
2018-02-15 18:09:57 +11:00
|
|
|
kodi_item['type'] = v.KODI_TYPE_SONG
|
|
|
|
kodi_item['id'] = kodi_id
|
|
|
|
kodi_item['type'] = None if kodi_id is None else kodi_item['type']
|
|
|
|
LOG.debug('Research results for kodi_item: %s', kodi_item)
|
|
|
|
return kodi_item
|
|
|
|
|
|
|
|
|
2016-12-28 03:33:52 +11:00
|
|
|
def playlist_item_from_plex(plex_id):
|
|
|
|
"""
|
|
|
|
Returns a playlist element providing the plex_id ("ratingKey")
|
2017-01-03 00:07:24 +11:00
|
|
|
|
|
|
|
Returns a Playlist_Item
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
|
|
|
item = Playlist_Item()
|
|
|
|
item.plex_id = plex_id
|
2017-01-05 06:57:16 +11:00
|
|
|
with plexdb.Get_Plex_DB() as plex_db:
|
|
|
|
plex_dbitem = plex_db.getItem_byId(plex_id)
|
2016-12-28 03:33:52 +11:00
|
|
|
try:
|
2017-05-07 23:02:45 +10:00
|
|
|
item.plex_type = plex_dbitem[5]
|
2017-01-05 06:57:16 +11:00
|
|
|
item.kodi_id = plex_dbitem[0]
|
|
|
|
item.kodi_type = plex_dbitem[4]
|
2017-12-21 19:28:06 +11:00
|
|
|
except (TypeError, IndexError):
|
2016-12-28 03:33:52 +11:00
|
|
|
raise KeyError('Could not find plex_id %s in database' % plex_id)
|
2017-12-21 19:28:06 +11:00
|
|
|
item.plex_uuid = plex_id
|
2017-03-06 03:51:58 +11:00
|
|
|
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
2017-12-21 19:28:06 +11:00
|
|
|
(item.plex_uuid, plex_id))
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Made playlist item from plex: %s', item)
|
2016-12-28 03:33:52 +11:00
|
|
|
return item
|
|
|
|
|
|
|
|
|
2018-02-23 23:06:18 +11:00
|
|
|
def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None):
|
2017-01-03 00:07:24 +11:00
|
|
|
"""
|
|
|
|
Returns a playlist element for the playqueue using the Plex xml
|
2017-12-08 03:25:24 +11:00
|
|
|
|
|
|
|
xml_video_element: etree xml piece 1 level underneath <MediaContainer>
|
2017-01-03 00:07:24 +11:00
|
|
|
"""
|
|
|
|
item = Playlist_Item()
|
|
|
|
api = API(xml_video_element)
|
2018-02-12 00:42:49 +11:00
|
|
|
item.plex_id = api.plex_id()
|
|
|
|
item.plex_type = api.plex_type()
|
2018-02-23 23:06:18 +11:00
|
|
|
# item.id will only be set if you passed in an xml_video_element from e.g.
|
|
|
|
# a playQueue
|
|
|
|
item.id = api.item_id()
|
2018-01-21 23:42:22 +11:00
|
|
|
if kodi_id is not None:
|
|
|
|
item.kodi_id = kodi_id
|
|
|
|
item.kodi_type = kodi_type
|
|
|
|
elif item.plex_id is not None:
|
2017-01-05 06:57:16 +11:00
|
|
|
with plexdb.Get_Plex_DB() as plex_db:
|
|
|
|
db_element = plex_db.getItem_byId(item.plex_id)
|
2017-01-03 00:07:24 +11:00
|
|
|
try:
|
2018-02-23 23:06:18 +11:00
|
|
|
item.kodi_id, item.kodi_type = db_element[0], db_element[4]
|
2017-01-03 00:07:24 +11:00
|
|
|
except TypeError:
|
|
|
|
pass
|
2018-02-23 23:06:18 +11:00
|
|
|
item.guid = api.guid_html_escaped()
|
|
|
|
item.playcount = api.viewcount()
|
|
|
|
item.offset = api.resume_point()
|
2017-12-08 03:25:24 +11:00
|
|
|
item.xml = xml_video_element
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Created new playlist item from xml: %s', item)
|
2017-01-03 00:07:24 +11:00
|
|
|
return item
|
|
|
|
|
|
|
|
|
2016-12-28 03:33:52 +11:00
|
|
|
def _get_playListVersion_from_xml(playlist, xml):
|
|
|
|
"""
|
|
|
|
Takes a PMS xml as input to overwrite the playlist version (e.g. Plex
|
2018-01-30 17:50:44 +11:00
|
|
|
playQueueVersion).
|
|
|
|
|
|
|
|
Raises PlaylistError if unsuccessful
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
playlist.version = int(xml.attrib['%sVersion' % playlist.kind])
|
|
|
|
except (TypeError, AttributeError, KeyError):
|
2018-01-30 17:50:44 +11:00
|
|
|
raise PlaylistError('Could not get new playlist Version for playlist '
|
2018-02-08 00:28:54 +11:00
|
|
|
'%s' % playlist)
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
|
2017-01-03 00:07:24 +11:00
|
|
|
def get_playlist_details_from_xml(playlist, xml):
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
|
|
|
Takes a PMS xml as input and overwrites all the playlist's details, e.g.
|
2017-12-29 04:29:51 +11:00
|
|
|
playlist.id with the XML's playQueueID
|
2018-01-30 17:50:44 +11:00
|
|
|
|
|
|
|
Raises PlaylistError if something went wrong.
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2018-05-02 00:41:10 +10:00
|
|
|
playlist.id = xml.get('%sID' % playlist.kind)
|
|
|
|
playlist.version = xml.get('%sVersion' % playlist.kind)
|
|
|
|
playlist.shuffled = xml.get('%sShuffled' % playlist.kind)
|
|
|
|
playlist.selectedItemID = xml.get('%sSelectedItemID' % playlist.kind)
|
|
|
|
playlist.selectedItemOffset = xml.get(
|
|
|
|
'%sSelectedItemOffset' % playlist.kind)
|
|
|
|
LOG.debug('Updated playlist from xml: %s', playlist)
|
2017-01-03 00:07:24 +11:00
|
|
|
|
|
|
|
|
|
|
|
def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
|
|
|
|
"""
|
|
|
|
Updates Kodi playlist using a new PMS playlist. Pass in playlist_id if we
|
|
|
|
need to fetch a new playqueue
|
|
|
|
|
|
|
|
If an xml is passed in, the playlist will be overwritten with its info
|
|
|
|
"""
|
|
|
|
if xml is None:
|
|
|
|
xml = get_PMS_playlist(playlist, playlist_id)
|
|
|
|
# Clear our existing playlist and the associated Kodi playlist
|
|
|
|
playlist.clear()
|
|
|
|
# Set new values
|
2018-01-30 17:50:44 +11:00
|
|
|
get_playlist_details_from_xml(playlist, xml)
|
2017-01-03 00:07:24 +11:00
|
|
|
for plex_item in xml:
|
2017-01-22 03:20:42 +11:00
|
|
|
playlist_item = add_to_Kodi_playlist(playlist, plex_item)
|
|
|
|
if playlist_item is not None:
|
|
|
|
playlist.items.append(playlist_item)
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
|
2018-05-02 17:33:37 +10:00
|
|
|
def init_plex_playlist(playlist, plex_id):
|
|
|
|
"""
|
2018-05-02 23:44:54 +10:00
|
|
|
Initializes a new playlist on the PMS side. Will set playlist.id and
|
|
|
|
playlist.plex_updatedat. Will raise PlaylistError if something went wrong.
|
2018-05-02 17:33:37 +10:00
|
|
|
"""
|
|
|
|
LOG.debug('Initializing the playlist with Plex id %s on the Plex side: %s',
|
|
|
|
plex_id, playlist)
|
|
|
|
params = {
|
2018-05-03 02:50:31 +10:00
|
|
|
'type': v.PLEX_PLAYLIST_TYPE_FROM_KODI[playlist.type],
|
2018-05-02 17:33:37 +10:00
|
|
|
'title': playlist.plex_name,
|
|
|
|
'smart': 0,
|
|
|
|
'uri': ('library://None/item/%s' % (urllib.quote('/library/metadata/%s'
|
|
|
|
% plex_id, safe='')))
|
|
|
|
}
|
|
|
|
xml = DU().downloadUrl(url='{server}/playlists',
|
|
|
|
action_type='POST',
|
|
|
|
parameters=params)
|
2018-05-02 23:17:58 +10:00
|
|
|
try:
|
|
|
|
xml[0].attrib
|
|
|
|
except (TypeError, IndexError, AttributeError):
|
|
|
|
LOG.error('Could not initialize playlist on Plex side with plex id %s',
|
|
|
|
plex_id)
|
2018-05-02 23:44:54 +10:00
|
|
|
raise PlaylistError('Could not initialize Plex playlist %s', plex_id)
|
2018-05-02 23:17:58 +10:00
|
|
|
api = API(xml[0])
|
|
|
|
playlist.id = api.plex_id()
|
|
|
|
playlist.plex_updatedat = api.updated_at()
|
2018-05-02 17:33:37 +10:00
|
|
|
|
|
|
|
|
2018-05-02 02:08:31 +10:00
|
|
|
def init_plex_playqueue(playlist, plex_id=None, kodi_item=None):
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2017-01-03 00:07:24 +11:00
|
|
|
Initializes the Plex side without changing the Kodi playlists
|
2018-05-02 23:44:54 +10:00
|
|
|
WILL ALSO UPDATE OUR PLAYLISTS.
|
2017-01-03 00:07:24 +11:00
|
|
|
|
2018-01-30 17:50:44 +11:00
|
|
|
Returns the first PKC playlist item or raises PlaylistError
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2018-05-02 17:33:37 +10:00
|
|
|
LOG.debug('Initializing the playqueue on the Plex side: %s', playlist)
|
2018-02-04 01:12:10 +11:00
|
|
|
playlist.clear(kodi=False)
|
2018-02-15 18:09:57 +11:00
|
|
|
verify_kodi_item(plex_id, kodi_item)
|
2017-02-27 01:16:03 +11:00
|
|
|
try:
|
|
|
|
if plex_id:
|
|
|
|
item = playlist_item_from_plex(plex_id)
|
|
|
|
else:
|
|
|
|
item = playlist_item_from_kodi(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(playlist, xml)
|
2017-12-21 19:28:06 +11:00
|
|
|
# Need to get the details for the playlist item
|
2018-02-23 23:06:18 +11:00
|
|
|
item = playlist_item_from_xml(xml[0])
|
2017-12-08 03:25:24 +11:00
|
|
|
except (KeyError, IndexError, TypeError):
|
2018-01-30 17:50:44 +11:00
|
|
|
raise PlaylistError('Could not init Plex playlist with plex_id %s and '
|
2018-02-04 00:49:56 +11:00
|
|
|
'kodi_item %s' % (plex_id, kodi_item))
|
2016-12-28 03:33:52 +11:00
|
|
|
playlist.items.append(item)
|
2018-05-02 17:33:37 +10:00
|
|
|
LOG.debug('Initialized the playqueue on the Plex side: %s', playlist)
|
2018-01-30 17:50:44 +11:00
|
|
|
return item
|
2017-01-03 00:07:24 +11:00
|
|
|
|
|
|
|
|
|
|
|
def add_listitem_to_playlist(playlist, pos, listitem, kodi_id=None,
|
|
|
|
kodi_type=None, plex_id=None, file=None):
|
|
|
|
"""
|
|
|
|
Adds a listitem to both the Kodi and Plex playlist at position pos [int].
|
|
|
|
|
|
|
|
If file is not None, file will overrule kodi_id!
|
2017-03-06 01:06:54 +11:00
|
|
|
|
|
|
|
file: str!!
|
2017-01-03 00:07:24 +11:00
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('add_listitem_to_playlist at position %s. Playlist before add: '
|
|
|
|
'%s', pos, playlist)
|
2017-01-03 00:07:24 +11:00
|
|
|
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
2017-12-09 05:43:06 +11:00
|
|
|
if playlist.id is None:
|
2018-05-02 02:08:31 +10:00
|
|
|
init_plex_playqueue(playlist, plex_id, kodi_item)
|
2017-01-03 00:07:24 +11:00
|
|
|
else:
|
2018-05-02 23:34:21 +10:00
|
|
|
add_item_to_plex_playqueue(playlist, pos, plex_id, kodi_item)
|
2017-01-03 00:07:24 +11:00
|
|
|
if kodi_id is None and playlist.items[pos].kodi_id:
|
|
|
|
kodi_id = playlist.items[pos].kodi_id
|
|
|
|
kodi_type = playlist.items[pos].kodi_type
|
|
|
|
if file is None:
|
|
|
|
file = playlist.items[pos].file
|
|
|
|
# Otherwise we double the item!
|
|
|
|
del playlist.items[pos]
|
|
|
|
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
|
|
|
add_listitem_to_Kodi_playlist(playlist,
|
|
|
|
pos,
|
|
|
|
listitem,
|
|
|
|
file,
|
|
|
|
kodi_item=kodi_item)
|
|
|
|
|
|
|
|
|
|
|
|
def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
|
|
|
plex_id=None, file=None):
|
|
|
|
"""
|
|
|
|
Adds an item to BOTH the Kodi and Plex playlist at position pos [int]
|
2018-01-30 17:50:44 +11:00
|
|
|
file: str!
|
2017-03-06 01:06:54 +11:00
|
|
|
|
2018-01-30 17:50:44 +11:00
|
|
|
Raises PlaylistError if something went wrong
|
2017-01-03 00:07:24 +11:00
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('add_item_to_playlist. Playlist before adding: %s', playlist)
|
2017-01-03 00:07:24 +11:00
|
|
|
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
|
2017-12-09 05:43:06 +11:00
|
|
|
if playlist.id is None:
|
2018-05-02 02:08:31 +10:00
|
|
|
item = init_plex_playqueue(playlist, plex_id, kodi_item)
|
2017-01-03 00:07:24 +11:00
|
|
|
else:
|
2018-05-02 23:34:21 +10:00
|
|
|
item = add_item_to_plex_playqueue(playlist, pos, plex_id, kodi_item)
|
2017-12-08 03:25:24 +11:00
|
|
|
params = {
|
|
|
|
'playlistid': playlist.playlistid,
|
|
|
|
'position': pos
|
|
|
|
}
|
|
|
|
if item.kodi_id is not None:
|
|
|
|
params['item'] = {'%sid' % item.kodi_type: int(item.kodi_id)}
|
|
|
|
else:
|
|
|
|
params['item'] = {'file': item.file}
|
2017-12-09 05:43:06 +11:00
|
|
|
reply = js.playlist_insert(params)
|
2017-12-08 03:25:24 +11:00
|
|
|
if reply.get('error') is not None:
|
2018-02-23 23:06:18 +11:00
|
|
|
raise PlaylistError('Could not add item to playlist. Kodi reply. %s'
|
|
|
|
% reply)
|
2018-01-30 17:50:44 +11:00
|
|
|
return item
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
|
2018-05-02 23:44:54 +10:00
|
|
|
def add_item_to_plex_playlist(playlist, plex_id):
|
|
|
|
"""
|
|
|
|
Adds the item with plex_id to the existing Plex playlist (at the end).
|
|
|
|
Will set playlist.plex_updatedat
|
|
|
|
Raises PlaylistError if that did not work out.
|
|
|
|
"""
|
|
|
|
params = {
|
|
|
|
'uri': ('library://None/item/%s' % (urllib.quote('/library/metadata/%s'
|
|
|
|
% plex_id, safe='')))
|
|
|
|
}
|
|
|
|
xml = DU().downloadUrl(url='{server}/playlists/%s/items' % playlist.id,
|
|
|
|
action_type='PUT',
|
|
|
|
parameters=params)
|
|
|
|
try:
|
|
|
|
xml[0].attrib
|
|
|
|
except (TypeError, IndexError, AttributeError):
|
|
|
|
LOG.error('Could not initialize playlist on Plex side with plex id %s',
|
|
|
|
plex_id)
|
|
|
|
raise PlaylistError('Could not item %s to Plex playlist %s',
|
|
|
|
plex_id, playlist)
|
|
|
|
api = API(xml[0])
|
|
|
|
playlist.plex_updatedat = api.updated_at()
|
|
|
|
|
|
|
|
|
2018-05-02 23:34:21 +10:00
|
|
|
def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None):
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2017-01-03 00:07:24 +11:00
|
|
|
Adds a new item to the playlist at position pos [int] only on the Plex
|
|
|
|
side of things (e.g. because the user changed the Kodi side)
|
|
|
|
WILL ALSO UPDATE OUR PLAYLISTS
|
2017-12-08 03:25:24 +11:00
|
|
|
|
2018-01-30 17:50:44 +11:00
|
|
|
Returns the PKC PlayList item or raises PlaylistError
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2018-02-15 18:09:57 +11:00
|
|
|
verify_kodi_item(plex_id, kodi_item)
|
2017-01-03 00:07:24 +11:00
|
|
|
if plex_id:
|
2018-01-30 17:50:44 +11:00
|
|
|
item = playlist_item_from_plex(plex_id)
|
2017-01-03 00:07:24 +11:00
|
|
|
else:
|
|
|
|
item = playlist_item_from_kodi(kodi_item)
|
2017-12-09 05:43:06 +11:00
|
|
|
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.id, item.uri)
|
2017-01-03 00:07:24 +11:00
|
|
|
# Will always put the new item at the end of the Plex playlist
|
2016-12-28 03:33:52 +11:00
|
|
|
xml = DU().downloadUrl(url, action_type="PUT")
|
|
|
|
try:
|
2018-02-23 23:06:18 +11:00
|
|
|
xml[-1].attrib
|
|
|
|
except (TypeError, AttributeError, KeyError, IndexError):
|
|
|
|
raise PlaylistError('Could not add item %s to playlist %s'
|
|
|
|
% (kodi_item, playlist))
|
|
|
|
api = API(xml[-1])
|
|
|
|
item.xml = xml[-1]
|
|
|
|
item.id = api.item_id()
|
|
|
|
item.guid = api.guid_html_escaped()
|
|
|
|
item.offset = api.resume_point()
|
|
|
|
item.playcount = api.viewcount()
|
2016-12-28 03:33:52 +11:00
|
|
|
playlist.items.append(item)
|
2017-01-03 00:07:24 +11:00
|
|
|
if pos == len(playlist.items) - 1:
|
2016-12-28 03:33:52 +11:00
|
|
|
# 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,
|
2017-01-03 00:07:24 +11:00
|
|
|
pos)
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Successfully added item on the Plex side: %s', playlist)
|
2018-01-30 17:50:44 +11:00
|
|
|
return item
|
2017-01-03 00:07:24 +11:00
|
|
|
|
|
|
|
|
|
|
|
def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
2018-01-11 06:14:05 +11:00
|
|
|
file=None, xml_video_element=None):
|
2017-01-03 00:07:24 +11:00
|
|
|
"""
|
2017-01-22 00:47:37 +11:00
|
|
|
Adds an item to the KODI playlist only. WILL ALSO UPDATE OUR PLAYLISTS
|
2017-01-03 00:07:24 +11:00
|
|
|
|
2018-01-30 17:50:44 +11:00
|
|
|
Returns the playlist item that was just added or raises PlaylistError
|
2017-03-06 01:06:54 +11:00
|
|
|
|
|
|
|
file: str!
|
2017-01-03 00:07:24 +11:00
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi '
|
|
|
|
'only at position %s for %s',
|
|
|
|
kodi_id, kodi_type, file, pos, playlist)
|
2017-01-03 00:07:24 +11:00
|
|
|
params = {
|
|
|
|
'playlistid': playlist.playlistid,
|
|
|
|
'position': pos
|
|
|
|
}
|
|
|
|
if kodi_id is not None:
|
|
|
|
params['item'] = {'%sid' % kodi_type: int(kodi_id)}
|
|
|
|
else:
|
|
|
|
params['item'] = {'file': file}
|
2017-12-09 05:43:06 +11:00
|
|
|
reply = js.playlist_insert(params)
|
2017-01-22 00:47:37 +11:00
|
|
|
if reply.get('error') is not None:
|
2018-01-30 17:50:44 +11:00
|
|
|
raise PlaylistError('Could not add item to playlist. Kodi reply. %s',
|
|
|
|
reply)
|
2018-01-11 06:14:05 +11:00
|
|
|
if xml_video_element is not None:
|
2018-02-23 23:06:18 +11:00
|
|
|
item = playlist_item_from_xml(xml_video_element)
|
2018-01-11 06:14:05 +11:00
|
|
|
item.kodi_id = kodi_id
|
|
|
|
item.kodi_type = kodi_type
|
|
|
|
item.file = file
|
|
|
|
elif kodi_id is not None:
|
|
|
|
item = playlist_item_from_kodi(
|
|
|
|
{'id': kodi_id, 'type': kodi_type, 'file': file})
|
|
|
|
if item.plex_id is not None:
|
|
|
|
xml = GetPlexMetadata(item.plex_id)
|
2017-12-08 03:25:24 +11:00
|
|
|
item.xml = xml[-1]
|
|
|
|
playlist.items.insert(pos, item)
|
2018-01-11 06:14:05 +11:00
|
|
|
return item
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
|
|
|
|
def move_playlist_item(playlist, before_pos, after_pos):
|
|
|
|
"""
|
2017-01-03 00:07:24 +11:00
|
|
|
Moves playlist item from before_pos [int] to after_pos [int] for Plex only.
|
|
|
|
|
2018-01-30 17:50:44 +11:00
|
|
|
WILL ALSO CHANGE OUR PLAYLISTS.
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Moving item from %s to %s on the Plex side for %s',
|
|
|
|
before_pos, after_pos, playlist)
|
2016-12-28 03:33:52 +11:00
|
|
|
if after_pos == 0:
|
|
|
|
url = "{server}/%ss/%s/items/%s/move?after=0" % \
|
|
|
|
(playlist.kind,
|
2017-12-09 05:43:06 +11:00
|
|
|
playlist.id,
|
|
|
|
playlist.items[before_pos].id)
|
2016-12-28 03:33:52 +11:00
|
|
|
else:
|
|
|
|
url = "{server}/%ss/%s/items/%s/move?after=%s" % \
|
|
|
|
(playlist.kind,
|
2017-12-09 05:43:06 +11:00
|
|
|
playlist.id,
|
|
|
|
playlist.items[before_pos].id,
|
|
|
|
playlist.items[after_pos - 1].id)
|
2016-12-28 03:33:52 +11:00
|
|
|
# We need to increment the playlistVersion
|
2018-01-30 17:50:44 +11:00
|
|
|
_get_playListVersion_from_xml(
|
|
|
|
playlist, DU().downloadUrl(url, action_type="PUT"))
|
2016-12-28 03:33:52 +11:00
|
|
|
# Move our item's position in our internal playlist
|
|
|
|
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
2018-01-30 17:50:44 +11:00
|
|
|
LOG.debug('Done moving for %s', playlist)
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
|
2018-04-28 17:12:29 +10:00
|
|
|
def get_all_playlists():
|
|
|
|
"""
|
|
|
|
Returns an XML with all Plex playlists or None
|
|
|
|
"""
|
|
|
|
xml = DU().downloadUrl("{server}/playlists",
|
|
|
|
headerOptions={'Accept': 'application/xml'})
|
|
|
|
try:
|
|
|
|
xml.attrib
|
|
|
|
except (AttributeError, TypeError):
|
2018-05-01 22:48:49 +10:00
|
|
|
LOG.error('Could not download a list of all playlists')
|
2018-04-28 17:12:29 +10:00
|
|
|
xml = None
|
|
|
|
return xml
|
|
|
|
|
|
|
|
|
2016-12-29 05:38:43 +11:00
|
|
|
def get_PMS_playlist(playlist, playlist_id=None):
|
|
|
|
"""
|
|
|
|
Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we
|
|
|
|
need to fetch a new playlist
|
|
|
|
|
|
|
|
Returns None if something went wrong
|
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
playlist_id = playlist_id if playlist_id else playlist.id
|
2018-05-02 00:27:18 +10:00
|
|
|
if playlist.kind == 'playList':
|
|
|
|
xml = DU().downloadUrl("{server}/playlists/%s/items" % playlist_id)
|
|
|
|
else:
|
|
|
|
xml = DU().downloadUrl("{server}/playqueues/%s" % playlist_id)
|
2016-12-29 05:38:43 +11:00
|
|
|
try:
|
2018-05-02 00:04:26 +10:00
|
|
|
xml.attrib
|
|
|
|
except AttributeError:
|
2016-12-29 05:38:43 +11:00
|
|
|
xml = None
|
|
|
|
return xml
|
|
|
|
|
|
|
|
|
|
|
|
def refresh_playlist_from_PMS(playlist):
|
|
|
|
"""
|
|
|
|
Only updates the selected item from the PMS side (e.g.
|
|
|
|
playQueueSelectedItemID). Will NOT check whether items still make sense.
|
|
|
|
"""
|
2018-01-30 17:50:44 +11:00
|
|
|
get_playlist_details_from_xml(playlist, get_PMS_playlist(playlist))
|
2016-12-29 05:38:43 +11:00
|
|
|
|
|
|
|
|
2017-01-03 00:07:24 +11:00
|
|
|
def delete_playlist_item_from_PMS(playlist, pos):
|
2016-12-29 05:38:43 +11:00
|
|
|
"""
|
2017-01-03 00:07:24 +11:00
|
|
|
Delete the item at position pos [int] on the Plex side and our playlists
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Deleting position %s for %s on the Plex side', pos, playlist)
|
2016-12-28 03:33:52 +11:00
|
|
|
xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" %
|
|
|
|
(playlist.kind,
|
2017-12-09 05:43:06 +11:00
|
|
|
playlist.id,
|
|
|
|
playlist.items[pos].id,
|
2016-12-28 03:33:52 +11:00
|
|
|
playlist.repeat),
|
|
|
|
action_type="DELETE")
|
|
|
|
_get_playListVersion_from_xml(playlist, xml)
|
2017-01-03 00:07:24 +11:00
|
|
|
del playlist.items[pos]
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
|
|
|
|
# Functions operating on the Kodi playlist objects ##########
|
|
|
|
|
2016-12-28 23:14:21 +11:00
|
|
|
def add_to_Kodi_playlist(playlist, xml_video_element):
|
|
|
|
"""
|
|
|
|
Adds a new item to the Kodi playlist via JSON (at the end of the playlist).
|
|
|
|
Pass in the PMS xml's video element (one level underneath MediaContainer).
|
|
|
|
|
2018-01-30 17:50:44 +11:00
|
|
|
Returns a Playlist_Item or raises PlaylistError
|
2016-12-28 23:14:21 +11:00
|
|
|
"""
|
2018-02-23 23:06:18 +11:00
|
|
|
item = playlist_item_from_xml(xml_video_element)
|
2016-12-28 23:14:21 +11:00
|
|
|
if item.kodi_id:
|
2017-12-09 05:43:06 +11:00
|
|
|
json_item = {'%sid' % item.kodi_type: item.kodi_id}
|
2016-12-28 23:14:21 +11:00
|
|
|
else:
|
2017-12-09 05:43:06 +11:00
|
|
|
json_item = {'file': item.file}
|
|
|
|
reply = js.playlist_add(playlist.playlistid, json_item)
|
2017-01-22 03:20:42 +11:00
|
|
|
if reply.get('error') is not None:
|
2018-01-30 17:50:44 +11:00
|
|
|
raise PlaylistError('Could not add item %s to Kodi playlist. Error: '
|
|
|
|
'%s', xml_video_element, reply)
|
2017-12-08 03:25:24 +11:00
|
|
|
return item
|
2016-12-28 23:14:21 +11:00
|
|
|
|
|
|
|
|
2017-01-03 00:07:24 +11:00
|
|
|
def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
|
|
|
|
xml_video_element=None, kodi_item=None):
|
2016-12-29 05:38:43 +11:00
|
|
|
"""
|
2017-01-03 00:07:24 +11:00
|
|
|
Adds an xbmc listitem to the Kodi playlist.xml_video_element
|
2016-12-29 05:38:43 +11:00
|
|
|
|
2017-01-03 00:07:24 +11:00
|
|
|
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
2017-03-06 01:06:54 +11:00
|
|
|
|
|
|
|
file: string!
|
2017-01-03 00:07:24 +11:00
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Insert listitem at position %s for Kodi only for %s',
|
|
|
|
pos, playlist)
|
2017-01-03 00:07:24 +11:00
|
|
|
# Add the item into Kodi playlist
|
2018-01-21 23:42:22 +11:00
|
|
|
playlist.kodi_pl.add(url=file, listitem=listitem, index=pos)
|
2017-01-03 00:07:24 +11:00
|
|
|
# We need to add this to our internal queue as well
|
|
|
|
if xml_video_element is not None:
|
2018-02-23 23:06:18 +11:00
|
|
|
item = playlist_item_from_xml(xml_video_element)
|
2016-12-28 03:33:52 +11:00
|
|
|
else:
|
2017-01-03 00:07:24 +11:00
|
|
|
item = playlist_item_from_kodi(kodi_item)
|
2017-05-08 00:58:12 +10:00
|
|
|
if file is not None:
|
|
|
|
item.file = file
|
2017-01-03 00:07:24 +11:00
|
|
|
playlist.items.insert(pos, item)
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Done inserting for %s', playlist)
|
2018-01-11 06:14:05 +11:00
|
|
|
return item
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
|
2017-12-09 05:43:06 +11:00
|
|
|
def remove_from_kodi_playlist(playlist, pos):
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2017-01-03 00:07:24 +11:00
|
|
|
Removes the item at position pos from the Kodi playlist using JSON.
|
|
|
|
|
|
|
|
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Removing position %s from Kodi only from %s', pos, playlist)
|
|
|
|
reply = js.playlist_remove(playlist.playlistid, pos)
|
2017-01-22 03:20:42 +11:00
|
|
|
if reply.get('error') is not None:
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.error('Could not delete the item from the playlist. Error: %s',
|
|
|
|
reply)
|
2017-01-22 03:20:42 +11:00
|
|
|
return
|
2017-12-09 05:43:06 +11:00
|
|
|
try:
|
|
|
|
del playlist.items[pos]
|
|
|
|
except IndexError:
|
|
|
|
LOG.error('Cannot delete position %s for %s', pos, playlist)
|
2017-05-31 21:44:04 +10:00
|
|
|
|
|
|
|
|
|
|
|
def get_pms_playqueue(playqueue_id):
|
|
|
|
"""
|
|
|
|
Returns the Plex playqueue as an etree XML or None if unsuccessful
|
|
|
|
"""
|
|
|
|
xml = DU().downloadUrl(
|
|
|
|
"{server}/playQueues/%s" % playqueue_id,
|
|
|
|
headerOptions={'Accept': 'application/xml'})
|
|
|
|
try:
|
|
|
|
xml.attrib
|
|
|
|
except AttributeError:
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.error('Could not download Plex playqueue %s', playqueue_id)
|
2017-05-31 21:44:04 +10:00
|
|
|
xml = None
|
|
|
|
return xml
|
|
|
|
|
|
|
|
|
|
|
|
def get_plextype_from_xml(xml):
|
|
|
|
"""
|
|
|
|
Needed if PMS returns an empty playqueue. Will get the Plex type from the
|
|
|
|
empty playlist playQueueSourceURI. Feed with (empty) etree xml
|
|
|
|
|
|
|
|
returns None if unsuccessful
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0]
|
|
|
|
except IndexError:
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.error('Could not get plex_id from xml: %s', xml.attrib)
|
2017-05-31 21:44:04 +10:00
|
|
|
return
|
|
|
|
new_xml = GetPlexMetadata(plex_id)
|
|
|
|
try:
|
|
|
|
new_xml[0].attrib
|
|
|
|
except (TypeError, IndexError, AttributeError):
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.error('Could not get plex metadata for plex id %s', plex_id)
|
2017-05-31 21:44:04 +10:00
|
|
|
return
|
|
|
|
return new_xml[0].attrib.get('type')
|
2018-04-28 17:12:29 +10:00
|
|
|
|
|
|
|
|
|
|
|
def delete_playlist_from_pms(playlist):
|
|
|
|
"""
|
|
|
|
Deletes the playlist from the PMS
|
|
|
|
"""
|
2018-05-03 02:14:33 +10:00
|
|
|
DU().downloadUrl("{server}/%ss/%s" % (playlist.kind.lower(), playlist.id),
|
|
|
|
action_type="DELETE")
|