Merge pull request #984 from croneter/beta-version

Bump master
This commit is contained in:
croneter 2019-09-08 15:10:48 +02:00 committed by GitHub
commit 0765583dda
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 144 additions and 138 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-2.9.3-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
[![beta version](https://img.shields.io/badge/beta_version-2.9.3-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
[![stable version](https://img.shields.io/badge/stable_version-2.9.5-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
[![beta version](https://img.shields.io/badge/beta_version-2.9.5-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)

View file

@ -1,5 +1,5 @@
<?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>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" />
@ -83,7 +83,18 @@
<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>
<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 (beta only):

View file

@ -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.2 for everyone

View file

@ -398,7 +398,11 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug('No Plex id obtained - aborting playback report')
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
return
try:
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
# Set the Plex container key (e.g. using the Plex playqueue)
container_key = None

View file

@ -234,14 +234,9 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
playqueue.clear()
if plex_type != v.PLEX_TYPE_CLIP:
# 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,
section_uuid,
mediatype=plex_type,
trailers=trailers)
xml = PF.init_plex_playqueue(plex_id, plex_type, trailers=trailers)
if xml is None:
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
plex_id, section_uuid)
LOG.error('Could not get a playqueue xml for plex id %s', plex_id)
# "Play error"
utils.dialog('notification',
utils.lang(29999),
@ -519,10 +514,8 @@ def process_indirect(key, offset, resolve=True):
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
playqueue.clear()
item = PL.Playlist_Item()
item.xml = xml[0]
item = PL.playlist_item_from_xml(xml[0])
item.offset = offset
item.plex_type = v.PLEX_TYPE_CLIP
item.playmethod = 'DirectStream'
# Need to get yet another xml to get the final playback url

View file

@ -129,19 +129,33 @@ class Playqueue_Object(object):
self.kodi_playlist_playback = False
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.
id = None [int] Plex playlist/playqueue id, e.g. playQueueItemID
plex_id = None [int] Plex unique item id, "ratingKey"
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_type = None [str] Kodi type: 'movie'
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
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
@ -151,21 +165,20 @@ class Playlist_Item(object):
force_transcode [bool] defaults to False
"""
def __init__(self):
self._id = None
self.id = None
self._plex_id = None
self.plex_type = None
self.plex_uuid = None
self._kodi_id = None
self.kodi_id = None
self.kodi_type = None
self.file = None
self.uri = None
self._uri = None
self.guid = None
self.xml = None
self.playmethod = None
self._playcount = None
self._offset = None
self.playcount = None
self.offset = None
# If Plex video consists of several parts; part number
self._part = 0
self.part = 0
self.force_transcode = False
# Shall we ask user to resume this item?
# None: ask user to resume
@ -179,82 +192,31 @@ class Playlist_Item(object):
@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
self._uri = ('server://%s/com.plexapp.plugins.library/library/metadata/%s' %
(app.CONN.machine_identifier, value))
@property
def id(self):
return self._id
def uri(self):
return self._uri
@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 = ("{{"
def __unicode__(self):
return ("{{"
"'id': {self.id}, "
"'plex_id': {self.plex_id}, "
"'plex_type': '{self.plex_type}', "
"'plex_uuid': '{self.plex_uuid}', "
"'kodi_id': {self.kodi_id}, "
"'kodi_type': '{self.kodi_type}', "
"'file': '{self.file}', "
"'uri': '{self.uri}', "
"'guid': '{self.guid}', "
"'playmethod': '{self.playmethod}', "
"'playcount': {self.playcount}, "
"'offset': {self.offset}, "
"'force_transcode': {self.force_transcode}, "
"'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)
"'part': {self.part}".format(self=self))
def __str__(self):
return self.__repr__()
def __repr__(self):
return self.__unicode__().encode('utf-8')
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.
kodi_item dict contains keys 'id', 'type', 'file' (if applicable)
"""
item = Playlist_Item()
item = PlaylistItem()
item.kodi_id = kodi_item.get('id')
item.kodi_type = kodi_item.get('type')
if item.kodi_id:
@ -328,7 +290,6 @@ def playlist_item_from_kodi(kodi_item):
if db_item:
item.plex_id = db_item['plex_id']
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')
if item.plex_id is None and item.file is not None:
try:
@ -338,13 +299,6 @@ def playlist_item_from_kodi(kodi_item):
query = dict(utils.parse_qsl(query))
item.plex_id = utils.cast(int, query.get('plex_id'))
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)
return item
@ -358,7 +312,8 @@ def verify_kodi_item(plex_id, kodi_item):
set to None if unsuccessful.
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:
# 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
not kodi_item['file'].startswith('plugin://%s' % v.ADDON_ID)) or
kodi_item['file'].startswith('http')):
LOG.info('kodi_item %s cannot be used for Plex playback', kodi_item)
raise PlaylistError
LOG.debug('kodi_item cannot be used for Plex playback: %s', kodi_item)
raise PlaylistError('kodi_item cannot be used for Plex playback')
LOG.debug('Starting research for Kodi id since we didnt get one: %s',
kodi_item)
# Try the VIDEO DB first - will find both movies and episodes
@ -388,6 +343,8 @@ def verify_kodi_item(plex_id, kodi_item):
db_type='music')
kodi_item['id'] = kodi_id
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)
return kodi_item
@ -398,7 +355,7 @@ def playlist_item_from_plex(plex_id):
Returns a Playlist_Item
"""
item = Playlist_Item()
item = PlaylistItem()
item.plex_id = plex_id
with PlexDB(lock=False) as plexdb:
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']
else:
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)
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>
"""
item = Playlist_Item()
item = PlaylistItem()
api = API(xml_video_element)
item.plex_id = api.plex_id
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:
item.kodi_id = kodi_id
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:
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:
item.kodi_id = db_element['kodi_id']
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
If an xml is passed in, the playlist will be overwritten with its info
Raises PlaylistError if something went wront
"""
if xml is None:
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
"""
LOG.debug('Initializing the playqueue on the Plex side: %s', playlist)
playlist.clear(kodi=False)
verify_kodi_item(plex_id, kodi_item)
playlist.clear(kodi=False)
try:
if 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,
action_type="POST",
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)
# Need to get the details for the playlist item
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
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
if playlist.kind == 'playList':
@ -716,7 +675,7 @@ def get_PMS_playlist(playlist, playlist_id=None):
try:
xml.attrib
except AttributeError:
xml = None
raise PlaylistError('Did not get a valid xml')
return xml

View file

@ -86,7 +86,11 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
playqueue.clear()
for i, child in enumerate(xml):
api = API(child)
try:
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
LOG.debug('Firing up Kodi player')
app.APP.player.play(playqueue.kodi_pl, None, False, 0)
@ -166,6 +170,14 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
except PL.PlaylistError:
# Could not add the element
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:
# This is really a hack - happens when using Addon Paths
# and repeatedly starting the same element. Kodi will then

View file

@ -17,7 +17,7 @@ class PlayUtils():
def __init__(self, api, playqueue_item):
"""
init with api (PlexAPI wrapper of the PMS xml element) and
playqueue_item (Playlist_Item())
playqueue_item (PlaylistItem())
"""
self.api = api
self.item = playqueue_item

View file

@ -49,13 +49,21 @@ def update_playqueue_from_PMS(playqueue,
if transient_token is None:
transient_token = playqueue.plex_transient_token
with app.APP.lock_playqueues:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
try:
xml.attrib
except AttributeError:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
except PL.PlaylistError:
LOG.error('Could now download playqueue %s', playqueue_id)
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()
# Get new metadata for the playqueue first
try:
PL.get_playlist_details_from_xml(playqueue, xml)
except PL.PlaylistError:
@ -63,10 +71,33 @@ def update_playqueue_from_PMS(playqueue,
return
playqueue.repeat = 0 if not repeat else int(repeat)
playqueue.plex_transient_token = transient_token
if new:
playback.play_xml(playqueue,
xml,
offset=offset,
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):
@ -165,7 +196,7 @@ class PlexCompanion(backgroundthread.KillableThread):
update_playqueue_from_PMS(playqueue,
playqueue_id=container_key,
repeat=query.get('repeat'),
offset=data.get('offset'),
offset=utils.cast(int, data.get('offset')),
transient_token=data.get('token'),
start_plex_id=key)

View file

@ -820,16 +820,15 @@ def get_plex_sections():
return xml
def init_plex_playqueue(plex_id, librarySectionUUID, mediatype='movie',
trailers=False):
def init_plex_playqueue(plex_id, plex_type, trailers=False):
"""
Returns raw API metadata XML dump for a playlist with e.g. trailers.
"""
url = "{server}/playQueues"
args = {
'type': mediatype,
'uri': ('library://{0}/item/%2Flibrary%2Fmetadata%2F{1}'.format(
librarySectionUUID, plex_id)),
'type': plex_type,
'uri': ('server://%s/com.plexapp.plugins.library/library/metadata/%s' %
(app.CONN.machine_identifier, plex_id)),
'includeChapters': '1',
'shuffle': '0',
'repeat': '0'

View file

@ -362,7 +362,7 @@ KODI_PLAYLIST_TYPE_FROM_KODI_TYPE = {
REMAP_TYPE_FROM_PLEXTYPE = {
PLEX_TYPE_MOVIE: 'movie',
PLEX_TYPE_CLIP: 'clip',
PLEX_TYPE_CLIP: 'movie',
PLEX_TYPE_SHOW: 'tv',
PLEX_TYPE_SEASON: 'tv',
PLEX_TYPE_EPISODE: 'tv',
@ -400,20 +400,6 @@ TRANSLATION_FROM_PLEXTYPE = {
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 = {
1: PLEX_TYPE_MOVIE,
2: PLEX_TYPE_SHOW,