GIF handling: skip GIFs in HTTPImageFetcher, filter GIF screenshots, fallback portrait URLs
All checks were successful
Build plugin / build (push) Successful in 1m11s
All checks were successful
Build plugin / build (push) Successful in 1m11s
- HTTPImageFetcher: skip GIF requests early (Godot can't decode), add webp support, try fallback formats - Art provider: filter GIF screenshots from banners, prefer non-GIF cover URLs via _best_portrait_url - Fix variable ordering bug (meta used before definition)
This commit is contained in:
parent
8a5dc41064
commit
d06cba872c
2 changed files with 54 additions and 13 deletions
|
|
@ -52,14 +52,10 @@ func get_boxart(item: LibraryItem, kind: LAYOUT) -> Texture2D:
|
||||||
|
|
||||||
var game_id := int(game.get("id", 0))
|
var game_id := int(game.get("id", 0))
|
||||||
var title: String = game.get("title", "")
|
var title: String = game.get("title", "")
|
||||||
var cover_url := _cover_url(game)
|
logger.info("get_boxart: title=%s id=%d layout=%s" % [title, game_id, str(kind)])
|
||||||
logger.info("get_boxart: title=%s id=%d cover_url=%s layout=%s" % [title, game_id, cover_url, str(kind)])
|
|
||||||
if title.is_empty():
|
if title.is_empty():
|
||||||
logger.warn("Game title is empty for id %d" % game_id)
|
logger.warn("Game title is empty for id %d" % game_id)
|
||||||
return null
|
return null
|
||||||
if cover_url.is_empty():
|
|
||||||
logger.warn("No cover URL for: %s (id=%d)" % [title, game_id])
|
|
||||||
return null
|
|
||||||
|
|
||||||
# Enrich on first access: scrape the itch.io page for screenshots.
|
# Enrich on first access: scrape the itch.io page for screenshots.
|
||||||
var meta: Dictionary = _enriched.get(game_id, {})
|
var meta: Dictionary = _enriched.get(game_id, {})
|
||||||
|
|
@ -77,15 +73,28 @@ func get_boxart(item: LibraryItem, kind: LAYOUT) -> Texture2D:
|
||||||
logger.warn("No URL in game dict for: %s" % title)
|
logger.warn("No URL in game dict for: %s" % title)
|
||||||
|
|
||||||
var screenshots: Array = meta.get("screenshots", [])
|
var screenshots: Array = meta.get("screenshots", [])
|
||||||
|
# Filter out GIF screenshots — Godot can't decode them.
|
||||||
|
var non_gif_screenshots: Array = screenshots.filter(
|
||||||
|
func(url): return not url.to_lower().ends_with(".gif")
|
||||||
|
)
|
||||||
|
if screenshots.size() != non_gif_screenshots.size():
|
||||||
|
logger.info("Filtered %d GIF screenshots for: %s" % [screenshots.size() - non_gif_screenshots.size(), title])
|
||||||
|
|
||||||
|
var cover_url := _best_portrait_url(game, non_gif_screenshots)
|
||||||
|
if cover_url.is_empty():
|
||||||
|
logger.warn("No cover URL for: %s (id=%d)" % [title, game_id])
|
||||||
|
return null
|
||||||
|
logger.info("cover_url=%s" % cover_url.left(80))
|
||||||
|
|
||||||
var cache_flags := Cache.FLAGS.NONE
|
var cache_flags := Cache.FLAGS.NONE
|
||||||
if use_caching:
|
if use_caching:
|
||||||
cache_flags = Cache.FLAGS.LOAD | Cache.FLAGS.SAVE
|
cache_flags = Cache.FLAGS.LOAD | Cache.FLAGS.SAVE
|
||||||
|
|
||||||
# Banner: combine two screenshots side-by-side when available.
|
# Banner: combine two screenshots side-by-side when available.
|
||||||
if kind == LAYOUT.BANNER and screenshots.size() >= 2:
|
if kind == LAYOUT.BANNER and non_gif_screenshots.size() >= 2:
|
||||||
logger.info("Fetching itch.io banner (2 screenshots) for: " + item.name)
|
logger.info("Fetching itch.io banner (2 screenshots) for: " + item.name)
|
||||||
var tex_a := await http_image.fetch(screenshots[0], cache_flags)
|
var tex_a := await http_image.fetch(non_gif_screenshots[0], cache_flags)
|
||||||
var tex_b := await http_image.fetch(screenshots[1], cache_flags)
|
var tex_b := await http_image.fetch(non_gif_screenshots[1], cache_flags)
|
||||||
if tex_a != null and tex_b != null:
|
if tex_a != null and tex_b != null:
|
||||||
return _combine_side_by_side(tex_a, tex_b)
|
return _combine_side_by_side(tex_a, tex_b)
|
||||||
# Fall through to single-image path if either failed.
|
# Fall through to single-image path if either failed.
|
||||||
|
|
@ -95,7 +104,7 @@ func get_boxart(item: LibraryItem, kind: LAYOUT) -> Texture2D:
|
||||||
if tex_b != null:
|
if tex_b != null:
|
||||||
return tex_b
|
return tex_b
|
||||||
|
|
||||||
var url := _url_for_layout(kind, cover_url, screenshots)
|
var url := _url_for_layout(kind, cover_url, non_gif_screenshots)
|
||||||
if url.is_empty():
|
if url.is_empty():
|
||||||
logger.warn("URL for layout %s is empty (cover=%s)" % [str(kind), cover_url])
|
logger.warn("URL for layout %s is empty (cover=%s)" % [str(kind), cover_url])
|
||||||
return null
|
return null
|
||||||
|
|
@ -130,20 +139,52 @@ func _url_for_layout(kind: LAYOUT, cover_url: String, screenshots: Array) -> Str
|
||||||
LAYOUT.GRID_PORTRAIT, LAYOUT.LOGO:
|
LAYOUT.GRID_PORTRAIT, LAYOUT.LOGO:
|
||||||
return cover_url
|
return cover_url
|
||||||
LAYOUT.GRID_LANDSCAPE:
|
LAYOUT.GRID_LANDSCAPE:
|
||||||
return screenshots[0] if not screenshots.is_empty() else cover_url
|
# Prefer a non-GIF screenshot
|
||||||
|
for url in screenshots:
|
||||||
|
if not url.to_lower().ends_with(".gif"):
|
||||||
|
return url
|
||||||
|
return cover_url
|
||||||
LAYOUT.BANNER:
|
LAYOUT.BANNER:
|
||||||
# Single screenshot fallback (two-screenshot case handled in get_boxart).
|
# Single screenshot fallback (two-screenshot case handled in get_boxart).
|
||||||
return screenshots[0] if not screenshots.is_empty() else cover_url
|
# Prefer a non-GIF screenshot.
|
||||||
|
for url in screenshots:
|
||||||
|
if not url.to_lower().ends_with(".gif"):
|
||||||
|
return url
|
||||||
|
return cover_url
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
func _cover_url(game: Dictionary) -> String:
|
func _cover_url(game: Dictionary) -> String:
|
||||||
var still: String = game.get("stillCoverUrl", "")
|
var still: String = game.get("stillCoverUrl", "")
|
||||||
if not still.is_empty():
|
if not still.is_empty() and not still.to_lower().ends_with(".gif"):
|
||||||
return still
|
return still
|
||||||
|
# stillCoverUrl is empty or is a GIF — prefer it if non-GIF alternatives
|
||||||
|
# aren't available, but log a warning.
|
||||||
|
if not still.is_empty():
|
||||||
|
logger.debug("stillCoverUrl is a GIF, will try alternatives: " + still)
|
||||||
return game.get("coverUrl", "")
|
return game.get("coverUrl", "")
|
||||||
|
|
||||||
|
|
||||||
|
## Returns a non-GIF URL suitable for portrait/logo from the game data.
|
||||||
|
## Falls back through: stillCoverUrl → coverUrl (non-GIF) → first non-GIF
|
||||||
|
## screenshot.
|
||||||
|
func _best_portrait_url(game: Dictionary, screenshots: Array) -> String:
|
||||||
|
# Prefer stillCoverUrl if it's not a GIF
|
||||||
|
var still: String = game.get("stillCoverUrl", "")
|
||||||
|
if not still.is_empty() and not still.to_lower().ends_with(".gif"):
|
||||||
|
return still
|
||||||
|
# Try coverUrl if it's not a GIF
|
||||||
|
var cover: String = game.get("coverUrl", "")
|
||||||
|
if not cover.is_empty() and not cover.to_lower().ends_with(".gif"):
|
||||||
|
return cover
|
||||||
|
# Try the first non-GIF screenshot
|
||||||
|
for url in screenshots:
|
||||||
|
if not url.to_lower().ends_with(".gif"):
|
||||||
|
return url
|
||||||
|
# Last resort: use whatever we have (GIF)
|
||||||
|
return still if not still.is_empty() else cover
|
||||||
|
|
||||||
|
|
||||||
## Combines two textures side-by-side into a single banner image.
|
## Combines two textures side-by-side into a single banner image.
|
||||||
## Both images are scaled to the same height, then placed left and right.
|
## Both images are scaled to the same height, then placed left and right.
|
||||||
func _combine_side_by_side(a: Texture2D, b: Texture2D) -> Texture2D:
|
func _combine_side_by_side(a: Texture2D, b: Texture2D) -> Texture2D:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"plugin.id": "itch-artprovider",
|
"plugin.id": "itch-artprovider",
|
||||||
"plugin.name": "itch.io Art Provider",
|
"plugin.name": "itch.io Art Provider",
|
||||||
"plugin.version": "0.1.3",
|
"plugin.version": "0.1.4",
|
||||||
"plugin.min-api-version": "1.1.0",
|
"plugin.min-api-version": "1.1.0",
|
||||||
"plugin.link": "https://forge.thergic.ar/jose/itchio-opengamepadui-artprovider-plugin",
|
"plugin.link": "https://forge.thergic.ar/jose/itchio-opengamepadui-artprovider-plugin",
|
||||||
"plugin.source": "https://forge.thergic.ar/jose/itchio-opengamepadui-artprovider-plugin",
|
"plugin.source": "https://forge.thergic.ar/jose/itchio-opengamepadui-artprovider-plugin",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue