commit
0765583dda
11 changed files with 144 additions and 138 deletions
|
@ -1,5 +1,5 @@
|
||||||
[ ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
|
[ ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
|
||||||
[ ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
[ ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
||||||
|
|
||||||
[](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
[](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
||||||
[](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
[](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
||||||
|
|
15
addon.xml
15
addon.xml
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.9.3" provider-name="croneter">
|
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.9.5" provider-name="croneter">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.1.0"/>
|
<import addon="xbmc.python" version="2.1.0"/>
|
||||||
<import addon="script.module.requests" version="2.9.1" />
|
<import addon="script.module.requests" version="2.9.1" />
|
||||||
|
@ -83,7 +83,18 @@
|
||||||
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
|
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
|
||||||
<description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description>
|
<description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description>
|
||||||
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
|
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
|
||||||
<news>version 2.9.3:
|
<news>version 2.9.5:
|
||||||
|
- Version 2.9.4 for everyone
|
||||||
|
|
||||||
|
version 2.9.4 (beta only):
|
||||||
|
- Fix extras not playing when path substitution is enabled
|
||||||
|
- Fix Plex Companion device restarting playback when reconnecting to PKC
|
||||||
|
- Fix playback report not working after having played a non-Plex video file
|
||||||
|
- Change how items are added to Plex playqueues by using PMS machine identifier
|
||||||
|
- Optimize code for playqueue items
|
||||||
|
- Fix rare AttributeError when shutting down Kodi
|
||||||
|
|
||||||
|
version 2.9.3:
|
||||||
- version 2.9.2 for everyone
|
- version 2.9.2 for everyone
|
||||||
|
|
||||||
version 2.9.2 (beta only):
|
version 2.9.2 (beta only):
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
version 2.9.5:
|
||||||
|
- Version 2.9.4 for everyone
|
||||||
|
|
||||||
|
version 2.9.4 (beta only):
|
||||||
|
- Fix extras not playing when path substitution is enabled
|
||||||
|
- Fix Plex Companion device restarting playback when reconnecting to PKC
|
||||||
|
- Fix playback report not working after having played a non-Plex video file
|
||||||
|
- Change how items are added to Plex playqueues by using PMS machine identifier
|
||||||
|
- Optimize code for playqueue items
|
||||||
|
- Fix rare AttributeError when shutting down Kodi
|
||||||
|
|
||||||
version 2.9.3:
|
version 2.9.3:
|
||||||
- version 2.9.2 for everyone
|
- version 2.9.2 for everyone
|
||||||
|
|
||||||
|
|
|
@ -398,7 +398,11 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
LOG.debug('No Plex id obtained - aborting playback report')
|
LOG.debug('No Plex id obtained - aborting playback report')
|
||||||
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
|
||||||
return
|
return
|
||||||
|
try:
|
||||||
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
|
item = PL.init_plex_playqueue(playqueue, plex_id=plex_id)
|
||||||
|
except PL.PlaylistError:
|
||||||
|
LOG.info('Could not initialize the Plex playlist')
|
||||||
|
return
|
||||||
item.file = path
|
item.file = path
|
||||||
# Set the Plex container key (e.g. using the Plex playqueue)
|
# Set the Plex container key (e.g. using the Plex playqueue)
|
||||||
container_key = None
|
container_key = None
|
||||||
|
|
|
@ -234,14 +234,9 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
if plex_type != v.PLEX_TYPE_CLIP:
|
if plex_type != v.PLEX_TYPE_CLIP:
|
||||||
# Post to the PMS to create a playqueue - in any case due to Companion
|
# Post to the PMS to create a playqueue - in any case due to Companion
|
||||||
section_uuid = xml.attrib.get('librarySectionUUID')
|
xml = PF.init_plex_playqueue(plex_id, plex_type, trailers=trailers)
|
||||||
xml = PF.init_plex_playqueue(plex_id,
|
|
||||||
section_uuid,
|
|
||||||
mediatype=plex_type,
|
|
||||||
trailers=trailers)
|
|
||||||
if xml is None:
|
if xml is None:
|
||||||
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
|
LOG.error('Could not get a playqueue xml for plex id %s', plex_id)
|
||||||
plex_id, section_uuid)
|
|
||||||
# "Play error"
|
# "Play error"
|
||||||
utils.dialog('notification',
|
utils.dialog('notification',
|
||||||
utils.lang(29999),
|
utils.lang(29999),
|
||||||
|
@ -519,10 +514,8 @@ 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.playlist_item_from_xml(xml[0])
|
||||||
item.xml = xml[0]
|
|
||||||
item.offset = offset
|
item.offset = offset
|
||||||
item.plex_type = v.PLEX_TYPE_CLIP
|
|
||||||
item.playmethod = 'DirectStream'
|
item.playmethod = 'DirectStream'
|
||||||
|
|
||||||
# Need to get yet another xml to get the final playback url
|
# Need to get yet another xml to get the final playback url
|
||||||
|
|
|
@ -129,19 +129,33 @@ class Playqueue_Object(object):
|
||||||
self.kodi_playlist_playback = False
|
self.kodi_playlist_playback = False
|
||||||
LOG.debug('Playlist cleared: %s', self)
|
LOG.debug('Playlist cleared: %s', self)
|
||||||
|
|
||||||
|
def position_from_plex_id(self, plex_id):
|
||||||
|
"""
|
||||||
|
Returns the position [int] for the very first item with plex_id [int]
|
||||||
|
(Plex seems uncapable of adding the same element multiple times to a
|
||||||
|
playqueue or playlist)
|
||||||
|
|
||||||
class Playlist_Item(object):
|
Raises KeyError if not found
|
||||||
|
"""
|
||||||
|
for position, item in enumerate(self.items):
|
||||||
|
if item.plex_id == plex_id:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise KeyError('Did not find plex_id %s in %s', plex_id, self)
|
||||||
|
return position
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistItem(object):
|
||||||
"""
|
"""
|
||||||
Object to fill our playqueues and playlists with.
|
Object to fill our playqueues and playlists with.
|
||||||
|
|
||||||
id = None [int] Plex playlist/playqueue id, e.g. playQueueItemID
|
id = None [int] Plex playlist/playqueue id, e.g. playQueueItemID
|
||||||
plex_id = None [int] Plex unique item id, "ratingKey"
|
plex_id = None [int] Plex unique item id, "ratingKey"
|
||||||
plex_type = None [str] Plex type, e.g. 'movie', 'clip'
|
plex_type = None [str] Plex type, e.g. 'movie', 'clip'
|
||||||
plex_uuid = None [str] Plex librarySectionUUID
|
|
||||||
kodi_id = None [int] Kodi unique kodi id (unique only within type!)
|
kodi_id = None [int] Kodi unique kodi id (unique only within type!)
|
||||||
kodi_type = None [str] Kodi type: 'movie'
|
kodi_type = None [str] Kodi type: 'movie'
|
||||||
file = None [str] Path to the item's file. STRING!!
|
file = None [str] Path to the item's file. STRING!!
|
||||||
uri = None [str] Weird Plex uri path involving plex_uuid. STRING!
|
uri = None [str] PMS path to item; will be auto-set with plex_id
|
||||||
guid = None [str] Weird Plex guid
|
guid = None [str] Weird Plex guid
|
||||||
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
|
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
|
||||||
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
|
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
|
||||||
|
@ -151,21 +165,20 @@ class Playlist_Item(object):
|
||||||
force_transcode [bool] defaults to False
|
force_transcode [bool] defaults to False
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._id = None
|
self.id = None
|
||||||
self._plex_id = None
|
self._plex_id = None
|
||||||
self.plex_type = None
|
self.plex_type = None
|
||||||
self.plex_uuid = None
|
self.kodi_id = None
|
||||||
self._kodi_id = None
|
|
||||||
self.kodi_type = None
|
self.kodi_type = None
|
||||||
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
|
# If Plex video consists of several parts; part number
|
||||||
self._part = 0
|
self.part = 0
|
||||||
self.force_transcode = False
|
self.force_transcode = False
|
||||||
# Shall we ask user to resume this item?
|
# Shall we ask user to resume this item?
|
||||||
# None: ask user to resume
|
# None: ask user to resume
|
||||||
|
@ -179,82 +192,31 @@ class Playlist_Item(object):
|
||||||
|
|
||||||
@plex_id.setter
|
@plex_id.setter
|
||||||
def plex_id(self, value):
|
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
|
self._plex_id = value
|
||||||
|
self._uri = ('server://%s/com.plexapp.plugins.library/library/metadata/%s' %
|
||||||
|
(app.CONN.machine_identifier, value))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def uri(self):
|
||||||
return self._id
|
return self._uri
|
||||||
|
|
||||||
@id.setter
|
def __unicode__(self):
|
||||||
def id(self, value):
|
return ("{{"
|
||||||
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}', "
|
||||||
"'uri': '{self.uri}', "
|
|
||||||
"'guid': '{self.guid}', "
|
"'guid': '{self.guid}', "
|
||||||
"'playmethod': '{self.playmethod}', "
|
"'playmethod': '{self.playmethod}', "
|
||||||
"'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}".format(self=self))
|
||||||
answ = answ.encode('utf-8')
|
|
||||||
# etree xml.__repr__() could return string, not unicode
|
|
||||||
return answ + b"'xml': \"{self.xml}\"}}".format(self=self)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __repr__(self):
|
||||||
return self.__repr__()
|
return self.__unicode__().encode('utf-8')
|
||||||
|
|
||||||
def plex_stream_index(self, kodi_stream_index, stream_type):
|
def plex_stream_index(self, kodi_stream_index, stream_type):
|
||||||
"""
|
"""
|
||||||
|
@ -319,7 +281,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:
|
||||||
|
@ -328,7 +290,6 @@ 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.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:
|
||||||
|
@ -338,13 +299,6 @@ def playlist_item_from_kodi(kodi_item):
|
||||||
query = dict(utils.parse_qsl(query))
|
query = dict(utils.parse_qsl(query))
|
||||||
item.plex_id = utils.cast(int, query.get('plex_id'))
|
item.plex_id = utils.cast(int, query.get('plex_id'))
|
||||||
item.plex_type = query.get('itemType')
|
item.plex_type = query.get('itemType')
|
||||||
if item.plex_id is None and item.file is not None:
|
|
||||||
item.uri = ('library://whatever/item/%s'
|
|
||||||
% utils.quote(item.file, safe=''))
|
|
||||||
else:
|
|
||||||
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
|
|
||||||
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
|
||||||
(item.plex_uuid, item.plex_id))
|
|
||||||
LOG.debug('Made playlist item from Kodi: %s', item)
|
LOG.debug('Made playlist item from Kodi: %s', item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@ -358,7 +312,8 @@ def verify_kodi_item(plex_id, kodi_item):
|
||||||
set to None if unsuccessful.
|
set to None if unsuccessful.
|
||||||
|
|
||||||
Will raise a PlaylistError if plex_id is None and kodi_item['file'] starts
|
Will raise a PlaylistError if plex_id is None and kodi_item['file'] starts
|
||||||
with either 'plugin' or 'http'
|
with either 'plugin' or 'http'.
|
||||||
|
Will raise KeyError if neither plex_id nor kodi_id are found
|
||||||
"""
|
"""
|
||||||
if plex_id is not None or kodi_item.get('id') is not None:
|
if plex_id is not None or kodi_item.get('id') is not None:
|
||||||
# Got all the info we need
|
# Got all the info we need
|
||||||
|
@ -375,8 +330,8 @@ def verify_kodi_item(plex_id, kodi_item):
|
||||||
if ((kodi_item['file'].startswith('plugin') and
|
if ((kodi_item['file'].startswith('plugin') and
|
||||||
not kodi_item['file'].startswith('plugin://%s' % v.ADDON_ID)) or
|
not kodi_item['file'].startswith('plugin://%s' % v.ADDON_ID)) or
|
||||||
kodi_item['file'].startswith('http')):
|
kodi_item['file'].startswith('http')):
|
||||||
LOG.info('kodi_item %s cannot be used for Plex playback', kodi_item)
|
LOG.debug('kodi_item cannot be used for Plex playback: %s', kodi_item)
|
||||||
raise PlaylistError
|
raise PlaylistError('kodi_item cannot be used for Plex playback')
|
||||||
LOG.debug('Starting research for Kodi id since we didnt get one: %s',
|
LOG.debug('Starting research for Kodi id since we didnt get one: %s',
|
||||||
kodi_item)
|
kodi_item)
|
||||||
# Try the VIDEO DB first - will find both movies and episodes
|
# Try the VIDEO DB first - will find both movies and episodes
|
||||||
|
@ -388,6 +343,8 @@ def verify_kodi_item(plex_id, kodi_item):
|
||||||
db_type='music')
|
db_type='music')
|
||||||
kodi_item['id'] = kodi_id
|
kodi_item['id'] = kodi_id
|
||||||
kodi_item['type'] = None if kodi_id is None else kodi_type
|
kodi_item['type'] = None if kodi_id is None else kodi_type
|
||||||
|
if plex_id is None and kodi_id is None:
|
||||||
|
raise KeyError('Neither Plex nor Kodi id found for %s' % kodi_item)
|
||||||
LOG.debug('Research results for kodi_item: %s', kodi_item)
|
LOG.debug('Research results for kodi_item: %s', kodi_item)
|
||||||
return kodi_item
|
return kodi_item
|
||||||
|
|
||||||
|
@ -398,7 +355,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)
|
||||||
|
@ -408,9 +365,6 @@ def playlist_item_from_plex(plex_id):
|
||||||
item.kodi_type = db_item['kodi_type']
|
item.kodi_type = db_item['kodi_type']
|
||||||
else:
|
else:
|
||||||
raise KeyError('Could not find plex_id %s in database' % plex_id)
|
raise KeyError('Could not find plex_id %s in database' % plex_id)
|
||||||
item.plex_uuid = plex_id
|
|
||||||
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
|
||||||
(item.plex_uuid, plex_id))
|
|
||||||
LOG.debug('Made playlist item from plex: %s', item)
|
LOG.debug('Made playlist item from plex: %s', item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@ -421,7 +375,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
|
||||||
|
@ -431,9 +385,10 @@ def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None):
|
||||||
if kodi_id is not None:
|
if kodi_id is not None:
|
||||||
item.kodi_id = kodi_id
|
item.kodi_id = kodi_id
|
||||||
item.kodi_type = kodi_type
|
item.kodi_type = kodi_type
|
||||||
elif item.plex_id is not None and item.plex_type != v.PLEX_TYPE_CLIP:
|
elif item.plex_type != v.PLEX_TYPE_CLIP:
|
||||||
with PlexDB(lock=False) as plexdb:
|
with PlexDB(lock=False) as plexdb:
|
||||||
db_element = plexdb.item_by_id(item.plex_id)
|
db_element = plexdb.item_by_id(item.plex_id,
|
||||||
|
plex_type=item.plex_type)
|
||||||
if db_element:
|
if db_element:
|
||||||
item.kodi_id = db_element['kodi_id']
|
item.kodi_id = db_element['kodi_id']
|
||||||
item.kodi_type = db_element['kodi_type']
|
item.kodi_type = db_element['kodi_type']
|
||||||
|
@ -487,6 +442,8 @@ def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
|
||||||
need to fetch a new playqueue
|
need to fetch a new playqueue
|
||||||
|
|
||||||
If an xml is passed in, the playlist will be overwritten with its info
|
If an xml is passed in, the playlist will be overwritten with its info
|
||||||
|
|
||||||
|
Raises PlaylistError if something went wront
|
||||||
"""
|
"""
|
||||||
if xml is None:
|
if xml is None:
|
||||||
xml = get_PMS_playlist(playlist, playlist_id)
|
xml = get_PMS_playlist(playlist, playlist_id)
|
||||||
|
@ -508,8 +465,8 @@ def init_plex_playqueue(playlist, plex_id=None, kodi_item=None):
|
||||||
Returns the first PKC playlist item or raises PlaylistError
|
Returns the first PKC playlist item or raises PlaylistError
|
||||||
"""
|
"""
|
||||||
LOG.debug('Initializing the playqueue on the Plex side: %s', playlist)
|
LOG.debug('Initializing the playqueue on the Plex side: %s', playlist)
|
||||||
playlist.clear(kodi=False)
|
|
||||||
verify_kodi_item(plex_id, kodi_item)
|
verify_kodi_item(plex_id, kodi_item)
|
||||||
|
playlist.clear(kodi=False)
|
||||||
try:
|
try:
|
||||||
if plex_id:
|
if plex_id:
|
||||||
item = playlist_item_from_plex(plex_id)
|
item = playlist_item_from_plex(plex_id)
|
||||||
|
@ -523,6 +480,8 @@ def init_plex_playqueue(playlist, plex_id=None, kodi_item=None):
|
||||||
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
|
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
|
||||||
action_type="POST",
|
action_type="POST",
|
||||||
parameters=params)
|
parameters=params)
|
||||||
|
if xml in (None, 401):
|
||||||
|
raise PlaylistError('Did not receive a valid xml from the PMS')
|
||||||
get_playlist_details_from_xml(playlist, xml)
|
get_playlist_details_from_xml(playlist, xml)
|
||||||
# Need to get the details for the playlist item
|
# Need to get the details for the playlist item
|
||||||
item = playlist_item_from_xml(xml[0])
|
item = playlist_item_from_xml(xml[0])
|
||||||
|
@ -706,7 +665,7 @@ def get_PMS_playlist(playlist, playlist_id=None):
|
||||||
Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we
|
Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we
|
||||||
need to fetch a new playlist
|
need to fetch a new playlist
|
||||||
|
|
||||||
Returns None if something went wrong
|
Raises PlaylistError if something went wrong
|
||||||
"""
|
"""
|
||||||
playlist_id = playlist_id if playlist_id else playlist.id
|
playlist_id = playlist_id if playlist_id else playlist.id
|
||||||
if playlist.kind == 'playList':
|
if playlist.kind == 'playList':
|
||||||
|
@ -716,7 +675,7 @@ def get_PMS_playlist(playlist, playlist_id=None):
|
||||||
try:
|
try:
|
||||||
xml.attrib
|
xml.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
xml = None
|
raise PlaylistError('Did not get a valid xml')
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,11 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
for i, child in enumerate(xml):
|
for i, child in enumerate(xml):
|
||||||
api = API(child)
|
api = API(child)
|
||||||
|
try:
|
||||||
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
|
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
|
||||||
|
except PL.PlaylistError:
|
||||||
|
LOG.error('Could not add Plex item to our playlist: %s, %s',
|
||||||
|
child.tag, child.attrib)
|
||||||
playqueue.plex_transient_token = transient_token
|
playqueue.plex_transient_token = transient_token
|
||||||
LOG.debug('Firing up Kodi player')
|
LOG.debug('Firing up Kodi player')
|
||||||
app.APP.player.play(playqueue.kodi_pl, None, False, 0)
|
app.APP.player.play(playqueue.kodi_pl, None, False, 0)
|
||||||
|
@ -166,6 +170,14 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
except PL.PlaylistError:
|
except PL.PlaylistError:
|
||||||
# Could not add the element
|
# Could not add the element
|
||||||
pass
|
pass
|
||||||
|
except KeyError:
|
||||||
|
# Catches KeyError from PL.verify_kodi_item()
|
||||||
|
# Hack: Kodi already started playback of a new item and we
|
||||||
|
# started playback already using kodimonitors
|
||||||
|
# PlayBackStart(), but the Kodi playlist STILL only shows
|
||||||
|
# the old element. Hence ignore playlist difference here
|
||||||
|
LOG.debug('Detected an outdated Kodi playlist - ignoring')
|
||||||
|
return
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# This is really a hack - happens when using Addon Paths
|
# This is really a hack - happens when using Addon Paths
|
||||||
# and repeatedly starting the same element. Kodi will then
|
# and repeatedly starting the same element. Kodi will then
|
||||||
|
|
|
@ -17,7 +17,7 @@ class PlayUtils():
|
||||||
def __init__(self, api, playqueue_item):
|
def __init__(self, api, playqueue_item):
|
||||||
"""
|
"""
|
||||||
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())
|
playqueue_item (PlaylistItem())
|
||||||
"""
|
"""
|
||||||
self.api = api
|
self.api = api
|
||||||
self.item = playqueue_item
|
self.item = playqueue_item
|
||||||
|
|
|
@ -49,13 +49,21 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
if transient_token is None:
|
if transient_token is None:
|
||||||
transient_token = playqueue.plex_transient_token
|
transient_token = playqueue.plex_transient_token
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
|
||||||
try:
|
try:
|
||||||
xml.attrib
|
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||||
except AttributeError:
|
except PL.PlaylistError:
|
||||||
LOG.error('Could now download playqueue %s', playqueue_id)
|
LOG.error('Could now download playqueue %s', playqueue_id)
|
||||||
return
|
return
|
||||||
|
if playqueue.id == playqueue_id:
|
||||||
|
# This seems to be happening ONLY if a Plex Companion device
|
||||||
|
# reconnects and Kodi is already playing something - silly, really
|
||||||
|
# For all other cases, a new playqueue is generated by Plex
|
||||||
|
LOG.debug('Update for existing playqueue detected')
|
||||||
|
new = False
|
||||||
|
else:
|
||||||
|
new = True
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
|
# Get new metadata for the playqueue first
|
||||||
try:
|
try:
|
||||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||||
except PL.PlaylistError:
|
except PL.PlaylistError:
|
||||||
|
@ -63,10 +71,33 @@ def update_playqueue_from_PMS(playqueue,
|
||||||
return
|
return
|
||||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||||
playqueue.plex_transient_token = transient_token
|
playqueue.plex_transient_token = transient_token
|
||||||
|
if new:
|
||||||
playback.play_xml(playqueue,
|
playback.play_xml(playqueue,
|
||||||
xml,
|
xml,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
start_plex_id=start_plex_id)
|
start_plex_id=start_plex_id)
|
||||||
|
return
|
||||||
|
# Updates to playqueues could potentially become a bit more ugly...
|
||||||
|
if app.APP.is_playing:
|
||||||
|
try:
|
||||||
|
playerid = js.get_player_ids()[0]
|
||||||
|
except IndexError:
|
||||||
|
LOG.error('Unexpectately could not get Kodi player id')
|
||||||
|
return
|
||||||
|
if app.PLAYSTATE.player_states[playerid]['plex_id'] == start_plex_id:
|
||||||
|
# Nothing to do - let's not seek to avoid jumps in playback
|
||||||
|
return
|
||||||
|
pos = playqueue.position_from_plex_id(start_plex_id)
|
||||||
|
LOG.debug('Skipping to position %s for %s', pos, playqueue)
|
||||||
|
js.skipto(pos)
|
||||||
|
if offset:
|
||||||
|
js.seek_to(offset)
|
||||||
|
return
|
||||||
|
# Need to initiate playback again using our existing playqueue
|
||||||
|
app.APP.player.play(playqueue.kodi_pl,
|
||||||
|
None,
|
||||||
|
False,
|
||||||
|
playqueue.position_from_plex_id(start_plex_id))
|
||||||
|
|
||||||
|
|
||||||
class PlexCompanion(backgroundthread.KillableThread):
|
class PlexCompanion(backgroundthread.KillableThread):
|
||||||
|
@ -165,7 +196,7 @@ class PlexCompanion(backgroundthread.KillableThread):
|
||||||
update_playqueue_from_PMS(playqueue,
|
update_playqueue_from_PMS(playqueue,
|
||||||
playqueue_id=container_key,
|
playqueue_id=container_key,
|
||||||
repeat=query.get('repeat'),
|
repeat=query.get('repeat'),
|
||||||
offset=data.get('offset'),
|
offset=utils.cast(int, data.get('offset')),
|
||||||
transient_token=data.get('token'),
|
transient_token=data.get('token'),
|
||||||
start_plex_id=key)
|
start_plex_id=key)
|
||||||
|
|
||||||
|
|
|
@ -820,16 +820,15 @@ def get_plex_sections():
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
|
||||||
def init_plex_playqueue(plex_id, librarySectionUUID, mediatype='movie',
|
def init_plex_playqueue(plex_id, plex_type, 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': ('server://%s/com.plexapp.plugins.library/library/metadata/%s' %
|
||||||
librarySectionUUID, plex_id)),
|
(app.CONN.machine_identifier, plex_id)),
|
||||||
'includeChapters': '1',
|
'includeChapters': '1',
|
||||||
'shuffle': '0',
|
'shuffle': '0',
|
||||||
'repeat': '0'
|
'repeat': '0'
|
||||||
|
|
|
@ -362,7 +362,7 @@ KODI_PLAYLIST_TYPE_FROM_KODI_TYPE = {
|
||||||
|
|
||||||
REMAP_TYPE_FROM_PLEXTYPE = {
|
REMAP_TYPE_FROM_PLEXTYPE = {
|
||||||
PLEX_TYPE_MOVIE: 'movie',
|
PLEX_TYPE_MOVIE: 'movie',
|
||||||
PLEX_TYPE_CLIP: 'clip',
|
PLEX_TYPE_CLIP: 'movie',
|
||||||
PLEX_TYPE_SHOW: 'tv',
|
PLEX_TYPE_SHOW: 'tv',
|
||||||
PLEX_TYPE_SEASON: 'tv',
|
PLEX_TYPE_SEASON: 'tv',
|
||||||
PLEX_TYPE_EPISODE: 'tv',
|
PLEX_TYPE_EPISODE: 'tv',
|
||||||
|
@ -400,20 +400,6 @@ TRANSLATION_FROM_PLEXTYPE = {
|
||||||
PLEX_TYPE_PHOTO: 1,
|
PLEX_TYPE_PHOTO: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
REMAP_TYPE_FROM_PLEXTYPE = {
|
|
||||||
'movie': 'movie',
|
|
||||||
'show': 'tv',
|
|
||||||
'season': 'tv',
|
|
||||||
'episode': 'tv',
|
|
||||||
'artist': 'music',
|
|
||||||
'album': 'music',
|
|
||||||
'song': 'music',
|
|
||||||
'track': 'music',
|
|
||||||
'clip': 'clip',
|
|
||||||
'photo': 'photo'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PLEX_TYPE_FROM_WEBSOCKET = {
|
PLEX_TYPE_FROM_WEBSOCKET = {
|
||||||
1: PLEX_TYPE_MOVIE,
|
1: PLEX_TYPE_MOVIE,
|
||||||
2: PLEX_TYPE_SHOW,
|
2: PLEX_TYPE_SHOW,
|
||||||
|
|
Loading…
Add table
Reference in a new issue