Composite manually added covers - fixes #177

This commit is contained in:
kramo
2023-08-26 14:39:38 +02:00
parent 38d47dae33
commit c2d671273a
6 changed files with 51 additions and 36 deletions

View File

@@ -19,6 +19,7 @@
import os import os
import shlex import shlex
from pathlib import Path
from time import time from time import time
from typing import Any, Optional from typing import Any, Optional
@@ -29,9 +30,10 @@ from src import shared
from src.errors.friendly_error import FriendlyError from src.errors.friendly_error import FriendlyError
from src.game import Game from src.game import Game
from src.game_cover import GameCover from src.game_cover import GameCover
from src.store.managers.cover_manager import CoverManager
from src.store.managers.sgdb_manager import SGDBManager from src.store.managers.sgdb_manager import SGDBManager
from src.utils.create_dialog import create_dialog 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") @Gtk.Template(resource_path=shared.PREFIX + "/gtk/details-window.ui")
@@ -281,15 +283,17 @@ class DetailsWindow(Adw.Window):
except GLib.GError: except GLib.GError:
return return
def resize() -> None: def thread_func() -> None:
if cover := resize_cover(path): if new_path := convert_cover(
self.game_cover.new_cover(cover) pixbuf=shared.store.managers[CoverManager].composite_cover(Path(path))
self.cover_button_delete_revealer.set_reveal_child(True) ):
self.cover_changed = True 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()
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: def set_executable(self, _source: Any, result: Gio.Task, *_args: Any) -> None:
try: try:

View File

@@ -18,7 +18,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Optional from typing import Optional
from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk
from PIL import Image, ImageFilter, ImageStat from PIL import Image, ImageFilter, ImageStat

View File

@@ -150,7 +150,6 @@ class DesktopSourceIterable(SourceIterable):
try: try:
icon_str = keyfile.get_string("Desktop Entry", "Icon") icon_str = keyfile.get_string("Desktop Entry", "Icon")
except GLib.GError: except GLib.GError:
print("AAAAAAAAAAAAAAAAAAAAAAA")
yield game yield game
continue continue
else: else:

View File

@@ -22,14 +22,14 @@ from pathlib import Path
from typing import NamedTuple from typing import NamedTuple
import requests import requests
from gi.repository import Gio, GdkPixbuf from gi.repository import GdkPixbuf, Gio
from requests.exceptions import HTTPError, SSLError from requests.exceptions import HTTPError, SSLError
from src import shared from src import shared
from src.game import Game from src.game import Game
from src.store.managers.manager import Manager from src.store.managers.manager import Manager
from src.store.managers.steam_api_manager import SteamAPIManager 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): class ImageSize(NamedTuple):
@@ -110,18 +110,16 @@ class CoverManager(Manager):
stretch = 1 - (resized_height / cover_size.height) stretch = 1 - (resized_height / cover_size.height)
return stretch <= max_stretch return stretch <= max_stretch
def save_composited_cover( def composite_cover(
self, self,
game: Game,
image_path: Path, image_path: Path,
scale: float = 1, scale: float = 1,
blur_size: ImageSize = ImageSize(2, 2), 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. 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 path: Path where the source image is located
:param scale: :param scale:
Scale of the smalled image side Scale of the smalled image side
@@ -130,14 +128,15 @@ class CoverManager(Manager):
""" """
# Load source image # 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()) source_size = ImageSize(source.get_width(), source.get_height())
cover_size = ImageSize._make(shared.image_size) cover_size = ImageSize._make(shared.image_size)
# Stretch if possible # Stretch if possible
if scale == 1 and self.is_stretchable(source_size, cover_size): if scale == 1 and self.is_stretchable(source_size, cover_size):
save_cover(game.game_id, resize_cover(pixbuf=source)) return source
return
# Create the blurred cover background # Create the blurred cover background
# fmt: off # fmt: off
@@ -164,7 +163,7 @@ class CoverManager(Manager):
GdkPixbuf.InterpType.BILINEAR, GdkPixbuf.InterpType.BILINEAR,
255, 255,
) )
save_cover(game.game_id, resize_cover(pixbuf=cover)) return cover
def main(self, game: Game, additional_data: dict) -> None: def main(self, game: Game, additional_data: dict) -> None:
if game.blacklisted: if game.blacklisted:
@@ -185,13 +184,15 @@ class CoverManager(Manager):
continue continue
# Icon cover # Icon cover
if key == "local_icon_path": composite_kwargs = {}
self.save_composited_cover(
game,
image_path,
scale=0.7,
blur_size=ImageSize(1, 2),
)
return
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)
),
)

View File

@@ -28,12 +28,22 @@ from PIL import Image, ImageSequence, UnidentifiedImageError
from src import shared from src import shared
def resize_cover( def convert_cover(
cover_path: Optional[Path] = None, pixbuf: Optional[GdkPixbuf.Pixbuf] = None cover_path: Optional[Path] = None,
pixbuf: Optional[GdkPixbuf.Pixbuf] = None,
resize: bool = True,
) -> Optional[Path]: ) -> Optional[Path]:
if not cover_path and not pixbuf: if not cover_path and not pixbuf:
return None 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: if pixbuf:
cover_path = Path(Gio.File.new_tmp("XXXXXX.tiff")[0].get_path()) cover_path = Path(Gio.File.new_tmp("XXXXXX.tiff")[0].get_path())
pixbuf.savev(str(cover_path), "tiff", ["compression"], ["1"]) pixbuf.savev(str(cover_path), "tiff", ["compression"], ["1"])
@@ -42,7 +52,8 @@ def resize_cover(
with Image.open(cover_path) as image: with Image.open(cover_path) as image:
if getattr(image, "is_animated", False): if getattr(image, "is_animated", False):
frames = tuple( 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()) tmp_path = Path(Gio.File.new_tmp("XXXXXX.gif")[0].get_path())
@@ -59,7 +70,7 @@ def resize_cover(
image = image.convert("RGBA") image = image.convert("RGBA")
tmp_path = Path(Gio.File.new_tmp("XXXXXX.tiff")[0].get_path()) 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, tmp_path,
compression="tiff_adobe_deflate" compression="tiff_adobe_deflate"
if shared.schema.get_boolean("high-quality-images") 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( Gdk.Texture.new_from_filename(str(cover_path)).save_to_tiff(
tmp_path := Gio.File.new_tmp("XXXXXX.tiff")[0].get_path() tmp_path := Gio.File.new_tmp("XXXXXX.tiff")[0].get_path()
) )
return resize_cover(tmp_path) return convert_cover(tmp_path)
except GLib.GError: except GLib.GError:
return None return None

View File

@@ -28,7 +28,7 @@ from requests.exceptions import HTTPError
from src import shared from src import shared
from src.game import Game 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): class SGDBError(Exception):
@@ -134,7 +134,7 @@ class SGDBHelper:
tmp_file = Gio.File.new_tmp()[0] tmp_file = Gio.File.new_tmp()[0]
tmp_file_path = tmp_file.get_path() tmp_file_path = tmp_file.get_path()
Path(tmp_file_path).write_bytes(response.content) 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: except SGDBAuthError as error:
# Let caller handle auth errors # Let caller handle auth errors
raise error raise error