2019-06-11 05:29:42 +10:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import absolute_import, division, unicode_literals
|
|
|
|
from logging import getLogger
|
|
|
|
|
|
|
|
from ..kodi_db import KodiVideoDB, KodiMusicDB
|
|
|
|
from ..downloadutils import DownloadUtils as DU
|
|
|
|
from .. import utils, variables as v, app
|
|
|
|
|
2020-12-19 01:35:20 +11:00
|
|
|
from . import fanart_lookup
|
|
|
|
|
2019-06-11 05:29:42 +10:00
|
|
|
LOG = getLogger('PLEX.api')
|
|
|
|
|
|
|
|
|
|
|
|
class Artwork(object):
|
|
|
|
def one_artwork(self, art_kind, aspect=None):
|
|
|
|
"""
|
|
|
|
aspect can be: 'square', '16:9', 'poster'. Defaults to 'poster'
|
|
|
|
"""
|
|
|
|
aspect = 'poster' if not aspect else aspect
|
|
|
|
if aspect == 'poster':
|
|
|
|
width = 1000
|
|
|
|
height = 1500
|
|
|
|
elif aspect == '16:9':
|
|
|
|
width = 1920
|
|
|
|
height = 1080
|
|
|
|
elif aspect == 'square':
|
|
|
|
width = 1000
|
|
|
|
height = 1000
|
|
|
|
else:
|
|
|
|
raise NotImplementedError('aspect ratio not yet implemented: %s'
|
|
|
|
% aspect)
|
|
|
|
artwork = self.xml.get(art_kind)
|
|
|
|
if not artwork or artwork.startswith('http'):
|
|
|
|
return artwork
|
|
|
|
if '/composite/' in artwork:
|
|
|
|
try:
|
|
|
|
# e.g. Plex collections where artwork already contains width and
|
|
|
|
# height. Need to upscale for better resolution
|
|
|
|
artwork, args = artwork.split('?')
|
|
|
|
args = dict(utils.parse_qsl(args))
|
|
|
|
width = int(args.get('width', 400))
|
|
|
|
height = int(args.get('height', 400))
|
|
|
|
# Adjust to 4k resolution 1920x1080
|
|
|
|
scaling = 1920.0 / float(max(width, height))
|
|
|
|
width = int(scaling * width)
|
|
|
|
height = int(scaling * height)
|
|
|
|
except ValueError:
|
|
|
|
# e.g. playlists
|
|
|
|
pass
|
|
|
|
artwork = '%s?width=%s&height=%s' % (artwork, width, height)
|
|
|
|
artwork = ('%s/photo/:/transcode?width=1920&height=1920&'
|
|
|
|
'minSize=1&upscale=0&url=%s'
|
|
|
|
% (app.CONN.server, utils.quote(artwork)))
|
|
|
|
artwork = self.attach_plex_token_to_url(artwork)
|
|
|
|
return artwork
|
|
|
|
|
|
|
|
def artwork_episode(self, full_artwork):
|
|
|
|
"""
|
|
|
|
Episodes are special, they only get the thumb, because all the other
|
|
|
|
artwork will be saved under season and show EXCEPT if you're
|
|
|
|
constructing a listitem and the item has NOT been synched to the Kodi db
|
|
|
|
"""
|
|
|
|
artworks = {}
|
|
|
|
# Item is currently NOT in the Kodi DB
|
|
|
|
art = self.one_artwork('thumb')
|
|
|
|
if art:
|
|
|
|
artworks['thumb'] = art
|
|
|
|
if not full_artwork:
|
|
|
|
# For episodes, only get the thumb. Everything else stemms from
|
|
|
|
# either the season or the show
|
|
|
|
return artworks
|
|
|
|
for kodi_artwork, plex_artwork in \
|
|
|
|
v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems():
|
|
|
|
art = self.one_artwork(plex_artwork)
|
|
|
|
if art:
|
|
|
|
artworks[kodi_artwork] = art
|
|
|
|
return artworks
|
|
|
|
|
|
|
|
def artwork(self, kodi_id=None, kodi_type=None, full_artwork=False):
|
|
|
|
"""
|
|
|
|
Gets the URLs to the Plex artwork. Dict keys will be missing if there
|
|
|
|
is no corresponding artwork.
|
|
|
|
Pass kodi_id and kodi_type to grab the artwork saved in the Kodi DB
|
|
|
|
(thus potentially more artwork, e.g. clearart, discart).
|
|
|
|
|
|
|
|
Output ('max' version)
|
|
|
|
{
|
|
|
|
'thumb'
|
|
|
|
'poster'
|
|
|
|
'banner'
|
|
|
|
'clearart'
|
|
|
|
'clearlogo'
|
|
|
|
'fanart'
|
|
|
|
}
|
|
|
|
'landscape' and 'icon' might be implemented later
|
|
|
|
Passing full_artwork=True returns ALL the artwork for the item, so not
|
|
|
|
just 'thumb' for episodes, but also season and show artwork
|
|
|
|
"""
|
|
|
|
if self.plex_type == v.PLEX_TYPE_EPISODE:
|
|
|
|
return self.artwork_episode(full_artwork)
|
|
|
|
artworks = {}
|
|
|
|
if kodi_id:
|
|
|
|
# in Kodi database, potentially with additional e.g. clearart
|
|
|
|
if self.plex_type in v.PLEX_VIDEOTYPES:
|
|
|
|
with KodiVideoDB(lock=False) as kodidb:
|
|
|
|
return kodidb.get_art(kodi_id, kodi_type)
|
|
|
|
else:
|
|
|
|
with KodiMusicDB(lock=False) as kodidb:
|
|
|
|
return kodidb.get_art(kodi_id, kodi_type)
|
|
|
|
|
|
|
|
for kodi_artwork, plex_artwork in v.KODI_TO_PLEX_ARTWORK.iteritems():
|
|
|
|
art = self.one_artwork(plex_artwork)
|
|
|
|
if art:
|
|
|
|
artworks[kodi_artwork] = art
|
|
|
|
if self.plex_type in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ALBUM):
|
|
|
|
# Get parent item artwork if the main item is missing artwork
|
|
|
|
if 'fanart' not in artworks:
|
|
|
|
art = self.one_artwork('parentArt')
|
|
|
|
if art:
|
|
|
|
artworks['fanart1'] = art
|
|
|
|
if 'poster' not in artworks:
|
|
|
|
art = self.one_artwork('parentThumb')
|
|
|
|
if art:
|
|
|
|
artworks['poster'] = art
|
|
|
|
if self.plex_type in (v.PLEX_TYPE_SONG,
|
|
|
|
v.PLEX_TYPE_ALBUM,
|
|
|
|
v.PLEX_TYPE_ARTIST):
|
|
|
|
# need to set poster also as thumb
|
|
|
|
art = self.one_artwork('thumb')
|
|
|
|
if art:
|
|
|
|
artworks['thumb'] = art
|
|
|
|
if self.plex_type == v.PLEX_TYPE_PLAYLIST:
|
|
|
|
art = self.one_artwork('composite')
|
|
|
|
if art:
|
|
|
|
artworks['thumb'] = art
|
|
|
|
return artworks
|
|
|
|
|
|
|
|
def fanart_artwork(self, artworks):
|
|
|
|
"""
|
|
|
|
Downloads additional fanart from third party sources (well, link to
|
|
|
|
fanart only).
|
|
|
|
"""
|
|
|
|
external_id = self.retrieve_external_item_id()
|
|
|
|
if external_id is not None:
|
|
|
|
artworks = self.lookup_fanart_tv(external_id[0], artworks)
|
|
|
|
return artworks
|
|
|
|
|
|
|
|
def set_artwork(self):
|
|
|
|
"""
|
|
|
|
Gets the URLs to the Plex artwork, or empty string if not found.
|
|
|
|
Only call on movies!
|
|
|
|
"""
|
|
|
|
artworks = {}
|
|
|
|
# Plex does not get much artwork - go ahead and get the rest from
|
|
|
|
# fanart tv only for movie or tv show
|
|
|
|
external_id = self.retrieve_external_item_id(collection=True)
|
|
|
|
if external_id is not None:
|
|
|
|
external_id, poster, background = external_id
|
|
|
|
if poster is not None:
|
|
|
|
artworks['poster'] = poster
|
|
|
|
if background is not None:
|
|
|
|
artworks['fanart'] = background
|
|
|
|
artworks = self.lookup_fanart_tv(external_id, artworks)
|
|
|
|
else:
|
|
|
|
LOG.info('Did not find a set/collection ID on TheMovieDB using %s.'
|
|
|
|
' Artwork will be missing.', self.title())
|
|
|
|
return artworks
|
|
|
|
|
|
|
|
def retrieve_external_item_id(self, collection=False):
|
|
|
|
"""
|
|
|
|
Returns the set
|
|
|
|
media_id [unicode]: the item's IMDB id for movies or tvdb id for
|
|
|
|
TV shows
|
|
|
|
poster [unicode]: path to the item's poster artwork
|
|
|
|
background [unicode]: path to the item's background artwork
|
|
|
|
|
|
|
|
The last two might be None if not found. Generally None is returned
|
|
|
|
if unsuccessful.
|
|
|
|
|
|
|
|
If not found in item's Plex metadata, check themovidedb.org.
|
|
|
|
"""
|
|
|
|
item = self.xml.attrib
|
|
|
|
media_type = self.plex_type
|
|
|
|
media_id = None
|
|
|
|
# Return the saved Plex id's, if applicable
|
|
|
|
# Always seek collection's ids since not provided by PMS
|
|
|
|
if collection is False:
|
|
|
|
if media_type == v.PLEX_TYPE_MOVIE:
|
2020-11-06 01:47:04 +11:00
|
|
|
media_id = self.guids.get('imdb')
|
2019-06-11 05:29:42 +10:00
|
|
|
elif media_type == v.PLEX_TYPE_SHOW:
|
2020-11-06 01:47:04 +11:00
|
|
|
media_id = self.guids.get('tvdb')
|
2019-06-11 05:29:42 +10:00
|
|
|
if media_id is not None:
|
|
|
|
return media_id, None, None
|
|
|
|
LOG.info('Plex did not provide ID for IMDB or TVDB. Start '
|
|
|
|
'lookup process')
|
|
|
|
else:
|
|
|
|
LOG.debug('Start movie set/collection lookup on themoviedb with %s',
|
|
|
|
item.get('title', ''))
|
2020-12-19 01:35:20 +11:00
|
|
|
return fanart_lookup.external_item_id(self.title(),
|
|
|
|
self.year(),
|
|
|
|
self.plex_type,
|
|
|
|
collection)
|
2019-06-11 05:29:42 +10:00
|
|
|
|
|
|
|
def lookup_fanart_tv(self, media_id, artworks):
|
|
|
|
"""
|
|
|
|
perform artwork lookup on fanart.tv
|
|
|
|
|
|
|
|
media_id: IMDB id for movies, tvdb id for TV shows
|
|
|
|
"""
|
|
|
|
api_key = utils.settings('FanArtTVAPIKey')
|
|
|
|
typus = self.plex_type
|
|
|
|
if typus == v.PLEX_TYPE_SHOW:
|
|
|
|
typus = 'tv'
|
|
|
|
|
|
|
|
if typus == v.PLEX_TYPE_MOVIE:
|
|
|
|
url = 'http://webservice.fanart.tv/v3/movies/%s?api_key=%s' \
|
|
|
|
% (media_id, api_key)
|
|
|
|
elif typus == 'tv':
|
|
|
|
url = 'http://webservice.fanart.tv/v3/tv/%s?api_key=%s' \
|
|
|
|
% (media_id, api_key)
|
|
|
|
else:
|
|
|
|
# Not supported artwork
|
|
|
|
return artworks
|
2021-09-25 00:24:57 +10:00
|
|
|
data = DU().downloadUrl(url,
|
|
|
|
authenticate=False,
|
|
|
|
timeout=15,
|
|
|
|
return_response=True)
|
|
|
|
if not data.ok:
|
|
|
|
LOG.debug('Could not download data from FanartTV')
|
2019-06-11 05:29:42 +10:00
|
|
|
return artworks
|
2021-09-25 00:24:57 +10:00
|
|
|
data = data.json()
|
2019-06-11 05:29:42 +10:00
|
|
|
|
|
|
|
fanart_tv_types = list(v.FANART_TV_TO_KODI_TYPE)
|
|
|
|
|
|
|
|
if typus == v.PLEX_TYPE_ARTIST:
|
|
|
|
fanart_tv_types.append(("thumb", "folder"))
|
|
|
|
else:
|
|
|
|
fanart_tv_types.append(("thumb", "thumb"))
|
|
|
|
|
|
|
|
prefixes = (
|
|
|
|
"hd" + typus,
|
|
|
|
"hd",
|
|
|
|
typus,
|
|
|
|
"",
|
|
|
|
)
|
|
|
|
for fanart_tv_type, kodi_type in fanart_tv_types:
|
|
|
|
# Skip the ones we already have
|
|
|
|
if kodi_type in artworks:
|
|
|
|
continue
|
|
|
|
for prefix in prefixes:
|
|
|
|
fanarttvimage = prefix + fanart_tv_type
|
|
|
|
if fanarttvimage not in data:
|
|
|
|
continue
|
|
|
|
# select image in preferred language
|
|
|
|
for entry in data[fanarttvimage]:
|
|
|
|
if entry.get("lang") == v.KODILANGUAGE:
|
|
|
|
artworks[kodi_type] = \
|
|
|
|
entry.get("url", "").replace(' ', '%20')
|
|
|
|
break
|
|
|
|
# just grab the first english OR undefinded one as fallback
|
|
|
|
# (so we're actually grabbing the more popular one)
|
|
|
|
if kodi_type not in artworks:
|
|
|
|
for entry in data[fanarttvimage]:
|
|
|
|
if entry.get("lang") in ("en", "00"):
|
|
|
|
artworks[kodi_type] = \
|
|
|
|
entry.get("url", "").replace(' ', '%20')
|
|
|
|
break
|
|
|
|
|
|
|
|
# grab extrafanarts in list
|
|
|
|
fanartcount = 1 if 'fanart' in artworks else ''
|
|
|
|
for prefix in prefixes:
|
|
|
|
fanarttvimage = prefix + 'background'
|
|
|
|
if fanarttvimage not in data:
|
|
|
|
continue
|
|
|
|
for entry in data[fanarttvimage]:
|
|
|
|
if entry.get("url") is None:
|
|
|
|
continue
|
|
|
|
artworks['fanart%s' % fanartcount] = \
|
|
|
|
entry['url'].replace(' ', '%20')
|
|
|
|
try:
|
|
|
|
fanartcount += 1
|
|
|
|
except TypeError:
|
|
|
|
fanartcount = 1
|
|
|
|
if fanartcount >= v.MAX_BACKGROUND_COUNT:
|
|
|
|
break
|
|
|
|
return artworks
|