emirkisa commited on
Commit
f855008
·
verified ·
1 Parent(s): 4dd8acd

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +44 -111
app.py CHANGED
@@ -380,58 +380,22 @@ def start_precache(fps: int = DEFAULT_FPS, workers: int = 4) -> None:
380
 
381
  # ── Gallery helpers ────────────────────────────────────────────────────────────
382
 
383
- # In-memory thumbnail cache keyed by (seq, overlay, alpha_bucket)
384
- _thumb_cache: dict[tuple, Image.Image] = {}
385
-
386
-
387
  def _make_thumb(seq: str, overlay: bool = False, alpha: float = 0.0) -> Image.Image:
388
- """Return a fresh copy of the (cached) base thumbnail."""
389
- key = (seq, overlay, round(alpha, 2))
390
- if key not in _thumb_cache:
391
- fps = _get_frame_paths(seq)
392
- if not fps:
393
- img = Image.new("RGB", (THUMB_W, THUMB_H), 30)
394
- else:
395
- img = (render_frame(seq, 0, overlay, alpha)
396
- if overlay else Image.open(fps[0]).convert("RGB"))
397
- img.thumbnail((THUMB_W, THUMB_H), Image.LANCZOS)
398
- _thumb_cache[key] = img
399
- return _thumb_cache[key].copy()
400
-
401
-
402
- def _apply_selection_glow(img: Image.Image) -> Image.Image:
403
- """Draw a multi-layer golden glow border indicating a selected thumbnail."""
404
- from PIL import ImageDraw
405
- draw = ImageDraw.Draw(img)
406
- w, h = img.size
407
- # 5-pixel layered glow: bright gold → orange → dark, outermost first
408
- glow = [(255, 230, 0), (255, 195, 0), (240, 150, 0), (200, 100, 0), (140, 60, 0)]
409
- for i, color in enumerate(glow):
410
- draw.rectangle([i, i, w - 1 - i, h - 1 - i], outline=color)
411
  return img
412
 
413
 
414
- def build_gallery_items(
415
- seqs: list[str],
416
- overlay: bool = False,
417
- selected: set[str] | None = None,
418
- max_reached: bool = False,
419
- ) -> list[tuple]:
420
- selected = selected or set()
421
  items = []
422
  for seq in seqs:
423
  row = DF[DF["sequence"] == seq].iloc[0]
424
- img = _make_thumb(seq, overlay)
425
- if seq in selected:
426
- img = _apply_selection_glow(img)
427
- caption = f"✅ {seq} [{row['frames']}f · {row['n_objects']}obj]"
428
- elif max_reached:
429
- # Dim thumbnails that can't be added
430
- img = img.point(lambda p: int(p * 0.45))
431
- caption = f"{seq} [{row['frames']}f · {row['n_objects']}obj]"
432
- else:
433
- caption = f"{seq} [{row['frames']}f · {row['n_objects']}obj]"
434
- items.append((img, caption))
435
  return items
436
 
437
 
@@ -754,24 +718,19 @@ def build_ui():
754
  g_f_inputs = [g_year, g_split, g_obj, g_fmin, g_fmax, g_srch]
755
 
756
  def _on_g_filter(*args):
757
- # args: *filter_fields, ov_thumb, sel_seqs
758
- sel_seqs = list(args[-1])
759
- ov = args[-2]
760
- fargs = args[:-2]
761
- fdf = filter_df(*fargs)
762
- seqs = fdf["sequence"].tolist()
763
- sel_set = set(sel_seqs)
764
- items = build_gallery_items(
765
- seqs, overlay=ov,
766
- selected=sel_set, max_reached=(len(sel_seqs) == 4))
767
  return (items, seqs,
768
- f"**{len(seqs)} sequences** — toggle to select (max 4)")
769
 
770
  for inp in g_f_inputs + [g_ov_th]:
771
- inp.change(_on_g_filter, g_f_inputs + [g_ov_th, g_selected_state],
772
  [gallery, g_seq_state, g_count_md])
773
 
774
- # ── Shared helpers ─────────────────────────────────────────
775
  def _build_video_updates(sel_seqs, ov, a, fps):
776
  """Return 4 gr.update() objects for video slots 0-3."""
777
  updates = []
@@ -788,84 +747,60 @@ def build_ui():
788
  updates.append(gr.update(visible=False, value=None))
789
  return updates
790
 
791
- def _rebuild_gallery(sel_seqs, g_seqs, ov_th):
792
- sel_set = set(sel_seqs)
793
- return build_gallery_items(
794
- g_seqs, overlay=ov_th,
795
- selected=sel_set, max_reached=(len(sel_seqs) == 4))
796
-
797
- # ── Gallery click → toggle selection ──────────────────────
798
- def _on_gallery_toggle(evt: gr.SelectData,
799
- sel_seqs, g_seqs, ov, a, fps, ov_th):
800
  if evt is None or not g_seqs:
801
- empty_gallery = _rebuild_gallery(sel_seqs, g_seqs, ov_th)
802
- return (sel_seqs, empty_gallery,
803
- gr.update(visible=True), gr.update(visible=False),
804
- gr.update(visible=False, value=None),
805
- gr.update(visible=False, value=None),
806
- gr.update(visible=False, value=None),
807
- gr.update(visible=False, value=None),
808
  gr.update(visible=False))
809
-
810
  seq = g_seqs[evt.index]
811
- max_before = len(sel_seqs) == 4
812
  if seq in sel_seqs:
813
  sel_seqs = [s for s in sel_seqs if s != seq]
814
- elif not max_before:
815
  sel_seqs = sel_seqs + [seq]
816
- # else: already at 4, ignoreglow state unchanged
817
 
818
  n = len(sel_seqs)
819
- new_gallery = _rebuild_gallery(sel_seqs, g_seqs, ov_th)
820
- vid_updates = _build_video_updates(sel_seqs, ov, a, fps)
821
-
822
- if n == 0:
823
- info_txt = ""
824
- elif n == 4:
825
- info_txt = ("▶ " + " · ".join(f"**{s}**" for s in sel_seqs) +
826
- " — **max 4 reached**, deselect to swap")
827
- else:
828
- info_txt = ("▶ " + " · ".join(f"**{s}**" for s in sel_seqs) +
829
- " *(click a glowing thumbnail to deselect)*")
830
-
831
  return (
832
  sel_seqs,
833
- new_gallery,
834
  gr.update(visible=(n == 0)),
835
  gr.update(visible=(n > 0), value=info_txt),
836
- vid_updates[0],
837
- vid_updates[1],
838
- vid_updates[2],
839
- vid_updates[3],
840
  gr.update(visible=(n > 0)),
841
  )
842
 
 
 
 
 
843
  gallery.select(
844
- _on_gallery_toggle,
845
- inputs=[g_selected_state, g_seq_state,
846
- g_vid_ov, g_vid_a, g_fps, g_ov_th],
847
- outputs=[g_selected_state, gallery,
848
- g_placeholder, g_sel_info,
849
- g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
 
850
  )
851
 
852
  # Re-encode when overlay / FPS settings change
853
- def _reload_settings(sel_seqs, ov, a, fps):
854
- return _build_video_updates(sel_seqs, ov, a, fps)
855
-
856
  for _inp in [g_vid_ov, g_vid_a, g_fps]:
857
  _inp.change(
858
- _reload_settings,
859
  inputs=[g_selected_state, g_vid_ov, g_vid_a, g_fps],
860
  outputs=[g_vid_0, g_vid_1, g_vid_2, g_vid_3],
861
  )
862
 
863
  # Clear selection button
864
- def _clear_selection(g_seqs, ov_th):
865
- fresh_gallery = build_gallery_items(g_seqs, overlay=ov_th)
866
  return (
867
  [],
868
- fresh_gallery,
869
  gr.update(visible=True),
870
  gr.update(visible=False, value=""),
871
  gr.update(visible=False, value=None),
@@ -877,9 +812,7 @@ def build_ui():
877
 
878
  g_clr_btn.click(
879
  _clear_selection,
880
- inputs=[g_seq_state, g_ov_th],
881
- outputs=[g_selected_state, gallery,
882
- g_placeholder, g_sel_info,
883
  g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
884
  )
885
 
 
380
 
381
  # ── Gallery helpers ────────────────────────────────────────────────────────────
382
 
 
 
 
 
383
  def _make_thumb(seq: str, overlay: bool = False, alpha: float = 0.0) -> Image.Image:
384
+ fps = _get_frame_paths(seq)
385
+ if not fps:
386
+ return Image.new("RGB", (THUMB_W, THUMB_H), 30)
387
+ img = render_frame(seq, 0, overlay, alpha) if overlay else Image.open(fps[0]).convert("RGB")
388
+ img = img.copy()
389
+ img.thumbnail((THUMB_W, THUMB_H), Image.LANCZOS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  return img
391
 
392
 
393
+ def build_gallery_items(seqs: list[str], overlay: bool = False) -> list[tuple]:
 
 
 
 
 
 
394
  items = []
395
  for seq in seqs:
396
  row = DF[DF["sequence"] == seq].iloc[0]
397
+ caption = f"{seq} [{row['frames']}f · {row['n_objects']}obj]"
398
+ items.append((_make_thumb(seq, overlay), caption))
 
 
 
 
 
 
 
 
 
399
  return items
400
 
401
 
 
718
  g_f_inputs = [g_year, g_split, g_obj, g_fmin, g_fmax, g_srch]
719
 
720
  def _on_g_filter(*args):
721
+ ov = args[-1]
722
+ fargs = args[:-1]
723
+ fdf = filter_df(*fargs)
724
+ seqs = fdf["sequence"].tolist()
725
+ items = build_gallery_items(seqs, overlay=ov)
 
 
 
 
 
726
  return (items, seqs,
727
+ f"**{len(seqs)} sequences** — click thumbnails to toggle (max 4)")
728
 
729
  for inp in g_f_inputs + [g_ov_th]:
730
+ inp.change(_on_g_filter, g_f_inputs + [g_ov_th],
731
  [gallery, g_seq_state, g_count_md])
732
 
733
+ # ── Toggle helpers ─────────────────────────────────────────
734
  def _build_video_updates(sel_seqs, ov, a, fps):
735
  """Return 4 gr.update() objects for video slots 0-3."""
736
  updates = []
 
747
  updates.append(gr.update(visible=False, value=None))
748
  return updates
749
 
750
+ # ── Gallery click → two-step: fast toggle, then load videos ──
751
+ # Step 1: update selection state + indicators only (no I/O, <1 ms).
752
+ # This commits the new state before any video encoding starts,
753
+ # preventing the "previous-click lag" caused by slow get_video.
754
+ def _toggle_sel(evt: gr.SelectData, sel_seqs, g_seqs):
 
 
 
 
755
  if evt is None or not g_seqs:
756
+ return (sel_seqs,
757
+ gr.update(visible=True),
758
+ gr.update(visible=False, value=""),
 
 
 
 
759
  gr.update(visible=False))
 
760
  seq = g_seqs[evt.index]
 
761
  if seq in sel_seqs:
762
  sel_seqs = [s for s in sel_seqs if s != seq]
763
+ elif len(sel_seqs) < 4:
764
  sel_seqs = sel_seqs + [seq]
765
+ # else: already 4 selectedsilently ignore
766
 
767
  n = len(sel_seqs)
768
+ info_txt = ("▶ " +
769
+ " · ".join(f"**{s}**" for s in sel_seqs) +
770
+ " *(click a thumbnail to deselect)*") if n > 0 else ""
 
 
 
 
 
 
 
 
 
771
  return (
772
  sel_seqs,
 
773
  gr.update(visible=(n == 0)),
774
  gr.update(visible=(n > 0), value=info_txt),
 
 
 
 
775
  gr.update(visible=(n > 0)),
776
  )
777
 
778
+ # Step 2: encode / fetch videos for the now-committed selection.
779
+ def _load_selected(sel_seqs, ov, a, fps):
780
+ return _build_video_updates(sel_seqs, ov, a, fps)
781
+
782
  gallery.select(
783
+ _toggle_sel,
784
+ inputs=[g_selected_state, g_seq_state],
785
+ outputs=[g_selected_state, g_placeholder, g_sel_info, g_clr_btn],
786
+ ).then(
787
+ _load_selected,
788
+ inputs=[g_selected_state, g_vid_ov, g_vid_a, g_fps],
789
+ outputs=[g_vid_0, g_vid_1, g_vid_2, g_vid_3],
790
  )
791
 
792
  # Re-encode when overlay / FPS settings change
 
 
 
793
  for _inp in [g_vid_ov, g_vid_a, g_fps]:
794
  _inp.change(
795
+ _load_selected,
796
  inputs=[g_selected_state, g_vid_ov, g_vid_a, g_fps],
797
  outputs=[g_vid_0, g_vid_1, g_vid_2, g_vid_3],
798
  )
799
 
800
  # Clear selection button
801
+ def _clear_selection():
 
802
  return (
803
  [],
 
804
  gr.update(visible=True),
805
  gr.update(visible=False, value=""),
806
  gr.update(visible=False, value=None),
 
812
 
813
  g_clr_btn.click(
814
  _clear_selection,
815
+ outputs=[g_selected_state, g_placeholder, g_sel_info,
 
 
816
  g_vid_0, g_vid_1, g_vid_2, g_vid_3, g_clr_btn],
817
  )
818