PlexKodiConnect/resources/lib/playlist_func.py

1214 lines
47 KiB
Python
Raw Normal View History

2018-07-12 05:24:27 +10:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
2017-12-09 05:43:06 +11:00
"""
Collection of functions associated with Kodi and Plex playlists and playqueues
"""
from __future__ import absolute_import, division, unicode_literals
2017-12-10 00:35:08 +11:00
from logging import getLogger
2016-12-28 03:33:52 +11:00
2019-04-29 02:03:20 +10:00
import xbmc
2018-06-22 03:24:37 +10:00
from .plex_api import API
2018-10-25 02:17:02 +11:00
from .plex_db import PlexDB
2018-06-22 03:24:37 +10:00
from . import plex_functions as PF
2019-04-29 02:03:20 +10:00
from .playutils import PlayUtils
from .kodi_db import kodiid_from_filename, KodiVideoDB
2018-06-22 03:24:37 +10:00
from .downloadutils import DownloadUtils as DU
2019-04-29 02:03:20 +10:00
from . import utils, json_rpc as js, variables as v, app, widgets
from .windows.resume import resume_dialog
2016-12-28 03:33:52 +11:00
###############################################################################
2018-06-22 03:24:37 +10:00
LOG = getLogger('PLEX.playlist_func')
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
2019-04-29 02:03:20 +10:00
class PlayQueue(object):
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
2019-04-29 02:03:20 +10:00
items = [] [list] of PlaylistItem
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'
def __init__(self):
self.id = None
self.type = None
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 = []
2018-06-15 00:27:13 +10:00
# To keep track if Kodi playback was initiated from a Kodi playlist
# There are a couple of pitfalls, unfortunately...
self.kodi_playlist_playback = False
2019-04-29 02:03:20 +10:00
# Playlist position/index used when initiating the playqueue
self.index = None
self.force_transcode = None
2019-04-29 02:03:20 +10:00
def __unicode__(self):
2019-04-12 02:21:14 +10:00
return ("{{"
"'playlistid': {self.playlistid}, "
"'id': {self.id}, "
"'version': {self.version}, "
"'type': '{self.type}', "
2019-04-12 02:21:14 +10:00
"'items': {items}, "
"'selectedItemID': {self.selectedItemID}, "
"'selectedItemOffset': {self.selectedItemOffset}, "
"'shuffled': {self.shuffled}, "
"'repeat': {self.repeat}, "
"'kodi_playlist_playback': {self.kodi_playlist_playback}, "
2019-04-12 02:21:14 +10:00
"'pkc_edit': {self.pkc_edit}, "
"}}").format(**{
2019-04-29 02:03:20 +10:00
'items': ['%s/%s: %s' % (x.plex_id, x.id, x.name)
for x in self.items],
2019-04-12 02:21:14 +10:00
'self': self
2019-04-29 02:03:20 +10:00
})
def __str__(self):
return unicode(self).encode('utf-8')
__repr__ = __str__
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 = []
self.kodi_playlist_playback = False
2019-04-29 02:03:20 +10:00
self.index = None
self.force_transcode = None
LOG.debug('Playlist cleared: %s', self)
2019-04-29 02:03:20 +10:00
def init(self, plex_id, plex_type=None, position=None, synched=True,
force_transcode=None):
"""
Initializes the playQueue with e.g. trailers and additional file parts
Pass synched=False if you're sure that this item has not been synched
to Kodi
"""
LOG.error('Current Kodi playlist: %s',
js.playlist_get_items(self.playlistid))
LOG.debug('Initializing with plex_id %s, plex_type %s, position %s, '
'synched %s, force_transcode %s, index %s', plex_id,
plex_type, position, synched, force_transcode, self.index)
2019-05-04 21:14:34 +10:00
self.index = position
2019-04-29 02:03:20 +10:00
if self.kodi_pl.size() != len(self.items):
# The original item that Kodi put into the playlist, e.g.
# {
# u'title': u'',
# u'type': u'unknown',
# u'file': u'http://127.0.0.1:57578/plex/kodi/....',
# u'label': u''
# }
# We CANNOT delete that item right now - so let's add a dummy
# on the PKC side
LOG.debug('Detected Kodi playlist size %s to be off for PKC: %s',
self.kodi_pl.size(), len(self.items))
while len(self.items) < self.kodi_pl.size():
LOG.debug('Adding a dummy item to our playqueue')
playlistitem = PlaylistItemDummy()
self.items.insert(0, playlistitem)
self.force_transcode = force_transcode
if synched:
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(plex_id, plex_type)
else:
db_item = None
if db_item:
xml = None
section_uuid = db_item['section_uuid']
plex_type = db_item['plex_type']
else:
xml = PF.GetPlexMetadata(plex_id)
if xml in (None, 401):
raise PlaylistError('Could not get Plex metadata %s', plex_id)
section_uuid = xml.get('librarySectionUUID')
api = API(xml[0])
plex_type = api.plex_type()
resume = self._resume_playback(db_item, xml)
trailers = False
if (not resume and plex_type == v.PLEX_TYPE_MOVIE and
utils.settings('enableCinema') == 'true'):
if utils.settings('askCinema') == "true":
# "Play trailers?"
trailers = utils.yesno_dialog(utils.lang(29999),
utils.lang(33016)) or False
else:
trailers = True
LOG.debug('Playing trailers: %s', trailers)
xml = PF.init_plex_playqueue(plex_id,
section_uuid,
plex_type=plex_type,
trailers=trailers)
if xml is None:
LOG.error('Could not get playqueue for plex_id %s UUID %s for %s',
plex_id, section_uuid, self)
raise PlaylistError('Could not get playqueue')
# See that we add trailers, if they exist in the xml return
self._add_intros(xml)
# Add the main item after the trailers
# Look at the LAST item
api = API(xml[-1])
self._kodi_add_xml(xml[-1], api, resume)
# Add additional file parts, if any exist
self._add_additional_parts(xml)
self.update_details_from_xml(xml)
@staticmethod
def _resume_playback(db_item=None, xml=None):
'''
Pass in either db_item or xml
Resume item if available. Returns bool or raise an PlayStrmException if
resume was cancelled by user.
'''
resume = app.PLAYSTATE.resume_playback
app.PLAYSTATE.resume_playback = None
if app.PLAYSTATE.autoplay:
resume = False
LOG.info('Skip resume for autoplay')
elif resume is None:
if db_item:
with KodiVideoDB(lock=False) as kodidb:
resume = kodidb.get_resume(db_item['kodi_fileid'])
else:
api = API(xml)
resume = api.resume_point()
if resume:
resume = resume_dialog(resume)
LOG.info('User chose resume: %s', resume)
if resume is None:
raise PlaylistError('User backed out of resume dialog')
app.PLAYSTATE.autoplay = True
return resume
def _add_intros(self, xml):
'''
if we have any play them when the movie/show is not being resumed.
'''
if not len(xml) > 1:
LOG.debug('No trailers returned from the PMS')
return
for i, intro in enumerate(xml):
if i + 1 == len(xml):
# The main item we're looking at - skip!
break
api = API(intro)
LOG.debug('Adding trailer: %s', api.title())
self._kodi_add_xml(intro, api)
def _add_additional_parts(self, xml):
''' Create listitems and add them to the stack of playlist.
'''
api = API(xml[0])
for part, _ in enumerate(xml[0][0]):
if part == 0:
# The first part that we've already added
continue
api.set_part_number(part)
LOG.debug('Adding addional part for %s: %s', api.title(), part)
self._kodi_add_xml(xml[0], api)
def _kodi_add_xml(self, xml, api, resume=False):
playlistitem = PlaylistItem(xml_video_element=xml)
playlistitem.part = api.part
playlistitem.force_transcode = self.force_transcode
listitem = widgets.get_listitem(xml, resume=True)
listitem.setSubtitles(api.cache_external_subs())
play = PlayUtils(api, playlistitem)
url = play.getPlayUrl()
listitem.setPath(url.encode('utf-8'))
self.kodi_add_item(playlistitem, self.index, listitem)
self.items.insert(self.index, playlistitem)
self.index += 1
def update_details_from_xml(self, xml):
"""
Updates the playlist details from the xml provided
"""
self.id = utils.cast(int, xml.get('%sID' % self.kind))
self.version = utils.cast(int, xml.get('%sVersion' % self.kind))
self.shuffled = utils.cast(int, xml.get('%sShuffled' % self.kind))
self.selectedItemID = utils.cast(int,
xml.get('%sSelectedItemID' % self.kind))
self.selectedItemOffset = utils.cast(int,
xml.get('%sSelectedItemOffset'
% self.kind))
LOG.debug('Updated playlist from xml: %s', self)
def add_item(self, item, pos, listitem=None):
"""
Adds a PlaylistItem to both Kodi and Plex at position pos [int]
Also changes self.items
Raises PlaylistError
"""
self.kodi_add_item(item, pos, listitem)
self.plex_add_item(item, pos)
def kodi_add_item(self, item, pos, listitem=None):
"""
Adds a PlaylistItem to Kodi only. Will not change self.items
Raises PlaylistError
"""
if not isinstance(item, PlaylistItem):
raise PlaylistError('Wrong item %s of type %s received'
% (item, type(item)))
if pos > len(self.items):
raise PlaylistError('Position %s too large for playlist length %s'
% (pos, len(self.items)))
LOG.debug('Adding item to Kodi playlist at position %s: %s', pos, item)
if item.kodi_id is not None and item.kodi_type is not None:
# This method ensures we have full Kodi metadata, potentially
# with more artwork, for example, than Plex provides
if pos == len(self.items):
answ = js.playlist_add(self.playlistid,
{'%sid' % item.kodi_type: item.kodi_id})
else:
answ = js.playlist_insert({'playlistid': self.playlistid,
'position': pos,
'item': {'%sid' % item.kodi_type: item.kodi_id}})
if 'error' in answ:
raise PlaylistError('Kodi did not add item to playlist: %s',
answ)
else:
if not listitem:
if item.xml is None:
LOG.debug('Need to get metadata for item %s', item)
item.xml = PF.GetPlexMetadata(item.plex_id)
if item.xml in (None, 401):
raise PlaylistError('Could not get metadata for %s', item)
listitem = widgets.get_listitem(item.xml, resume=True)
url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT
args = {
'plex_id': self.plex_id,
'plex_type': self.plex_type
}
if item.force_transcode:
args['transcode'] = 'true'
url = utils.extend_url(url, args)
item.file = url
listitem.setPath(url.encode('utf-8'))
self.kodi_pl.add(url=listitem.getPath(),
listitem=listitem,
index=pos)
def plex_add_item(self, item, pos):
"""
Adds a new PlaylistItem to the playlist at position pos [int] only on
the Plex side of things. Also changes self.items
Raises PlaylistError
"""
if not isinstance(item, PlaylistItem) or not item.uri:
raise PlaylistError('Wrong item %s of type %s received'
% (item, type(item)))
if pos > len(self.items):
raise PlaylistError('Position %s too large for playlist length %s'
% (pos, len(self.items)))
LOG.debug('Adding item to Plex playlist at position %s: %s', pos, item)
url = '{server}/%ss/%s?uri=%s' % (self.kind, self.id, item.uri)
# Will usually put the new item at the end of the Plex playlist
xml = DU().downloadUrl(url, action_type='PUT')
try:
xml[0].attrib
except (TypeError, AttributeError, KeyError, IndexError):
raise PlaylistError('Could not add item %s to playlist %s'
% (item, self))
if len(xml) != len(self.items) + 1:
raise PlaylistError('Could not add item %s to playlist %s - wrong'
' length received' % (item, self))
for actual_pos, xml_video_element in enumerate(xml):
api = API(xml_video_element)
if api.plex_id() == item.plex_id:
break
else:
raise PlaylistError('Something went wrong - Plex id not found')
item.from_xml(xml[actual_pos])
self.items.insert(actual_pos, item)
self.update_details_from_xml(xml)
if actual_pos != pos:
self.plex_move_item(actual_pos, pos)
LOG.debug('Added item %s on Plex side: %s', item, self)
def kodi_remove_item(self, pos):
"""
Only manipulates the Kodi playlist. Won't change self.items
"""
LOG.debug('Removing position %s on the Kodi side for %s', pos, self)
answ = js.playlist_remove(self.playlistid, pos)
if 'error' in answ:
raise PlaylistError('Could not remove item: %s' % answ)
def plex_move_item(self, before, after):
"""
Moves playlist item from before [int] to after [int] for Plex only.
2016-12-28 03:33:52 +11:00
2019-04-29 02:03:20 +10:00
Will also change self.items
"""
if before > len(self.items):
raise PlaylistError('Original position %s larger than current '
'playlist length %s',
before, len(self.items))
elif after > len(self.items):
raise PlaylistError('Desired position %s larger than current '
'playlist length %s',
after, len(self.items))
elif after == before:
raise PlaylistError('Desired position and original position are '
'identical: %s', after)
LOG.debug('Moving item from %s to %s on the Plex side for %s',
before, after, self)
if after == 0:
url = "{server}/%ss/%s/items/%s/move?after=0" % \
(self.kind,
self.id,
self.items[before].id)
else:
url = "{server}/%ss/%s/items/%s/move?after=%s" % \
(self.kind,
self.id,
self.items[before].id,
self.items[after - 1].id)
xml = DU().downloadUrl(url, action_type="PUT")
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
raise PlaylistError('Could not move playlist item from %s to %s '
'for %s' % (before, after, self))
self.update_details_from_xml(xml)
self.items.insert(after, self.items.pop(before))
LOG.debug('Done moving items for %s', self)
def start_playback(self, pos=0):
LOG.info('Starting playback at %s for %s', pos, self)
xbmc.Player().play(self.kodi_pl, startpos=pos, windowed=False)
class PlaylistItem(object):
2017-12-06 21:40:27 +11:00
"""
Object to fill our playqueues and playlists with.
id = None [int] Plex playlist/playqueue id, e.g. playQueueItemID
plex_id = None [int] Plex unique item id, "ratingKey"
2017-12-07 04:05:01 +11:00
plex_type = None [str] Plex type, e.g. 'movie', 'clip'
2017-12-21 19:28:06 +11:00
plex_uuid = None [str] Plex librarySectionUUID
kodi_id = None [int] 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
2019-04-13 21:05:52 +10:00
2019-04-29 02:03:20 +10:00
PlaylistItem compare as equal, if they
2019-04-13 21:05:52 +10:00
- have the same plex_id
- OR: have the same kodi_id AND kodi_type
- OR: have the same file
2017-12-06 21:40:27 +11:00
"""
2019-04-29 02:03:20 +10:00
def __init__(self, plex_id=None, plex_type=None, xml_video_element=None,
kodi_id=None, kodi_type=None, grab_xml=False,
lookup_kodi=True):
"""
Pass grab_xml=True in order to get Plex metadata from the PMS while
passing a plex_id.
Pass lookup_kodi=False to NOT check the plex.db for kodi_id and
kodi_type if they're missing (won't be done for clips anyway)
"""
self.name = None
self.id = None
self.plex_id = plex_id
self.plex_type = plex_type
2017-12-21 19:28:06 +11:00
self.plex_uuid = None
2019-04-29 02:03:20 +10:00
self.kodi_id = kodi_id
self.kodi_type = kodi_type
2017-12-21 19:28:06 +11:00
self.file = None
self.uri = None
self.guid = None
self.xml = None
2018-01-08 03:50:30 +11:00
self.playmethod = None
2019-04-29 02:03:20 +10:00
self.playcount = None
self.offset = None
self.part = 0
2018-02-03 22:45:48 +11:00
self.force_transcode = False
2019-04-29 02:03:20 +10:00
if grab_xml and plex_id is not None and xml_video_element is None:
xml_video_element = PF.GetPlexMetadata(plex_id)
try:
xml_video_element = xml_video_element[0]
except (TypeError, IndexError):
xml_video_element = None
if xml_video_element is not None:
self.from_xml(xml_video_element)
if (lookup_kodi and (kodi_id is None or kodi_type is None) and
self.plex_type != v.PLEX_TYPE_CLIP):
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(plex_id, plex_type)
if db_item is not None:
self.kodi_id = db_item['kodi_id']
self.kodi_type = db_item['kodi_type']
self.plex_uuid = db_item['section_uuid']
self.set_uri()
def __eq__(self, other):
if self.plex_id is not None and other.plex_id is not None:
return self.plex_id == other.plex_id
elif (self.kodi_id is not None and other.kodi_id is not None and
self.kodi_type and other.kodi_type):
return (self.kodi_id == other.kodi_id and
self.kodi_type == other.kodi_type)
elif self.file and other.file:
return self.file == other.file
raise RuntimeError('PlaylistItems not fully defined: %s, %s' %
(self, other))
def __ne__(self, other):
return not self == other
def __unicode__(self):
return ("{{"
"'name': '{self.name}', "
"'id': {self.id}, "
"'plex_id': {self.plex_id}, "
"'plex_type': '{self.plex_type}', "
"'kodi_id': {self.kodi_id}, "
"'kodi_type': '{self.kodi_type}', "
"'file': '{self.file}', "
"'uri': '{self.uri}', "
"'guid': '{self.guid}', "
"'playmethod': '{self.playmethod}', "
"'playcount': {self.playcount}, "
"'offset': {self.offset}, "
"'force_transcode': {self.force_transcode}, "
2019-04-29 02:03:20 +10:00
"'part': {self.part}"
"}}".format(self=self))
def __str__(self):
2019-04-29 02:03:20 +10:00
return unicode(self).encode('utf-8')
__repr__ = __str__
def from_xml(self, xml_video_element):
"""
xml_video_element: etree xml piece 1 level underneath <MediaContainer>
item.id will only be set if you passed in an xml_video_element from
e.g. a playQueue
"""
api = API(xml_video_element)
self.name = api.title()
self.plex_id = api.plex_id()
self.plex_type = api.plex_type()
self.id = api.item_id()
self.guid = api.guid_html_escaped()
self.playcount = api.viewcount()
self.offset = api.resume_point()
self.xml = xml_video_element
def from_kodi(self, playlist_item):
"""
playlist_item: dict contains keys 'id', 'type', 'file' (if applicable)
Will thus set the attributes kodi_id, kodi_type, file, if applicable
If kodi_id & kodi_type are provided, plex_id and plex_type will be
looked up (if not already set)
"""
self.kodi_id = playlist_item.get('id')
self.kodi_type = playlist_item.get('type')
self.file = playlist_item.get('file')
if self.plex_id is None and self.kodi_id is not None and self.kodi_type:
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_kodi_id(self.kodi_id, self.kodi_type)
if db_item:
self.plex_id = db_item['plex_id']
self.plex_type = db_item['plex_type']
self.plex_uuid = db_item['section_uuid']
if self.plex_id is None and self.file is not None:
try:
query = self.file.split('?', 1)[1]
except IndexError:
query = ''
query = dict(utils.parse_qsl(query))
self.plex_id = utils.cast(int, query.get('plex_id'))
self.plex_type = query.get('itemType')
self.set_uri()
LOG.debug('Made playlist item from Kodi: %s', self)
def set_uri(self):
if self.plex_id is None and self.file is not None:
self.uri = ('library://whatever/item/%s'
% utils.quote(self.file, safe=''))
elif self.plex_id is not None and self.plex_uuid is not None:
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
self.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
(self.plex_uuid, self.plex_id))
elif self.plex_id is not None:
self.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
(self.plex_id, self.plex_id))
else:
self.uri = None
2016-12-28 03:33:52 +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
if kodi_stream_index == -1:
# Kodi telling us "it's the last one"
iterator = list(reversed(self.xml[0][self.part]))
kodi_stream_index = 0
else:
iterator = self.xml[0][self.part]
# Kodi indexes differently than Plex
for stream in iterator:
if (stream.attrib['streamType'] == stream_type and
'key' in stream.attrib):
if count == kodi_stream_index:
return stream.attrib['id']
count += 1
for stream in iterator:
if (stream.attrib['streamType'] == stream_type and
'key' not in stream.attrib):
if count == kodi_stream_index:
return stream.attrib['id']
count += 1
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]:
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):
if stream.attrib['id'] == plex_stream_index:
return count
count += 1
2016-12-28 03:33:52 +11:00
2019-04-29 02:03:20 +10:00
class PlaylistItemDummy(PlaylistItem):
"""
Let e.g. Kodimonitor detect that this is a dummy item
"""
def __init__(self, *args, **kwargs):
super(PlaylistItemDummy, self).__init__(*args, **kwargs)
self.name = 'dummy item'
self.id = 0
self.plex_id = 0
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)
"""
2019-04-29 02:03:20 +10:00
item = PlaylistItem()
2016-12-29 00:48:23 +11:00
item.kodi_id = kodi_item.get('id')
item.kodi_type = kodi_item.get('type')
2016-12-29 00:48:23 +11:00
if item.kodi_id:
with PlexDB(lock=False) as plexdb:
2018-10-25 02:17:02 +11:00
db_item = plexdb.item_by_kodi_id(kodi_item['id'], kodi_item['type'])
if db_item:
item.plex_id = db_item['plex_id']
item.plex_type = db_item['plex_type']
2019-04-29 02:03:20 +10:00
item.plex_uuid = db_item['section_uuid']
2017-01-03 00:07:24 +11:00
item.file = kodi_item.get('file')
if item.plex_id is None and item.file is not None:
2019-03-30 20:32:56 +11:00
try:
query = item.file.split('?', 1)[1]
except IndexError:
query = ''
query = dict(utils.parse_qsl(query))
2018-11-07 05:06:48 +11:00
item.plex_id = utils.cast(int, 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:
item.uri = ('library://whatever/item/%s'
2019-03-30 20:32:56 +11:00
% utils.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
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
# Special case playlist startup - got type but no id
2018-11-19 00:59:17 +11:00
if (not app.SYNC.direct_paths and app.SYNC.enable_music and
kodi_item.get('type') == v.KODI_TYPE_SONG and
kodi_item['file'].startswith('http')):
2018-11-09 07:22:16 +11:00
kodi_item['id'], _ = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_SONG)
LOG.debug('Detected song. Research results: %s', kodi_item)
return kodi_item
# Need more info since we don't have kodi_id nor type. Use file path.
if ((kodi_item['file'].startswith('plugin') and
not kodi_item['file'].startswith('plugin://%s' % v.ADDON_ID)) or
kodi_item['file'].startswith('http')):
2018-07-08 19:30:02 +10:00
LOG.info('kodi_item %s cannot be used for Plex playback', kodi_item)
2018-06-22 03:24:37 +10:00
raise PlaylistError
LOG.debug('Starting research for Kodi id since we didnt get one: %s',
kodi_item)
# Try the VIDEO DB first - will find both movies and episodes
kodi_id, kodi_type = kodiid_from_filename(kodi_item['file'],
db_type='video')
if not kodi_id:
# No movie or episode found - try MUSIC DB now for songs
kodi_id, kodi_type = kodiid_from_filename(kodi_item['file'],
db_type='music')
kodi_item['id'] = kodi_id
kodi_item['type'] = None if kodi_id is None else kodi_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
"""
2019-04-29 02:03:20 +10:00
item = PlaylistItem()
2016-12-28 03:33:52 +11:00
item.plex_id = plex_id
with PlexDB(lock=False) as plexdb:
2018-10-25 02:17:02 +11:00
db_item = plexdb.item_by_id(plex_id)
if db_item:
item.plex_type = db_item['plex_type']
item.kodi_id = db_item['kodi_id']
item.kodi_type = db_item['kodi_type']
else:
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
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
"""
2019-04-29 02:03:20 +10:00
item = PlaylistItem()
2017-01-03 00:07:24 +11:00
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()
# item.id will only be set if you passed in an xml_video_element from e.g.
# a playQueue
item.id = api.item_id()
if kodi_id is not None and kodi_type is not None:
2018-01-21 23:42:22 +11:00
item.kodi_id = kodi_id
item.kodi_type = kodi_type
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
"""
playlist.version = utils.cast(int,
xml.get('%sVersion' % playlist.kind))
if playlist.version is None:
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
"""
playlist.id = utils.cast(int,
xml.get('%sID' % playlist.kind))
playlist.version = utils.cast(int,
xml.get('%sVersion' % playlist.kind))
playlist.shuffled = utils.cast(int,
xml.get('%sShuffled' % playlist.kind))
playlist.selectedItemID = utils.cast(int,
xml.get('%sSelectedItemID'
% playlist.kind))
playlist.selectedItemOffset = utils.cast(int,
xml.get('%sSelectedItemOffset'
% playlist.kind))
2018-05-02 00:41:10 +10:00
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 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
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
"""
LOG.debug('Initializing the playqueue on the Plex side: %s', playlist)
2018-02-04 01:12:10 +11:00
playlist.clear(kodi=False)
verify_kodi_item(plex_id, kodi_item)
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
item = playlist_item_from_xml(xml[0])
2017-12-08 03:25:24 +11:00
except (KeyError, IndexError, TypeError):
2018-06-22 03:24:37 +10:00
LOG.error('Could not init Plex playlist: plex_id %s, kodi_item %s',
plex_id, kodi_item)
raise PlaylistError
2016-12-28 03:33:52 +11:00
playlist.items.append(item)
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!
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!
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:
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: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
"""
2019-04-29 02:03:20 +10:00
LOG.debug('Adding item to Plex playqueue with plex id %s, kodi_item %s at '
'position %s', plex_id, kodi_item, pos)
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)
2019-04-29 02:03:20 +10: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:
xml[-1].attrib
except (TypeError, AttributeError, KeyError, IndexError):
raise PlaylistError('Could not add item %s to playlist %s'
% (kodi_item, playlist))
2019-04-29 02:03:20 +10:00
if len(xml) != len(playlist.items) + 1:
raise PlaylistError('Couldnt add item %s to playlist %s - wrong length'
% (kodi_item, playlist))
for actual_pos, xml_video_element in enumerate(xml):
api = API(xml_video_element)
if api.plex_id() == item.plex_id:
break
else:
raise PlaylistError('Something went terribly wrong!')
utils.dump_xml(xml)
LOG.debug('Plex added the new item at position %s', actual_pos)
item.xml = xml[actual_pos]
item.id = api.item_id()
item.guid = api.guid_html_escaped()
item.offset = api.resume_point()
item.playcount = api.viewcount()
2019-04-29 02:03:20 +10:00
playlist.items.insert(actual_pos, item)
_get_playListVersion_from_xml(playlist, xml)
if actual_pos != pos:
2016-12-28 03:33:52 +11:00
# Move the new item to the correct position
2019-04-29 02:03:20 +10:00
move_playlist_item(playlist, actual_pos, 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
"""
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
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)
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:
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:
2018-06-22 03:24:37 +10:00
xml = PF.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
xml = DU().downloadUrl(url, action_type="PUT")
try:
xml[0].attrib
except (TypeError, IndexError, AttributeError):
LOG.error('Could not move playlist item')
return
_get_playListVersion_from_xml(playlist, xml)
2019-04-29 02:03:20 +10:00
utils.dump_xml(xml)
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
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
if playlist.kind == 'playList':
xml = DU().downloadUrl("{server}/playlists/%s/items" % playlist_id)
else:
2018-08-04 04:45:10 +10:00
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
"""
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
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:
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)
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)
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 = utils.REGEX_PLEX_ID_FROM_URL.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)
return
2018-06-22 03:24:37 +10:00
new_xml = PF.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)
return
return new_xml[0].attrib.get('type').decode('utf-8')