|
| 1 | +import xbmc |
| 2 | +import xbmcaddon |
| 3 | + |
| 4 | +import os |
| 5 | +from datetime import timedelta |
| 6 | +import requests |
| 7 | +import requests_cache |
| 8 | + |
| 9 | +from util import log |
| 10 | + |
| 11 | +__addon__ = xbmcaddon.Addon() |
| 12 | +artFallbackEnabled = __addon__.getSetting("enable-art-fallback") == 'true' # Kodi stores boolean settings as strings |
| 13 | +monthsBeforeArtsExpiration = int(__addon__.getSetting("arts-expire-after-months")) # Default is 2 months |
| 14 | + |
| 15 | +# define the cache file to reside in the ..\Kodi\userdata\addon_data\(your addon) |
| 16 | +addonUserDataFolder = xbmc.translatePath(__addon__.getAddonInfo('profile')) |
| 17 | +ART_AVAILABILITY_CACHE_FILE = xbmc.translatePath(os.path.join(addonUserDataFolder, 'requests_cache_arts')) |
| 18 | + |
| 19 | +cached_requests = requests_cache.core.CachedSession(ART_AVAILABILITY_CACHE_FILE, backend='sqlite', |
| 20 | + expire_after= timedelta(weeks=4*monthsBeforeArtsExpiration), |
| 21 | + allowable_methods=('HEAD',), allowable_codes=(200, 404), |
| 22 | + old_data_on_error=True, |
| 23 | + fast_save=True) |
| 24 | +# Existing Steam art types urls, to format to format with appid / img_icon_path |
| 25 | +STEAM_ARTS_TYPES = { # img_icon_path is provided by steam API to get the icon. https://developer.valvesoftware.com/wiki/Steam_Web_API#GetOwnedGames_.28v0001.29 |
| 26 | + 'poster': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/library_600x900.jpg', # Can return 404 |
| 27 | + 'hero': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/library_hero.jpg', # Can return 404 |
| 28 | + 'header': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/header.jpg', |
| 29 | + 'generated_bg': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/page_bg_generated_v6b.jpg', # Auto generated background with a shade of blue. |
| 30 | + 'icon': 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{appid}/{img_icon_path}.jpg', |
| 31 | + 'clearlogo': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/logo.png' # Can return 404 |
| 32 | +} |
| 33 | + |
| 34 | +# Dictionary containing for each art type, a url for the art (to format with appid / img_icon_path afterwards), and a fallback art type. |
| 35 | +# Having no fallback also means that the art url won't be tested |
| 36 | +ARTS_ASSIGNMENTS = { |
| 37 | + 'poster': {'url': STEAM_ARTS_TYPES['poster'], 'fallback': 'landscape'}, |
| 38 | + 'banner': {'url': STEAM_ARTS_TYPES['hero'], 'fallback': 'landscape'}, |
| 39 | + 'fanart': {'url': STEAM_ARTS_TYPES['hero'], 'fallback': 'fanart1'}, |
| 40 | + 'fanart1': {'url': STEAM_ARTS_TYPES['header'], 'fallback': None}, |
| 41 | + 'fanart2': {'url': STEAM_ARTS_TYPES['generated_bg'], 'fallback': None}, # Multiple fanart https://kodi.wiki/view/Artwork_types#fanart.23 |
| 42 | + 'landscape': {'url': STEAM_ARTS_TYPES['header'], 'fallback': None}, |
| 43 | + 'thumb': {'url': STEAM_ARTS_TYPES['header'], 'fallback': None}, |
| 44 | + 'icon': {'url': STEAM_ARTS_TYPES['icon'], 'fallback': None}, |
| 45 | + 'clearlogo': {'url': STEAM_ARTS_TYPES['clearlogo'], 'fallback': None} |
| 46 | +} |
| 47 | + |
| 48 | + |
| 49 | +def is_art_url_available(url, timeout=2): |
| 50 | + """ |
| 51 | + Sends a HEAD request to check if an online resource is available. Uses a cache mechanism to speed things up or serve offline if a connection is unavailable. |
| 52 | +
|
| 53 | + :param url: url to check availability |
| 54 | + :param timeout: timeout of the request in seconds. Default is 2 |
| 55 | + :return: boolean False if the status code is between 400&600 , True otherwise |
| 56 | + """ |
| 57 | + result = False |
| 58 | + try: |
| 59 | + response = cached_requests.head(url, timeout=timeout) |
| 60 | + if not 400 <= response.status_code < 600: # We consider valid any status codes below 400 or above 600 |
| 61 | + result = True |
| 62 | + except IOError: |
| 63 | + result = False |
| 64 | + return result |
| 65 | + |
| 66 | + |
| 67 | +def resolve_art_url(art_type, appid, img_icon_path='', art_fallback_enabled=artFallbackEnabled): |
| 68 | + """ |
| 69 | + Resolve the art url of a specified game/app, for a given art type defined in the :const:`ARTS_DATA` dictionary. |
| 70 | + Handles fallback to another art type if needed (ie the requested one is unavailable and fallback is enabled). |
| 71 | +
|
| 72 | + :param art_type: a valid art type, defined in :const:`ARTS_DATA` |
| 73 | + :param appid: appid of the game/app we want to get the art for. |
| 74 | + :param img_icon_path: A path provided by steam to get the icon art url. https://developer.valvesoftware.com/wiki/Steam_Web_API#GetOwnedGames_.28v0001.29 |
| 75 | + :param art_fallback_enabled: Whether to fall back to another art type if an art is unavailable. Defaults to the user addon settings, which default to true |
| 76 | + :return: resolved art URL. Can be the URL of another available art if . |
| 77 | + """ |
| 78 | + valid_art_url = None |
| 79 | + requested_art = ARTS_ASSIGNMENTS.get(art_type, None) |
| 80 | + |
| 81 | + while valid_art_url is None and requested_art is not None: # If the current media type is defined and we did not find a valid url yet |
| 82 | + art_url = requested_art.get('url').format(appid=appid, img_icon_path=img_icon_path) # We replace "{appid}" and "{img_icon_path}" in the url |
| 83 | + fallback_art_type = requested_art.get("fallback", None) |
| 84 | + if (not art_fallback_enabled) or (fallback_art_type is None) or is_art_url_available(art_url): |
| 85 | + # If art fallback is disabled, or if there is no fallback defined, we directly assume the art url as valid. |
| 86 | + # Otherwise, if art fallback is enabled and there is a fallback defined, we check if is_art_url_available before proceeding |
| 87 | + valid_art_url = art_url |
| 88 | + else: # If art fallback is enabled and art is not available, we set the current art data to the defined fallback, before retrying. |
| 89 | + requested_art = ARTS_ASSIGNMENTS.get(fallback_art_type, None) # Art data will be None if the fallback_art_type does not exist in the art_urls dict |
| 90 | + |
| 91 | + if valid_art_url is None: # If the previous loop could not find a valid media url among the defined art types |
| 92 | + log("Issue resolving media {0} for app id {1}".format(art_type, appid)) |
| 93 | + |
| 94 | + return valid_art_url |
| 95 | + |
| 96 | + |
| 97 | +def delete_cache(): |
| 98 | + """ |
| 99 | + Deletes the cache containing the data about which art types are available or not |
| 100 | + """ |
| 101 | + os.remove(ART_AVAILABILITY_CACHE_FILE + ".sqlite") |
0 commit comments