|
8 | 8 | from util import show_error |
9 | 9 |
|
10 | 10 | __addon__ = xbmcaddon.Addon() |
11 | | -enableArtFallback = __addon__.getSetting("enable-art-fallback") == 'true' # Kodi stores boolean settings as strings |
| 11 | +artFallbackEnabled = __addon__.getSetting("enable-art-fallback") == 'true' # Kodi stores boolean settings as strings |
12 | 12 | minutesBeforeArtsExpiration = int(__addon__.getSetting("arts-expire-after-minutes")) # Default is 2 months |
13 | 13 |
|
14 | 14 | # define the cache file to reside in the ..\Kodi\userdata\addon_data\(your addon) |
|
17 | 17 |
|
18 | 18 | cached_requests = requests_cache.core.CachedSession(ART_AVAILABILITY_CACHE_FILE, backend='sqlite', |
19 | 19 | expire_after=60 * minutesBeforeArtsExpiration, |
20 | | - allowable_methods=('HEAD',), |
21 | | - allowable_codes=(200, 404), |
| 20 | + allowable_methods=('HEAD',), allowable_codes=(200, 404), |
22 | 21 | old_data_on_error=True, |
23 | 22 | fast_save=True) |
24 | 23 |
|
25 | | -# Todo : tweak fallbacks and arts to have the best look in Kodi |
26 | | -# TODO note which are safe to use and which can be missing |
27 | | - |
28 | 24 | # Dictionary containing for each art type, a base_url for the art (to format with appid / img_icon_path afterwards), and a fallback art type. |
29 | | -arts_urls = { # {0} is appid, {1} is a special path provided by steam api for the icon |
30 | | - 'poster': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{0}/library_600x900.jpg', 'fallback_media': 'landscape'}, |
31 | | - 'banner': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{0}/library_hero.jpg', 'fallback_media': 'landscape'}, |
32 | | - 'landscape': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{0}/header.jpg', 'fallback_media': None}, |
33 | | - 'thumb': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{0}/header.jpg', 'fallback_media': None}, |
34 | | - 'fanart': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{0}/library_hero.jpg', 'fallback_media': 'fanart2'}, |
35 | | - 'fanart2': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{0}/header.jpg', 'fallback_media': 'fanart3'}, |
36 | | - 'fanart3': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{0}/page_bg_generated_v6b.jpg', 'fallback_media': None}, |
37 | | - 'icon': {'base_url': 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.jpg', 'fallback_media': None}, |
38 | | - 'clearlogo': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{0}/logo.png', 'fallback_media': None} # No fallback possible for clearlogo |
| 25 | +ARTS_DATA = { # img_icon_path is a path provided by steam to get the icon. https://developer.valvesoftware.com/wiki/Steam_Web_API#GetOwnedGames_.28v0001.29 |
| 26 | + 'poster': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/library_600x900.jpg', 'fallback_media': 'landscape'}, # Can return 404 |
| 27 | + 'banner': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/library_hero.jpg', 'fallback_media': 'landscape'}, # Can return 404 |
| 28 | + 'landscape': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/header.jpg', 'fallback_media': None}, |
| 29 | + 'thumb': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/header.jpg', 'fallback_media': None}, |
| 30 | + 'fanart': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/library_hero.jpg', 'fallback_media': 'fanart2'}, # Can return 404 |
| 31 | + 'fanart2': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/header.jpg', 'fallback_media': 'fanart3'}, |
| 32 | + 'fanart3': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/page_bg_generated_v6b.jpg', 'fallback_media': None}, |
| 33 | + 'icon': {'base_url': 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{appid}/{img_icon_path}.jpg', 'fallback_media': None}, |
| 34 | + 'clearlogo': {'base_url': 'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/logo.png', 'fallback_media': None} # Can return 404 |
39 | 35 | } |
40 | 36 |
|
41 | 37 |
|
42 | 38 | def is_art_url_available(url, timeout=2): |
43 | 39 | """ |
44 | | - Sends a HEAD request to check if an online resource is available. Uses a cache mechanism to speed things up or serve offline. |
| 40 | + 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. |
45 | 41 |
|
46 | 42 | :param url: url to check availability |
47 | | - :param timeout: timeout of the request in seconds. Default 2 |
| 43 | + :param timeout: timeout of the request in seconds. Default is 2 |
48 | 44 | :return: boolean False if the status code is between 400&600 , True otherwise |
49 | 45 | """ |
50 | 46 | result = False |
51 | 47 | try: |
52 | 48 | response = cached_requests.head(url, timeout=timeout) |
53 | | - if not 400 <= response.status_code < 600: # We consider valid any status codes belwo 400 or above 600 |
| 49 | + if not 400 <= response.status_code < 600: # We consider valid any status codes below 400 or above 600 |
54 | 50 | result = True |
55 | 51 | except IOError: |
56 | 52 | result = False |
57 | 53 | return result |
58 | 54 |
|
59 | 55 |
|
60 | | -def resolve_media_url(media_type, appid, img_icon_path=''): |
61 | | - valid_media_url = None |
62 | | - |
63 | | - art_data = arts_urls.get(media_type, None) |
64 | | - |
65 | | - while valid_media_url is None and art_data is not None: |
66 | | - art_url = art_data.get('base_url').format(appid, img_icon_path) |
67 | | - if not enableArtFallback or is_art_url_available(art_url): |
68 | | - # If art fallback is disabled, we directly assume the art url as valid. |
69 | | - # If art fallback is enabled, we check if the art is available before proceeding |
70 | | - valid_media_url = art_url |
71 | | - else: |
72 | | - # If the art is not available (and art fallback is enabled) we try with the defined fallback |
73 | | - fallback_media_type = art_data.get("fallback", None) |
74 | | - art_data = arts_urls.get(fallback_media_type, None) |
75 | | - |
76 | | - if valid_media_url is None: |
77 | | - show_error(None, "Issue obtaining a media", False) |
| 56 | +def resolve_art_url(art_type, appid, img_icon_path='', art_fallback_enabled=artFallbackEnabled): |
| 57 | + """ |
| 58 | + Resolve the art url of a specified game/app, for a given art type defined in the :const:`ARTS_DATA` dictionary. |
| 59 | + Handles fallback to another art type if needed (ie the requested one is unavailable and fallback is enabled). |
| 60 | +
|
| 61 | + :param art_type: a valid art type, defined in :const:`ARTS_DATA` |
| 62 | + :param appid: appid of the game/app we want to get the art for. |
| 63 | + :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 |
| 64 | + :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 |
| 65 | + :return: resolved art URL. Can be the URL of another available art if . |
| 66 | + """ |
| 67 | + valid_art_url = None |
| 68 | + requested_art_data = ARTS_DATA.get(art_type, None) |
| 69 | + |
| 70 | + while valid_art_url is None and requested_art_data is not None: # If the current media type is defined and we did not find a valid url yet |
| 71 | + art_url = requested_art_data.get('base_url').format(appid=appid, img_icon_path=img_icon_path) # We replace "{appid}" and "{img_icon_path}" in the url |
| 72 | + fallback_art_type = requested_art_data.get("fallback_media", None) |
| 73 | + if (not art_fallback_enabled) or (fallback_art_type is None) or is_art_url_available(art_url): |
| 74 | + # If art fallback is disabled, or if there is no fallback defined, we directly assume the art url as valid. |
| 75 | + # Otherwise, if art fallback is enabled and there is a fallback defined, we check if is_art_url_available before proceeding |
| 76 | + valid_art_url = art_url |
| 77 | + else: # If art fallback is enabled and art is not available, we set the current art data to the defined fallback, before retrying. |
| 78 | + requested_art_data = ARTS_DATA.get(fallback_art_type, None) # Art data will be None if the fallback_art_type does not exist in the art_urls dict |
| 79 | + |
| 80 | + if valid_art_url is None: # If the previous loop could not find a valid media url among the defined art types |
| 81 | + show_error(None, "Issue obtaining a media", display_notification=False) |
78 | 82 | return |
79 | 83 |
|
80 | | - return valid_media_url |
| 84 | + return valid_art_url |
81 | 85 |
|
82 | 86 |
|
83 | 87 | def delete_cache(): |
| 88 | + """ |
| 89 | + Deletes the cache containing the data about which art types are available or not |
| 90 | + """ |
84 | 91 | os.remove(ART_AVAILABILITY_CACHE_FILE + ".sqlite") |
0 commit comments