trydrama-neuralgarage commited on
Commit
e78d314
·
verified ·
1 Parent(s): c17dceb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -4
app.py CHANGED
@@ -1127,6 +1127,51 @@ def load_examples():
1127
  return rows
1128
 
1129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1130
  def _file_url(path):
1131
  return f"/gradio_api/file={path}"
1132
 
@@ -1433,6 +1478,21 @@ body.dr-sliding, body.dr-sliding * { cursor: grabbing !important; user-select: n
1433
  line-height: 1.5 !important; text-align: left !important; }
1434
  .gradio-container .dr-video-fig figcaption b { display: block; color: #fff !important; font-weight: 600 !important; font-size: 14px !important; }
1435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1436
  /* example grid */
1437
  .gradio-container .dr-ex { width: min(1440px, calc(100vw - 64px)); position: relative; left: 50%;
1438
  transform: translateX(-50%); min-width: 0; }
@@ -1509,6 +1569,7 @@ body.dr-sliding, body.dr-sliding * { cursor: grabbing !important; user-select: n
1509
  .gradio-container .dr-meta span + span::before { content: none; }
1510
  .gradio-container .dr-hero-actions a.dr-btn { flex: 1 1 100%; text-align: center; }
1511
  .gradio-container .dr-head { min-height: 0; }
 
1512
  .gradio-container #dr-login-cta, .gradio-container #dr-login-cta button { font-size: 15px !important; }
1513
  .gradio-container .dr-card { padding: 16px !important; }
1514
  .gradio-container .dr-mark { height: 28px !important; } .dr-word { height: 17px !important; }
@@ -1570,6 +1631,13 @@ FORCE_DARK = """
1570
  b.classList.toggle('dr-signed-in', /^\\s*sign out/i.test(b.textContent || ''));
1571
  };
1572
  new MutationObserver(markSignedIn).observe(document.body, { childList: true, subtree: true, characterData: true });
 
 
 
 
 
 
 
1573
  // Trim timeline: preselect the first MAX seconds so the person only has to press Trim.
1574
  // Gradio has no option for this, so we move the end handle the way a mouse drag would.
1575
  const MAXS = __MAXSEC__, MINS = __MINSEC__, ACCEPT = __ACCEPT__;
@@ -1683,6 +1751,7 @@ FORCE_DARK = """
1683
  """.replace("__MAXSEC__", str(MAX_SECONDS)).replace("__MINSEC__", str(MIN_SECONDS)).replace("__ACCEPT__", str(ACCEPT_SECONDS))
1684
 
1685
  EXAMPLE_ROWS = load_examples()
 
1686
 
1687
  def remaining_html(profile: gr.OAuthProfile | None = None):
1688
  """Free allowance line next to the Generate button, with the live balance for signed-in users."""
@@ -1702,6 +1771,23 @@ def remaining_html(profile: gr.OAuthProfile | None = None):
1702
  HIDE_DL = gr.update(visible=False, value=None)
1703
 
1704
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1705
  def signin_state(profile: gr.OAuthProfile | None = None):
1706
  """Signed out: the big sign-in button sits where Generate goes. Signed in: show Generate."""
1707
  signed_in = profile is not None
@@ -1756,9 +1842,11 @@ with gr.Blocks(title=PAGE_TITLE) as demo:
1756
  video_in = gr.Video(show_label=False, sources=["upload"], height=PLAYER_H, elem_classes="dr-media",
1757
  elem_id="dr-in")
1758
  trim_info = gr.HTML(visible=False, elem_classes="dr-trim")
1759
- samples = [[p] for p in sorted(glob.glob("examples/*.mp4")) if "_after." not in p]
1760
- if samples:
1761
- gr.Examples(samples, inputs=[video_in], label="No clip handy? Try one of these")
 
 
1762
 
1763
  gr.HTML('<div class="dr-label"><b>02</b>Choose an expression</div>')
1764
  preset = gr.Radio(list(PRESETS), value=DEFAULT_PRESET, show_label=False,
@@ -1865,6 +1953,10 @@ with gr.Blocks(title=PAGE_TITLE) as demo:
1865
  slider.release(lambda: CUSTOM, outputs=[preset],
1866
  api_visibility="private", show_progress="hidden")
1867
  video_in.upload(prepare_upload, inputs=[video_in], outputs=[video_in], api_visibility="private")
 
 
 
 
1868
  video_in.change(check_length, inputs=[video_in], outputs=[trim_info], api_visibility="private",
1869
  show_progress="hidden", trigger_mode="always_last")
1870
  video_in.clear(lambda: (None, IDLE, HIDE_DL), outputs=[video_out, status_out, dl_btn],
@@ -1894,5 +1986,5 @@ def favicon_file():
1894
 
1895
  startup_check()
1896
  demo.queue(max_size=20).launch(theme=THEME, css=CSS, js=FORCE_DARK, head=HEAD,
1897
- allowed_paths=[EX_WEB, EX_DOWNLOAD, *(os.path.abspath(f) for f in EXAMPLES_FOLDERS)],
1898
  favicon_path=favicon_file(), footer_links=[])
 
1127
  return rows
1128
 
1129
 
1130
+ # ---------- sample input clips ("Try a sample clip") ----------
1131
+ # Before-clips from the model repo's examples/ folder, offered under the upload box for people without a
1132
+ # clip of their own. All are under 15 seconds, so they never need trimming.
1133
+ SAMPLE_INPUTS = [ # file number in examples/, caption
1134
+ (1, "Outdoor interview"),
1135
+ (2, "Talking head"),
1136
+ (5, "Street portrait"),
1137
+ (7, "Webinar host"),
1138
+ (8, "Explainer to camera"),
1139
+ ]
1140
+ SAMPLE_WEB = os.path.abspath("sample_inputs")
1141
+
1142
+
1143
+ def load_sample_inputs():
1144
+ """Returns [(video_path, poster_path, caption), ...] for the sample strip."""
1145
+ wanted = [n for n, _ in SAMPLE_INPUTS]
1146
+ local = lambda n: sorted(glob.glob(os.path.join("examples", f"{n}_before.*")) +
1147
+ glob.glob(os.path.join(EX_DOWNLOAD, "examples", f"{n}_before.*")))
1148
+ if not all(local(n) for n in wanted):
1149
+ try:
1150
+ from huggingface_hub import snapshot_download
1151
+ snapshot_download(EXAMPLES_REPO, repo_type="model", token=HF_TOKEN or None, local_dir=EX_DOWNLOAD,
1152
+ allow_patterns=[f"examples/{n}_before.*" for n in wanted])
1153
+ except Exception as e:
1154
+ log.warning(f"[samples] couldn't copy sample clips from {EXAMPLES_REPO}/examples "
1155
+ f"({type(e).__name__}: {short(str(e), 200)})")
1156
+ os.makedirs(SAMPLE_WEB, exist_ok=True)
1157
+ out = []
1158
+ for n, caption in SAMPLE_INPUTS:
1159
+ found = local(n)
1160
+ if not found:
1161
+ continue
1162
+ src = found[0]
1163
+ if video_seconds(src) > ACCEPT_SECONDS:
1164
+ log.warning(f"[samples] {os.path.basename(src)} is longer than {ACCEPT_SECONDS:g}s, skipped")
1165
+ continue
1166
+ poster = os.path.join(SAMPLE_WEB, f"{n}_before.jpg")
1167
+ if not os.path.exists(poster):
1168
+ subprocess.run(["ffmpeg", "-y", "-ss", "1", "-i", src, "-frames:v", "1", "-vf", "scale=480:-2",
1169
+ "-q:v", "4", poster], capture_output=True, text=True)
1170
+ out.append((src, poster if os.path.exists(poster) else None, caption))
1171
+ log.info(f"[samples] {len(out)} sample clips ready")
1172
+ return out
1173
+
1174
+
1175
  def _file_url(path):
1176
  return f"/gradio_api/file={path}"
1177
 
 
1478
  line-height: 1.5 !important; text-align: left !important; }
1479
  .gradio-container .dr-video-fig figcaption b { display: block; color: #fff !important; font-weight: 600 !important; font-size: 14px !important; }
1480
 
1481
+ /* sample clip strip under the upload box */
1482
+ .gradio-container .dr-samples-label { font-size: 13px !important; color: var(--dr-muted) !important; margin: 4px 0 8px !important; }
1483
+ .gradio-container .dr-samples-label b { color: var(--dr-text) !important; font-weight: 600 !important; }
1484
+ .gradio-container .dr-samples { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; margin-bottom: 10px; }
1485
+ .gradio-container .dr-sample { display: block; padding: 0 !important; margin: 0 !important; cursor: pointer; text-align: left;
1486
+ background: transparent !important; border: 0 !important; min-width: 0; }
1487
+ .gradio-container .dr-sample img, .gradio-container .dr-sample .dr-sample-ph { display: block; width: 100%;
1488
+ aspect-ratio: 16 / 10; object-fit: cover; border-radius: 8px; border: 1px solid var(--dr-line); background: #000;
1489
+ transition: border-color .15s; }
1490
+ .gradio-container .dr-sample span:not(.dr-sample-ph) { display: block; margin-top: 5px; font-size: 11px !important;
1491
+ line-height: 1.3 !important; color: var(--dr-muted) !important; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1492
+ .gradio-container .dr-sample:hover img { border-color: #6a6a75; }
1493
+ .gradio-container .dr-sample.is-on img { border-color: var(--dr-red); box-shadow: 0 0 0 1px var(--dr-red); }
1494
+ .gradio-container .dr-sample.is-on span:not(.dr-sample-ph) { color: #fff !important; }
1495
+
1496
  /* example grid */
1497
  .gradio-container .dr-ex { width: min(1440px, calc(100vw - 64px)); position: relative; left: 50%;
1498
  transform: translateX(-50%); min-width: 0; }
 
1569
  .gradio-container .dr-meta span + span::before { content: none; }
1570
  .gradio-container .dr-hero-actions a.dr-btn { flex: 1 1 100%; text-align: center; }
1571
  .gradio-container .dr-head { min-height: 0; }
1572
+ .gradio-container .dr-samples { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1573
  .gradio-container #dr-login-cta, .gradio-container #dr-login-cta button { font-size: 15px !important; }
1574
  .gradio-container .dr-card { padding: 16px !important; }
1575
  .gradio-container .dr-mark { height: 28px !important; } .dr-word { height: 17px !important; }
 
1631
  b.classList.toggle('dr-signed-in', /^\\s*sign out/i.test(b.textContent || ''));
1632
  };
1633
  new MutationObserver(markSignedIn).observe(document.body, { childList: true, subtree: true, characterData: true });
1634
+ // Sample clips: tell the app which one was picked
1635
+ document.addEventListener('click', (e) => {
1636
+ const card = e.target.closest && e.target.closest('.dr-sample'); if (!card) return;
1637
+ document.querySelectorAll('.dr-sample').forEach((c) => c.classList.toggle('is-on', c === card));
1638
+ const btn = document.getElementById('dr-sample-' + card.dataset.i);
1639
+ if (btn) (btn.tagName === 'BUTTON' ? btn : btn.querySelector('button') || btn).click();
1640
+ });
1641
  // Trim timeline: preselect the first MAX seconds so the person only has to press Trim.
1642
  // Gradio has no option for this, so we move the end handle the way a mouse drag would.
1643
  const MAXS = __MAXSEC__, MINS = __MINSEC__, ACCEPT = __ACCEPT__;
 
1751
  """.replace("__MAXSEC__", str(MAX_SECONDS)).replace("__MINSEC__", str(MIN_SECONDS)).replace("__ACCEPT__", str(ACCEPT_SECONDS))
1752
 
1753
  EXAMPLE_ROWS = load_examples()
1754
+ SAMPLE_CLIPS = load_sample_inputs()
1755
 
1756
  def remaining_html(profile: gr.OAuthProfile | None = None):
1757
  """Free allowance line next to the Generate button, with the live balance for signed-in users."""
 
1771
  HIDE_DL = gr.update(visible=False, value=None)
1772
 
1773
 
1774
+ def samples_strip_html():
1775
+ items = "".join(
1776
+ f'<button type="button" class="dr-sample" data-i="{i}" title="Use the {caption} sample">'
1777
+ + (f'<img src="{_file_url(poster)}" alt="" loading="lazy">' if poster else '<span class="dr-sample-ph"></span>')
1778
+ + f'<span>{caption}</span></button>'
1779
+ for i, (_, poster, caption) in enumerate(SAMPLE_CLIPS))
1780
+ return (f'<div class="dr-samples-label">No clip handy? <b>Try a sample clip</b></div>'
1781
+ f'<div class="dr-samples">{items}</div>')
1782
+
1783
+
1784
+ def pick_sample(index):
1785
+ """Loads the chosen sample into the upload box (converted like an upload) and clears the old result."""
1786
+ video, _, caption = SAMPLE_CLIPS[index]
1787
+ log.info(f"[samples] picked {caption}")
1788
+ return prepare_upload(video), None, IDLE, HIDE_DL
1789
+
1790
+
1791
  def signin_state(profile: gr.OAuthProfile | None = None):
1792
  """Signed out: the big sign-in button sits where Generate goes. Signed in: show Generate."""
1793
  signed_in = profile is not None
 
1842
  video_in = gr.Video(show_label=False, sources=["upload"], height=PLAYER_H, elem_classes="dr-media",
1843
  elem_id="dr-in")
1844
  trim_info = gr.HTML(visible=False, elem_classes="dr-trim")
1845
+ if SAMPLE_CLIPS:
1846
+ gr.HTML(samples_strip_html())
1847
+ # one hidden button per sample; the thumbnails click these (see the page script)
1848
+ sample_btns = [gr.Button(f"Sample {i + 1}", visible="hidden", elem_id=f"dr-sample-{i}")
1849
+ for i in range(len(SAMPLE_CLIPS))]
1850
 
1851
  gr.HTML('<div class="dr-label"><b>02</b>Choose an expression</div>')
1852
  preset = gr.Radio(list(PRESETS), value=DEFAULT_PRESET, show_label=False,
 
1953
  slider.release(lambda: CUSTOM, outputs=[preset],
1954
  api_visibility="private", show_progress="hidden")
1955
  video_in.upload(prepare_upload, inputs=[video_in], outputs=[video_in], api_visibility="private")
1956
+ if SAMPLE_CLIPS:
1957
+ for i, btn in enumerate(sample_btns):
1958
+ btn.click(lambda i=i: pick_sample(i), inputs=None, outputs=[video_in, video_out, status_out, dl_btn],
1959
+ api_visibility="private", show_progress="minimal")
1960
  video_in.change(check_length, inputs=[video_in], outputs=[trim_info], api_visibility="private",
1961
  show_progress="hidden", trigger_mode="always_last")
1962
  video_in.clear(lambda: (None, IDLE, HIDE_DL), outputs=[video_out, status_out, dl_btn],
 
1986
 
1987
  startup_check()
1988
  demo.queue(max_size=20).launch(theme=THEME, css=CSS, js=FORCE_DARK, head=HEAD,
1989
+ allowed_paths=[EX_WEB, EX_DOWNLOAD, SAMPLE_WEB, os.path.abspath("examples"), *(os.path.abspath(f) for f in EXAMPLES_FOLDERS)],
1990
  favicon_path=favicon_file(), footer_links=[])