diff --git a/cht/ui/scrub_bar.py b/cht/ui/scrub_bar.py index 5440718..6cd24e5 100644 --- a/cht/ui/scrub_bar.py +++ b/cht/ui/scrub_bar.py @@ -131,24 +131,39 @@ class ScrubBar(Gtk.DrawingArea): self.queue_draw() def add_frame(self, timestamp: float, path: str) -> None: - """Add a single frame thumbnail incrementally.""" + """Add a single frame thumbnail from file path.""" if not Path(path).exists(): return thumb_h = BAR_HEIGHT - 4 thumb_w = int(thumb_h * 16 / 9) try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, thumb_w, thumb_h, True) - surface = self._pixbuf_to_surface(pixbuf) - self._frame_thumbs.append({ - "timestamp": timestamp, - "surface": surface, - "width": pixbuf.get_width(), - "height": pixbuf.get_height(), - }) - self.queue_draw() + self.add_frame_from_pixbuf(timestamp, pixbuf) except Exception as e: log.debug("Thumb load failed for %s: %s", path, e) + def add_frame_from_pixbuf(self, timestamp: float, pixbuf) -> None: + """Add a single frame thumbnail from an already-loaded pixbuf (shared with frames panel).""" + thumb_h = BAR_HEIGHT - 4 + thumb_w = int(thumb_h * 16 / 9) + scaled = pixbuf.scale_simple(thumb_w, thumb_h, GdkPixbuf.InterpType.BILINEAR) + surface = self._pixbuf_to_surface(scaled) + self._frame_thumbs.append({ + "timestamp": timestamp, + "surface": surface, + "width": scaled.get_width(), + "height": scaled.get_height(), + }) + self.queue_draw() + + def set_frames_from_pixbufs(self, frames: list[dict]) -> None: + """Bulk set thumbnails from already-loaded pixbufs. Each dict: {timestamp, pixbuf}.""" + self._frame_thumbs = [] + for f in frames: + self.add_frame_from_pixbuf(f["timestamp"], f["pixbuf"]) + # queue_draw already called per frame, but one more to be safe + self.queue_draw() + @staticmethod def _pixbuf_to_surface(pixbuf): """Convert a GdkPixbuf to a cairo ImageSurface.""" diff --git a/cht/window.py b/cht/window.py index 967a9fa..74b72ef 100644 --- a/cht/window.py +++ b/cht/window.py @@ -647,9 +647,9 @@ class ChtWindow(Adw.ApplicationWindow): self._frames_panel.load_items(items) self._known_frames = {item["id"] for item in items} self._agent_output.append(f" Loaded {len(items)} frame thumbnails.\n") - # Update scrub bar thumbnails - self._timeline_controls.scrub_bar.set_frames( - [{"timestamp": e["timestamp"], "path": str(e["path"])} for e in entries] + # Update scrub bar thumbnails — reuse already-loaded pixbufs + self._timeline_controls.scrub_bar.set_frames_from_pixbufs( + [{"timestamp": it["timestamp"], "pixbuf": it["pixbuf"]} for it in items] ) def _load_existing_transcript(self): @@ -676,7 +676,7 @@ class ChtWindow(Adw.ApplicationWindow): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(str(entry["path"]), 256, 144, True) auto = self._timeline.state.live and not self._transcript_panel.has_selection self._frames_panel.add_item(fid, pixbuf, entry["timestamp"], auto_select=auto) - self._timeline_controls.scrub_bar.add_frame(entry["timestamp"], str(entry["path"])) + self._timeline_controls.scrub_bar.add_frame_from_pixbuf(entry["timestamp"], pixbuf) except Exception as e: log.warning("Thumbnail load failed for %s: %s", fid, e) return True