From 86716e83b440b37111e74570a037fbbc4b5bf638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Thu, 26 Jun 2025 21:49:16 +0200 Subject: [PATCH] Cache thumbnails --- lazy_player/main_window.py | 19 +++++++++++++++++ lazy_player/thumbnailer.py | 42 +++++++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/lazy_player/main_window.py b/lazy_player/main_window.py index b24d241..0cb6ba4 100644 --- a/lazy_player/main_window.py +++ b/lazy_player/main_window.py @@ -161,6 +161,8 @@ class MainWindow(Gtk.ApplicationWindow, Watcher): return cast(FileItem, selected_item) if selected_item else None def _setup_list_item(self, factory: Gtk.SignalListItemFactory, list_item: Gtk.ListItem): + factory = factory + # Create horizontal box to hold icon and label box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) box.set_spacing(8) @@ -178,6 +180,8 @@ class MainWindow(Gtk.ApplicationWindow, Watcher): list_item.set_child(box) def _bind_list_item(self, factory: Gtk.SignalListItemFactory, list_item: Gtk.ListItem): + factory = factory + box = cast(Gtk.Box, list_item.get_child()) icon = cast(Gtk.Image, box.get_first_child()) label = cast(Gtk.Label, box.get_last_child()) @@ -206,6 +210,8 @@ class MainWindow(Gtk.ApplicationWindow, Watcher): update_icon() def _on_activate(self, widget: Gtk.ListView, index: int): + widget = widget + selected_item = self.selection_model.get_item(index) if selected_item: file_item = cast(FileItem, selected_item) @@ -262,6 +268,8 @@ class MainWindow(Gtk.ApplicationWindow, Watcher): position: int, n_items: int, ): + n_items = n_items + position = selection_model.get_selected() if position == Gtk.INVALID_LIST_POSITION: @@ -289,6 +297,9 @@ class MainWindow(Gtk.ApplicationWindow, Watcher): keycode: int, state: Gdk.ModifierType, ) -> bool: + keycode = keycode + state = state + if keyval == Gdk.keyval_from_name("space"): self._toggle_play_pause() return True @@ -387,6 +398,9 @@ class MainWindow(Gtk.ApplicationWindow, Watcher): keycode: int, state: Gdk.ModifierType, ) -> bool: + keycode = keycode + state = state + if keyval == Gdk.keyval_from_name("q"): self.close() return True @@ -412,6 +426,8 @@ class MainWindow(Gtk.ApplicationWindow, Watcher): keycode: int, state: Gdk.ModifierType, ) -> bool: + controller = controller + # Handle keys differently based on which view is active if self.stack.get_visible_child_name() == "player": return self._on_player_key_pressed(keyval, keycode, state) @@ -438,6 +454,9 @@ class MainWindow(Gtk.ApplicationWindow, Watcher): file_item.saved_duration.value = duration def _on_tick(self, widget: Gtk.Widget, frame_clock: Gdk.FrameClock, data: Any) -> bool: + widget = widget + data = data + # Update all reactive values for whole application. update_all_computed() diff --git a/lazy_player/thumbnailer.py b/lazy_player/thumbnailer.py index 6613aaf..a1b5e13 100644 --- a/lazy_player/thumbnailer.py +++ b/lazy_player/thumbnailer.py @@ -26,6 +26,11 @@ __all__ = ["Thumbnailer", "generate_thumbnail_sync"] MAX_WORKERS = max(1, multiprocessing.cpu_count() // 2) +CACHE_HOME = os.environ.get( + "XDG_CACHE_HOME", os.path.join(os.environ.get("HOME", "/tmp"), ".cache") +) +CACHE_PATH = os.path.join(CACHE_HOME, "lazy-player", "thumbnails") + class Thumbnailer(ThreadPoolExecutor): def __init__(self): @@ -51,7 +56,35 @@ class Thumbnailer(ThreadPoolExecutor): def generate_thumbnail_sync_nicely(file_item: FileItem): os.nice(10) - return generate_thumbnail_sync(file_item) + + stat = os.stat(file_item.full_path) + name = f"{stat.st_ino}-{stat.st_size}.jpeg" + path = os.path.join(CACHE_PATH, name) + + if os.path.exists(path): + with open(path, "rb") as fp: + thumbnail = fp.read() + + def set_thumbnail(): + file_item.thumbnail.value = thumbnail + + GLib.idle_add(set_thumbnail) + return + + else: + os.makedirs(CACHE_PATH, exist_ok=True) + thumbnail = generate_thumbnail_sync(file_item) + + if thumbnail is None: + return + + with open(path, "wb") as fp: + fp.write(thumbnail) + + def set_thumbnail(): + file_item.thumbnail.value = thumbnail + + GLib.idle_add(set_thumbnail) def generate_thumbnail_sync(file_item: FileItem): @@ -118,12 +151,7 @@ def generate_thumbnail_sync(file_item: FileItem): for i in range(3): candidates.append(get_sample(duration // 3 - Gst.SECOND + i * Gst.SECOND)) - thumbnail = max(candidates, key=len) - - def set_thumbnail(): - file_item.thumbnail.value = thumbnail - - GLib.idle_add(set_thumbnail) + return max(candidates, key=len) except Exception as err: print("[thumbnailer] Error:", file_item.full_path.name, file=sys.stderr)