Big update
This commit is contained in:
parent
578ced789f
commit
1218cde0a2
10 changed files with 634 additions and 403 deletions
|
@ -219,16 +219,20 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
{
|
{
|
||||||
u'playlistid': 1,
|
u'playlistid': 1,
|
||||||
}
|
}
|
||||||
|
Let's NOT use this as Kodi's responses when e.g. playing an entire
|
||||||
|
folder are NOT threadsafe: Playlist.OnAdd might be added first, then
|
||||||
|
Playlist.OnClear might be received LATER
|
||||||
"""
|
"""
|
||||||
if self.playlistid == data['playlistid']:
|
if self.playlistid == data['playlistid']:
|
||||||
LOG.debug('Resetting autoplay')
|
LOG.debug('Resetting autoplay')
|
||||||
app.PLAYSTATE.autoplay = False
|
app.PLAYSTATE.autoplay = False
|
||||||
playqueue = PQ.PLAYQUEUES[data['playlistid']]
|
return
|
||||||
if not playqueue.is_pkc_clear():
|
# playqueue = PQ.PLAYQUEUES[data['playlistid']]
|
||||||
playqueue.pkc_edit = True
|
# if not playqueue.is_pkc_clear():
|
||||||
playqueue.clear(kodi=False)
|
# playqueue.pkc_edit = True
|
||||||
else:
|
# playqueue.clear(kodi=False)
|
||||||
LOG.debug('Detected PKC clear - ignoring')
|
# else:
|
||||||
|
# LOG.debug('Detected PKC clear - ignoring')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_ids(kodi_id, kodi_type, path):
|
def _get_ids(kodi_id, kodi_type, path):
|
||||||
|
@ -311,7 +315,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
|
|
||||||
def _check_playing_item(self, data):
|
def _check_playing_item(self, data):
|
||||||
"""
|
"""
|
||||||
Returns a PF.Playlist_Item() for the currently playing item
|
Returns a PF.PlaylistItem() for the currently playing item
|
||||||
Raises MonitorError or IndexError if we need to init the PKC playqueue
|
Raises MonitorError or IndexError if we need to init the PKC playqueue
|
||||||
"""
|
"""
|
||||||
info = js.get_player_props(self.playerid)
|
info = js.get_player_props(self.playerid)
|
||||||
|
@ -320,26 +324,13 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
kodi_playlist = js.playlist_get_items(self.playerid)
|
kodi_playlist = js.playlist_get_items(self.playerid)
|
||||||
LOG.debug('Current Kodi playlist: %s', kodi_playlist)
|
LOG.debug('Current Kodi playlist: %s', kodi_playlist)
|
||||||
kodi_item = PL.playlist_item_from_kodi(kodi_playlist[position])
|
kodi_item = PL.playlist_item_from_kodi(kodi_playlist[position])
|
||||||
if (position == 1 and
|
if isinstance(self.playqueue.items[0], PL.PlaylistItemDummy):
|
||||||
len(kodi_playlist) == len(self.playqueue.items) + 1 and
|
# Get rid of the very first element in the queue that Kodi marked
|
||||||
kodi_playlist[0].get('type') == 'unknown' and
|
# as unplayed (the one to init the queue)
|
||||||
kodi_playlist[0].get('file') and
|
|
||||||
kodi_playlist[0].get('file').startswith('http://127.0.0.1')):
|
|
||||||
if kodi_item == self.playqueue.items[0]:
|
|
||||||
# Delete the very first item that we used to start playback:
|
|
||||||
# {
|
|
||||||
# u'title': u'',
|
|
||||||
# u'type': u'unknown',
|
|
||||||
# u'file': u'http://127.0.0.1:57578/plex/kodi/....',
|
|
||||||
# u'label': u''
|
|
||||||
# }
|
|
||||||
LOG.debug('Deleting the very first playqueue item')
|
LOG.debug('Deleting the very first playqueue item')
|
||||||
js.playlist_remove(self.playqueue.playlistid, 0)
|
js.playlist_remove(self.playqueue.playlistid, 0)
|
||||||
|
del self.playqueue.items[0]
|
||||||
position = 0
|
position = 0
|
||||||
else:
|
|
||||||
LOG.debug('Different item in PKC playlist: %s vs. %s',
|
|
||||||
self.playqueue.items[0], kodi_item)
|
|
||||||
raise MonitorError()
|
|
||||||
elif kodi_item != self.playqueue.items[position]:
|
elif kodi_item != self.playqueue.items[position]:
|
||||||
LOG.debug('Different playqueue items: %s vs. %s ',
|
LOG.debug('Different playqueue items: %s vs. %s ',
|
||||||
kodi_item, self.playqueue.items[position])
|
kodi_item, self.playqueue.items[position])
|
||||||
|
@ -349,7 +340,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
|
|
||||||
def _load_playerstate(self, item):
|
def _load_playerstate(self, item):
|
||||||
"""
|
"""
|
||||||
Pass in a PF.Playlist_Item(). Will then set the currently playing
|
Pass in a PF.PlaylistItem(). Will then set the currently playing
|
||||||
state with app.PLAYSTATE.player_states[self.playerid]
|
state with app.PLAYSTATE.player_states[self.playerid]
|
||||||
"""
|
"""
|
||||||
if self.playqueue.id:
|
if self.playqueue.id:
|
||||||
|
@ -431,6 +422,7 @@ def _playback_cleanup(ended=False):
|
||||||
# We might have saved a transient token from a user flinging media via
|
# We might have saved a transient token from a user flinging media via
|
||||||
# Companion (if we could not use the playqueue to store the token)
|
# Companion (if we could not use the playqueue to store the token)
|
||||||
app.CONN.plex_transient_token = None
|
app.CONN.plex_transient_token = None
|
||||||
|
LOG.debug('Playstate is: %s', app.PLAYSTATE.player_states)
|
||||||
for playerid in app.PLAYSTATE.active_players:
|
for playerid in app.PLAYSTATE.active_players:
|
||||||
status = app.PLAYSTATE.player_states[playerid]
|
status = app.PLAYSTATE.player_states[playerid]
|
||||||
# Remember the last played item later
|
# Remember the last played item later
|
||||||
|
@ -498,6 +490,9 @@ def _record_playstate(status, ended):
|
||||||
playcount += 1
|
playcount += 1
|
||||||
time = 0
|
time = 0
|
||||||
with kodi_db.KodiVideoDB() as kodidb:
|
with kodi_db.KodiVideoDB() as kodidb:
|
||||||
|
LOG.error('Setting file_id %s, time %s, totaltime %s, playcount %s, '
|
||||||
|
'last_played %s',
|
||||||
|
db_item['kodi_fileid'], time, totaltime, playcount, last_played)
|
||||||
kodidb.set_resume(db_item['kodi_fileid'],
|
kodidb.set_resume(db_item['kodi_fileid'],
|
||||||
time,
|
time,
|
||||||
totaltime,
|
totaltime,
|
||||||
|
|
|
@ -464,7 +464,7 @@ def process_indirect(key, offset, resolve=True):
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = PQ.get_playqueue_from_type(
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
item = PL.Playlist_Item()
|
item = PL.PlaylistItem()
|
||||||
item.xml = xml[0]
|
item.xml = xml[0]
|
||||||
item.offset = offset
|
item.offset = offset
|
||||||
item.plex_type = v.PLEX_TYPE_CLIP
|
item.plex_type = v.PLEX_TYPE_CLIP
|
||||||
|
|
|
@ -6,15 +6,16 @@ Collection of functions associated with Kodi and Plex playlists and playqueues
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
|
||||||
from .plex_api import API
|
from .plex_api import API
|
||||||
from .plex_db import PlexDB
|
from .plex_db import PlexDB
|
||||||
from . import plex_functions as PF
|
from . import plex_functions as PF
|
||||||
from .kodi_db import kodiid_from_filename
|
from .playutils import PlayUtils
|
||||||
|
from .kodi_db import kodiid_from_filename, KodiVideoDB
|
||||||
from .downloadutils import DownloadUtils as DU
|
from .downloadutils import DownloadUtils as DU
|
||||||
from . import utils
|
from . import utils, json_rpc as js, variables as v, app, widgets
|
||||||
from . import json_rpc as js
|
from .windows.resume import resume_dialog
|
||||||
from . import variables as v
|
|
||||||
from . import app
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -30,14 +31,14 @@ class PlaylistError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Playqueue_Object(object):
|
class PlayQueue(object):
|
||||||
"""
|
"""
|
||||||
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
||||||
|
|
||||||
playlistid = None [int] Kodi playlist id (0, 1, 2)
|
playlistid = None [int] Kodi playlist id (0, 1, 2)
|
||||||
type = None [str] Kodi type: 'audio', 'video', 'picture'
|
type = None [str] Kodi type: 'audio', 'video', 'picture'
|
||||||
kodi_pl = None Kodi xbmc.PlayList object
|
kodi_pl = None Kodi xbmc.PlayList object
|
||||||
items = [] [list] of Playlist_Items
|
items = [] [list] of PlaylistItem
|
||||||
id = None [str] Plex playQueueID, unique Plex identifier
|
id = None [str] Plex playQueueID, unique Plex identifier
|
||||||
version = None [int] Plex version of the playQueue
|
version = None [int] Plex version of the playQueue
|
||||||
selectedItemID = None
|
selectedItemID = None
|
||||||
|
@ -74,8 +75,11 @@ class Playqueue_Object(object):
|
||||||
# To keep track if Kodi playback was initiated from a Kodi playlist
|
# To keep track if Kodi playback was initiated from a Kodi playlist
|
||||||
# There are a couple of pitfalls, unfortunately...
|
# There are a couple of pitfalls, unfortunately...
|
||||||
self.kodi_playlist_playback = False
|
self.kodi_playlist_playback = False
|
||||||
|
# Playlist position/index used when initiating the playqueue
|
||||||
|
self.index = None
|
||||||
|
self.force_transcode = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __unicode__(self):
|
||||||
return ("{{"
|
return ("{{"
|
||||||
"'playlistid': {self.playlistid}, "
|
"'playlistid': {self.playlistid}, "
|
||||||
"'id': {self.id}, "
|
"'id': {self.id}, "
|
||||||
|
@ -89,10 +93,14 @@ class Playqueue_Object(object):
|
||||||
"'kodi_playlist_playback': {self.kodi_playlist_playback}, "
|
"'kodi_playlist_playback': {self.kodi_playlist_playback}, "
|
||||||
"'pkc_edit': {self.pkc_edit}, "
|
"'pkc_edit': {self.pkc_edit}, "
|
||||||
"}}").format(**{
|
"}}").format(**{
|
||||||
'items': [x.plex_id for x in self.items or []],
|
'items': ['%s/%s: %s' % (x.plex_id, x.id, x.name)
|
||||||
|
for x in self.items],
|
||||||
'self': self
|
'self': self
|
||||||
}).encode('utf-8')
|
})
|
||||||
__str__ = __repr__
|
|
||||||
|
def __str__(self):
|
||||||
|
return unicode(self).encode('utf-8')
|
||||||
|
__repr__ = __str__
|
||||||
|
|
||||||
def is_pkc_clear(self):
|
def is_pkc_clear(self):
|
||||||
"""
|
"""
|
||||||
|
@ -127,10 +135,319 @@ class Playqueue_Object(object):
|
||||||
self.plex_transient_token = None
|
self.plex_transient_token = None
|
||||||
self.old_kodi_pl = []
|
self.old_kodi_pl = []
|
||||||
self.kodi_playlist_playback = False
|
self.kodi_playlist_playback = False
|
||||||
|
self.index = None
|
||||||
|
self.force_transcode = None
|
||||||
LOG.debug('Playlist cleared: %s', self)
|
LOG.debug('Playlist cleared: %s', self)
|
||||||
|
|
||||||
|
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))
|
||||||
|
if position is not None:
|
||||||
|
self.index = position
|
||||||
|
else:
|
||||||
|
# Do NOT use kodi_pl.getposition() as that appears to be buggy
|
||||||
|
self.index = max(js.get_position(self.playlistid), 0)
|
||||||
|
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)
|
||||||
|
LOG.error('Actual start: %s', js.get_position(self.playlistid))
|
||||||
|
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)
|
||||||
|
|
||||||
class Playlist_Item(object):
|
@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.
|
||||||
|
|
||||||
|
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):
|
||||||
"""
|
"""
|
||||||
Object to fill our playqueues and playlists with.
|
Object to fill our playqueues and playlists with.
|
||||||
|
|
||||||
|
@ -150,110 +467,75 @@ class Playlist_Item(object):
|
||||||
part = 0 [int] part number if Plex video consists of mult. parts
|
part = 0 [int] part number if Plex video consists of mult. parts
|
||||||
force_transcode [bool] defaults to False
|
force_transcode [bool] defaults to False
|
||||||
|
|
||||||
Playlist_items compare as equal, if they
|
PlaylistItem compare as equal, if they
|
||||||
- have the same plex_id
|
- have the same plex_id
|
||||||
- OR: have the same kodi_id AND kodi_type
|
- OR: have the same kodi_id AND kodi_type
|
||||||
- OR: have the same file
|
- OR: have the same file
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, plex_id=None, plex_type=None, xml_video_element=None,
|
||||||
self._id = None
|
kodi_id=None, kodi_type=None, grab_xml=False,
|
||||||
self._plex_id = None
|
lookup_kodi=True):
|
||||||
self.plex_type = None
|
"""
|
||||||
|
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
|
||||||
self.plex_uuid = None
|
self.plex_uuid = None
|
||||||
self._kodi_id = None
|
self.kodi_id = kodi_id
|
||||||
self.kodi_type = None
|
self.kodi_type = kodi_type
|
||||||
self.file = None
|
self.file = None
|
||||||
self.uri = None
|
self.uri = None
|
||||||
self.guid = None
|
self.guid = None
|
||||||
self.xml = None
|
self.xml = None
|
||||||
self.playmethod = None
|
self.playmethod = None
|
||||||
self._playcount = None
|
self.playcount = None
|
||||||
self._offset = None
|
self.offset = None
|
||||||
# If Plex video consists of several parts; part number
|
self.part = 0
|
||||||
self._part = 0
|
|
||||||
self.force_transcode = False
|
self.force_transcode = False
|
||||||
|
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, item):
|
def __eq__(self, other):
|
||||||
if self.plex_id is not None and item.plex_id is not None:
|
if self.plex_id is not None and other.plex_id is not None:
|
||||||
return self.plex_id == item.plex_id
|
return self.plex_id == other.plex_id
|
||||||
elif (self.kodi_id is not None and item.kodi_id is not None and
|
elif (self.kodi_id is not None and other.kodi_id is not None and
|
||||||
self.kodi_type and item.kodi_type):
|
self.kodi_type and other.kodi_type):
|
||||||
return (self.kodi_id == item.kodi_id and
|
return (self.kodi_id == other.kodi_id and
|
||||||
self.kodi_type == item.kodi_type)
|
self.kodi_type == other.kodi_type)
|
||||||
elif self.file and item.file:
|
elif self.file and other.file:
|
||||||
return self.file == item.file
|
return self.file == other.file
|
||||||
raise RuntimeError('playlist items not fully defined: %s, %s' %
|
raise RuntimeError('PlaylistItems not fully defined: %s, %s' %
|
||||||
(self, item))
|
(self, other))
|
||||||
|
|
||||||
def __ne__(self, item):
|
def __ne__(self, other):
|
||||||
return not self == item
|
return not self == other
|
||||||
|
|
||||||
@property
|
def __unicode__(self):
|
||||||
def plex_id(self):
|
return ("{{"
|
||||||
return self._plex_id
|
"'name': '{self.name}', "
|
||||||
|
|
||||||
@plex_id.setter
|
|
||||||
def plex_id(self, value):
|
|
||||||
if not isinstance(value, int) and value is not None:
|
|
||||||
raise TypeError('Passed %s instead of int!' % type(value))
|
|
||||||
self._plex_id = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@id.setter
|
|
||||||
def id(self, value):
|
|
||||||
if not isinstance(value, int) and value is not None:
|
|
||||||
raise TypeError('Passed %s instead of int!' % type(value))
|
|
||||||
self._id = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def kodi_id(self):
|
|
||||||
return self._kodi_id
|
|
||||||
|
|
||||||
@kodi_id.setter
|
|
||||||
def kodi_id(self, value):
|
|
||||||
if not isinstance(value, int) and value is not None:
|
|
||||||
raise TypeError('Passed %s instead of int!' % type(value))
|
|
||||||
self._kodi_id = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def playcount(self):
|
|
||||||
return self._playcount
|
|
||||||
|
|
||||||
@playcount.setter
|
|
||||||
def playcount(self, value):
|
|
||||||
if not isinstance(value, int) and value is not None:
|
|
||||||
raise TypeError('Passed %s instead of int!' % type(value))
|
|
||||||
self._playcount = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def offset(self):
|
|
||||||
return self._offset
|
|
||||||
|
|
||||||
@offset.setter
|
|
||||||
def offset(self, value):
|
|
||||||
if not isinstance(value, (int, float)) and value is not None:
|
|
||||||
raise TypeError('Passed %s instead of int!' % type(value))
|
|
||||||
self._offset = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def part(self):
|
|
||||||
return self._part
|
|
||||||
|
|
||||||
@part.setter
|
|
||||||
def part(self, value):
|
|
||||||
if not isinstance(value, int) and value is not None:
|
|
||||||
raise TypeError('Passed %s instead of int!' % type(value))
|
|
||||||
self._part = value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
answ = ("{{"
|
|
||||||
"'id': {self.id}, "
|
"'id': {self.id}, "
|
||||||
"'plex_id': {self.plex_id}, "
|
"'plex_id': {self.plex_id}, "
|
||||||
"'plex_type': '{self.plex_type}', "
|
"'plex_type': '{self.plex_type}', "
|
||||||
"'plex_uuid': '{self.plex_uuid}', "
|
|
||||||
"'kodi_id': {self.kodi_id}, "
|
"'kodi_id': {self.kodi_id}, "
|
||||||
"'kodi_type': '{self.kodi_type}', "
|
"'kodi_type': '{self.kodi_type}', "
|
||||||
"'file': '{self.file}', "
|
"'file': '{self.file}', "
|
||||||
|
@ -263,13 +545,71 @@ class Playlist_Item(object):
|
||||||
"'playcount': {self.playcount}, "
|
"'playcount': {self.playcount}, "
|
||||||
"'offset': {self.offset}, "
|
"'offset': {self.offset}, "
|
||||||
"'force_transcode': {self.force_transcode}, "
|
"'force_transcode': {self.force_transcode}, "
|
||||||
"'part': {self.part}, ".format(self=self))
|
"'part': {self.part}"
|
||||||
answ = answ.encode('utf-8')
|
"}}".format(self=self))
|
||||||
# etree xml.__repr__() could return string, not unicode
|
|
||||||
return answ + b"'xml': \"{self.xml}\"}}".format(self=self)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.__repr__()
|
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
|
||||||
|
|
||||||
def plex_stream_index(self, kodi_stream_index, stream_type):
|
def plex_stream_index(self, kodi_stream_index, stream_type):
|
||||||
"""
|
"""
|
||||||
|
@ -327,6 +667,17 @@ class Playlist_Item(object):
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
def playlist_item_from_kodi(kodi_item):
|
def playlist_item_from_kodi(kodi_item):
|
||||||
"""
|
"""
|
||||||
Turns the JSON answer from Kodi into a playlist element
|
Turns the JSON answer from Kodi into a playlist element
|
||||||
|
@ -334,7 +685,7 @@ def playlist_item_from_kodi(kodi_item):
|
||||||
Supply with data['item'] as returned from Kodi JSON-RPC interface.
|
Supply with data['item'] as returned from Kodi JSON-RPC interface.
|
||||||
kodi_item dict contains keys 'id', 'type', 'file' (if applicable)
|
kodi_item dict contains keys 'id', 'type', 'file' (if applicable)
|
||||||
"""
|
"""
|
||||||
item = Playlist_Item()
|
item = PlaylistItem()
|
||||||
item.kodi_id = kodi_item.get('id')
|
item.kodi_id = kodi_item.get('id')
|
||||||
item.kodi_type = kodi_item.get('type')
|
item.kodi_type = kodi_item.get('type')
|
||||||
if item.kodi_id:
|
if item.kodi_id:
|
||||||
|
@ -343,7 +694,7 @@ def playlist_item_from_kodi(kodi_item):
|
||||||
if db_item:
|
if db_item:
|
||||||
item.plex_id = db_item['plex_id']
|
item.plex_id = db_item['plex_id']
|
||||||
item.plex_type = db_item['plex_type']
|
item.plex_type = db_item['plex_type']
|
||||||
item.plex_uuid = db_item['plex_id'] # we dont need the uuid yet :-)
|
item.plex_uuid = db_item['section_uuid']
|
||||||
item.file = kodi_item.get('file')
|
item.file = kodi_item.get('file')
|
||||||
if item.plex_id is None and item.file is not None:
|
if item.plex_id is None and item.file is not None:
|
||||||
try:
|
try:
|
||||||
|
@ -413,7 +764,7 @@ def playlist_item_from_plex(plex_id):
|
||||||
|
|
||||||
Returns a Playlist_Item
|
Returns a Playlist_Item
|
||||||
"""
|
"""
|
||||||
item = Playlist_Item()
|
item = PlaylistItem()
|
||||||
item.plex_id = plex_id
|
item.plex_id = plex_id
|
||||||
with PlexDB(lock=False) as plexdb:
|
with PlexDB(lock=False) as plexdb:
|
||||||
db_item = plexdb.item_by_id(plex_id)
|
db_item = plexdb.item_by_id(plex_id)
|
||||||
|
@ -436,7 +787,7 @@ def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None):
|
||||||
|
|
||||||
xml_video_element: etree xml piece 1 level underneath <MediaContainer>
|
xml_video_element: etree xml piece 1 level underneath <MediaContainer>
|
||||||
"""
|
"""
|
||||||
item = Playlist_Item()
|
item = PlaylistItem()
|
||||||
api = API(xml_video_element)
|
api = API(xml_video_element)
|
||||||
item.plex_id = api.plex_id()
|
item.plex_id = api.plex_id()
|
||||||
item.plex_type = api.plex_type()
|
item.plex_type = api.plex_type()
|
||||||
|
@ -612,12 +963,14 @@ def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None):
|
||||||
|
|
||||||
Returns the PKC PlayList item or raises PlaylistError
|
Returns the PKC PlayList item or raises PlaylistError
|
||||||
"""
|
"""
|
||||||
|
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)
|
verify_kodi_item(plex_id, kodi_item)
|
||||||
if plex_id:
|
if plex_id:
|
||||||
item = playlist_item_from_plex(plex_id)
|
item = playlist_item_from_plex(plex_id)
|
||||||
else:
|
else:
|
||||||
item = playlist_item_from_kodi(kodi_item)
|
item = playlist_item_from_kodi(kodi_item)
|
||||||
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.id, item.uri)
|
url = '{server}/%ss/%s?uri=%s' % (playlist.kind, playlist.id, item.uri)
|
||||||
# Will always put the new item at the end of the Plex playlist
|
# Will always put the new item at the end of the Plex playlist
|
||||||
xml = DU().downloadUrl(url, action_type="PUT")
|
xml = DU().downloadUrl(url, action_type="PUT")
|
||||||
try:
|
try:
|
||||||
|
@ -625,21 +978,27 @@ def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None):
|
||||||
except (TypeError, AttributeError, KeyError, IndexError):
|
except (TypeError, AttributeError, KeyError, IndexError):
|
||||||
raise PlaylistError('Could not add item %s to playlist %s'
|
raise PlaylistError('Could not add item %s to playlist %s'
|
||||||
% (kodi_item, playlist))
|
% (kodi_item, playlist))
|
||||||
api = API(xml[-1])
|
if len(xml) != len(playlist.items) + 1:
|
||||||
item.xml = xml[-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.id = api.item_id()
|
||||||
item.guid = api.guid_html_escaped()
|
item.guid = api.guid_html_escaped()
|
||||||
item.offset = api.resume_point()
|
item.offset = api.resume_point()
|
||||||
item.playcount = api.viewcount()
|
item.playcount = api.viewcount()
|
||||||
playlist.items.append(item)
|
playlist.items.insert(actual_pos, item)
|
||||||
if pos == len(playlist.items) - 1:
|
|
||||||
# Item was added at the end
|
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_get_playListVersion_from_xml(playlist, xml)
|
||||||
else:
|
if actual_pos != pos:
|
||||||
# Move the new item to the correct position
|
# Move the new item to the correct position
|
||||||
move_playlist_item(playlist,
|
move_playlist_item(playlist, actual_pos, pos)
|
||||||
len(playlist.items) - 1,
|
|
||||||
pos)
|
|
||||||
LOG.debug('Successfully added item on the Plex side: %s', playlist)
|
LOG.debug('Successfully added item on the Plex side: %s', playlist)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@ -710,6 +1069,7 @@ def move_playlist_item(playlist, before_pos, after_pos):
|
||||||
LOG.error('Could not move playlist item')
|
LOG.error('Could not move playlist item')
|
||||||
return
|
return
|
||||||
_get_playListVersion_from_xml(playlist, xml)
|
_get_playListVersion_from_xml(playlist, xml)
|
||||||
|
utils.dump_xml(xml)
|
||||||
# Move our item's position in our internal playlist
|
# Move our item's position in our internal playlist
|
||||||
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
|
||||||
LOG.debug('Done moving for %s', playlist)
|
LOG.debug('Done moving for %s', playlist)
|
||||||
|
|
|
@ -18,7 +18,7 @@ LOG = getLogger('PLEX.playqueue')
|
||||||
|
|
||||||
PLUGIN = 'plugin://%s' % v.ADDON_ID
|
PLUGIN = 'plugin://%s' % v.ADDON_ID
|
||||||
|
|
||||||
# Our PKC playqueues (3 instances of Playqueue_Object())
|
# Our PKC playqueues (3 instances PlayQueue())
|
||||||
PLAYQUEUES = []
|
PLAYQUEUES = []
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ def init_playqueues():
|
||||||
for queue in js.get_playlists():
|
for queue in js.get_playlists():
|
||||||
if queue['playlistid'] != i:
|
if queue['playlistid'] != i:
|
||||||
continue
|
continue
|
||||||
playqueue = PL.Playqueue_Object()
|
playqueue = PL.PlayQueue()
|
||||||
playqueue.playlistid = i
|
playqueue.playlistid = i
|
||||||
playqueue.type = queue['type']
|
playqueue.type = queue['type']
|
||||||
# Initialize each Kodi playlist
|
# Initialize each Kodi playlist
|
||||||
|
@ -206,6 +206,8 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
for playqueue in PLAYQUEUES:
|
for playqueue in PLAYQUEUES:
|
||||||
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
||||||
|
playqueue.old_kodi_pl = list(kodi_pl)
|
||||||
|
continue
|
||||||
if playqueue.old_kodi_pl != kodi_pl:
|
if playqueue.old_kodi_pl != kodi_pl:
|
||||||
if playqueue.id is None and (not app.SYNC.direct_paths or
|
if playqueue.id is None and (not app.SYNC.direct_paths or
|
||||||
app.PLAYSTATE.context_menu_play):
|
app.PLAYSTATE.context_menu_play):
|
||||||
|
@ -215,5 +217,4 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
else:
|
else:
|
||||||
# compare old and new playqueue
|
# compare old and new playqueue
|
||||||
self._compare_playqueues(playqueue, kodi_pl)
|
self._compare_playqueues(playqueue, kodi_pl)
|
||||||
playqueue.old_kodi_pl = list(kodi_pl)
|
|
||||||
app.APP.monitor.waitForAbort(0.2)
|
app.APP.monitor.waitForAbort(0.2)
|
||||||
|
|
|
@ -2,13 +2,8 @@
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
import xbmc
|
from . import app, utils, json_rpc, variables as v, playlist_func as PL, \
|
||||||
|
playqueue as PQ
|
||||||
from .plex_api import API
|
|
||||||
from .playutils import PlayUtils
|
|
||||||
from .windows.resume import resume_dialog
|
|
||||||
from . import app, plex_functions as PF, utils, json_rpc, variables as v, \
|
|
||||||
widgets, playlist_func as PL, playqueue as PQ
|
|
||||||
|
|
||||||
|
|
||||||
LOG = getLogger('PLEX.playstrm')
|
LOG = getLogger('PLEX.playstrm')
|
||||||
|
@ -27,15 +22,8 @@ class PlayStrm(object):
|
||||||
webserivce returns a dummy file to play. Meanwhile, PlayStrm adds the real
|
webserivce returns a dummy file to play. Meanwhile, PlayStrm adds the real
|
||||||
listitems for items to play to the playlist.
|
listitems for items to play to the playlist.
|
||||||
'''
|
'''
|
||||||
def __init__(self, params, server_id=None):
|
def __init__(self, params):
|
||||||
LOG.debug('Starting PlayStrm with server_id %s, params: %s',
|
LOG.debug('Starting PlayStrm with params: %s', params)
|
||||||
server_id, params)
|
|
||||||
self.xml = None
|
|
||||||
self.playqueue_item = None
|
|
||||||
self.api = None
|
|
||||||
self.start_index = None
|
|
||||||
self.index = None
|
|
||||||
self.server_id = server_id
|
|
||||||
self.plex_id = utils.cast(int, params['plex_id'])
|
self.plex_id = utils.cast(int, params['plex_id'])
|
||||||
self.plex_type = params.get('plex_type')
|
self.plex_type = params.get('plex_type')
|
||||||
if params.get('synched') and params['synched'].lower() == 'false':
|
if params.get('synched') and params['synched'].lower() == 'false':
|
||||||
|
@ -44,97 +32,46 @@ class PlayStrm(object):
|
||||||
self.synched = True
|
self.synched = True
|
||||||
self.kodi_id = utils.cast(int, params.get('kodi_id'))
|
self.kodi_id = utils.cast(int, params.get('kodi_id'))
|
||||||
self.kodi_type = params.get('kodi_type')
|
self.kodi_type = params.get('kodi_type')
|
||||||
self._get_xml()
|
self.force_transcode = params.get('transcode') == 'true'
|
||||||
self.name = self.api.title()
|
|
||||||
if ((self.kodi_id is None or self.kodi_type is None) and
|
|
||||||
self.xml[0].get('pkc_db_item')):
|
|
||||||
self.kodi_id = self.xml[0].get('pkc_db_item')['kodi_id']
|
|
||||||
self.kodi_type = self.xml[0].get('pkc_db_item')['kodi_type']
|
|
||||||
self.transcode = params.get('transcode')
|
|
||||||
if self.transcode is None:
|
|
||||||
self.transcode = utils.settings('playFromTranscode.bool') if utils.settings('playFromStream.bool') else None
|
|
||||||
if app.PLAYSTATE.audioplaylist:
|
if app.PLAYSTATE.audioplaylist:
|
||||||
LOG.debug('Audio playlist detected')
|
LOG.debug('Audio playlist detected')
|
||||||
self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO)
|
self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO)
|
||||||
else:
|
else:
|
||||||
LOG.debug('Video playlist detected')
|
LOG.debug('Video playlist detected')
|
||||||
self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO)
|
self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO)
|
||||||
self.kodi_playlist = self.playqueue.kodi_pl
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __unicode__(self):
|
||||||
return ("{{"
|
return ("{{"
|
||||||
"'name': '{self.name}', "
|
|
||||||
"'plex_id': {self.plex_id}, "
|
"'plex_id': {self.plex_id}, "
|
||||||
"'plex_type': '{self.plex_type}', "
|
"'plex_type': '{self.plex_type}', "
|
||||||
"'kodi_id': {self.kodi_id}, "
|
"'kodi_id': {self.kodi_id}, "
|
||||||
"'kodi_type': '{self.kodi_type}', "
|
"'kodi_type': '{self.kodi_type}', "
|
||||||
"'server_id': '{self.server_id}', "
|
"}}").format(self=self)
|
||||||
"'transcode': {self.transcode}, "
|
|
||||||
"'start_index': {self.start_index}, "
|
|
||||||
"'index': {self.index}"
|
|
||||||
"}}").format(self=self).encode('utf-8')
|
|
||||||
__str__ = __repr__
|
|
||||||
|
|
||||||
def playlist_add_json(self):
|
def __str__(self):
|
||||||
playlistid = self.kodi_playlist.getPlayListId()
|
return unicode(self).encode('utf-8')
|
||||||
LOG.debug('Adding kodi_id %s, kodi_type %s to playlist %s at index %s',
|
__repr__ = __str__
|
||||||
self.kodi_id, self.kodi_type, playlistid, self.index)
|
|
||||||
if self.index is None:
|
|
||||||
json_rpc.playlist_add(playlistid,
|
|
||||||
{'%sid' % self.kodi_type: self.kodi_id})
|
|
||||||
else:
|
|
||||||
json_rpc.playlist_insert({'playlistid': playlistid,
|
|
||||||
'position': self.index,
|
|
||||||
'item': {'%sid' % self.kodi_type: self.kodi_id}})
|
|
||||||
|
|
||||||
def playlist_add(self, url, listitem):
|
|
||||||
self.kodi_playlist.add(url=url, listitem=listitem, index=self.index)
|
|
||||||
self.playqueue_item.file = url.decode('utf-8')
|
|
||||||
self.playqueue.items.insert(self.index, self.playqueue_item)
|
|
||||||
self.index += 1
|
|
||||||
|
|
||||||
def remove_from_playlist(self, index):
|
|
||||||
LOG.debug('Removing playlist item number %s from %s', index, self)
|
|
||||||
json_rpc.playlist_remove(self.kodi_playlist.getPlayListId(),
|
|
||||||
index)
|
|
||||||
|
|
||||||
def _get_xml(self):
|
|
||||||
self.xml = PF.GetPlexMetadata(self.plex_id)
|
|
||||||
if self.xml in (None, 401):
|
|
||||||
raise PlayStrmException('No xml received from the PMS')
|
|
||||||
if self.synched:
|
|
||||||
# Adds a new key 'pkc_db_item' to self.xml[0].attrib
|
|
||||||
widgets.attach_kodi_ids(self.xml)
|
|
||||||
else:
|
|
||||||
self.xml[0].set('pkc_db_item', None)
|
|
||||||
self.api = API(self.xml[0])
|
|
||||||
|
|
||||||
def set_playqueue_item(self, xml, kodi_id, kodi_type):
|
|
||||||
self.playqueue_item = PL.playlist_item_from_xml(xml,
|
|
||||||
kodi_id=kodi_id,
|
|
||||||
kodi_type=kodi_type)
|
|
||||||
self.playqueue_item.force_transcode = self.transcode
|
|
||||||
|
|
||||||
def start_playback(self, index=0):
|
|
||||||
LOG.debug('Starting playback at %s', index)
|
|
||||||
xbmc.Player().play(self.kodi_playlist, startpos=index, windowed=False)
|
|
||||||
|
|
||||||
def play(self, start_position=None, delayed=True):
|
def play(self, start_position=None, delayed=True):
|
||||||
'''
|
'''
|
||||||
Create and add listitems to the Kodi playlist.
|
Create and add a single listitem to the Kodi playlist, potentially
|
||||||
|
with trailers and different file-parts
|
||||||
'''
|
'''
|
||||||
LOG.debug('play called with start_position %s, delayed %s',
|
LOG.debug('play called with start_position %s, delayed %s',
|
||||||
start_position, delayed)
|
start_position, delayed)
|
||||||
if start_position is not None:
|
LOG.debug('Kodi playlist BEFORE: %s',
|
||||||
self.start_index = start_position
|
json_rpc.playlist_get_items(self.playqueue.playlistid))
|
||||||
else:
|
self.playqueue.init(self.plex_id,
|
||||||
self.start_index = max(self.kodi_playlist.getposition(), 0)
|
plex_type=self.plex_type,
|
||||||
self.index = self.start_index
|
position=start_position,
|
||||||
self._set_playlist()
|
synched=self.synched,
|
||||||
|
force_transcode=self.force_transcode)
|
||||||
LOG.info('Initiating play for %s', self)
|
LOG.info('Initiating play for %s', self)
|
||||||
|
LOG.debug('Kodi playlist AFTER: %s',
|
||||||
|
json_rpc.playlist_get_items(self.playqueue.playlistid))
|
||||||
if not delayed:
|
if not delayed:
|
||||||
self.start_playback(self.start_index)
|
self.playqueue.start_playback(start_position)
|
||||||
return self.index
|
return self.playqueue.index
|
||||||
|
|
||||||
def play_folder(self, position=None):
|
def play_folder(self, position=None):
|
||||||
'''
|
'''
|
||||||
|
@ -142,136 +79,13 @@ class PlayStrm(object):
|
||||||
provided, add as Kodi would, otherwise queue playlist items using strm
|
provided, add as Kodi would, otherwise queue playlist items using strm
|
||||||
links to setup playback later.
|
links to setup playback later.
|
||||||
'''
|
'''
|
||||||
self.start_index = position or max(self.kodi_playlist.size(), 0)
|
start_position = position or max(self.playqueue.kodi_pl.size(), 0)
|
||||||
self.index = self.start_index + 1
|
index = start_position + 1
|
||||||
LOG.info('Play folder plex_id %s, index: %s', self.plex_id, self.index)
|
LOG.info('Play folder plex_id %s, index: %s', self.plex_id, index)
|
||||||
if self.kodi_id and self.kodi_type:
|
item = PL.PlaylistItem(plex_id=self.plex_id,
|
||||||
self.playlist_add_json()
|
plex_type=self.plex_type,
|
||||||
self.index += 1
|
kodi_id=self.kodi_id,
|
||||||
else:
|
kodi_type=self.kodi_type)
|
||||||
listitem = widgets.get_listitem(self.xml[0], resume=True)
|
self.playqueue.add_item(item, index)
|
||||||
url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT
|
index += 1
|
||||||
args = {
|
return index - 1
|
||||||
'plex_id': self.plex_id,
|
|
||||||
'plex_type': self.api.plex_type()
|
|
||||||
}
|
|
||||||
if self.kodi_id:
|
|
||||||
args['kodi_id'] = self.kodi_id
|
|
||||||
if self.kodi_type:
|
|
||||||
args['kodi_type'] = self.kodi_type
|
|
||||||
if self.server_id:
|
|
||||||
args['server_id'] = self.server_id
|
|
||||||
if self.transcode:
|
|
||||||
args['transcode'] = 'true'
|
|
||||||
url = utils.extend_url(url, args).encode('utf-8')
|
|
||||||
listitem.setPath(url)
|
|
||||||
self.playlist_add(url, listitem)
|
|
||||||
return self.index - 1
|
|
||||||
|
|
||||||
def _set_playlist(self):
|
|
||||||
'''
|
|
||||||
Verify seektime, set intros, set main item and set additional parts.
|
|
||||||
Detect the seektime for video type content. Verify the default video
|
|
||||||
action set in Kodi for accurate resume behavior.
|
|
||||||
'''
|
|
||||||
seektime = self._resume()
|
|
||||||
trailers = False
|
|
||||||
if (not seektime and self.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(self.plex_id,
|
|
||||||
self.xml.get('librarySectionUUID'),
|
|
||||||
mediatype=self.plex_type,
|
|
||||||
trailers=trailers)
|
|
||||||
if xml is None:
|
|
||||||
LOG.error('Could not get playqueue for UUID %s for %s',
|
|
||||||
self.xml.get('librarySectionUUID'), self)
|
|
||||||
# "Play error"
|
|
||||||
utils.dialog('notification',
|
|
||||||
utils.lang(29999),
|
|
||||||
utils.lang(30128),
|
|
||||||
icon='{error}')
|
|
||||||
app.PLAYSTATE.context_menu_play = False
|
|
||||||
app.PLAYSTATE.resume_playback = False
|
|
||||||
return
|
|
||||||
PL.get_playlist_details_from_xml(self.playqueue, xml)
|
|
||||||
# See that we add trailers, if they exist in the xml return
|
|
||||||
self._add_intros(xml)
|
|
||||||
# Add the main item
|
|
||||||
if seektime:
|
|
||||||
listitem = widgets.get_listitem(self.xml[0], resume=True)
|
|
||||||
else:
|
|
||||||
listitem = widgets.get_listitem(self.xml[0], resume=False)
|
|
||||||
listitem.setSubtitles(self.api.cache_external_subs())
|
|
||||||
self.set_playqueue_item(self.xml[0], self.kodi_id, self.kodi_type)
|
|
||||||
play = PlayUtils(self.api, self.playqueue_item)
|
|
||||||
url = play.getPlayUrl().encode('utf-8')
|
|
||||||
listitem.setPath(url)
|
|
||||||
self.playlist_add(url, listitem)
|
|
||||||
# Add additional file parts, if any exist
|
|
||||||
self._add_additional_parts()
|
|
||||||
|
|
||||||
def _resume(self):
|
|
||||||
'''
|
|
||||||
Resume item if available. Returns bool or raise an PlayStrmException if
|
|
||||||
resume was cancelled by user.
|
|
||||||
'''
|
|
||||||
seektime = app.PLAYSTATE.resume_playback
|
|
||||||
app.PLAYSTATE.resume_playback = None
|
|
||||||
if app.PLAYSTATE.autoplay:
|
|
||||||
seektime = False
|
|
||||||
LOG.info('Skip resume for autoplay')
|
|
||||||
elif seektime is None:
|
|
||||||
resume = self.api.resume_point()
|
|
||||||
if resume:
|
|
||||||
seektime = resume_dialog(resume)
|
|
||||||
LOG.info('User chose resume: %s', seektime)
|
|
||||||
if seektime is None:
|
|
||||||
raise PlayStrmException('User backed out of resume dialog.')
|
|
||||||
app.PLAYSTATE.autoplay = True
|
|
||||||
return seektime
|
|
||||||
|
|
||||||
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 intro in xml:
|
|
||||||
api = API(intro)
|
|
||||||
if not api.plex_type() == v.PLEX_TYPE_CLIP:
|
|
||||||
# E.g. the main item we're looking at - skip!
|
|
||||||
continue
|
|
||||||
LOG.debug('Adding trailer: %s', api.title())
|
|
||||||
listitem = widgets.get_listitem(intro, resume=False)
|
|
||||||
self.set_playqueue_item(intro, None, None)
|
|
||||||
play = PlayUtils(api, self.playqueue_item)
|
|
||||||
url = play.getPlayUrl().encode('utf-8')
|
|
||||||
listitem.setPath(url)
|
|
||||||
self.playlist_add(url, listitem)
|
|
||||||
|
|
||||||
def _add_additional_parts(self):
|
|
||||||
''' Create listitems and add them to the stack of playlist.
|
|
||||||
'''
|
|
||||||
for part, _ in enumerate(self.xml[0][0]):
|
|
||||||
if part == 0:
|
|
||||||
# The first part that we've already added
|
|
||||||
continue
|
|
||||||
self.api.set_part_number(part)
|
|
||||||
LOG.debug('Adding addional part %s', part)
|
|
||||||
self.set_playqueue_item(self.xml[0], self.kodi_id, self.kodi_type)
|
|
||||||
self.playqueue_item.part = part
|
|
||||||
listitem = widgets.get_listitem(self.xml[0], resume=False)
|
|
||||||
listitem.setSubtitles(self.api.cache_external_subs())
|
|
||||||
playqueue_item = PL.playlist_item_from_xml(self.xml[0])
|
|
||||||
play = PlayUtils(self.api, playqueue_item)
|
|
||||||
url = play.getPlayUrl().encode('utf-8')
|
|
||||||
listitem.setPath(url)
|
|
||||||
self.playlist_add(url, listitem)
|
|
||||||
|
|
|
@ -14,13 +14,13 @@ LOG = getLogger('PLEX.playutils')
|
||||||
|
|
||||||
class PlayUtils():
|
class PlayUtils():
|
||||||
|
|
||||||
def __init__(self, api, playqueue_item):
|
def __init__(self, api, playlistitem):
|
||||||
"""
|
"""
|
||||||
init with api (PlexAPI wrapper of the PMS xml element) and
|
init with api (PlexAPI wrapper of the PMS xml element) and
|
||||||
playqueue_item (Playlist_Item())
|
playlistitem [PlaylistItem()]
|
||||||
"""
|
"""
|
||||||
self.api = api
|
self.api = api
|
||||||
self.item = playqueue_item
|
self.item = playlistitem
|
||||||
|
|
||||||
def getPlayUrl(self):
|
def getPlayUrl(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Playlists(object):
|
||||||
|
|
||||||
def delete_playlist(self, playlist):
|
def delete_playlist(self, playlist):
|
||||||
"""
|
"""
|
||||||
Removes the entry for playlist [Playqueue_Object] from the Plex
|
Removes the entry for playlist [PlayQueue] from the Plex
|
||||||
playlists table.
|
playlists table.
|
||||||
Be sure to either set playlist.id or playlist.kodi_path
|
Be sure to either set playlist.id or playlist.kodi_path
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -820,14 +820,14 @@ def get_plex_sections():
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
|
||||||
def init_plex_playqueue(plex_id, librarySectionUUID, mediatype='movie',
|
def init_plex_playqueue(plex_id, librarySectionUUID, plex_type='movie',
|
||||||
trailers=False):
|
trailers=False):
|
||||||
"""
|
"""
|
||||||
Returns raw API metadata XML dump for a playlist with e.g. trailers.
|
Returns raw API metadata XML dump for a playlist with e.g. trailers.
|
||||||
"""
|
"""
|
||||||
url = "{server}/playQueues"
|
url = "{server}/playQueues"
|
||||||
args = {
|
args = {
|
||||||
'type': mediatype,
|
'type': plex_type,
|
||||||
'uri': ('library://{0}/item/%2Flibrary%2Fmetadata%2F{1}'.format(
|
'uri': ('library://{0}/item/%2Flibrary%2Fmetadata%2F{1}'.format(
|
||||||
librarySectionUUID, plex_id)),
|
librarySectionUUID, plex_id)),
|
||||||
'includeChapters': '1',
|
'includeChapters': '1',
|
||||||
|
|
|
@ -69,6 +69,16 @@ def getGlobalProperty(key):
|
||||||
'Window(10000).Property(plugin.video.plexkodiconnect.{0})'.format(key))
|
'Window(10000).Property(plugin.video.plexkodiconnect.{0})'.format(key))
|
||||||
|
|
||||||
|
|
||||||
|
def dump_xml(xml):
|
||||||
|
tree = etree.ElementTree(xml)
|
||||||
|
i = 0
|
||||||
|
while path_ops.exists(path_ops.path.join(v.ADDON_PROFILE, 'xml%s.xml' % i)):
|
||||||
|
i += 1
|
||||||
|
tree.write(path_ops.path.join(v.ADDON_PROFILE, 'xml%s.xml' % i),
|
||||||
|
encoding='utf-8')
|
||||||
|
LOG.debug('Dumped to xml: %s', 'xml%s.xml' % i)
|
||||||
|
|
||||||
|
|
||||||
def reboot_kodi(message=None):
|
def reboot_kodi(message=None):
|
||||||
"""
|
"""
|
||||||
Displays an OK prompt with 'Kodi will now restart to apply the changes'
|
Displays an OK prompt with 'Kodi will now restart to apply the changes'
|
||||||
|
|
|
@ -13,8 +13,8 @@ import Queue
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcvfs
|
import xbmcvfs
|
||||||
|
|
||||||
from . import backgroundthread, utils, variables as v, app
|
from . import backgroundthread, utils, variables as v, app, playqueue as PQ
|
||||||
from .playstrm import PlayStrm
|
from . import playlist_func as PL, json_rpc as js
|
||||||
|
|
||||||
|
|
||||||
LOG = getLogger('PLEX.webservice')
|
LOG = getLogger('PLEX.webservice')
|
||||||
|
@ -270,58 +270,110 @@ class QueuePlay(backgroundthread.KillableThread):
|
||||||
|
|
||||||
def __init__(self, server):
|
def __init__(self, server):
|
||||||
self.server = server
|
self.server = server
|
||||||
|
self.plex_id = None
|
||||||
|
self.plex_type = None
|
||||||
|
self.kodi_id = None
|
||||||
|
self.kodi_type = None
|
||||||
|
self.synched = None
|
||||||
|
self.force_transcode = None
|
||||||
super(QueuePlay, self).__init__()
|
super(QueuePlay, self).__init__()
|
||||||
|
|
||||||
|
def load_params(self, params):
|
||||||
|
self.plex_id = utils.cast(int, params['plex_id'])
|
||||||
|
self.plex_type = params.get('plex_type')
|
||||||
|
self.kodi_id = utils.cast(int, params.get('kodi_id'))
|
||||||
|
self.kodi_type = params.get('kodi_type')
|
||||||
|
if params.get('synched') and params['synched'].lower() == 'false':
|
||||||
|
self.synched = False
|
||||||
|
else:
|
||||||
|
self.synched = True
|
||||||
|
if params.get('transcode') and params['transcode'].lower() == 'true':
|
||||||
|
self.force_transcode = True
|
||||||
|
else:
|
||||||
|
self.force_transcode = False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
LOG.info('##===---- Starting QueuePlay ----===##')
|
LOG.debug('##===---- Starting QueuePlay ----===##')
|
||||||
|
if app.PLAYSTATE.audioplaylist:
|
||||||
|
LOG.debug('Audio playlist detected')
|
||||||
|
playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO)
|
||||||
|
else:
|
||||||
|
LOG.debug('Video playlist detected')
|
||||||
|
playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO)
|
||||||
|
abort = False
|
||||||
play_folder = False
|
play_folder = False
|
||||||
play = None
|
# Position to start playback from (!!)
|
||||||
start_position = None
|
# Do NOT use kodi_pl.getposition() as that appears to be buggy
|
||||||
position = None
|
start_position = max(js.get_position(playqueue.playlistid), 0)
|
||||||
|
# Position to add next element to queue - we're doing this at the end
|
||||||
# Let Kodi catch up
|
# of our playqueue
|
||||||
|
position = playqueue.kodi_pl.size()
|
||||||
|
LOG.debug('start %s, position %s for current playqueue: %s',
|
||||||
|
start_position, position, playqueue)
|
||||||
|
# Make sure we got at least 2 items in the queue - ugly
|
||||||
|
# TODO: find a better solution
|
||||||
xbmc.sleep(200)
|
xbmc.sleep(200)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
params = self.server.queue.get(timeout=0.1)
|
params = self.server.queue.get(timeout=0.1)
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
count = 20
|
count = 50
|
||||||
while not utils.window('plex.playlist.ready'):
|
while not utils.window('plex.playlist.ready'):
|
||||||
xbmc.sleep(50)
|
xbmc.sleep(50)
|
||||||
if not count:
|
if not count:
|
||||||
LOG.info('Playback aborted')
|
LOG.info('Playback aborted')
|
||||||
raise Exception('PlaybackAborted')
|
raise Exception('Playback aborted')
|
||||||
count -= 1
|
count -= 1
|
||||||
LOG.info('Starting playback at position: %s', start_position)
|
|
||||||
if play_folder:
|
if play_folder:
|
||||||
LOG.info('Start playing folder')
|
LOG.info('Start playing folder')
|
||||||
xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
|
xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
|
||||||
play.start_playback()
|
playqueue.start_playback(start_position)
|
||||||
else:
|
else:
|
||||||
|
# TODO - do we need to do anything here?
|
||||||
|
# Originally, 1st failable item should have been removed
|
||||||
utils.window('plex.playlist.play', value='true')
|
utils.window('plex.playlist.play', value='true')
|
||||||
# xbmc.sleep(1000)
|
# playqueue.kodi_remove_item(start_position)
|
||||||
play.remove_from_playlist(start_position)
|
|
||||||
break
|
break
|
||||||
play = PlayStrm(params, params.get('ServerId'))
|
self.load_params(params)
|
||||||
|
|
||||||
if start_position is None:
|
|
||||||
start_position = max(play.kodi_playlist.getposition(), 0)
|
|
||||||
position = start_position + 1
|
|
||||||
if play_folder:
|
if play_folder:
|
||||||
position = play.play_folder(position)
|
# position = play.play_folder(position)
|
||||||
|
item = PL.PlaylistItem(plex_id=self.plex_id,
|
||||||
|
plex_type=self.plex_type,
|
||||||
|
kodi_id=self.kodi_id,
|
||||||
|
kodi_type=self.kodi_type)
|
||||||
|
item.force_transcode = self.force_transcode
|
||||||
|
playqueue.add_item(item, position)
|
||||||
|
position += 1
|
||||||
else:
|
else:
|
||||||
if self.server.pending.count(params['plex_id']) != len(self.server.pending):
|
if self.server.pending.count(params['plex_id']) != len(self.server.pending):
|
||||||
|
LOG.debug('Folder playback detected')
|
||||||
play_folder = True
|
play_folder = True
|
||||||
utils.window('plex.playlist.start', str(start_position))
|
utils.window('plex.playlist.start', str(start_position))
|
||||||
position = play.play(position)
|
playqueue.init(self.plex_id,
|
||||||
|
plex_type=self.plex_type,
|
||||||
|
position=position,
|
||||||
|
synched=self.synched,
|
||||||
|
force_transcode=self.force_transcode)
|
||||||
|
# Do NOT start playback here - because Kodi already started
|
||||||
|
# it!
|
||||||
|
# playqueue.start_playback(position)
|
||||||
|
position = playqueue.index
|
||||||
if play_folder:
|
if play_folder:
|
||||||
xbmc.executebuiltin('Activateutils.window(busydialognocancel)')
|
xbmc.executebuiltin('Activateutils.window(busydialognocancel)')
|
||||||
|
except PL.PlaylistError as error:
|
||||||
|
abort = True
|
||||||
|
LOG.warn('Not playing due to the following: %s', error)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
abort = True
|
||||||
utils.ERROR()
|
utils.ERROR()
|
||||||
play.kodi_playlist.clear()
|
try:
|
||||||
|
self.server.queue.task_done()
|
||||||
|
except ValueError:
|
||||||
|
# "task_done() called too many times"
|
||||||
|
pass
|
||||||
|
if abort:
|
||||||
|
playqueue.clear()
|
||||||
xbmc.Player().stop()
|
xbmc.Player().stop()
|
||||||
self.server.queue.queue.clear()
|
self.server.queue.queue.clear()
|
||||||
if play_folder:
|
if play_folder:
|
||||||
|
@ -329,11 +381,10 @@ class QueuePlay(backgroundthread.KillableThread):
|
||||||
else:
|
else:
|
||||||
utils.window('plex.playlist.aborted', value='true')
|
utils.window('plex.playlist.aborted', value='true')
|
||||||
break
|
break
|
||||||
self.server.queue.task_done()
|
|
||||||
|
|
||||||
utils.window('plex.playlist.ready', clear=True)
|
utils.window('plex.playlist.ready', clear=True)
|
||||||
utils.window('plex.playlist.start', clear=True)
|
utils.window('plex.playlist.start', clear=True)
|
||||||
app.PLAYSTATE.audioplaylist = None
|
app.PLAYSTATE.audioplaylist = None
|
||||||
self.server.threads.remove(self)
|
self.server.threads.remove(self)
|
||||||
self.server.pending = []
|
self.server.pending = []
|
||||||
LOG.info('##===---- QueuePlay Stopped ----===##')
|
LOG.debug('##===---- QueuePlay Stopped ----===##')
|
||||||
|
|
Loading…
Reference in a new issue