From c2d671273ae47f5dd51cc25e33bdb56817e681cf Mon Sep 17 00:00:00 2001 From: kramo Date: Sat, 26 Aug 2023 14:39:38 +0200 Subject: [PATCH] Composite manually added covers - fixes #177 --- src/details_window.py | 18 ++++++----- src/game_cover.py | 2 +- src/importer/sources/desktop_source.py | 1 - src/store/managers/cover_manager.py | 41 +++++++++++++------------- src/utils/save_cover.py | 21 +++++++++---- src/utils/steamgriddb.py | 4 +-- 6 files changed, 51 insertions(+), 36 deletions(-) diff --git a/src/details_window.py b/src/details_window.py index c6f0e0d..c290537 100644 --- a/src/details_window.py +++ b/src/details_window.py @@ -19,6 +19,7 @@ import os import shlex +from pathlib import Path from time import time from typing import Any, Optional @@ -29,9 +30,10 @@ from src import shared from src.errors.friendly_error import FriendlyError from src.game import Game from src.game_cover import GameCover +from src.store.managers.cover_manager import CoverManager from src.store.managers.sgdb_manager import SGDBManager from src.utils.create_dialog import create_dialog -from src.utils.save_cover import resize_cover, save_cover +from src.utils.save_cover import convert_cover, save_cover @Gtk.Template(resource_path=shared.PREFIX + "/gtk/details-window.ui") @@ -281,15 +283,17 @@ class DetailsWindow(Adw.Window): except GLib.GError: return - def resize() -> None: - if cover := resize_cover(path): - self.game_cover.new_cover(cover) - self.cover_button_delete_revealer.set_reveal_child(True) - self.cover_changed = True + def thread_func() -> None: + if new_path := convert_cover( + pixbuf=shared.store.managers[CoverManager].composite_cover(Path(path)) + ): + self.game_cover.new_cover(new_path) + self.cover_button_delete_revealer.set_reveal_child(True) + self.cover_changed = True self.toggle_loading() self.toggle_loading() - GLib.Thread.new(None, resize) + GLib.Thread.new(None, thread_func) def set_executable(self, _source: Any, result: Gio.Task, *_args: Any) -> None: try: diff --git a/src/game_cover.py b/src/game_cover.py index 006af33..40d48cb 100644 --- a/src/game_cover.py +++ b/src/game_cover.py @@ -18,7 +18,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from pathlib import Path -from typing import Any, Callable, Optional +from typing import Optional from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk from PIL import Image, ImageFilter, ImageStat diff --git a/src/importer/sources/desktop_source.py b/src/importer/sources/desktop_source.py index 6e78b0b..347f116 100644 --- a/src/importer/sources/desktop_source.py +++ b/src/importer/sources/desktop_source.py @@ -150,7 +150,6 @@ class DesktopSourceIterable(SourceIterable): try: icon_str = keyfile.get_string("Desktop Entry", "Icon") except GLib.GError: - print("AAAAAAAAAAAAAAAAAAAAAAA") yield game continue else: diff --git a/src/store/managers/cover_manager.py b/src/store/managers/cover_manager.py index 4496e80..217ef7b 100644 --- a/src/store/managers/cover_manager.py +++ b/src/store/managers/cover_manager.py @@ -22,14 +22,14 @@ from pathlib import Path from typing import NamedTuple import requests -from gi.repository import Gio, GdkPixbuf +from gi.repository import GdkPixbuf, Gio from requests.exceptions import HTTPError, SSLError from src import shared from src.game import Game from src.store.managers.manager import Manager from src.store.managers.steam_api_manager import SteamAPIManager -from src.utils.save_cover import resize_cover, save_cover +from src.utils.save_cover import convert_cover, save_cover class ImageSize(NamedTuple): @@ -110,18 +110,16 @@ class CoverManager(Manager): stretch = 1 - (resized_height / cover_size.height) return stretch <= max_stretch - def save_composited_cover( + def composite_cover( self, - game: Game, image_path: Path, scale: float = 1, blur_size: ImageSize = ImageSize(2, 2), - ) -> None: + ) -> GdkPixbuf.Pixbuf: """ - Save the image composited with a background blur. + Return the image composited with a background blur. If the image is stretchable, just stretch it. - :param game: The game to save the cover for :param path: Path where the source image is located :param scale: Scale of the smalled image side @@ -130,14 +128,15 @@ class CoverManager(Manager): """ # Load source image - source = GdkPixbuf.Pixbuf.new_from_file(str(image_path)) + source = GdkPixbuf.Pixbuf.new_from_file( + str(convert_cover(image_path, resize=False)) + ) source_size = ImageSize(source.get_width(), source.get_height()) cover_size = ImageSize._make(shared.image_size) # Stretch if possible if scale == 1 and self.is_stretchable(source_size, cover_size): - save_cover(game.game_id, resize_cover(pixbuf=source)) - return + return source # Create the blurred cover background # fmt: off @@ -164,7 +163,7 @@ class CoverManager(Manager): GdkPixbuf.InterpType.BILINEAR, 255, ) - save_cover(game.game_id, resize_cover(pixbuf=cover)) + return cover def main(self, game: Game, additional_data: dict) -> None: if game.blacklisted: @@ -185,13 +184,15 @@ class CoverManager(Manager): continue # Icon cover - if key == "local_icon_path": - self.save_composited_cover( - game, - image_path, - scale=0.7, - blur_size=ImageSize(1, 2), - ) - return + composite_kwargs = {} - self.save_composited_cover(game, image_path) + if key == "local_icon_path": + composite_kwargs["scale"] = 0.7 + composite_kwargs["blur_size"] = ImageSize(1, 2) + + save_cover( + game.game_id, + convert_cover( + pixbuf=self.composite_cover(image_path, **composite_kwargs) + ), + ) diff --git a/src/utils/save_cover.py b/src/utils/save_cover.py index 0bb2381..336161d 100644 --- a/src/utils/save_cover.py +++ b/src/utils/save_cover.py @@ -28,12 +28,22 @@ from PIL import Image, ImageSequence, UnidentifiedImageError from src import shared -def resize_cover( - cover_path: Optional[Path] = None, pixbuf: Optional[GdkPixbuf.Pixbuf] = None +def convert_cover( + cover_path: Optional[Path] = None, + pixbuf: Optional[GdkPixbuf.Pixbuf] = None, + resize: bool = True, ) -> Optional[Path]: if not cover_path and not pixbuf: return None + pixbuf_extensions = set() + for pixbuf_format in GdkPixbuf.Pixbuf.get_formats(): + for pixbuf_extension in pixbuf_format.get_extensions(): + pixbuf_extensions.add(pixbuf_extension) + + if not resize and cover_path and cover_path.suffix.lower()[1:] in pixbuf_extensions: + return cover_path + if pixbuf: cover_path = Path(Gio.File.new_tmp("XXXXXX.tiff")[0].get_path()) pixbuf.savev(str(cover_path), "tiff", ["compression"], ["1"]) @@ -42,7 +52,8 @@ def resize_cover( with Image.open(cover_path) as image: if getattr(image, "is_animated", False): frames = tuple( - frame.resize((200, 300)) for frame in ImageSequence.Iterator(image) + frame.resize((200, 300)) if resize else frame + for frame in ImageSequence.Iterator(image) ) tmp_path = Path(Gio.File.new_tmp("XXXXXX.gif")[0].get_path()) @@ -59,7 +70,7 @@ def resize_cover( image = image.convert("RGBA") tmp_path = Path(Gio.File.new_tmp("XXXXXX.tiff")[0].get_path()) - image.resize(shared.image_size).save( + (image.resize(shared.image_size) if resize else image).save( tmp_path, compression="tiff_adobe_deflate" if shared.schema.get_boolean("high-quality-images") @@ -70,7 +81,7 @@ def resize_cover( Gdk.Texture.new_from_filename(str(cover_path)).save_to_tiff( tmp_path := Gio.File.new_tmp("XXXXXX.tiff")[0].get_path() ) - return resize_cover(tmp_path) + return convert_cover(tmp_path) except GLib.GError: return None diff --git a/src/utils/steamgriddb.py b/src/utils/steamgriddb.py index c87da1c..4ee4aa6 100644 --- a/src/utils/steamgriddb.py +++ b/src/utils/steamgriddb.py @@ -28,7 +28,7 @@ from requests.exceptions import HTTPError from src import shared from src.game import Game -from src.utils.save_cover import resize_cover, save_cover +from src.utils.save_cover import convert_cover, save_cover class SGDBError(Exception): @@ -134,7 +134,7 @@ class SGDBHelper: tmp_file = Gio.File.new_tmp()[0] tmp_file_path = tmp_file.get_path() Path(tmp_file_path).write_bytes(response.content) - save_cover(game.game_id, resize_cover(tmp_file_path)) + save_cover(game.game_id, convert_cover(tmp_file_path)) except SGDBAuthError as error: # Let caller handle auth errors raise error