diff --git a/README.md b/README.md index 498f80e9..53854fde 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![stable version](https://img.shields.io/badge/stable_version-1.8.0-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip) -[![beta version](https://img.shields.io/badge/beta_version-1.8.0-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip) +[![stable version](https://img.shields.io/badge/stable_version-1.8.1-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip) +[![beta version](https://img.shields.io/badge/beta_version-1.8.1-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.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) diff --git a/addon.xml b/addon.xml index 04fbf2a8..f24edd75 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -44,7 +44,13 @@ Gebruik op eigen risico 使用風險由您自己承擔 Usar a su propio riesgo - version 1.8.0 + version 1.8.1: +- Fix library sync crash due to UnicodeDecodeError +- Fix fanart for collections +- Comply with themoviedb.org terms of use +- Add some translations + +version 1.8.0 Featuring: - Major music overhaul: Direct Paths should now work! Many thanks @Memesa for the pointers! Don't forget to reset your database - Big transcoding overhaul diff --git a/changelog.txt b/changelog.txt index 5b7d3fd1..8f7b807d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +version 1.8.1: +- Fix library sync crash due to UnicodeDecodeError +- Fix fanart for collections +- Comply with themoviedb.org terms of use +- Add some translations + version 1.8.0 Featuring: - Major music overhaul: Direct Paths should now work! Many thanks @Memesa for the pointers! Don't forget to reset your database diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 73e05ece..6a5d051c 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -1943,4 +1943,14 @@ msgstr "" # Shown once on first installation to comply with the terms of use of themoviedb.org msgctxt "#39717" msgid "PKC uses free additional artwork from www.themoviedb.org. Many thanks!" -msgstr "" \ No newline at end of file +msgstr "" + +# Shown during very first PKC setup only +msgctxt "#39718" +msgid "Do you want to replace your custom user ratings with an indicator of how many versions of a media item you posses?" +msgstr "" + +# In PKC Settings under Sync +msgctxt "#39719" +msgid "Replace user ratings with number of media versions" +msgstr "" diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 670bba42..03f360f6 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -1288,10 +1288,17 @@ class API(): except (KeyError, ValueError): lastPlayedDate = None - try: - userrating = int(float(item['userRating'])) - except (KeyError, ValueError): + if state.INDICATE_MEDIA_VERSIONS is True: userrating = 0 + for entry in self.item.findall('./Media'): + userrating += 1 + # Don't show a value of '1' + userrating = 0 if userrating == 1 else userrating + else: + try: + userrating = int(float(item['userRating'])) + except (KeyError, ValueError): + userrating = 0 try: rating = float(item['audienceRating']) @@ -1881,7 +1888,8 @@ class API(): If not found in item's Plex metadata, check themovidedb.org - collection=True will try to return the collection's ID + collection=True will try to return the three-tuple: + collection ID, poster-path, background-path None is returned if unsuccessful """ @@ -1900,7 +1908,8 @@ class API(): log.info('Plex did not provide ID for IMDB or TVDB. Start ' 'lookup process') else: - log.info('Start movie set/collection lookup on themoviedb') + log.info('Start movie set/collection lookup on themoviedb using %s' + % item.get('title', '')) apiKey = settings('themoviedbAPIKey') if media_type == v.PLEX_TYPE_SHOW: @@ -1909,7 +1918,7 @@ class API(): # if the title has the year in remove it as tmdb cannot deal with it... # replace e.g. 'The Americans (2015)' with 'The Americans' title = sub(r'\s*\(\d{4}\)$', '', title, count=1) - url = 'http://api.themoviedb.org/3/search/%s' % media_type + url = 'https://api.themoviedb.org/3/search/%s' % media_type parameters = { 'api_key': apiKey, 'language': v.KODILANGUAGE, @@ -2001,10 +2010,10 @@ class API(): for language in [v.KODILANGUAGE, "en"]: parameters['language'] = language if media_type == "movie": - url = 'http://api.themoviedb.org/3/movie/%s' % tmdbId + url = 'https://api.themoviedb.org/3/movie/%s' % tmdbId parameters['append_to_response'] = 'videos' elif media_type == "tv": - url = 'http://api.themoviedb.org/3/tv/%s' % tmdbId + url = 'https://api.themoviedb.org/3/tv/%s' % tmdbId parameters['append_to_response'] = 'external_ids,videos' data = DownloadUtils().downloadUrl( url, @@ -2025,9 +2034,28 @@ class API(): mediaId = str(data["external_ids"].get("tvdb_id")) break else: - if data.get("belongs_to_collection") is not None: - mediaId = str(data.get("belongs_to_collection").get("id")) - log.debug('Retrieved collections tmdb id %s' % mediaId) + if data.get("belongs_to_collection") is None: + continue + mediaId = str(data.get("belongs_to_collection").get("id")) + log.debug('Retrieved collections tmdb id %s for %s' + % (mediaId, title)) + url = 'https://api.themoviedb.org/3/collection/%s' % mediaId + data = DownloadUtils().downloadUrl( + url, + authenticate=False, + parameters=parameters, + timeout=7) + try: + data.get('poster_path') + except AttributeError: + log.info('Could not find TheMovieDB poster paths for %s in' + 'the language %s' % (title, language)) + continue + else: + poster = 'https://image.tmdb.org/t/p/original%s' % data.get('poster_path') + background = 'https://image.tmdb.org/t/p/original%s' % data.get('backdrop_path') + mediaId = mediaId, poster, background + break return mediaId def getFanartTVArt(self, mediaId, allartworks, setInfo=False): @@ -2158,7 +2186,18 @@ class API(): # fanart tv only for movie or tv show externalId = self.getExternalItemId(collection=True) if externalId is not None: + try: + externalId, poster, background = externalId + except TypeError: + poster, background = None, None + if poster is not None: + allartworks['Primary'] = poster + if background is not None: + allartworks['Backdrop'].append(background) allartworks = self.getFanartTVArt(externalId, allartworks, True) + else: + log.info('Did not find a set/collection ID on TheMovieDB using %s.' + ' Artwork will be missing.' % self.getTitle()[0]) return allartworks def shouldStream(self): diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py index a16bf50e..9cf71bf5 100644 --- a/resources/lib/PlexCompanion.py +++ b/resources/lib/PlexCompanion.py @@ -12,6 +12,7 @@ from plexbmchelper import listener, plexgdm, subscribers, functions, \ httppersist, plexsettings from PlexFunctions import ParseContainerKey, GetPlexMetadata from PlexAPI import API +from playlist_func import get_pms_playqueue, get_plextype_from_xml import player import variables as v import state @@ -149,6 +150,26 @@ class PlexCompanion(Thread): offset=data.get('offset')) playqueue.plex_transient_token = token + elif task['action'] == 'refreshPlayQueue': + # example data: {'playQueueID': '8475', 'commandID': '11'} + xml = get_pms_playqueue(data['playQueueID']) + if xml is None: + return + if len(xml) == 0: + log.debug('Empty playqueue received - clearing playqueue') + plex_type = get_plextype_from_xml(xml) + if plex_type is None: + return + playqueue = self.mgr.playqueue.get_playqueue_from_type( + v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]) + playqueue.clear() + return + playqueue = self.mgr.playqueue.get_playqueue_from_type( + v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']]) + self.mgr.playqueue.update_playqueue_from_PMS( + playqueue, + data['playQueueID']) + def run(self): # Ensure that sockets will be closed no matter what try: diff --git a/resources/lib/companion.py b/resources/lib/companion.py index 9733acce..7608c920 100644 --- a/resources/lib/companion.py +++ b/resources/lib/companion.py @@ -110,11 +110,19 @@ def process_command(request_path, params, queue=None): 'data': params }) + elif request_path == 'player/playback/refreshPlayQueue': + queue.put({ + 'action': 'refreshPlayQueue', + 'data': params + }) + elif request_path == "player/playback/setParameters": if 'volume' in params: volume = int(params['volume']) log.debug("Adjusting the volume to %s" % volume) JSONRPC('Application.SetVolume').execute({"volume": volume}) + else: + log.error('Unknown parameters: %s' % params) elif request_path == "player/playback/play": for playerid in getPlayerIds(): diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 0c1d83ad..f29afa72 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -488,10 +488,16 @@ class InitialSetup(): if dialog.yesno(heading=lang(29999), line1=lang(39061)): log.debug("User opted to use FanArtTV") settings('FanartTV', value="true") + # Do you want to replace your custom user ratings with an indicator of + # how many versions of a media item you posses? + if dialog.yesno(heading=lang(29999), line1=lang(39718)): + log.debug("User opted to replace user ratings with version number") + settings('indicate_media_versions', value="true") # If you use several Plex libraries of one kind, e.g. "Kids Movies" and # "Parents Movies", be sure to check https://goo.gl/JFtQV9 dialog.ok(heading=lang(29999), line1=lang(39076)) + # Need to tell about our image source for collections: themoviedb.org dialog.ok(heading=lang(29999), line1=lang(39717)) # Make sure that we only ask these questions upon first installation diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py index f1371acd..61ab9ac3 100644 --- a/resources/lib/playlist_func.py +++ b/resources/lib/playlist_func.py @@ -1,16 +1,19 @@ import logging from urllib import quote from urlparse import parse_qsl, urlsplit +from re import compile as re_compile import plexdb_functions as plexdb from downloadutils import DownloadUtils as DU from utils import JSONRPC, tryEncode, escape_html from PlexAPI import API +from PlexFunctions import GetPlexMetadata ############################################################################### log = logging.getLogger("PLEX."+__name__) +REGEX = re_compile(r'''metadata%2F(\d+)''') ############################################################################### # kodi_item dict: @@ -63,9 +66,6 @@ class Playlist_Object_Baseclase(object): self.plex_transient_token = None log.debug('Playlist cleared: %s' % self) - def log_Kodi_playlist(self): - log.debug('Current Kodi playlist: %s' % get_kodi_playlist_items(self)) - class Playlist_Object(Playlist_Object_Baseclase): kind = 'playList' @@ -566,3 +566,39 @@ def remove_from_Kodi_playlist(playlist, pos): del playlist.items[pos] except IndexError: log.error('Cannot delete position %s for %s' % (pos, playlist)) + + +def get_pms_playqueue(playqueue_id): + """ + Returns the Plex playqueue as an etree XML or None if unsuccessful + """ + xml = DU().downloadUrl( + "{server}/playQueues/%s" % playqueue_id, + headerOptions={'Accept': 'application/xml'}) + try: + xml.attrib + except AttributeError: + log.error('Could not download Plex playqueue %s' % playqueue_id) + xml = None + return xml + + +def get_plextype_from_xml(xml): + """ + Needed if PMS returns an empty playqueue. Will get the Plex type from the + empty playlist playQueueSourceURI. Feed with (empty) etree xml + + returns None if unsuccessful + """ + try: + plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0] + except IndexError: + log.error('Could not get plex_id from xml: %s' % xml.attrib) + return + new_xml = GetPlexMetadata(plex_id) + try: + new_xml[0].attrib + except (TypeError, IndexError, AttributeError): + log.error('Could not get plex metadata for plex id %s' % plex_id) + return + return new_xml[0].attrib.get('type') diff --git a/resources/lib/state.py b/resources/lib/state.py index 865f4556..b364f749 100644 --- a/resources/lib/state.py +++ b/resources/lib/state.py @@ -22,6 +22,8 @@ RESTRICTED_USER = False # Direct Paths (True) or Addon Paths (False)? Along with # window('useDirectPaths') DIRECT_PATHS = False +# Shall we replace custom user ratings with the number of versions available? +INDICATE_MEDIA_VERSIONS = False # Along with window('plex_authenticated') AUTHENTICATED = False diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 587bf1c7..f9671263 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -165,6 +165,8 @@ class UserClient(threading.Thread): if settings('useDirectPaths') == "1" else 'false') state.DIRECT_PATHS = True if settings('useDirectPaths') == "1" \ else False + state.INDICATE_MEDIA_VERSIONS = True \ + if settings('indicate_media_versions') == "true" else False window('plex_force_transcode_pix', value='true' if settings('force_transcode_pix') == "1" else 'false') diff --git a/resources/settings.xml b/resources/settings.xml index 8ff636c0..52865be1 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -59,6 +59,7 @@ +