Ensure new cover images only use basic image formats (#1229)

- Add new filter `ensure_magic_type` (`_plugins/ensure_magic_type.rb`) that checks the input magic type and compares it to its parameter. If it doesn't match, it fails the build. There's optional context parameters to explain the check.

- Add new filter `is_legacy_cover_image` (`_plugins/is_legacy.rb`) that
is essentially a grand-fathered clause check to see if we apply
a `ensure_magic_type` check for the cover image.
This commit is contained in:
Adam Scott
2026-01-21 05:40:32 -05:00
committed by GitHub
parent 079eb08a11
commit f18a4e1810
7 changed files with 401 additions and 1 deletions

View File

@@ -13,3 +13,6 @@ gem 'base64'
gem 'bigdecimal' gem 'bigdecimal'
gem "logger", "~> 1.7" gem "logger", "~> 1.7"
gem 'ruby-magic', '~> 0.6.0'

View File

@@ -96,6 +96,7 @@ GEM
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0) logger (1.7.0)
mercenary (0.4.0) mercenary (0.4.0)
mini_portile2 (2.8.9)
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
public_suffix (6.0.2) public_suffix (6.0.2)
@@ -105,6 +106,8 @@ GEM
ffi (~> 1.0) ffi (~> 1.0)
rexml (3.4.2) rexml (3.4.2)
rouge (4.5.2) rouge (4.5.2)
ruby-magic (0.6.0)
mini_portile2 (~> 2.8)
safe_yaml (1.0.5) safe_yaml (1.0.5)
sass-embedded (1.89.2) sass-embedded (1.89.2)
google-protobuf (~> 4.31) google-protobuf (~> 4.31)
@@ -178,6 +181,7 @@ DEPENDENCIES
jekyll-redirect-from jekyll-redirect-from
jekyll-watch jekyll-watch
logger (~> 1.7) logger (~> 1.7)
ruby-magic (~> 0.6.0)
webrick (~> 1.8) webrick (~> 1.8)
BUNDLED WITH BUNDLED WITH

View File

@@ -15,7 +15,12 @@
<meta property="og:url" content="https://godotengine.org{{ page.url }}"> <meta property="og:url" content="https://godotengine.org{{ page.url }}">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:description" content="{% if page.description %}{{ page.description }}{% elsif page.excerpt %}{{ page.excerpt }}{% else %}{% t meta.description %}{% endif %}"> <meta property="og:description" content="{% if page.description %}{{ page.description }}{% elsif page.excerpt %}{{ page.excerpt }}{% else %}{% t meta.description %}{% endif %}">
<meta property="og:image" content="{{ site.url }}{{ page.image | default: '/assets/share-image.webp' }}"> {% assign page_image = page.image | default: '/assets/share-image.jpg' %}
{% assign is_page_image_legacy = page_image | is_legacy_cover_image %}
{% if is_page_image_legacy != true %}
{% assign page_image = page_image | ensure_magic_type: 'jpeg,png,gif', 'Some websites (like Reddit) seem to fail to render cover images in formats other than basic ones.', 'webp:We know that WebP is one of the best formats for the Web. But for now, we ban the use of WebP images only in the context of cover images.' %}
{% endif %}
<meta property="og:image" content="{{ site.url }}{{ page_image }}">
<!-- meta tags --> <!-- meta tags -->
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta property="twitter:domain" content="godotengine.org"> <meta property="twitter:domain" content="godotengine.org">

View File

@@ -0,0 +1,111 @@
# Plugin for Jekyll that ensures known magic types.
# `ensure_magic_type` always returns the passed value.
# Though, it will raise an exception if the types don't match.
#
# Usage:
# {% assign page_image = page.image | ensure_magic_type: 'webp', 'WebP are preferred.', 'jpg:JPGs are way too big!' %}
#
# The first argument is a list of "known types". It's a comma-separated string of the below @@known_types keys.
#
# The last arguments are error contexts.
# If the error context begins with `<known_type>:`, it will be printed to add context to the exception.
# If the error don't begin with this prefix, it will be used as a general context to explain why checks are made.
require 'magic'
module Jekyll
module EnsureMagicTypeFilter
@@known_types = {
"jpeg" => "JPEG image data",
"jxl" => "JPEG XL codestream",
"png" => "PNG image data",
"gif" => "GIF image data",
"webp" => "Web/P image",
"avif" => "AVIF Image",
}
def ensure_magic_type(input, type, *error_contexts)
types = type.split(",")
types.each do |type|
if not @@known_types.key? type
raise Exception.new "\"#{type}\" is not a \"known type\" registered in `_plugins/ensure_magic_type.rb`. Please add the type in the `@@known_types` hash before testing."
end
end
site_config = @context.registers[:site].config
site_source = site_config["source"]
input_path = File.join(site_source, input)
magic = Magic.new
magic_of_input = magic.file(input_path)
magic_of_input_split = magic_of_input.split(", ")
detected_type = false
detected_type_key = nil
detected_type_text = ""
@@known_types.each do |known_type_name, known_type_magic|
if magic_of_input_split.include? known_type_magic
detected_type = true
detected_type_key = known_type_name
detected_type_text = known_type_name
break
end
end
raise_exception = false
if detected_type
if not types.include? detected_type_key
raise_exception = true
end
else
raise_exception = true
detected_type_text = magic_of_input
end
if raise_exception
msg = "\"#{input}\" is not of type \"#{types.join(", ")}\" (is of type \"#{detected_type_text}\")"
if error_contexts.size > 0
generic_error_context = nil
specific_error_context = nil
error_contexts.each do |error_context|
starts_with_known_type = false
@@known_types.keys.each do |known_type_key|
prefix = "#{known_type_key}:"
if error_context.start_with? prefix
starts_with_known_type = true
if detected_type && known_type_key == detected_type_text
specific_error_context = error_context[prefix.length..]
end
break
end
end
if not starts_with_known_type
if generic_error_context != nil
raise Exception.new "Multiple generic errors given."
end
generic_error_context = error_context
end
end
if generic_error_context != nil
msg += "\n#{generic_error_context}"
end
if detected_type and specific_error_context != nil
msg += "\n[#{detected_type_text}]: #{specific_error_context}"
end
end
raise Exception.new msg
end
input
end
end
end
Liquid::Template.register_filter(Jekyll::EnsureMagicTypeFilter)

277
_plugins/is_legacy.rb Normal file
View File

@@ -0,0 +1,277 @@
require "uri"
module Jekyll
module IsLegacyCoverImageFilter
@@legacy_covers = [
# WebP images.
"/assets/showcase/badaboom.webp",
"/assets/showcase/beat-invaders-header.webp",
"/assets/showcase/beat-invaders.webp",
"/assets/showcase/blastronaut-1.webp",
"/assets/showcase/blastronaut-2.webp",
"/assets/showcase/blastronaut-3.webp",
"/assets/showcase/blastronaut-header.webp",
"/assets/showcase/brotato-1.webp",
"/assets/showcase/brotato-2.webp",
"/assets/showcase/brotato-3.webp",
"/assets/showcase/brotato-header.webp",
"/assets/showcase/callbreak.webp",
"/assets/showcase/cassette-beasts-1.webp",
"/assets/showcase/cassette-beasts-2.webp",
"/assets/showcase/cassette-beasts-3.webp",
"/assets/showcase/cassette-beasts-header.webp",
"/assets/showcase/city-game-studio-header.webp",
"/assets/showcase/city-game-studio.webp",
"/assets/showcase/cruelty-squad-header.webp",
"/assets/showcase/delta-v-rings-of-saturn-header.webp",
"/assets/showcase/delta-v-rings-of-saturn.webp",
"/assets/showcase/dogwalk/header.webp",
"/assets/showcase/dome-keeper-header.webp",
"/assets/showcase/dome-keeper.webp",
"/assets/showcase/dungeondraft.webp",
"/assets/showcase/endoparasitic-header.webp",
"/assets/showcase/endoparasitic.webp",
"/assets/showcase/ex-zodiac-header.webp",
"/assets/showcase/ex-zodiac.webp",
"/assets/showcase/fist-of-the-forgotten-header.webp",
"/assets/showcase/fist-of-the-forgotten.webp",
"/assets/showcase/gnome-1.webp",
"/assets/showcase/gnome-2.webp",
"/assets/showcase/gnome-3.webp",
"/assets/showcase/gnome-header.webp",
"/assets/showcase/gravity-ace-header.webp",
"/assets/showcase/gravity-ace.webp",
"/assets/showcase/haiki-header.webp",
"/assets/showcase/haiki.webp",
"/assets/showcase/halls-of-torment-1.webp",
"/assets/showcase/halls-of-torment-2.webp",
"/assets/showcase/halls-of-torment-3.webp",
"/assets/showcase/halls-of-torment-4.webp",
"/assets/showcase/halls-of-torment.webp",
"/assets/showcase/hive-time.webp",
"/assets/showcase/human-diaspora-header.webp",
"/assets/showcase/human-diaspora.webp",
"/assets/showcase/kingdoms-of-the-dump-header.webp",
"/assets/showcase/lumencraft-1.webp",
"/assets/showcase/lumencraft-3.webp",
"/assets/showcase/lumencraft-header.webp",
"/assets/showcase/madness-endless-header.webp",
"/assets/showcase/of-life-and-land-1.webp",
"/assets/showcase/of-life-and-land-2.webp",
"/assets/showcase/of-life-and-land-3.webp",
"/assets/showcase/of-life-and-land-4.webp",
"/assets/showcase/of-life-and-land-header.webp",
"/assets/showcase/outskirts-godot-environment-demo.webp",
"/assets/showcase/pingo-adventure-header.webp",
"/assets/showcase/pingo-adventure.webp",
"/assets/showcase/precipice-header.webp",
"/assets/showcase/precipice.webp",
"/assets/showcase/primal-light-1.webp",
"/assets/showcase/primal-light-2.webp",
"/assets/showcase/primal-light-header.webp",
"/assets/showcase/resolutiion-1.webp",
"/assets/showcase/resolutiion-2.webp",
"/assets/showcase/resolutiion-header.webp",
"/assets/showcase/rogue-state-revolution-header.webp",
"/assets/showcase/rogue-state-revolution.webp",
"/assets/showcase/rpg-in-a-box-header.webp",
"/assets/showcase/rpg-in-a-box.webp",
"/assets/showcase/sandals-1.webp",
"/assets/showcase/sandals-2.webp",
"/assets/showcase/sandals-header.webp",
"/assets/showcase/tail-quest-header.webp",
"/assets/showcase/tail-quest.webp",
"/assets/showcase/tailquest-1.webp",
"/assets/showcase/tailquest-2.webp",
"/assets/showcase/until-then-1.webp",
"/assets/showcase/until-then-2.webp",
"/assets/showcase/until-then-3.webp",
"/assets/showcase/until-then-4.webp",
"/assets/showcase/until-then-header.webp",
"/assets/showcase/usagi-shima-1.webp",
"/assets/showcase/usagi-shima-4.webp",
"/assets/showcase/usagi-shima-5.webp",
"/assets/showcase/usagi-shima-header.webp",
"/assets/showcase/usagi-shima-scr03.webp",
"/assets/showcase/usagi-shima.webp",
"/assets/showcase/wrought-flesh-1.webp",
"/assets/showcase/wrought-flesh-header.webp",
"/storage/blog/2024-cherry-picks/2024-cherry-picks.webp",
"/storage/blog/covers/about-official-console-ports.webp",
"/storage/blog/covers/android-and-vulkan.webp",
"/storage/blog/covers/august-2025-update-godot-xr-community.webp",
"/storage/blog/covers/beyond-100000-you-re-breathtaking.webp",
"/storage/blog/covers/big-2023.webp",
"/storage/blog/covers/cassette-beasts.webp",
"/storage/blog/covers/community-poll-2024.webp",
"/storage/blog/covers/december-2024-update-godot-xr-community.webp",
"/storage/blog/covers/design-of-the-skeleton-modifier-3d.webp",
"/storage/blog/covers/dev-snapshot-godot-3-6-beta-3.webp",
"/storage/blog/covers/dev-snapshot-godot-3-6-beta-4.webp",
"/storage/blog/covers/dev-snapshot-godot-3-6-beta-5.webp",
"/storage/blog/covers/dev-snapshot-godot-4-1-beta-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-1-beta-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-1-beta-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-beta-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-beta-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-beta-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-beta-4.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-beta-5.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-beta-6.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-dev-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-dev-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-dev-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-dev-4.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-dev-5.webp",
"/storage/blog/covers/dev-snapshot-godot-4-2-dev-6.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-beta-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-beta-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-beta-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-dev-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-dev-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-dev-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-dev-4.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-dev-5.webp",
"/storage/blog/covers/dev-snapshot-godot-4-3-dev-6.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-beta-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-beta-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-beta-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-beta-4.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-dev-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-dev-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-dev-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-dev-4.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-dev-5.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-dev-6.webp",
"/storage/blog/covers/dev-snapshot-godot-4-4-dev-7.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-beta-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-beta-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-beta-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-beta-4.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-beta-5.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-beta-6.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-beta-7.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-dev-1.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-dev-2.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-dev-3.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-dev-4.webp",
"/storage/blog/covers/dev-snapshot-godot-4-5-dev-5.webp",
"/storage/blog/covers/dev-snapshot-godot-4-6-dev-1.webp",
"/storage/blog/covers/development-fund-perks.webp",
"/storage/blog/covers/ensuring-quality-godot-documentation.webp",
"/storage/blog/covers/february-2025-update-godot-xr-community.webp",
"/storage/blog/covers/funding-breakdown-and-hiring-process.webp",
"/storage/blog/covers/gamescom-cover-2024.webp",
"/storage/blog/covers/gamescom-latam-2024.webp",
"/storage/blog/covers/godot-3-6-finally-released.webp",
"/storage/blog/covers/godot-4-0-sets-sail.webp",
"/storage/blog/covers/godot-4-1-is-here.webp",
"/storage/blog/covers/godot-4-2-arrives-in-style.webp",
"/storage/blog/covers/godot-4-3-a-shared-effort.webp",
"/storage/blog/covers/godot-4-4-a-unified-experience.webp",
"/storage/blog/covers/godot-4-5-making-dreams-accessible.webp",
"/storage/blog/covers/godot-and-android-and-vulkan.webp",
"/storage/blog/covers/godot-awards-stayathomedev.webp",
"/storage/blog/covers/godot-editor-horizon-store-early-access-release.webp",
"/storage/blog/covers/godot-foundation-update-2024.webp",
"/storage/blog/covers/godot-jam.webp",
"/storage/blog/covers/godot-jetbrains.webp",
"/storage/blog/covers/godot-openxr-vendors-plugin-400.webp",
"/storage/blog/covers/godot-sdk-integrations.webp",
"/storage/blog/covers/godot-showcase-the-somar-project.webp",
"/storage/blog/covers/godot-showreel-2023.webp",
"/storage/blog/covers/godot-showreel-2024.webp",
"/storage/blog/covers/godot-xr-update-sep-2023.webp",
"/storage/blog/covers/godot_peluche_2025_5.webp",
"/storage/blog/covers/godotcon-2023-we-need-you.webp",
"/storage/blog/covers/godotcon-2023.webp",
"/storage/blog/covers/godotcon-2024-post.webp",
"/storage/blog/covers/godotcon-2024.webp",
"/storage/blog/covers/godotcon-boston-2025-android-xr.webp",
"/storage/blog/covers/godotcon-boston-2025.webp",
"/storage/blog/covers/godotfest-2025.webp",
"/storage/blog/covers/godotsharp-packages-net8.webp",
"/storage/blog/covers/gpu-sync-progress-report.webp",
"/storage/blog/covers/introducing-new-forum.webp",
"/storage/blog/covers/introducing-reverse-z.webp",
"/storage/blog/covers/live-from-godotcon-boston-web-net-prototype.webp",
"/storage/blog/covers/maintenance-release-godot-3-5-3.webp",
"/storage/blog/covers/maintenance-release-godot-4-0-4.webp",
"/storage/blog/covers/maintenance-release-godot-4-1-1.webp",
"/storage/blog/covers/maintenance-release-godot-4-1-2.webp",
"/storage/blog/covers/maintenance-release-godot-4-1-3.webp",
"/storage/blog/covers/maintenance-release-godot-4-2-1.webp",
"/storage/blog/covers/maintenance-release-godot-4-2-2-and-4-1-4.webp",
"/storage/blog/covers/maintenance-release-godot-4-4-1.webp",
"/storage/blog/covers/march-2025-update-godot-xr-community.webp",
"/storage/blog/covers/migrating-animations-from-godot-4-0-to-4-3.webp",
"/storage/blog/covers/moat_logo_large_colorful_over_screenshot.webp",
"/storage/blog/covers/october-2024-xr-progress-update.webp",
"/storage/blog/covers/progress-report-new-ufbx-importer.webp",
"/storage/blog/covers/progress-report-parallax2d.webp",
"/storage/blog/covers/progress-report-state-of-particles.webp",
"/storage/blog/covers/progress-report-web-export-in-4-3.webp",
"/storage/blog/covers/progress-update-csharp-2.webp",
"/storage/blog/covers/release-candidate-godot-3-5-3-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-3-6-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-0-4-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-1-1-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-1-2-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-1-3-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-1-4-and-4-2-2-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-1-4-and-4-2-2-rc-2.webp",
"/storage/blog/covers/release-candidate-godot-4-1-4-and-4-2-2-rc-3.webp",
"/storage/blog/covers/release-candidate-godot-4-1-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-1-rc-2.webp",
"/storage/blog/covers/release-candidate-godot-4-1-rc-3.webp",
"/storage/blog/covers/release-candidate-godot-4-2-1-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-2-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-2-rc-2.webp",
"/storage/blog/covers/release-candidate-godot-4-3-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-3-rc-2.webp",
"/storage/blog/covers/release-candidate-godot-4-3-rc-3.webp",
"/storage/blog/covers/release-candidate-godot-4-4-1-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-4-1-rc-2.webp",
"/storage/blog/covers/release-candidate-godot-4-4-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-4-rc-2.webp",
"/storage/blog/covers/release-candidate-godot-4-4-rc-3.webp",
"/storage/blog/covers/release-candidate-godot-4-5-1-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-5-1-rc-2.webp",
"/storage/blog/covers/release-candidate-godot-4-5-rc-1.webp",
"/storage/blog/covers/release-candidate-godot-4-5-rc-2.webp",
"/storage/blog/covers/rendering-priorities-jan-2024.webp",
"/storage/blog/covers/rendering-priorities-july-2023.webp",
"/storage/blog/covers/rendering-priorities-september-2024.webp",
"/storage/blog/covers/review-gamescom-2024.webp",
"/storage/blog/covers/statement-on-godloader-malware-loader.webp",
"/storage/blog/covers/uid-changes-coming-to-godot-4-4.webp",
"/storage/blog/covers/upcoming-serious-web-performance-boost.webp",
"/storage/blog/covers/usagi-shima.webp",
"/storage/blog/gamescom/2023/gamescom-cover.webp",
"/storage/blog/gdc/2024/gdc-thumbnail.webp",
"/storage/blog/gdc/2024/gdc2.webp",
"/storage/blog/godotcon-us-2025/looking-back-godotcon-2025.webp",
# SVG images.
"/storage/app/uploads/public/57f/218/5f6/57f2185f638af084238366.svg",
"/storage/app/uploads/public/57e/f83/c4e/57ef83c4e718d700719754.svg",
"/storage/app/uploads/public/5a9/681/3c1/5a96813c154bd636778829.svg",
"/storage/app/uploads/public/5dc/019/1f9/5dc0191f92385004371530.svg",
]
def is_legacy_cover_image(input)
input_elements = Array(input)
input_elements.each do |input_element|
uri = URI(input_element)
if not @@legacy_covers.include? uri.path
return false
end
end
true
end
end
end
Liquid::Template.register_filter(Jekyll::IsLegacyCoverImageFilter)

BIN
assets/share-image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB