🎨 Made OnlineCoverManager more general
- Does compositing of image with a blurred background - Stretches the original image if it's not too much - Handles images that are too wide and images that are too tall - Removed ItchCoverManager
This commit is contained in:
@@ -50,7 +50,7 @@ class ItchSourceIterator(SourceIterator):
|
||||
"game_id": self.source.game_id_format.format(game_id=row[0]),
|
||||
"executable": self.source.executable_format.format(cave_id=row[4]),
|
||||
}
|
||||
additional_data = {"itch_cover_url": row[2], "itch_still_cover_url": row[3]}
|
||||
additional_data = {"online_cover_url": row[3] or row[2]}
|
||||
game = Game(values, allow_side_effects=False)
|
||||
yield (game, additional_data)
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@ from src.importer.sources.itch_source import ItchSource
|
||||
from src.importer.sources.legendary_source import LegendarySource
|
||||
from src.importer.sources.lutris_source import LutrisSource
|
||||
from src.importer.sources.steam_source import SteamSource
|
||||
from src.logging.setup import setup_logging, log_system_info
|
||||
from src.logging.setup import log_system_info, setup_logging
|
||||
from src.preferences import PreferencesWindow
|
||||
from src.store.managers.display_manager import DisplayManager
|
||||
from src.store.managers.file_manager import FileManager
|
||||
from src.store.managers.itch_cover_manager import ItchCoverManager
|
||||
from src.store.managers.local_cover_manager import LocalCoverManager
|
||||
from src.store.managers.online_cover_manager import OnlineCoverManager
|
||||
from src.store.managers.sgdb_manager import SGDBManager
|
||||
from src.store.managers.steam_api_manager import SteamAPIManager
|
||||
from src.store.store import Store
|
||||
@@ -89,7 +89,7 @@ class CartridgesApplication(Adw.Application):
|
||||
# Add rest of the managers for game imports
|
||||
shared.store.add_manager(LocalCoverManager())
|
||||
shared.store.add_manager(SteamAPIManager())
|
||||
shared.store.add_manager(ItchCoverManager())
|
||||
shared.store.add_manager(OnlineCoverManager())
|
||||
shared.store.add_manager(SGDBManager())
|
||||
shared.store.add_manager(FileManager())
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from gi.repository import GdkPixbuf, Gio
|
||||
from requests.exceptions import HTTPError, SSLError
|
||||
|
||||
from src import shared # pylint: disable=no-name-in-module
|
||||
from src.game import Game
|
||||
from src.store.managers.local_cover_manager import LocalCoverManager
|
||||
from src.store.managers.manager import Manager
|
||||
from src.utils.save_cover import resize_cover, save_cover
|
||||
|
||||
|
||||
# TODO Remove by generalizing OnlineCoverManager
|
||||
|
||||
|
||||
class ItchCoverManager(Manager):
|
||||
"""Manager in charge of downloading the game's cover from itch.io"""
|
||||
|
||||
run_after = (LocalCoverManager,)
|
||||
retryable_on = (HTTPError, SSLError)
|
||||
|
||||
def manager_logic(self, game: Game, additional_data: dict) -> None:
|
||||
# Get the first matching cover url
|
||||
base_cover_url: str = additional_data.get("itch_cover_url", None)
|
||||
still_cover_url: str = additional_data.get("itch_still_cover_url", None)
|
||||
cover_url = still_cover_url or base_cover_url
|
||||
if not cover_url:
|
||||
return
|
||||
|
||||
# Download cover
|
||||
tmp_file = Gio.File.new_tmp()[0]
|
||||
with requests.get(cover_url, timeout=5) as cover:
|
||||
cover.raise_for_status()
|
||||
Path(tmp_file.get_path()).write_bytes(cover.content)
|
||||
|
||||
# Create background blur
|
||||
game_cover = GdkPixbuf.Pixbuf.new_from_stream_at_scale(
|
||||
tmp_file.read(), 2, 2, False
|
||||
).scale_simple(*shared.image_size, GdkPixbuf.InterpType.BILINEAR)
|
||||
|
||||
# Resize square image
|
||||
itch_pixbuf = GdkPixbuf.Pixbuf.new_from_stream(tmp_file.read())
|
||||
itch_pixbuf = itch_pixbuf.scale_simple(
|
||||
shared.image_size[0],
|
||||
itch_pixbuf.get_height() * (shared.image_size[0] / itch_pixbuf.get_width()),
|
||||
GdkPixbuf.InterpType.BILINEAR,
|
||||
)
|
||||
|
||||
# Composite
|
||||
itch_pixbuf.composite(
|
||||
game_cover,
|
||||
0,
|
||||
(shared.image_size[1] - itch_pixbuf.get_height()) / 2,
|
||||
itch_pixbuf.get_width(),
|
||||
itch_pixbuf.get_height(),
|
||||
0,
|
||||
(shared.image_size[1] - itch_pixbuf.get_height()) / 2,
|
||||
1.0,
|
||||
1.0,
|
||||
GdkPixbuf.InterpType.BILINEAR,
|
||||
255,
|
||||
)
|
||||
|
||||
# Resize and save the cover
|
||||
save_cover(game.game_id, resize_cover(pixbuf=game_cover))
|
||||
@@ -1,9 +1,12 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gio, GdkPixbuf
|
||||
from requests.exceptions import HTTPError, SSLError
|
||||
from PIL import Image
|
||||
|
||||
from src import shared
|
||||
from src.game import Game
|
||||
from src.store.managers.local_cover_manager import LocalCoverManager
|
||||
from src.store.managers.manager import Manager
|
||||
@@ -16,15 +19,89 @@ class OnlineCoverManager(Manager):
|
||||
run_after = (LocalCoverManager,)
|
||||
retryable_on = (HTTPError, SSLError)
|
||||
|
||||
def save_composited_cover(
|
||||
self,
|
||||
game: Game,
|
||||
image_file: Gio.File,
|
||||
original_width: int,
|
||||
original_height: int,
|
||||
target_width: int,
|
||||
target_height: int,
|
||||
) -> None:
|
||||
"""Save the image composited with a background blur to fit the cover size"""
|
||||
|
||||
logging.debug(
|
||||
"Compositing image for %s (%s) %dx%d -> %dx%d",
|
||||
game.name,
|
||||
game.game_id,
|
||||
original_width,
|
||||
original_height,
|
||||
target_width,
|
||||
target_height,
|
||||
)
|
||||
|
||||
# Load game image
|
||||
image = GdkPixbuf.Pixbuf.new_from_stream(image_file.read())
|
||||
|
||||
# Create background blur of the size of the cover
|
||||
cover = image.scale_simple(2, 2, GdkPixbuf.InterpType.BILINEAR).scale_simple(
|
||||
target_width, target_height, GdkPixbuf.InterpType.BILINEAR
|
||||
)
|
||||
|
||||
# Center the image above the blurred background
|
||||
scale = min(target_width / original_width, target_height / original_height)
|
||||
left_padding = (target_width - original_width * scale) / 2
|
||||
top_padding = (target_height - original_height * scale) / 2
|
||||
image.composite(
|
||||
cover,
|
||||
# Top left of overwritten area on the destination
|
||||
left_padding,
|
||||
top_padding,
|
||||
# Size of the overwritten area on the destination
|
||||
original_width * scale,
|
||||
original_height * scale,
|
||||
# Offset
|
||||
left_padding,
|
||||
top_padding,
|
||||
# Scale to apply to the resized image
|
||||
scale,
|
||||
scale,
|
||||
# Compositing stuff
|
||||
GdkPixbuf.InterpType.BILINEAR,
|
||||
255,
|
||||
)
|
||||
|
||||
# Resize and save the cover
|
||||
save_cover(game.game_id, resize_cover(pixbuf=cover))
|
||||
|
||||
def manager_logic(self, game: Game, additional_data: dict) -> None:
|
||||
# Ensure that we have a cover to download
|
||||
cover_url = additional_data.get("online_cover_url", None)
|
||||
cover_url = additional_data.get("online_cover_url")
|
||||
if not cover_url:
|
||||
return
|
||||
|
||||
# Download cover
|
||||
tmp_file = Gio.File.new_tmp()[0]
|
||||
image_file = Gio.File.new_tmp()[0]
|
||||
image_path = Path(image_file.get_path())
|
||||
with requests.get(cover_url, timeout=5) as cover:
|
||||
cover.raise_for_status()
|
||||
Path(tmp_file.get_path()).write_bytes(cover.content)
|
||||
# Resize and save
|
||||
save_cover(game.game_id, resize_cover(tmp_file.get_path()))
|
||||
image_path.write_bytes(cover.content)
|
||||
|
||||
# Get image size
|
||||
cover_width, cover_height = shared.image_size
|
||||
with Image.open(image_path) as pil_image:
|
||||
width, height = pil_image.size
|
||||
|
||||
# Composite the image if its aspect ratio differs too much
|
||||
# (allow the side that is smaller to be stretched by a small percentage)
|
||||
max_diff_proportion = 0.12
|
||||
scale = min(cover_width / width, cover_height / height)
|
||||
width_diff = (cover_width - (width * scale)) / cover_width
|
||||
height_diff = (cover_height - (height * scale)) / cover_height
|
||||
diff = width_diff + height_diff
|
||||
if diff < max_diff_proportion:
|
||||
save_cover(game.game_id, resize_cover(image_path))
|
||||
else:
|
||||
self.save_composited_cover(
|
||||
game, image_file, width, height, cover_width, cover_height
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from requests.exceptions import HTTPError, SSLError
|
||||
|
||||
from src.game import Game
|
||||
from src.store.managers.async_manager import AsyncManager
|
||||
from src.store.managers.itch_cover_manager import ItchCoverManager
|
||||
from src.store.managers.online_cover_manager import OnlineCoverManager
|
||||
from src.store.managers.local_cover_manager import LocalCoverManager
|
||||
from src.store.managers.steam_api_manager import SteamAPIManager
|
||||
from src.utils.steamgriddb import SGDBAuthError, SGDBHelper
|
||||
@@ -13,7 +13,7 @@ from src.utils.steamgriddb import SGDBAuthError, SGDBHelper
|
||||
class SGDBManager(AsyncManager):
|
||||
"""Manager in charge of downloading a game's cover from steamgriddb"""
|
||||
|
||||
run_after = (SteamAPIManager, LocalCoverManager, ItchCoverManager)
|
||||
run_after = (SteamAPIManager, LocalCoverManager, OnlineCoverManager)
|
||||
retryable_on = (HTTPError, SSLError, ConnectionError, JSONDecodeError)
|
||||
|
||||
def manager_logic(self, game: Game, _additional_data: dict) -> None:
|
||||
|
||||
Reference in New Issue
Block a user