From 25d80521c7f4aff4cab0fc5d2e1e897f4053f96e Mon Sep 17 00:00:00 2001 From: croneter Date: Tue, 24 Jul 2018 21:04:31 +0200 Subject: [PATCH] Enable Kodi libraries for Plex Music libraries --- resources/lib/librarysync.py | 110 ++++++++++++++++----------------- resources/lib/playback.py | 21 ++++--- resources/lib/playlist_func.py | 3 +- resources/lib/plex_api.py | 71 +++++++++++++++++++++ resources/lib/videonodes.py | 32 +++++++--- 5 files changed, 164 insertions(+), 73 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 653d9cae..cc23ade0 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -329,8 +329,7 @@ class LibrarySync(Thread): utils.playlist_xsp(mediatype, foldername, folderid, viewtype) lists.append(foldername) # Create the video node - if (foldername not in nodes and - mediatype != v.PLEX_TYPE_ARTIST): + if foldername not in nodes: vnodes.viewNode(sorted_views.index(foldername), foldername, mediatype, @@ -362,42 +361,41 @@ class LibrarySync(Thread): # Update view with new info plex_db.updateView(foldername, tagid, folderid) - if mediatype != "artist": - if plex_db.getView_byName(current_viewname) is None: - # The tag could be a combined view. Ensure there's - # no other tags with the same name before deleting - # playlist. - utils.playlist_xsp(mediatype, - current_viewname, - folderid, - current_viewtype, - True) - # Delete video node - if mediatype != "musicvideos": - vnodes.viewNode( - indexnumber=sorted_views.index(foldername), - tagname=current_viewname, - mediatype=mediatype, - viewtype=current_viewtype, - viewid=folderid, - delete=True) - # Added new playlist - if (foldername not in lists and mediatype in - (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): - utils.playlist_xsp(mediatype, - foldername, - folderid, - viewtype) - lists.append(foldername) - # Add new video node - if foldername not in nodes and mediatype != "musicvideos": - vnodes.viewNode(sorted_views.index(foldername), - foldername, - mediatype, - viewtype, - folderid) - nodes.append(foldername) - totalnodes += 1 + if plex_db.getView_byName(current_viewname) is None: + # The tag could be a combined view. Ensure there's + # no other tags with the same name before deleting + # playlist. + utils.playlist_xsp(mediatype, + current_viewname, + folderid, + current_viewtype, + True) + # Delete video node + if mediatype != "musicvideos": + vnodes.viewNode( + indexnumber=sorted_views.index(foldername), + tagname=current_viewname, + mediatype=mediatype, + viewtype=current_viewtype, + viewid=folderid, + delete=True) + # Added new playlist + if (foldername not in lists and mediatype in + (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): + utils.playlist_xsp(mediatype, + foldername, + folderid, + viewtype) + lists.append(foldername) + # Add new video node + if foldername not in nodes and mediatype != "musicvideos": + vnodes.viewNode(sorted_views.index(foldername), + foldername, + mediatype, + viewtype, + folderid) + nodes.append(foldername) + totalnodes += 1 # Update items with new tag items = plex_db.getItem_byView(folderid) @@ -407,23 +405,22 @@ class LibrarySync(Thread): current_tagid, tagid, item[0], current_viewtype[:-1]) else: # Validate the playlist exists or recreate it - if mediatype != v.PLEX_TYPE_ARTIST: - if (foldername not in lists and mediatype in - (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): - utils.playlist_xsp(mediatype, - foldername, - folderid, - viewtype) - lists.append(foldername) - # Create the video node if not already exists - if foldername not in nodes and mediatype != "musicvideos": - vnodes.viewNode(sorted_views.index(foldername), - foldername, - mediatype, - viewtype, - folderid) - nodes.append(foldername) - totalnodes += 1 + if (foldername not in lists and mediatype in + (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): + utils.playlist_xsp(mediatype, + foldername, + folderid, + viewtype) + lists.append(foldername) + # Create the video node if not already exists + if foldername not in nodes and mediatype != "musicvideos": + vnodes.viewNode(sorted_views.index(foldername), + foldername, + mediatype, + viewtype, + folderid) + nodes.append(foldername) + totalnodes += 1 return totalnodes def maintain_views(self): @@ -454,7 +451,8 @@ class LibrarySync(Thread): for view in sections: 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']) LOG.debug('Sorted views: %s', self.sorted_views) diff --git a/resources/lib/playback.py b/resources/lib/playback.py index 922e81a9..14c87c14 100644 --- a/resources/lib/playback.py +++ b/resources/lib/playback.py @@ -69,14 +69,19 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True): try: pos = js.get_position(playqueue.playlistid) except KeyError: - LOG.error('Still no position - abort') - # "Play error" - utils.dialog('notification', - utils.lang(29999), - utils.lang(30128), - icon='{error}') - _ensure_resolve(abort=True) - return + LOG.info('Assuming video instead of audio playlist playback') + playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_VIDEO) + try: + pos = js.get_position(playqueue.playlistid) + except KeyError: + LOG.error('Still no position - abort') + # "Play error" + 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 items = js.playlist_get_items(playqueue.playlistid) try: diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py index d2f1105d..98900242 100644 --- a/resources/lib/playlist_func.py +++ b/resources/lib/playlist_func.py @@ -313,7 +313,8 @@ def verify_kodi_item(plex_id, kodi_item): LOG.debug('Detected song. Research results: %s', kodi_item) return kodi_item # Need more info since we don't have kodi_id nor type. Use file path. - if (kodi_item['file'].startswith('plugin') or + 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 diff --git a/resources/lib/plex_api.py b/resources/lib/plex_api.py index 7d13b7df..f40f0e6f 100644 --- a/resources/lib/plex_api.py +++ b/resources/lib/plex_api.py @@ -1474,6 +1474,9 @@ class API(object): # Only set the bare minimum of artwork listitem.setArt({'icon': 'DefaultPicture.png', 'fanart': self.one_artwork('thumb')}) + elif self.plex_type() == v.PLEX_TYPE_SONG: + listitem = self._create_audio_listitem(listitem) + listitem.setArt(self.artwork()) else: listitem = self._create_video_listitem(listitem, append_show_title, @@ -1596,6 +1599,74 @@ class API(object): pass 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): """ Add media stream information to xbmcgui.ListItem diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index c2bb9b67..9f2ab613 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -54,7 +54,8 @@ class VideoNodes(object): 'show': 'tvshows', 'photo': 'photos', 'homevideo': 'homevideos', - 'musicvideos': 'musicvideos' + 'musicvideos': 'musicvideos', + 'artist': 'albums' } mediatype = mediatypes[mediatype] @@ -196,6 +197,15 @@ class VideoNodes(object): '4': 30257, '6': 30258, '13': 39702 + }, + + 'albums': + { + '1': tagname, + '2': 517, # Recently played albums + '2': 359, # Recently added albums + '13': 39702, # browse by folder + '14': 136 # Playlists } } @@ -317,17 +327,23 @@ class VideoNodes(object): label=label, tagname=tagname, roottype=2) - etree.SubElement(root, 'path').text = path - etree.SubElement(root, 'content').text = "episodes" else: root = self.commonRoot(order=sortorder[node], label=label, tagname=tagname) - if nodetype in ('recentepisodes', 'inprogressepisodes'): - etree.SubElement(root, 'content').text = "episodes" - else: - etree.SubElement(root, 'content').text = mediatype - + # Set the content type + if mediatype == 'tvshows': + etree.SubElement(root, 'content').text = 'episodes' + 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 if nodetype == "all": etree.SubElement(root,