Artwork overhaul part 2

This commit is contained in:
croneter 2018-03-04 13:39:18 +01:00
parent b4716ba511
commit 8272a67b5f
5 changed files with 196 additions and 325 deletions

View file

@ -54,19 +54,6 @@ LOG = getLogger("PLEX." + __name__)
REGEX_IMDB = re_compile(r'''/(tt\d+)''') REGEX_IMDB = re_compile(r'''/(tt\d+)''')
REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''') REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''')
# we need to use a little mapping between fanart.tv arttypes and kodi
# artttypes
FANART_TV_TYPES = [
("logo", "Logo"),
("musiclogo", "clearlogo"),
("disc", "Disc"),
("clearart", "Art"),
("banner", "Banner"),
("clearlogo", "Logo"),
("background", "fanart"),
("showbackground", "fanart"),
("characterart", "characterart")
]
############################################################################### ###############################################################################
@ -755,10 +742,9 @@ class API(object):
'banner' 'banner'
'clearart' 'clearart'
'clearlogo' 'clearlogo'
'landscape'
'icon'
'fanart' 'fanart'
} }
'landscape' and 'icon' might be implemented later
""" """
if kodi_id: if kodi_id:
# in Kodi database, potentially with additional e.g. clearart # in Kodi database, potentially with additional e.g. clearart
@ -783,7 +769,7 @@ class API(object):
if art: if art:
artworks['tvshow.poster'] = art artworks['tvshow.poster'] = art
# Get parent item artwork if the main item is missing artwork # Get parent item artwork if the main item is missing artwork
if 'fanart1' 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
@ -791,39 +777,31 @@ class API(object):
art = self._one_artwork('parentThumb') art = self._one_artwork('parentThumb')
if art: if art:
artworks['poster'] = art artworks['poster'] = art
LOG.debug('artworks: %s', artworks)
return artworks return artworks
def fanart_artwork(self, allartworks): def fanart_artwork(self, artworks):
""" """
Downloads additional fanart from third party sources (well, link to Downloads additional fanart from third party sources (well, link to
fanart only). fanart only).
allartworks = {
'Primary': "",
'Art': "",
'Banner': "",
'Logo': "",
'Thumb': "",
'Disc': "",
'Backdrop': []
}
""" """
external_id = self.retrieve_external_item_id() external_id = self.retrieve_external_item_id()
if external_id is not None: if external_id is not None:
allartworks = self.lookup_fanart_tv(external_id, allartworks) artworks = self.lookup_fanart_tv(external_id[0], artworks)
return allartworks LOG.debug('fanart artworks: %s', artworks)
return artworks
def retrieve_external_item_id(self, collection=False): def retrieve_external_item_id(self, collection=False):
""" """
Returns the item's IMDB id for movies or tvdb id for TV shows 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
If not found in item's Plex metadata, check themovidedb.org The last two might be None if not found. Generally None is returned
if unsuccessful.
collection=True will try to return the three-tuple: If not found in item's Plex metadata, check themovidedb.org.
collection ID, poster-path, background-path
None is returned if unsuccessful
""" """
item = self.item.attrib item = self.item.attrib
media_type = item.get('type') media_type = item.get('type')
@ -836,7 +814,7 @@ class API(object):
elif media_type == v.PLEX_TYPE_SHOW: elif media_type == v.PLEX_TYPE_SHOW:
media_id = self.provider('tvdb') media_id = self.provider('tvdb')
if media_id is not None: if media_id is not None:
return media_id return media_id, None, None
LOG.info('Plex did not provide ID for IMDB or TVDB. Start ' LOG.info('Plex did not provide ID for IMDB or TVDB. Start '
'lookup process') 'lookup process')
else: else:
@ -935,9 +913,8 @@ class API(object):
media_type = entry.get("media_type") media_type = entry.get("media_type")
name = entry.get("name", entry.get("title")) name = entry.get("name", entry.get("title"))
# lookup external tmdb_id and perform artwork lookup on fanart.tv # lookup external tmdb_id and perform artwork lookup on fanart.tv
parameters = { parameters = {'api_key': api_key}
'api_key': api_key media_id, poster, background = None, None, None
}
for language in [v.KODILANGUAGE, "en"]: for language in [v.KODILANGUAGE, "en"]:
parameters['language'] = language parameters['language'] = language
if media_type == "movie": if media_type == "movie":
@ -977,7 +954,7 @@ class API(object):
try: try:
data.get('poster_path') data.get('poster_path')
except AttributeError: except AttributeError:
LOG.info('Could not find TheMovieDB poster paths for %s in' LOG.info('Could not find TheMovieDB poster paths for %s in '
'the language %s', title, language) 'the language %s', title, language)
continue continue
else: else:
@ -985,23 +962,21 @@ class API(object):
data.get('poster_path')) data.get('poster_path'))
background = ('https://image.tmdb.org/t/p/original%s' % background = ('https://image.tmdb.org/t/p/original%s' %
data.get('backdrop_path')) data.get('backdrop_path'))
media_id = media_id, poster, background
break break
return media_id return media_id, poster, background
def lookup_fanart_tv(self, media_id, allartworks, set_info=False): def lookup_fanart_tv(self, media_id, artworks, set_info=False):
""" """
perform artwork lookup on fanart.tv perform artwork lookup on fanart.tv
media_id: IMDB id for movies, tvdb id for TV shows media_id: IMDB id for movies, tvdb id for TV shows
""" """
item = self.item.attrib
api_key = settings('FanArtTVAPIKey') api_key = settings('FanArtTVAPIKey')
typus = item.get('type') typus = self.plex_type()
if typus == 'show': if typus == v.PLEX_TYPE_SHOW:
typus = 'tv' typus = 'tv'
if typus == "movie": if typus == v.PLEX_TYPE_MOVIE:
url = 'http://webservice.fanart.tv/v3/movies/%s?api_key=%s' \ url = 'http://webservice.fanart.tv/v3/movies/%s?api_key=%s' \
% (media_id, api_key) % (media_id, api_key)
elif typus == 'tv': elif typus == 'tv':
@ -1009,24 +984,20 @@ class API(object):
% (media_id, api_key) % (media_id, api_key)
else: else:
# Not supported artwork # Not supported artwork
return allartworks return artworks
data = DU().downloadUrl(url, data = DU().downloadUrl(url, authenticate=False, timeout=15)
authenticate=False,
timeout=15)
try: try:
data.get('test') data.get('test')
except AttributeError: except AttributeError:
LOG.error('Could not download data from FanartTV') LOG.error('Could not download data from FanartTV')
return allartworks return artworks
fanart_tv_types = list(FANART_TV_TYPES) fanart_tv_types = list(v.FANART_TV_TO_KODI_TYPE)
if typus == "artist": if typus == v.PLEX_TYPE_ARTIST:
fanart_tv_types.append(("thumb", "folder")) fanart_tv_types.append(("thumb", "folder"))
else: else:
fanart_tv_types.append(("thumb", "Thumb")) fanart_tv_types.append(("thumb", "thumb"))
if set_info:
fanart_tv_types.append(("poster", "Primary"))
prefixes = ( prefixes = (
"hd" + typus, "hd" + typus,
@ -1034,32 +1005,31 @@ class API(object):
typus, typus,
"", "",
) )
for fanarttype in fanart_tv_types: for fanart_tv_type, kodi_type in fanart_tv_types:
# Skip the ones we already have # Skip the ones we already have
if allartworks.get(fanarttype[1]): if kodi_type in artworks:
continue continue
for prefix in prefixes: for prefix in prefixes:
fanarttvimage = prefix + fanarttype[0] fanarttvimage = prefix + fanart_tv_type
if fanarttvimage not in data: if fanarttvimage not in data:
continue continue
# select image in preferred language # select image in preferred language
for entry in data[fanarttvimage]: for entry in data[fanarttvimage]:
if entry.get("lang") == v.KODILANGUAGE: if entry.get("lang") == v.KODILANGUAGE:
allartworks[fanarttype[1]] = \ artworks[kodi_type] = \
entry.get("url", "").replace(' ', '%20') entry.get("url", "").replace(' ', '%20')
break break
# just grab the first english OR undefinded one as fallback # just grab the first english OR undefinded one as fallback
# (so we're actually grabbing the more popular one) # (so we're actually grabbing the more popular one)
if not allartworks.get(fanarttype[1]): if kodi_type not in artworks:
for entry in data[fanarttvimage]: for entry in data[fanarttvimage]:
if entry.get("lang") in ("en", "00"): if entry.get("lang") in ("en", "00"):
allartworks[fanarttype[1]] = \ artworks[kodi_type] = \
entry.get("url", "").replace(' ', '%20') entry.get("url", "").replace(' ', '%20')
break break
# grab extrafanarts in list # grab extrafanarts in list
maxfanarts = 10 fanartcount = 1 if 'fanart' in artworks else ''
fanartcount = 0
for prefix in prefixes: for prefix in prefixes:
fanarttvimage = prefix + 'background' fanarttvimage = prefix + 'background'
if fanarttvimage not in data: if fanarttvimage not in data:
@ -1067,60 +1037,38 @@ class API(object):
for entry in data[fanarttvimage]: for entry in data[fanarttvimage]:
if entry.get("url") is None: if entry.get("url") is None:
continue continue
if fanartcount > maxfanarts: artworks['fanart%s' % fanartcount] = \
entry['url'].replace(' ', '%20')
try:
fanartcount += 1
except TypeError:
fanartcount = 1
if fanartcount >= v.MAX_BACKGROUND_COUNT:
break break
allartworks['Backdrop'].append( return artworks
entry['url'].replace(' ', '%20'))
fanartcount += 1
return allartworks
def set_artwork(self): def set_artwork(self):
""" """
Gets the URLs to the Plex artwork, or empty string if not found. Gets the URLs to the Plex artwork, or empty string if not found.
parentInfo=True will check for parent's artwork if None is found
Only call on movies Only call on movies
Output:
{
'Primary'
'Art'
'Banner'
'Logo'
'Thumb'
'Disc'
'Backdrop' : LIST with the first entry xml key "art"
}
""" """
allartworks = { artworks = {}
'Primary': "",
'Art': "",
'Banner': "",
'Logo': "",
'Thumb': "",
'Disc': "",
'Backdrop': []
}
# Plex does not get much artwork - go ahead and get the rest from # Plex does not get much artwork - go ahead and get the rest from
# fanart tv only for movie or tv show # fanart tv only for movie or tv show
external_id = self.retrieve_external_item_id(collection=True) external_id = self.retrieve_external_item_id(collection=True)
if external_id is not None: if external_id is not None:
try: external_id, poster, background = external_id
external_id, poster, background = external_id
except TypeError:
poster, background = None, None
if poster is not None: if poster is not None:
allartworks['Primary'] = poster artworks['poster'] = poster
if background is not None: if background is not None:
allartworks['Backdrop'].append(background) artworks['fanart'] = background
allartworks = self.lookup_fanart_tv(external_id, artworks = self.lookup_fanart_tv(external_id,
allartworks, artworks,
set_info=True) set_info=True)
else: else:
LOG.info('Did not find a set/collection ID on TheMovieDB using %s.' LOG.info('Did not find a set/collection ID on TheMovieDB using %s.'
' Artwork will be missing.', self.titles()[0]) ' Artwork will be missing.', self.titles()[0])
return allartworks return artworks
def should_stream(self): def should_stream(self):
""" """

View file

@ -21,9 +21,8 @@ LOG = getLogger("PLEX." + __name__)
# Disable annoying requests warnings # Disable annoying requests warnings
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
###############################################################################
ARTWORK_QUEUE = Queue() ARTWORK_QUEUE = Queue()
###############################################################################
def double_urlencode(text): def double_urlencode(text):
@ -88,26 +87,25 @@ class Image_Cache_Thread(Thread):
# Server thinks its a DOS attack, ('error 10053') # Server thinks its a DOS attack, ('error 10053')
# Wait before trying again # Wait before trying again
if sleeptime > 5: if sleeptime > 5:
LOG.error('Repeatedly got ConnectionError for url %s' LOG.error('Repeatedly got ConnectionError for url %s',
% double_urldecode(url)) double_urldecode(url))
break break
LOG.debug('Were trying too hard to download art, server ' LOG.debug('Were trying too hard to download art, server '
'over-loaded. Sleep %s seconds before trying ' 'over-loaded. Sleep %s seconds before trying '
'again to download %s' 'again to download %s',
% (2**sleeptime, double_urldecode(url))) 2**sleeptime, double_urldecode(url))
sleep((2**sleeptime)*1000) sleep((2**sleeptime)*1000)
sleeptime += 1 sleeptime += 1
continue continue
except Exception as e: except Exception as e:
LOG.error('Unknown exception for url %s: %s' LOG.error('Unknown exception for url %s: %s'.
% (double_urldecode(url), e)) double_urldecode(url), e)
import traceback import traceback
LOG.error("Traceback:\n%s" % traceback.format_exc()) LOG.error("Traceback:\n%s", traceback.format_exc())
break break
# We did not even get a timeout # We did not even get a timeout
break break
queue.task_done() queue.task_done()
LOG.debug('Cached art: %s' % double_urldecode(url))
# Sleep for a bit to reduce CPU strain # Sleep for a bit to reduce CPU strain
sleep(sleep_between) sleep(sleep_between)
LOG.info("---===### Stopped Image_Cache_Thread ###===---") LOG.info("---===### Stopped Image_Cache_Thread ###===---")
@ -135,7 +133,7 @@ class Artwork():
path = try_decode(translatePath("special://thumbnails/")) path = try_decode(translatePath("special://thumbnails/"))
if exists_dir(path): if exists_dir(path):
rmtree(path, ignore_errors=True) rmtree(path, ignore_errors=True)
self.restoreCacheDirectories() self.restore_cache_directories()
# remove all existing data from texture DB # remove all existing data from texture DB
connection = kodi_sql('texture') connection = kodi_sql('texture')
@ -158,167 +156,91 @@ class Artwork():
cursor.execute(query, ('actor', )) cursor.execute(query, ('actor', ))
result = cursor.fetchall() result = cursor.fetchall()
total = len(result) total = len(result)
LOG.info("Image cache sync about to process %s video images" % total) LOG.info("Image cache sync about to process %s video images", total)
connection.close() connection.close()
for url in result: for url in result:
self.cacheTexture(url[0]) self.cache_texture(url[0])
# Cache all entries in music DB # Cache all entries in music DB
connection = kodi_sql('music') connection = kodi_sql('music')
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("SELECT url FROM art") cursor.execute("SELECT url FROM art")
result = cursor.fetchall() result = cursor.fetchall()
total = len(result) total = len(result)
LOG.info("Image cache sync about to process %s music images" % total) LOG.info("Image cache sync about to process %s music images", total)
connection.close() connection.close()
for url in result: for url in result:
self.cacheTexture(url[0]) self.cache_texture(url[0])
def cacheTexture(self, url): def cache_texture(self, url):
# Cache a single image url to the texture cache '''
Cache a single image url to the texture cache
'''
if url and self.enableTextureCache: if url and self.enableTextureCache:
self.queue.put(double_urlencode(try_encode(url))) self.queue.put(double_urlencode(try_encode(url)))
def addArtwork(self, artwork, kodiId, mediaType, cursor): def modify_artwork(self, artworks, kodi_id, kodi_type, cursor):
# Kodi conversion table """
kodiart = { Pass in an artworks dict (see PlexAPI) to set an items artwork.
'Primary': ["thumb", "poster"], """
'Banner': "banner", for kodi_art, url in artworks.iteritems():
'Logo': "clearlogo", self.modify_art(url, kodi_id, kodi_type, kodi_art, cursor)
'Art': "clearart",
'Thumb': "landscape",
'Disc': "discart",
'Backdrop': "fanart",
'BoxRear': "poster"
}
# Artwork is a dictionary def modify_art(self, url, kodi_id, kodi_type, kodi_art, cursor):
for art in artwork: """
if art == "Backdrop": Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
# Backdrop entry is a list Kodi art table for item kodi_id/kodi_type. Will also cache everything
# Process extra fanart for artwork downloader (fanart, fanart1, except actor portraits.
# fanart2...) """
backdrops = artwork[art] query = '''
backdropsNumber = len(backdrops) SELECT url FROM art
WHERE media_id = ? AND media_type = ? AND type = ?
query = ' '.join(( LIMIT 1
"SELECT url", '''
"FROM art", cursor.execute(query, (kodi_id, kodi_type, kodi_art,))
"WHERE media_id = ?",
"AND media_type = ?",
"AND type LIKE ?"
))
cursor.execute(query, (kodiId, mediaType, "fanart%",))
rows = cursor.fetchall()
if len(rows) > backdropsNumber:
# More backdrops in database. Delete extra fanart.
query = ' '.join((
"DELETE FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type LIKE ?"
))
cursor.execute(query, (kodiId, mediaType, "fanart_",))
# Process backdrops and extra fanart
index = ""
for backdrop in backdrops:
self.addOrUpdateArt(
imageUrl=backdrop,
kodiId=kodiId,
mediaType=mediaType,
imageType="%s%s" % ("fanart", index),
cursor=cursor)
if backdropsNumber > 1:
try: # Will only fail on the first try, str to int.
index += 1
except TypeError:
index = 1
elif art == "Primary":
# Primary art is processed as thumb and poster for Kodi.
for artType in kodiart[art]:
self.addOrUpdateArt(
imageUrl=artwork[art],
kodiId=kodiId,
mediaType=mediaType,
imageType=artType,
cursor=cursor)
elif kodiart.get(art):
# Process the rest artwork type that Kodi can use
self.addOrUpdateArt(
imageUrl=artwork[art],
kodiId=kodiId,
mediaType=mediaType,
imageType=kodiart[art],
cursor=cursor)
def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
if not imageUrl:
# Possible that the imageurl is an empty string
return
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type = ?"
))
cursor.execute(query, (kodiId, mediaType, imageType,))
try: try:
# Update the artwork # Update the artwork
url = cursor.fetchone()[0] old_url = cursor.fetchone()[0]
except TypeError: except TypeError:
# Add the artwork # Add the artwork
LOG.debug("Adding Art Link for kodiId: %s (%s)" LOG.debug('Adding Art Link for %s kodi_id %s, kodi_type %s: %s',
% (kodiId, imageUrl)) kodi_art, kodi_id, kodi_type, url)
query = ( query = '''
'''
INSERT INTO art(media_id, media_type, type, url) INSERT INTO art(media_id, media_type, type, url)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
''' '''
) cursor.execute(query, (kodi_id, kodi_type, kodi_art, url))
cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
else: else:
if url == imageUrl: if url == old_url:
# Only cache artwork if it changed # Only cache artwork if it changed
return return
# Only for the main backdrop, poster self.delete_cached_artwork(old_url)
if (window('plex_initialScan') != "true" and LOG.debug("Updating Art url for %s kodi_id %s, kodi_type %s to %s",
imageType in ("fanart", "poster")): kodi_art, kodi_id, kodi_type, url)
# Delete current entry before updating with the new one query = '''
self.deleteCachedArtwork(url) UPDATE art SET url = ?
LOG.debug("Updating Art url for %s kodiId %s %s -> (%s)" WHERE media_id = ? AND media_type = ? AND type = ?
% (imageType, kodiId, url, imageUrl)) '''
query = ' '.join(( cursor.execute(query, (url, kodi_id, kodi_type, kodi_art))
"UPDATE art",
"SET url = ?",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type = ?"
))
cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
# Cache fanart and poster in Kodi texture cache # Cache fanart and poster in Kodi texture cache
if mediaType != 'actor': if kodi_type != 'actor':
self.cacheTexture(imageUrl) self.cache_texture(url)
def deleteArtwork(self, kodiId, mediaType, cursor): def delete_artwork(self, kodiId, mediaType, cursor):
query = 'SELECT url FROM art WHERE media_id = ? AND media_type = ?' query = 'SELECT url FROM art WHERE media_id = ? AND media_type = ?'
cursor.execute(query, (kodiId, mediaType,)) cursor.execute(query, (kodiId, mediaType,))
for row in cursor.fetchall(): for row in cursor.fetchall():
self.deleteCachedArtwork(row[0]) self.delete_cached_artwork(row[0])
def deleteCachedArtwork(self, url): @staticmethod
# Only necessary to remove and apply a new backdrop or poster def delete_cached_artwork(url):
"""
Deleted the cached artwork with path url (if it exists)
"""
connection = kodi_sql('texture') connection = kodi_sql('texture')
cursor = connection.cursor() cursor = connection.cursor()
try: try:
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1",
(url,)) (url,))
cachedurl = cursor.fetchone()[0] cachedurl = cursor.fetchone()[0]
except TypeError: except TypeError:
@ -327,7 +249,7 @@ class Artwork():
else: else:
# Delete thumbnail as well as the entry # Delete thumbnail as well as the entry
path = translatePath("special://thumbnails/%s" % cachedurl) path = translatePath("special://thumbnails/%s" % cachedurl)
LOG.debug("Deleting cached thumbnail: %s" % path) LOG.debug("Deleting cached thumbnail: %s", path)
if exists(path): if exists(path):
rmtree(try_decode(path), ignore_errors=True) rmtree(try_decode(path), ignore_errors=True)
cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
@ -336,8 +258,11 @@ class Artwork():
connection.close() connection.close()
@staticmethod @staticmethod
def restoreCacheDirectories(): def restore_cache_directories():
LOG.info("Restoring cache directories...") LOG.info("Restoring cache directories...")
paths = ("","0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","Video","plex") paths = ("", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
for p in paths: "a", "b", "c", "d", "e", "f",
makedirs(try_decode(translatePath("special://thumbnails/%s" % p))) "Video", "plex")
for path in paths:
makedirs(try_decode(translatePath("special://thumbnails/%s"
% path)))

View file

@ -82,11 +82,11 @@ class Items(object):
allartworks = None allartworks = None
else: else:
with kodidb.GetKodiDB('video') as kodi_db: with kodidb.GetKodiDB('video') as kodi_db:
allartworks = kodi_db.existingArt(kodi_id, kodi_type) allartworks = kodi_db.get_art(kodi_id, kodi_type)
# Check if we even need to get additional art # Check if we even need to get additional art
needsupdate = False needsupdate = False
for key, value in allartworks.iteritems(): for key in v.ALL_KODI_ARTWORK:
if not value and not key == 'BoxRear': if key not in allartworks:
needsupdate = True needsupdate = True
break break
if needsupdate is False: if needsupdate is False:
@ -107,19 +107,19 @@ class Items(object):
api = API(xml[0]) api = API(xml[0])
if allartworks is None: if allartworks is None:
allartworks = api.artwork() allartworks = api.artwork()
self.artwork.addArtwork(api.fanart_artwork(allartworks), self.artwork.modify_artwork(api.fanart_artwork(allartworks),
kodi_id, kodi_id,
kodi_type, kodi_type,
self.kodicursor) self.kodicursor)
# Also get artwork for collections/movie sets # Also get artwork for collections/movie sets
if kodi_type == v.KODI_TYPE_MOVIE: if kodi_type == v.KODI_TYPE_MOVIE:
for setname in api.collection_list(): for setname in api.collection_list():
LOG.debug('Getting artwork for movie set %s', setname) LOG.debug('Getting artwork for movie set %s', setname)
setid = self.kodi_db.createBoxset(setname) setid = self.kodi_db.createBoxset(setname)
self.artwork.addArtwork(api.set_artwork(), self.artwork.modify_artwork(api.set_artwork(),
setid, setid,
v.KODI_TYPE_SET, v.KODI_TYPE_SET,
self.kodicursor) self.kodicursor)
self.kodi_db.assignBoxset(setid, kodi_id) self.kodi_db.assignBoxset(setid, kodi_id)
return True return True
@ -445,7 +445,10 @@ class Movies(Items):
# Process genres # Process genres
self.kodi_db.modify_genres(movieid, v.KODI_TYPE_MOVIE, genres) self.kodi_db.modify_genres(movieid, v.KODI_TYPE_MOVIE, genres)
# Process artwork # Process artwork
artwork.addArtwork(api.artwork(), movieid, "movie", kodicursor) artwork.modify_artwork(api.artwork(),
movieid,
v.KODI_TYPE_MOVIE,
kodicursor)
# Process stream details # Process stream details
self.kodi_db.modify_streams(fileid, api.mediastreams(), runtime) self.kodi_db.modify_streams(fileid, api.mediastreams(), runtime)
# Process studios # Process studios
@ -482,7 +485,7 @@ class Movies(Items):
# Remove the plex reference # Remove the plex reference
plex_db.removeItem(itemid) plex_db.removeItem(itemid)
# Remove artwork # Remove artwork
artwork.deleteArtwork(kodi_id, kodi_type, kodicursor) artwork.delete_artwork(kodi_id, kodi_type, kodicursor)
if kodi_type == v.KODI_TYPE_MOVIE: if kodi_type == v.KODI_TYPE_MOVIE:
set_id = self.kodi_db.get_set_id(kodi_id) set_id = self.kodi_db.get_set_id(kodi_id)
@ -743,7 +746,10 @@ class TVShows(Items):
self.kodi_db.modify_people(showid, v.KODI_TYPE_SHOW, api.people_list()) self.kodi_db.modify_people(showid, v.KODI_TYPE_SHOW, api.people_list())
self.kodi_db.modify_genres(showid, v.KODI_TYPE_SHOW, genres) self.kodi_db.modify_genres(showid, v.KODI_TYPE_SHOW, genres)
artwork.addArtwork(api.artwork(), showid, v.KODI_TYPE_SHOW, kodicursor) artwork.modify_artwork(api.artwork(),
showid,
v.KODI_TYPE_SHOW,
kodicursor)
# Process studios # Process studios
self.kodi_db.modify_studios(showid, v.KODI_TYPE_SHOW, studios) self.kodi_db.modify_studios(showid, v.KODI_TYPE_SHOW, studios)
# Process tags: view, PMS collection tags # Process tags: view, PMS collection tags
@ -784,7 +790,10 @@ class TVShows(Items):
# Process artwork # Process artwork
allartworks = api.artwork() allartworks = api.artwork()
artwork.addArtwork(allartworks, seasonid, "season", kodicursor) artwork.modify_artwork(allartworks,
seasonid,
v.KODI_TYPE_SEASON,
kodicursor)
if update_item: if update_item:
# Update a reference: checksum in plex table # Update a reference: checksum in plex table
@ -1095,8 +1104,8 @@ class TVShows(Items):
if poster: if poster:
poster = api.attach_plex_token_to_url( poster = api.attach_plex_token_to_url(
"%s%s" % (self.server, poster)) "%s%s" % (self.server, poster))
artwork.addOrUpdateArt( artwork.modify_art(
poster, episodeid, "episode", "thumb", kodicursor) poster, episodeid, v.KODI_TYPE_EPISODE, "thumb", kodicursor)
# Process stream details # Process stream details
streams = api.mediastreams() streams = api.mediastreams()
@ -1223,7 +1232,7 @@ class TVShows(Items):
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW) self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW) self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW) self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW)
self.artwork.deleteArtwork(kodi_id, v.KODI_TYPE_SHOW, kodicursor) self.artwork.delete_artwork(kodi_id, v.KODI_TYPE_SHOW, kodicursor)
kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodi_id,)) kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodi_id,))
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
@ -1235,7 +1244,7 @@ class TVShows(Items):
Remove a season, and only a season, not the show or episodes Remove a season, and only a season, not the show or episodes
""" """
kodicursor = self.kodicursor kodicursor = self.kodicursor
self.artwork.deleteArtwork(kodi_id, "season", kodicursor) self.artwork.delete_artwork(kodi_id, "season", kodicursor)
kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?",
(kodi_id,)) (kodi_id,))
LOG.info("Removed season: %s.", kodi_id) LOG.info("Removed season: %s.", kodi_id)
@ -1248,7 +1257,7 @@ class TVShows(Items):
self.kodi_db.modify_people(kodi_id, v.KODI_TYPE_EPISODE) self.kodi_db.modify_people(kodi_id, v.KODI_TYPE_EPISODE)
self.kodi_db.modify_streams(file_id) self.kodi_db.modify_streams(file_id)
self.kodi_db.delete_playstate(file_id) self.kodi_db.delete_playstate(file_id)
self.artwork.deleteArtwork(kodi_id, "episode", kodicursor) self.artwork.delete_artwork(kodi_id, "episode", kodicursor)
kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?",
(kodi_id,)) (kodi_id,))
kodicursor.execute("DELETE FROM files WHERE idFile = ?", (file_id,)) kodicursor.execute("DELETE FROM files WHERE idFile = ?", (file_id,))
@ -1359,7 +1368,10 @@ class Music(Items):
dateadded, artistid)) dateadded, artistid))
# Update artwork # Update artwork
artwork.addArtwork(artworks, artistid, v.KODI_TYPE_ARTIST, kodicursor) artwork.modify_artwork(artworks,
artistid,
v.KODI_TYPE_ARTIST,
kodicursor)
@catch_exceptions(warnuser=True) @catch_exceptions(warnuser=True)
def add_updateAlbum(self, item, viewtag=None, viewid=None, children=None, def add_updateAlbum(self, item, viewtag=None, viewid=None, children=None,
@ -1553,7 +1565,7 @@ class Music(Items):
# Add genres # Add genres
self.kodi_db.addMusicGenres(albumid, self.genres, v.KODI_TYPE_ALBUM) self.kodi_db.addMusicGenres(albumid, self.genres, v.KODI_TYPE_ALBUM)
# Update artwork # Update artwork
artwork.addArtwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor) artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor)
# Add all children - all tracks # Add all children - all tracks
if scan_children: if scan_children:
for child in children: for child in children:
@ -1847,10 +1859,11 @@ class Music(Items):
# Add genres # Add genres
if genres: if genres:
self.kodi_db.addMusicGenres(songid, genres, v.KODI_TYPE_SONG) self.kodi_db.addMusicGenres(songid, genres, v.KODI_TYPE_SONG)
artwork.addArtwork(api.artwork(), songid, v.KODI_TYPE_SONG, kodicursor) artworks = api.artwork()
artwork.modify_artwork(artworks, songid, v.KODI_TYPE_SONG, kodicursor)
if item.get('parentKey') is None: if item.get('parentKey') is None:
# Update album artwork # Update album artwork
artwork.addArtwork(allart, albumid, v.KODI_TYPE_ALBUM, kodicursor) artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor)
def remove(self, itemid): def remove(self, itemid):
""" """
@ -1937,7 +1950,7 @@ class Music(Items):
""" """
Remove song, and only the song Remove song, and only the song
""" """
self.artwork.deleteArtwork(kodiid, v.KODI_TYPE_SONG, self.kodicursor) self.artwork.delete_artwork(kodiid, v.KODI_TYPE_SONG, self.kodicursor)
self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", self.kodicursor.execute("DELETE FROM song WHERE idSong = ?",
(kodiid,)) (kodiid,))
@ -1945,7 +1958,7 @@ class Music(Items):
""" """
Remove an album, and only the album Remove an album, and only the album
""" """
self.artwork.deleteArtwork(kodiid, v.KODI_TYPE_ALBUM, self.kodicursor) self.artwork.delete_artwork(kodiid, v.KODI_TYPE_ALBUM, self.kodicursor)
self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?",
(kodiid,)) (kodiid,))
@ -1953,7 +1966,7 @@ class Music(Items):
""" """
Remove an artist, and only the artist Remove an artist, and only the artist
""" """
self.artwork.deleteArtwork(kodiid, self.artwork.delete_artwork(kodiid,
v.KODI_TYPE_ARTIST, v.KODI_TYPE_ARTIST,
self.kodicursor) self.kodicursor)
self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?",

View file

@ -406,7 +406,7 @@ class KodiDBMethods(object):
self.cursor.execute(query_actor_delete, (person[0],)) self.cursor.execute(query_actor_delete, (person[0],))
if kind == 'actor': if kind == 'actor':
# Delete any associated artwork # Delete any associated artwork
self.artwork.deleteArtwork(person[0], 'actor', self.cursor) self.artwork.delete_artwork(person[0], 'actor', self.cursor)
# Save new people to Kodi DB by iterating over the remaining entries # Save new people to Kodi DB by iterating over the remaining entries
if kind == 'actor': if kind == 'actor':
query = 'INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)' query = 'INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)'
@ -454,11 +454,11 @@ class KodiDBMethods(object):
'VALUES (?, ?)', 'VALUES (?, ?)',
(actor_id, name)) (actor_id, name))
if art_url: if art_url:
self.artwork.addOrUpdateArt(art_url, self.artwork.modify_art(art_url,
actor_id, actor_id,
'actor', 'actor',
"thumb", 'thumb',
self.cursor) self.cursor)
return actor_id return actor_id
def get_art(self, kodi_id, kodi_type): def get_art(self, kodi_id, kodi_type):
@ -468,11 +468,11 @@ class KodiDBMethods(object):
'thumb' 'thumb'
'poster' 'poster'
'banner' 'banner'
'fanart'
'clearart' 'clearart'
'clearlogo' 'clearlogo'
'landscape' 'landscape'
'icon' 'icon'
'fanart' and also potentially more fanart 'fanart1', 2, 3, ...
} }
Missing fanart will not appear in the dict. Missing fanart will not appear in the dict.
""" """
@ -480,48 +480,6 @@ class KodiDBMethods(object):
self.cursor.execute(query, (kodi_id, kodi_type)) self.cursor.execute(query, (kodi_id, kodi_type))
return dict(self.cursor.fetchall()) return dict(self.cursor.fetchall())
def existingArt(self, kodiId, mediaType, refresh=False):
"""
For kodiId, returns an artwork dict with already existing art from
the Kodi db
"""
# Only get EITHER poster OR thumb (should have same URL)
kodiToPKC = {
'banner': 'Banner',
'clearart': 'Art',
'clearlogo': 'Logo',
'discart': 'Disc',
'landscape': 'Thumb',
'thumb': 'Primary'
}
# BoxRear yet unused
result = {'BoxRear': ''}
for art in kodiToPKC:
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type = ?"
))
self.cursor.execute(query, (kodiId, mediaType, art,))
try:
url = self.cursor.fetchone()[0]
except TypeError:
url = ""
result[kodiToPKC[art]] = url
# There may be several fanart URLs saved
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type LIKE ?"
))
data = self.cursor.execute(query, (kodiId, mediaType, "fanart%",))
result['Backdrop'] = [d[0] for d in data]
return result
def modify_streams(self, fileid, streamdetails=None, runtime=None): def modify_streams(self, fileid, streamdetails=None, runtime=None):
""" """
Leave streamdetails and runtime empty to delete all stream entries for Leave streamdetails and runtime empty to delete all stream entries for

View file

@ -321,9 +321,36 @@ PLEX_TYPE_FROM_WEBSOCKET = {
KODI_TO_PLEX_ARTWORK = { KODI_TO_PLEX_ARTWORK = {
'poster': 'thumb', 'poster': 'thumb',
'banner': 'banner', 'banner': 'banner',
'fanart1': 'art' 'fanart': 'art'
} }
# Might be implemented in the future: 'icon', 'landscape' (16:9)
ALL_KODI_ARTWORK = (
'thumb',
'poster',
'banner',
'clearart',
'clearlogo',
'fanart',
'discart'
)
# we need to use a little mapping between fanart.tv arttypes and kodi artttypes
FANART_TV_TO_KODI_TYPE = [
('poster', 'poster'),
('logo', 'clearlogo'),
('musiclogo', 'clearlogo'),
('disc', 'discart'),
('clearart', 'clearart'),
('banner', 'banner'),
('clearlogo', 'clearlogo'),
('background', 'fanart'),
('showbackground', 'fanart'),
('characterart', 'characterart')
]
# How many different backgrounds do we want to load from fanart.tv?
MAX_BACKGROUND_COUNT = 10
# extensions from: # extensions from:
# http://kodi.wiki/view/Features_and_supported_codecs#Format_support (RAW image # http://kodi.wiki/view/Features_and_supported_codecs#Format_support (RAW image