Update app.py
Browse files
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 |
-
|
| 1760 |
-
|
| 1761 |
-
|
|
|
|
|
|
|
| 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=[])
|