Merge branch 'beta-version'

This commit is contained in:
croneter 2018-07-27 07:50:31 +02:00
commit 352cbece62
12 changed files with 306 additions and 120 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-2.1.6-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip) [![stable version](https://img.shields.io/badge/stable_version-2.1.6-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.2.15-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip) [![beta version](https://img.shields.io/badge/beta_version-2.2.16-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) [![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) [![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"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.2.15" provider-name="croneter"> <addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.2.16" 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" />
@ -73,7 +73,13 @@
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary> <summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description> <description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
<disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer> <disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer>
<news>version 2.2.15 (beta only): <news>version 2.2.16 (beta only):
- Enable Kodi libraries for Plex Music libraries
- New Playlists menu item for video libraries
- Only show Plex libraries in the applicable Kodi media category
- Optimize code
version 2.2.15 (beta only):
- Fix ImportError on first PKC run - Fix ImportError on first PKC run
version 2.2.14 (beta only): version 2.2.14 (beta only):

View file

@ -1,3 +1,9 @@
version 2.2.16 (beta only):
- Enable Kodi libraries for Plex Music libraries
- New Playlists menu item for video libraries
- Only show Plex libraries in the applicable Kodi media category
- Optimize code
version 2.2.15 (beta only): version 2.2.15 (beta only):
- Fix ImportError on first PKC run - Fix ImportError on first PKC run

View file

@ -136,6 +136,9 @@ class Main():
plexId = itemid or None plexId = itemid or None
entrypoint.get_video_files(plexId, params) entrypoint.get_video_files(plexId, params)
elif mode == 'playlists':
entrypoint.playlists(params.get('type'))
else: else:
entrypoint.show_main_menu(content_type=params.get('content_type')) entrypoint.show_main_menu(content_type=params.get('content_type'))

View file

@ -145,8 +145,12 @@ def show_main_menu(content_type=None):
# nodes in the video library and all nodes in any other window # nodes in the video library and all nodes in any other window
if node_type == 'photos' and content_type == 'image': if node_type == 'photos' and content_type == 'image':
directory_item(label, path) directory_item(label, path)
elif (node_type != 'photos' and elif node_type == 'albums' and content_type == 'audio':
content_type not in ('image', 'audio')): directory_item(label, path)
elif node_type in ('movies',
'tvshows',
'homevideos',
'musicvideos') and content_type == 'video':
directory_item(label, path) directory_item(label, path)
# Plex Watch later # Plex Watch later
@ -524,6 +528,24 @@ def extra_fanart(plex_id, plex_path):
xbmcplugin.endOfDirectory(int(argv[1])) xbmcplugin.endOfDirectory(int(argv[1]))
def _wait_for_auth():
"""
Call to be sure that PKC is authenticated, e.g. for widgets on Kodi
startup. Will wait for at most 30s, then fail if not authenticated.
Will set xbmcplugin.endOfDirectory(int(argv[1]), False) if failed
"""
counter = 0
while utils.window('plex_authenticated') != 'true':
counter += 1
if counter == 300:
LOG.error('Aborting view, we were not authenticated for PMS')
xbmcplugin.endOfDirectory(int(argv[1]), False)
return False
sleep(100)
return True
def on_deck_episodes(viewid, tagname, limit): def on_deck_episodes(viewid, tagname, limit):
""" """
Retrieves Plex On Deck items, currently only for TV shows Retrieves Plex On Deck items, currently only for TV shows
@ -539,15 +561,8 @@ def on_deck_episodes(viewid, tagname, limit):
if utils.settings('OnDeckTVextended') == 'false': if utils.settings('OnDeckTVextended') == 'false':
# Chances are that this view is used on Kodi startup # Chances are that this view is used on Kodi startup
# Wait till we've connected to a PMS. At most 30s # Wait till we've connected to a PMS. At most 30s
counter = 0 if not _wait_for_auth():
while utils.window('plex_authenticated') != 'true': return
counter += 1
if counter == 300:
LOG.error('Aborting On Deck view, we were not authenticated '
'for the PMS')
xbmcplugin.endOfDirectory(int(argv[1]), False)
return
sleep(100)
xml = DU().downloadUrl('{server}/library/sections/%s/onDeck' % viewid) xml = DU().downloadUrl('{server}/library/sections/%s/onDeck' % viewid)
if xml in (None, 401): if xml in (None, 401):
LOG.error('Could not download PMS xml for view %s', viewid) LOG.error('Could not download PMS xml for view %s', viewid)
@ -659,6 +674,39 @@ def on_deck_episodes(viewid, tagname, limit):
xbmcplugin.endOfDirectory(handle=int(argv[1])) xbmcplugin.endOfDirectory(handle=int(argv[1]))
def playlists(plex_playlist_type):
"""
Lists all Plex playlists of the media type plex_playlist_type
"""
LOG.debug('Listing Plex %s playlists', plex_playlist_type)
if not _wait_for_auth():
return
xbmcplugin.setContent(int(argv[1]), 'files')
from .playlists.pms import all_playlists
xml = all_playlists()
if xml is None:
return
for item in xml:
api = API(item)
if not api.playlist_type() == plex_playlist_type:
continue
listitem = ListItem(api.title())
listitem.setArt({'thumb': api.one_artwork('composite')})
url = "plugin://%s/" % v.ADDON_ID
key = api.path_and_plex_id()
params = {
'mode': "browseplex",
'key': key,
}
xbmcplugin.addDirectoryItem(handle=int(argv[1]),
url="%s?%s" % (url, urlencode(params)),
isFolder=True,
listitem=listitem)
xbmcplugin.endOfDirectory(
handle=int(argv[1]),
cacheToDisc=utils.settings('enableTextureCache') == 'true')
def watchlater(): def watchlater():
""" """
Listing for plex.tv Watch Later section (if signed in to plex.tv) Listing for plex.tv Watch Later section (if signed in to plex.tv)
@ -714,6 +762,7 @@ def browse_plex(key=None, plex_section_id=None):
Lists the content of a Plex folder, e.g. channels. Either pass in key (to Lists the content of a Plex folder, e.g. channels. Either pass in key (to
be used directly for PMS url {server}<key>) or the plex_section_id be used directly for PMS url {server}<key>) or the plex_section_id
""" """
LOG.debug('Browsing to key %s, section %s', key, plex_section_id)
if key: if key:
xml = DU().downloadUrl('{server}%s' % key) xml = DU().downloadUrl('{server}%s' % key)
else: else:
@ -721,7 +770,8 @@ def browse_plex(key=None, plex_section_id=None):
try: try:
xml[0].attrib xml[0].attrib
except (ValueError, AttributeError, IndexError, TypeError): except (ValueError, AttributeError, IndexError, TypeError):
LOG.error('Could not browse to %s', key) LOG.error('Could not browse to key %s, section %s',
key, plex_section_id)
return xbmcplugin.endOfDirectory(int(argv[1]), False) return xbmcplugin.endOfDirectory(int(argv[1]), False)
photos = False photos = False
@ -761,37 +811,48 @@ def browse_plex(key=None, plex_section_id=None):
# Set the correct content type # Set the correct content type
if movies is True: if movies is True:
LOG.debug('Setting view to movies')
xbmcplugin.setContent(int(argv[1]), 'movies') xbmcplugin.setContent(int(argv[1]), 'movies')
sort_methods = v.SORT_METHODS_MOVIES sort_methods = v.SORT_METHODS_MOVIES
elif clips is True: elif clips is True:
LOG.debug('Clips -> Setting view to movies')
xbmcplugin.setContent(int(argv[1]), 'movies') xbmcplugin.setContent(int(argv[1]), 'movies')
sort_methods = v.SORT_METHODS_CLIPS sort_methods = v.SORT_METHODS_CLIPS
elif photos is True: elif photos is True:
LOG.debug('Setting view to images')
xbmcplugin.setContent(int(argv[1]), 'images') xbmcplugin.setContent(int(argv[1]), 'images')
sort_methods = v.SORT_METHODS_PHOTOS sort_methods = v.SORT_METHODS_PHOTOS
elif tvshows is True: elif tvshows is True:
LOG.debug('Setting view to tvshows')
xbmcplugin.setContent(int(argv[1]), 'tvshows') xbmcplugin.setContent(int(argv[1]), 'tvshows')
sort_methods = v.SORT_METHOD_TVSHOWS sort_methods = v.SORT_METHOD_TVSHOWS
elif episodes is True: elif episodes is True:
LOG.debug('Setting view to episodes')
xbmcplugin.setContent(int(argv[1]), 'episodes') xbmcplugin.setContent(int(argv[1]), 'episodes')
sort_methods = v.SORT_METHODS_EPISODES sort_methods = v.SORT_METHODS_EPISODES
elif songs is True: elif songs is True:
LOG.debug('Setting view to songs')
xbmcplugin.setContent(int(argv[1]), 'songs') xbmcplugin.setContent(int(argv[1]), 'songs')
sort_methods = v.SORT_METHODS_SONGS sort_methods = v.SORT_METHODS_SONGS
elif artists is True: elif artists is True:
LOG.debug('Setting view to artists')
xbmcplugin.setContent(int(argv[1]), 'artists') xbmcplugin.setContent(int(argv[1]), 'artists')
sort_methods = v.SORT_METHODS_ARTISTS sort_methods = v.SORT_METHODS_ARTISTS
elif albums is True: elif albums is True:
LOG.debug('Setting view to albums')
xbmcplugin.setContent(int(argv[1]), 'albums') xbmcplugin.setContent(int(argv[1]), 'albums')
sort_methods = v.SORT_METHODS_ALBUMS sort_methods = v.SORT_METHODS_ALBUMS
elif musicvideos is True: elif musicvideos is True:
LOG.debug('Setting view to musicvideos')
xbmcplugin.setContent(int(argv[1]), 'musicvideos') xbmcplugin.setContent(int(argv[1]), 'musicvideos')
sort_methods = v.SORT_METHODS_MOVIES sort_methods = v.SORT_METHODS_MOVIES
else: else:
LOG.debug('Setting view to files')
xbmcplugin.setContent(int(argv[1]), 'files') xbmcplugin.setContent(int(argv[1]), 'files')
sort_methods = v.SORT_METHODS_DIRECTORY sort_methods = v.SORT_METHODS_DIRECTORY
for method in sort_methods: for method in sort_methods:
LOG.debug('Adding Kodi sort method %s', method)
xbmcplugin.addSortMethod(int(argv[1]), getattr(xbmcplugin, method)) xbmcplugin.addSortMethod(int(argv[1]), getattr(xbmcplugin, method))
# Set the Kodi title for this view # Set the Kodi title for this view
@ -840,8 +901,6 @@ def __build_item(xml_element, direct_paths):
url = api.get_picture_path() url = api.get_picture_path()
else: else:
url = api.path(direct_paths=direct_paths) url = api.path(direct_paths=direct_paths)
if api.resume_point():
listitem.setProperty('resumetime', str(api.resume_point()))
xbmcplugin.addDirectoryItem(handle=int(argv[1]), xbmcplugin.addDirectoryItem(handle=int(argv[1]),
url=url, url=url,
listitem=listitem) listitem=listitem)

View file

@ -472,8 +472,8 @@ class SpecialMonitor(Thread):
def run(self): def run(self):
LOG.info("----====# Starting Special Monitor #====----") LOG.info("----====# Starting Special Monitor #====----")
# "Start from beginning", "Play from beginning" # "Start from beginning", "Play from beginning"
strings = (utils.try_encode(xbmc.getLocalizedString(12021)), strings = (utils.try_encode(utils.lang(12021)),
utils.try_encode(xbmc.getLocalizedString(12023))) utils.try_encode(utils.lang(12023)))
while not self.stopped(): while not self.stopped():
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'): if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings: if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:

View file

@ -329,8 +329,7 @@ class LibrarySync(Thread):
utils.playlist_xsp(mediatype, foldername, folderid, viewtype) utils.playlist_xsp(mediatype, foldername, folderid, viewtype)
lists.append(foldername) lists.append(foldername)
# Create the video node # Create the video node
if (foldername not in nodes and if foldername not in nodes:
mediatype != v.PLEX_TYPE_ARTIST):
vnodes.viewNode(sorted_views.index(foldername), vnodes.viewNode(sorted_views.index(foldername),
foldername, foldername,
mediatype, mediatype,
@ -362,42 +361,41 @@ class LibrarySync(Thread):
# Update view with new info # Update view with new info
plex_db.updateView(foldername, tagid, folderid) plex_db.updateView(foldername, tagid, folderid)
if mediatype != "artist": if plex_db.getView_byName(current_viewname) is None:
if plex_db.getView_byName(current_viewname) is None: # The tag could be a combined view. Ensure there's
# The tag could be a combined view. Ensure there's # no other tags with the same name before deleting
# no other tags with the same name before deleting # playlist.
# playlist. utils.playlist_xsp(mediatype,
utils.playlist_xsp(mediatype, current_viewname,
current_viewname, folderid,
folderid, current_viewtype,
current_viewtype, True)
True) # Delete video node
# Delete video node if mediatype != "musicvideos":
if mediatype != "musicvideos": vnodes.viewNode(
vnodes.viewNode( indexnumber=sorted_views.index(foldername),
indexnumber=sorted_views.index(foldername), tagname=current_viewname,
tagname=current_viewname, mediatype=mediatype,
mediatype=mediatype, viewtype=current_viewtype,
viewtype=current_viewtype, viewid=folderid,
viewid=folderid, delete=True)
delete=True) # Added new playlist
# Added new playlist if (foldername not in lists and mediatype in
if (foldername not in lists and mediatype in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): utils.playlist_xsp(mediatype,
utils.playlist_xsp(mediatype, foldername,
foldername, folderid,
folderid, viewtype)
viewtype) lists.append(foldername)
lists.append(foldername) # Add new video node
# Add new video node if foldername not in nodes and mediatype != "musicvideos":
if foldername not in nodes and mediatype != "musicvideos": vnodes.viewNode(sorted_views.index(foldername),
vnodes.viewNode(sorted_views.index(foldername), foldername,
foldername, mediatype,
mediatype, viewtype,
viewtype, folderid)
folderid) nodes.append(foldername)
nodes.append(foldername) totalnodes += 1
totalnodes += 1
# Update items with new tag # Update items with new tag
items = plex_db.getItem_byView(folderid) items = plex_db.getItem_byView(folderid)
@ -407,23 +405,22 @@ class LibrarySync(Thread):
current_tagid, tagid, item[0], current_viewtype[:-1]) current_tagid, tagid, item[0], current_viewtype[:-1])
else: else:
# Validate the playlist exists or recreate it # Validate the playlist exists or recreate it
if mediatype != v.PLEX_TYPE_ARTIST: if (foldername not in lists and mediatype in
if (foldername not in lists and mediatype in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): utils.playlist_xsp(mediatype,
utils.playlist_xsp(mediatype, foldername,
foldername, folderid,
folderid, viewtype)
viewtype) lists.append(foldername)
lists.append(foldername) # Create the video node if not already exists
# Create the video node if not already exists if foldername not in nodes and mediatype != "musicvideos":
if foldername not in nodes and mediatype != "musicvideos": vnodes.viewNode(sorted_views.index(foldername),
vnodes.viewNode(sorted_views.index(foldername), foldername,
foldername, mediatype,
mediatype, viewtype,
viewtype, folderid)
folderid) nodes.append(foldername)
nodes.append(foldername) totalnodes += 1
totalnodes += 1
return totalnodes return totalnodes
def maintain_views(self): def maintain_views(self):
@ -454,7 +451,8 @@ class LibrarySync(Thread):
for view in sections: for view in sections:
if (view.attrib['type'] in if (view.attrib['type'] in
(v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW, v.PLEX_TYPE_PHOTO)): (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW, v.PLEX_TYPE_PHOTO,
v.PLEX_TYPE_ARTIST)):
self.sorted_views.append(view.attrib['title']) self.sorted_views.append(view.attrib['title'])
LOG.debug('Sorted views: %s', self.sorted_views) LOG.debug('Sorted views: %s', self.sorted_views)

View file

@ -69,14 +69,19 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
try: try:
pos = js.get_position(playqueue.playlistid) pos = js.get_position(playqueue.playlistid)
except KeyError: except KeyError:
LOG.error('Still no position - abort') LOG.info('Assuming video instead of audio playlist playback')
# "Play error" playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_VIDEO)
utils.dialog('notification', try:
utils.lang(29999), pos = js.get_position(playqueue.playlistid)
utils.lang(30128), except KeyError:
icon='{error}') LOG.error('Still no position - abort')
_ensure_resolve(abort=True) # "Play error"
return utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
_ensure_resolve(abort=True)
return
# HACK to detect playback of playlists for add-on paths # HACK to detect playback of playlists for add-on paths
items = js.playlist_get_items(playqueue.playlistid) items = js.playlist_get_items(playqueue.playlistid)
try: try:

View file

@ -313,7 +313,8 @@ def verify_kodi_item(plex_id, kodi_item):
LOG.debug('Detected song. Research results: %s', kodi_item) LOG.debug('Detected song. Research results: %s', kodi_item)
return kodi_item return kodi_item
# Need more info since we don't have kodi_id nor type. Use file path. # Need more info since we don't have kodi_id nor type. Use file path.
if (kodi_item['file'].startswith('plugin') or if ((kodi_item['file'].startswith('plugin') and
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.info('kodi_item %s cannot be used for Plex playback', kodi_item)
raise PlaylistError raise PlaylistError

View file

@ -816,20 +816,25 @@ class API(object):
'subtitle': subtitlelanguages 'subtitle': subtitlelanguages
} }
def _one_artwork(self, art_kind): def one_artwork(self, art_kind):
artwork = self.item.get(art_kind) artwork = self.item.get(art_kind)
if artwork and not artwork.startswith('http'): if artwork and not artwork.startswith('http'):
if '/composite/' in artwork: if '/composite/' in artwork:
# e.g. Plex collections where artwork already contains width try:
# and height. Need to upscale for better resolution # e.g. Plex collections where artwork already contains
artwork, args = artwork.split('?') # width and height. Need to upscale for better resolution
args = dict(parse_qsl(args)) artwork, args = artwork.split('?')
width = int(args.get('width', 400)) args = dict(parse_qsl(args))
height = int(args.get('height', 400)) width = int(args.get('width', 400))
# Adjust to 4k resolution 3,840x2,160 height = int(args.get('height', 400))
scaling = 3840.0 / float(max(width, height)) # Adjust to 4k resolution 3,840x2,160
width = int(scaling * width) scaling = 3840.0 / float(max(width, height))
height = int(scaling * height) width = int(scaling * width)
height = int(scaling * height)
except ValueError:
# e.g. playlists
width = 3840
height = 3840
artwork = '%s?width=%s&height=%s' % (artwork, width, height) artwork = '%s?width=%s&height=%s' % (artwork, width, height)
artwork = ('%s/photo/:/transcode?width=3840&height=3840&' artwork = ('%s/photo/:/transcode?width=3840&height=3840&'
'minSize=1&upscale=0&url=%s' 'minSize=1&upscale=0&url=%s'
@ -864,13 +869,13 @@ class API(object):
# the other artwork will be saved under season and show # the other artwork will be saved under season and show
# EXCEPT if you're constructing a listitem # EXCEPT if you're constructing a listitem
if not full_artwork: if not full_artwork:
art = self._one_artwork('thumb') art = self.one_artwork('thumb')
if art: if art:
artworks['thumb'] = art artworks['thumb'] = art
return artworks return artworks
for kodi_artwork, plex_artwork in \ for kodi_artwork, plex_artwork in \
v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems(): v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems():
art = self._one_artwork(plex_artwork) art = self.one_artwork(plex_artwork)
if art: if art:
artworks[kodi_artwork] = art artworks[kodi_artwork] = art
if not full_artwork: if not full_artwork:
@ -912,24 +917,24 @@ class API(object):
# if self.plex_type() == v.PLEX_TYPE_EPISODE: # if self.plex_type() == v.PLEX_TYPE_EPISODE:
for kodi_artwork, plex_artwork in v.KODI_TO_PLEX_ARTWORK.iteritems(): for kodi_artwork, plex_artwork in v.KODI_TO_PLEX_ARTWORK.iteritems():
art = self._one_artwork(plex_artwork) art = self.one_artwork(plex_artwork)
if art: if art:
artworks[kodi_artwork] = art artworks[kodi_artwork] = art
if self.plex_type() in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ALBUM): if self.plex_type() in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ALBUM):
# Get parent item artwork if the main item is missing artwork # Get parent item artwork if the main item is missing artwork
if 'fanart' not in artworks: if 'fanart' not in artworks:
art = self._one_artwork('parentArt') art = self.one_artwork('parentArt')
if art: if art:
artworks['fanart1'] = art artworks['fanart1'] = art
if 'poster' not in artworks: if 'poster' not in artworks:
art = self._one_artwork('parentThumb') art = self.one_artwork('parentThumb')
if art: if art:
artworks['poster'] = art artworks['poster'] = art
if self.plex_type() in (v.PLEX_TYPE_SONG, if self.plex_type() in (v.PLEX_TYPE_SONG,
v.PLEX_TYPE_ALBUM, v.PLEX_TYPE_ALBUM,
v.PLEX_TYPE_ARTIST): v.PLEX_TYPE_ARTIST):
# need to set poster also as thumb # need to set poster also as thumb
art = self._one_artwork('thumb') art = self.one_artwork('thumb')
if art: if art:
artworks['thumb'] = art artworks['thumb'] = art
return artworks return artworks
@ -1468,7 +1473,10 @@ class API(object):
listitem = self._create_photo_listitem(listitem) listitem = self._create_photo_listitem(listitem)
# Only set the bare minimum of artwork # Only set the bare minimum of artwork
listitem.setArt({'icon': 'DefaultPicture.png', listitem.setArt({'icon': 'DefaultPicture.png',
'fanart': self._one_artwork('thumb')}) 'fanart': self.one_artwork('thumb')})
elif self.plex_type() == v.PLEX_TYPE_SONG:
listitem = self._create_audio_listitem(listitem)
listitem.setArt(self.artwork())
else: else:
listitem = self._create_video_listitem(listitem, listitem = self._create_video_listitem(listitem,
append_show_title, append_show_title,
@ -1591,6 +1599,74 @@ class API(object):
pass pass
return listitem return listitem
def track_number(self):
"""
Returns the song's track number as an int or None if not found
"""
try:
return int(self.item.get('index'))
except TypeError:
pass
def disc_number(self):
"""
Returns the song's disc number as an int or None if not found
"""
try:
return int(self.item.get('parentIndex'))
except TypeError:
pass
def _create_audio_listitem(self, listitem=None):
"""
Use for songs only
Call on a child level of PMS xml response (e.g. in a for loop)
listitem : existing xbmcgui.ListItem to work with
otherwise, a new one is created
Returns XBMC listitem for this PMS library item
"""
if listitem is None:
listitem = ListItem(self.title())
else:
listitem.setLabel(self.title())
listitem.setProperty('IsPlayable', 'true')
userdata = self.userdata()
metadata = {
'mediatype': 'song',
'tracknumber': self.track_number(),
'discnumber': self.track_number(),
'duration': userdata['Runtime'],
'year': self.year(),
# Kodi does not support list of str
'genre': ','.join(self.genre_list()) or None,
'album': self.item.get('parentTitle'),
'artist': self.item.get('originalTitle') or self.grandparent_title(),
'title': self.title(),
'rating': self.audience_rating(),
'playcount': userdata['PlayCount'],
'lastplayed': userdata['LastPlayedDate'],
# lyrics string (On a dark desert highway...)
# userrating integer - range is 1..10
# comment string (This is a great song)
# listeners integer (25614)
# musicbrainztrackid string (cd1de9af-0b71-4503-9f96-9f5efe27923c)
# musicbrainzartistid string (d87e52c5-bb8d-4da8-b941-9f4928627dc8)
# musicbrainzalbumid string (24944755-2f68-3778-974e-f572a9e30108)
# musicbrainzalbumartistid string (d87e52c5-bb8d-4da8-b941-9f4928627dc8)
}
plex_id = self.plex_id()
listitem.setProperty('plexid', plex_id)
if v.KODIVERSION >= 18:
with plexdb.Get_Plex_DB() as plex_db:
kodi_id = plex_db.getItem_byId(plex_id)
if kodi_id:
kodi_id = kodi_id[0]
metadata['dbid'] = kodi_id
listitem.setInfo('music', infoLabels=metadata)
return listitem
def add_video_streams(self, listitem): def add_video_streams(self, listitem):
""" """
Add media stream information to xbmcgui.ListItem Add media stream information to xbmcgui.ListItem

View file

@ -115,9 +115,12 @@ def settings(setting, value=None):
def lang(stringid): def lang(stringid):
""" """
Central string retrieval from strings.po Central string retrieval from strings.po. If not found within PKC,
standard XBMC/Kodi strings are retrieved.
Will return unicode
""" """
return ADDON.getLocalizedString(stringid) return (ADDON.getLocalizedString(stringid) or
xbmc.getLocalizedString(stringid))
def dialog(typus, *args, **kwargs): def dialog(typus, *args, **kwargs):

View file

@ -54,7 +54,8 @@ class VideoNodes(object):
'show': 'tvshows', 'show': 'tvshows',
'photo': 'photos', 'photo': 'photos',
'homevideo': 'homevideos', 'homevideo': 'homevideos',
'musicvideos': 'musicvideos' 'musicvideos': 'musicvideos',
'artist': 'albums'
} }
mediatype = mediatypes[mediatype] mediatype = mediatypes[mediatype]
@ -135,7 +136,8 @@ class VideoNodes(object):
'10': "random", '10': "random",
'11': "recommended", '11': "recommended",
'12': "ondeck", '12': "ondeck",
'13': 'browsefiles' '13': 'browsefiles',
'14': 'playlists'
} }
mediatypes = { mediatypes = {
# label according to nodetype per mediatype # label according to nodetype per mediatype
@ -150,7 +152,8 @@ class VideoNodes(object):
'10': 30227, '10': 30227,
'11': 30230, '11': 30230,
'12': 39500, '12': 39500,
'13': 39702 '13': 39702,
'14': 136
}, },
'tvshows': 'tvshows':
@ -165,7 +168,8 @@ class VideoNodes(object):
'10': 30227, '10': 30227,
# '11': 30230, # '11': 30230,
'12': 39500, '12': 39500,
'13': 39702 '13': 39702,
'14': 136
}, },
'homevideos': 'homevideos':
@ -173,7 +177,8 @@ class VideoNodes(object):
'1': tagname, '1': tagname,
'2': 30251, '2': 30251,
'11': 30253, '11': 30253,
'13': 39702 '13': 39702,
'14': 136
}, },
'photos': 'photos':
@ -192,6 +197,15 @@ class VideoNodes(object):
'4': 30257, '4': 30257,
'6': 30258, '6': 30258,
'13': 39702 '13': 39702
},
'albums':
{
'1': tagname,
'2': 517, # Recently played albums
'2': 359, # Recently added albums
'13': 39702, # browse by folder
'14': 136 # Playlists
} }
} }
@ -209,7 +223,8 @@ class VideoNodes(object):
'10': '8', # "random", '10': '8', # "random",
'11': '5', # "recommended", '11': '5', # "recommended",
'12': '1', # "ondeck" '12': '1', # "ondeck"
'13': '9' # browse by folder '13': '9', # browse by folder
'14': '10' # Playlists
} }
nodes = mediatypes[mediatype] nodes = mediatypes[mediatype]
@ -221,8 +236,6 @@ class VideoNodes(object):
stringid = nodes[node] stringid = nodes[node]
if node != "1": if node != "1":
label = utils.lang(stringid) label = utils.lang(stringid)
if not label:
label = xbmc.getLocalizedString(stringid)
else: else:
label = stringid label = stringid
@ -257,6 +270,12 @@ class VideoNodes(object):
nodetype = 'inprogress' nodetype = 'inprogress'
elif nodetype == 'browsefiles': elif nodetype == 'browsefiles':
path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid
elif nodetype == 'playlists':
path = 'plugin://plugin.video.plexkodiconnect?mode=playlists'
if mediatype in ('movies', 'tvshows', 'homevideos'):
path += '&type=%s' % v.PLEX_TYPE_VIDEO_PLAYLIST
else:
path += '&type=%s' % v.PLEX_TYPE_AUDIO_PLAYLIST
else: else:
path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype) path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype)
@ -298,23 +317,33 @@ class VideoNodes(object):
continue continue
# Create the root # Create the root
if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes', 'browsefiles') or mediatype == "homevideos"): if (nodetype in ("nextepisodes",
"ondeck",
'recentepisodes',
'browsefiles',
'playlists') or mediatype == "homevideos"):
# Folder type with plugin path # Folder type with plugin path
root = self.commonRoot(order=sortorder[node], root = self.commonRoot(order=sortorder[node],
label=label, label=label,
tagname=tagname, tagname=tagname,
roottype=2) roottype=2)
etree.SubElement(root, 'path').text = path
etree.SubElement(root, 'content').text = "episodes"
else: else:
root = self.commonRoot(order=sortorder[node], root = self.commonRoot(order=sortorder[node],
label=label, label=label,
tagname=tagname) tagname=tagname)
if nodetype in ('recentepisodes', 'inprogressepisodes'): # Set the content type
etree.SubElement(root, 'content').text = "episodes" if mediatype == 'tvshows':
else: etree.SubElement(root, 'content').text = 'episodes'
etree.SubElement(root, 'content').text = mediatype else:
etree.SubElement(root, 'content').text = mediatype
# Now fill the view
if (nodetype in ("nextepisodes",
"ondeck",
'recentepisodes',
'browsefiles',
'playlists') or mediatype == "homevideos"):
etree.SubElement(root, 'path').text = path
else:
# Elements per nodetype # Elements per nodetype
if nodetype == "all": if nodetype == "all":
etree.SubElement(root, etree.SubElement(root,