fix: handle itch.zone serving PNG for .jpg URLs

itch.zone returns content-type: image/png for .jpg URLs. HTTPImageFetcher
uses URL extension to pick the decoder, causing load_jpg_from_buffer to
fail on PNG data. Added format detection fallback that checks Content-Type
headers when the normal fetch fails for .jpg/.jpeg URLs.

Bump to v0.1.16
This commit is contained in:
Jose Falanga 2026-08-20 00:11:46 -03:00
parent ce30f647c8
commit 101bbd17cd

View file

@ -22,6 +22,7 @@ const LOG_URL_MAX_LEN := 80
const SETTING_SECTION := "plugin.artprovider" const SETTING_SECTION := "plugin.artprovider"
const SETTING_ANIMATED_GIFS := "animated_gifs" const SETTING_ANIMATED_GIFS := "animated_gifs"
const GIF_DEFAULT_FRAME_DELAY_MS := 100 const GIF_DEFAULT_FRAME_DELAY_MS := 100
const CACHE_DIR := "images"
@export var use_caching: bool = true @export var use_caching: bool = true
var http_image := HTTPImageFetcher.new() var http_image := HTTPImageFetcher.new()
@ -118,6 +119,71 @@ func _is_gif_url(url: String) -> bool:
return _sanitize_url(url).to_lower().ends_with(".gif") return _sanitize_url(url).to_lower().ends_with(".gif")
## Returns whether a URL points to a JPEG image.
func _is_jpg_url(url: String) -> bool:
var clean := _sanitize_url(url).to_lower()
return clean.ends_with(".jpg") or clean.ends_with(".jpeg")
## Removes and frees an HTTPRequest node.
func _remove_http(http: HTTPRequest) -> void:
remove_child(http)
http.queue_free()
## Downloads an image and detects the actual format from content-type headers.
## Used as a fallback when HTTPImageFetcher fails due to extension mismatch.
func _fetch_image_with_format_detection(url: String, cache_flags: int) -> Texture2D:
var http := HTTPRequest.new()
http.timeout = HTTP_TIMEOUT_MS / 1000.0
add_child.call_deferred(http)
await http.ready
if http.request(url) != OK:
_remove_http(http)
return null
var args: Array = await http.request_completed
var result: int = args[0]
var response_code: int = args[1]
var headers: PackedStringArray = args[2]
var body: PackedByteArray = args[3]
_remove_http(http)
if result != HTTPRequest.RESULT_SUCCESS or response_code != 200:
return null
# Detect actual format from Content-Type header.
var content_type := ""
for h in headers:
if h.to_lower().begins_with("content-type:"):
content_type = h.split(":", true, 1)[1].strip_edges().to_lower()
break
var image := Image.new()
var err: int = ERR_INVALID_DATA
if content_type.find("png") != -1:
err = image.load_png_from_buffer(body)
elif content_type.find("jpeg") != -1 or content_type.find("jpg") != -1:
err = image.load_jpg_from_buffer(body)
elif content_type.find("webp") != -1:
err = image.load_webp_from_buffer(body)
else:
# Unknown type — try PNG first, then JPEG.
err = image.load_png_from_buffer(body)
if err != OK:
err = image.load_jpg_from_buffer(body)
if err != OK:
logger.warn("Format detection failed for %s (content-type: %s)" % [url.left(LOG_URL_MAX_LEN), content_type])
return null
var texture := ImageTexture.create_from_image(image)
if cache_flags & Cache.FLAGS.SAVE:
Cache.save_image(CACHE_DIR, url, texture)
return texture
func get_boxart(item: LibraryItem, kind: LAYOUT) -> Texture2D: func get_boxart(item: LibraryItem, kind: LAYOUT) -> Texture2D:
if not kind in layout_map: if not kind in layout_map:
logger.error("Unsupported boxart layout: {0}".format([kind])) logger.error("Unsupported boxart layout: {0}".format([kind]))
@ -373,7 +439,13 @@ func _fetch_image(url: String, cache_flags: int) -> Texture2D:
_last_animated_setting = animated _last_animated_setting = animated
logger.info("Animated GIF setting changed to %s — cache cleared" % str(animated)) logger.info("Animated GIF setting changed to %s — cache cleared" % str(animated))
return await _fetch_gif_as_texture(url, cache_flags) return await _fetch_gif_as_texture(url, cache_flags)
return await http_image.fetch(url, cache_flags) # HTTPImageFetcher uses the URL extension to pick the decoder, but
# itch.zone often serves PNG data for .jpg URLs. If the normal fetch
# fails for a .jpg/.jpeg URL, retry by detecting the actual format.
var texture := await http_image.fetch(url, cache_flags)
if texture == null and _is_jpg_url(url):
texture = await _fetch_image_with_format_detection(url, cache_flags)
return texture
## Downloads a GIF and converts it to a Godot texture. When animated GIFs ## Downloads a GIF and converts it to a Godot texture. When animated GIFs