PlexKodiConnect/resources/lib/plex_api/artwork.py
2021-09-24 16:59:56 +02:00

287 lines
11 KiB
Python

#!/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
from . import fanart_lookup
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:
media_id = self.guids.get('imdb')
elif media_type == v.PLEX_TYPE_SHOW:
media_id = self.guids.get('tvdb')
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', ''))
return fanart_lookup.external_item_id(self.title(),
self.year(),
self.plex_type,
collection)
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
data = DU().downloadUrl(url,
authenticate=False,
timeout=15,
return_response=True)
if not data.ok:
LOG.debug('Could not download data from FanartTV')
return artworks
data = data.json()
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