Update app.py
Browse files
app.py
CHANGED
|
@@ -426,8 +426,9 @@ def length_note(seconds, after_trim=False):
|
|
| 426 |
f'{int(MAX_SECONDS)} seconds before uploading.</div>'))
|
| 427 |
return gr.update(visible=True, value=(
|
| 428 |
f'<div class="dr-trim-note"><b>Your clip is {clock(seconds)} long.</b> The demo takes up to '
|
| 429 |
-
f'{int(MAX_SECONDS)} seconds. Click the scissors icon at the top right of the video
|
| 430 |
-
f'
|
|
|
|
| 431 |
|
| 432 |
|
| 433 |
def prepare_upload(video):
|
|
@@ -922,6 +923,59 @@ FAQ = [
|
|
| 922 |
"make a real person appear to express something in order to deceive."),
|
| 923 |
]
|
| 924 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 925 |
PROMO_VIDEOS = [ # YouTube video ID, accessible title
|
| 926 |
("WYi2y1NOhhc", "Introducing trydrama.io"),
|
| 927 |
("NngMqkyotx8", "Acting is Solved!"),
|
|
@@ -1140,6 +1194,31 @@ CSS = """
|
|
| 1140 |
.gradio-container .dr-video iframe { position: absolute; inset: 0; width: 100% !important; height: 100% !important;
|
| 1141 |
border: 0 !important; }
|
| 1142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1143 |
/* FAQ */
|
| 1144 |
.gradio-container .dr-faq { display: grid; border-top: 1px solid var(--dr-line); }
|
| 1145 |
.gradio-container .dr-faq details { border-bottom: 1px solid var(--dr-line); padding: 0 !important; margin: 0 !important;
|
|
@@ -1177,6 +1256,8 @@ CSS = """
|
|
| 1177 |
.gradio-container { padding: 0 16px 32px !important; }
|
| 1178 |
.gradio-container .dr-list { grid-template-columns: 1fr; }
|
| 1179 |
.gradio-container .dr-videos { grid-template-columns: 1fr; }
|
|
|
|
|
|
|
| 1180 |
.gradio-container .dr-hero { padding: 40px 0 28px; }
|
| 1181 |
.gradio-container .dr-hero .dr-sub { font-size: 16px !important; }
|
| 1182 |
.gradio-container .dr-meta { gap: 4px 14px; }
|
|
@@ -1187,13 +1268,80 @@ CSS = """
|
|
| 1187 |
.gradio-container .dr-cta-btns { width: 100%; } .gradio-container a.dr-btn { flex: 1; text-align: center; }
|
| 1188 |
.gradio-container .dr-section { padding-top: 64px !important; }
|
| 1189 |
.gradio-container .dr-foot { grid-template-columns: 1fr; margin-top: 64px; }
|
| 1190 |
-
.gradio-container .dr-foot nav { justify-self: start;
|
|
|
|
| 1191 |
}
|
| 1192 |
"""
|
| 1193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1194 |
FORCE_DARK = """
|
| 1195 |
-
() => {
|
| 1196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1197 |
|
| 1198 |
with gr.Blocks(title=PAGE_TITLE) as demo:
|
| 1199 |
gr.HTML('<div class="dr-glow"></div>')
|
|
@@ -1251,6 +1399,8 @@ with gr.Blocks(title=PAGE_TITLE) as demo:
|
|
| 1251 |
<div class="dr-cta-btns"><a class="dr-btn" href="{DOCS}" target="_blank">API docs</a>
|
| 1252 |
<a class="dr-btn dr-btn-primary" href="{SITE}" target="_blank">Try DRAMA Studio</a></div></div>""")
|
| 1253 |
|
|
|
|
|
|
|
| 1254 |
videos = "".join(
|
| 1255 |
f'<div class="dr-video"><iframe src="https://www.youtube-nocookie.com/embed/{vid}?rel=0&modestbranding=1" '
|
| 1256 |
f'title="{title}" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" '
|
|
@@ -1325,4 +1475,5 @@ def favicon_file():
|
|
| 1325 |
|
| 1326 |
startup_check()
|
| 1327 |
demo.queue(max_size=20).launch(theme=THEME, css=CSS, js=FORCE_DARK, head=HEAD,
|
|
|
|
| 1328 |
favicon_path=favicon_file(), footer_links=[])
|
|
|
|
| 426 |
f'{int(MAX_SECONDS)} seconds before uploading.</div>'))
|
| 427 |
return gr.update(visible=True, value=(
|
| 428 |
f'<div class="dr-trim-note"><b>Your clip is {clock(seconds)} long.</b> The demo takes up to '
|
| 429 |
+
f'{int(MAX_SECONDS)} seconds. Click the scissors icon at the top right of the video: the first '
|
| 430 |
+
f'{int(MAX_SECONDS)} seconds are selected for you. Press <b>Trim</b>, or move the handles to pick '
|
| 431 |
+
f'a different part first.</div>'))
|
| 432 |
|
| 433 |
|
| 434 |
def prepare_upload(video):
|
|
|
|
| 923 |
"make a real person appear to express something in order to deceive."),
|
| 924 |
]
|
| 925 |
|
| 926 |
+
# Before/after samples. Files are N_before.<ext> / N_after.<ext>.
|
| 927 |
+
# Served from a local "samples/" folder in the Space if it exists, otherwise from SAMPLES_BASE
|
| 928 |
+
# (the model repo's assets folder; that only works for visitors once the model repo is public).
|
| 929 |
+
SAMPLES_DIR = "samples"
|
| 930 |
+
SAMPLES_BASE = os.environ.get(
|
| 931 |
+
"SAMPLES_BASE", "https://huggingface.co/trydrama-neuralgarage/DRAMA/resolve/main/assets").rstrip("/")
|
| 932 |
+
SAMPLES = [ # number, scene, what DRAMA changed, file extension
|
| 933 |
+
(1, "Outdoor interview", "Neutral to a warm smile", "mp4"),
|
| 934 |
+
(2, "Talking head", "Serious to friendly", "mp4"),
|
| 935 |
+
(3, "Studio presenter", "Neutral to smiling", "mov"),
|
| 936 |
+
(4, "Home vlog", "Neutral to smiling", "mp4"),
|
| 937 |
+
(5, "Street portrait", "Serious to a subtle smile", "mp4"),
|
| 938 |
+
(6, "Outdoor portrait", "Smile to a big smile", "mp4"),
|
| 939 |
+
(7, "Webinar host", "Neutral to smiling", "mp4"),
|
| 940 |
+
(8, "Explainer to camera", "Neutral to smiling", "mp4"),
|
| 941 |
+
]
|
| 942 |
+
|
| 943 |
+
|
| 944 |
+
def sample_url(n, kind, ext):
|
| 945 |
+
local = sorted(glob.glob(os.path.join(SAMPLES_DIR, f"{n}_{kind}.*")))
|
| 946 |
+
if local:
|
| 947 |
+
return f"/gradio_api/file={os.path.abspath(local[0])}#t=0.1"
|
| 948 |
+
return f"{SAMPLES_BASE}/{n}_{kind}.{ext}#t=0.1"
|
| 949 |
+
|
| 950 |
+
|
| 951 |
+
def samples_html():
|
| 952 |
+
chips, first = [], None
|
| 953 |
+
for i, (n, scene, change, ext) in enumerate(SAMPLES):
|
| 954 |
+
before, after = sample_url(n, "before", ext), sample_url(n, "after", ext)
|
| 955 |
+
first = first or (before, after, scene, change)
|
| 956 |
+
chips.append(f'<button type="button" class="dr-ba-chip{" is-on" if i == 0 else ""}" '
|
| 957 |
+
f'data-before="{before}" data-after="{after}" data-scene="{scene}" '
|
| 958 |
+
f'data-change="{change}"><b>{n:02d}</b>{scene}</button>')
|
| 959 |
+
before, after, scene, change = first
|
| 960 |
+
return f"""
|
| 961 |
+
<section class="dr-section" id="examples">
|
| 962 |
+
<div class="dr-center-head"><div class="dr-kicker">Before and after</div>
|
| 963 |
+
<h2>Same take. New expression.</h2>
|
| 964 |
+
<div class="dr-section-sub">Real footage edited with DRAMA. Pick an example and press play;
|
| 965 |
+
both videos play in sync.</div></div>
|
| 966 |
+
<div class="dr-ba">
|
| 967 |
+
<div class="dr-ba-chips" role="tablist">{''.join(chips)}</div>
|
| 968 |
+
<div class="dr-ba-stage">
|
| 969 |
+
<figure><figcaption>Original</figcaption>
|
| 970 |
+
<video class="dr-ba-before" src="{before}" muted playsinline controls preload="metadata"></video></figure>
|
| 971 |
+
<figure><figcaption class="dr-ba-red">With DRAMA</figcaption>
|
| 972 |
+
<video class="dr-ba-after" src="{after}" muted playsinline controls preload="metadata"></video></figure>
|
| 973 |
+
</div>
|
| 974 |
+
<div class="dr-ba-cap"><b>{scene}</b><span>{change}</span></div>
|
| 975 |
+
</div>
|
| 976 |
+
</section>"""
|
| 977 |
+
|
| 978 |
+
|
| 979 |
PROMO_VIDEOS = [ # YouTube video ID, accessible title
|
| 980 |
("WYi2y1NOhhc", "Introducing trydrama.io"),
|
| 981 |
("NngMqkyotx8", "Acting is Solved!"),
|
|
|
|
| 1194 |
.gradio-container .dr-video iframe { position: absolute; inset: 0; width: 100% !important; height: 100% !important;
|
| 1195 |
border: 0 !important; }
|
| 1196 |
|
| 1197 |
+
/* before / after samples */
|
| 1198 |
+
.gradio-container .dr-ba { max-width: 1100px; min-width: 0; margin: 0 auto; }
|
| 1199 |
+
.gradio-container .dr-ba-chips { display: flex; gap: 8px; overflow-x: auto; padding: 2px 2px 14px; scrollbar-width: thin;
|
| 1200 |
+
justify-content: center; flex-wrap: wrap; }
|
| 1201 |
+
.gradio-container .dr-ba-chip { flex: none; display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
|
| 1202 |
+
padding: 8px 14px !important; border-radius: 999px !important; border: 1px solid var(--dr-line) !important;
|
| 1203 |
+
background: transparent !important; color: var(--dr-muted) !important; font-size: 13px !important; font-weight: 500 !important;
|
| 1204 |
+
line-height: 1.2 !important; transition: border-color .15s, color .15s; }
|
| 1205 |
+
.gradio-container .dr-ba-chip b { font: 600 11px/1 ui-monospace, "SF Mono", Menlo, monospace !important; color: var(--dr-faint) !important; }
|
| 1206 |
+
.gradio-container .dr-ba-chip:hover { color: #fff !important; border-color: #4a4a55 !important; }
|
| 1207 |
+
.gradio-container .dr-ba-chip.is-on { color: #fff !important; border-color: var(--dr-red) !important; background: var(--dr-red-soft) !important; }
|
| 1208 |
+
.gradio-container .dr-ba-chip.is-on b { color: var(--dr-red) !important; }
|
| 1209 |
+
.gradio-container .dr-ba-stage { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
| 1210 |
+
.gradio-container .dr-ba-stage figure { margin: 0 !important; position: relative; }
|
| 1211 |
+
.gradio-container .dr-ba-stage figcaption { position: absolute; top: 10px; left: 10px; z-index: 2; pointer-events: none;
|
| 1212 |
+
font-size: 12px !important; font-weight: 600 !important; letter-spacing: .02em; color: #fff !important;
|
| 1213 |
+
background: rgba(0,0,0,.55); padding: 4px 9px; border-radius: 6px; }
|
| 1214 |
+
.gradio-container .dr-ba-stage figcaption.dr-ba-red { background: var(--dr-red); }
|
| 1215 |
+
.gradio-container .dr-ba-stage video { display: block; width: 100% !important; aspect-ratio: 16 / 9; object-fit: contain;
|
| 1216 |
+
background: #000; border-radius: 12px; border: 1px solid var(--dr-line); }
|
| 1217 |
+
.gradio-container .dr-ba-cap { display: flex; gap: 10px; justify-content: center; align-items: baseline; flex-wrap: wrap;
|
| 1218 |
+
margin-top: 14px; font-size: 14px !important; color: var(--dr-muted) !important; }
|
| 1219 |
+
.gradio-container .dr-ba-cap b { color: #fff !important; font-weight: 600 !important; }
|
| 1220 |
+
.gradio-container .dr-ba-cap span::before { content: "·"; margin-right: 10px; color: var(--dr-faint); }
|
| 1221 |
+
|
| 1222 |
/* FAQ */
|
| 1223 |
.gradio-container .dr-faq { display: grid; border-top: 1px solid var(--dr-line); }
|
| 1224 |
.gradio-container .dr-faq details { border-bottom: 1px solid var(--dr-line); padding: 0 !important; margin: 0 !important;
|
|
|
|
| 1256 |
.gradio-container { padding: 0 16px 32px !important; }
|
| 1257 |
.gradio-container .dr-list { grid-template-columns: 1fr; }
|
| 1258 |
.gradio-container .dr-videos { grid-template-columns: 1fr; }
|
| 1259 |
+
.gradio-container .dr-ba-stage { grid-template-columns: 1fr; }
|
| 1260 |
+
.gradio-container .dr-ba-chip { padding: 7px 12px !important; font-size: 12px !important; }
|
| 1261 |
.gradio-container .dr-hero { padding: 40px 0 28px; }
|
| 1262 |
.gradio-container .dr-hero .dr-sub { font-size: 16px !important; }
|
| 1263 |
.gradio-container .dr-meta { gap: 4px 14px; }
|
|
|
|
| 1268 |
.gradio-container .dr-cta-btns { width: 100%; } .gradio-container a.dr-btn { flex: 1; text-align: center; }
|
| 1269 |
.gradio-container .dr-section { padding-top: 64px !important; }
|
| 1270 |
.gradio-container .dr-foot { grid-template-columns: 1fr; margin-top: 64px; }
|
| 1271 |
+
.gradio-container .dr-foot nav { justify-self: start; display: flex; flex-wrap: wrap; gap: 4px 18px; }
|
| 1272 |
+
.gradio-container .dr-foot nav a { margin: 0 !important; padding: 0 !important; white-space: nowrap; }
|
| 1273 |
}
|
| 1274 |
"""
|
| 1275 |
|
| 1276 |
+
|
| 1277 |
+
def _fix_media_rules(css):
|
| 1278 |
+
"""Gradio rewrites selectors inside @media blocks to
|
| 1279 |
+
".gradio-container.gradio-container-X .contain <selector>", so a selector that itself
|
| 1280 |
+
starts with ".gradio-container" never matches. Inside @media, drop that leading part
|
| 1281 |
+
so the rewritten selector lands on our elements."""
|
| 1282 |
+
import re
|
| 1283 |
+
out, i = [], 0
|
| 1284 |
+
for m in re.finditer(r"@media[^{]+\{", css):
|
| 1285 |
+
start = m.end()
|
| 1286 |
+
depth, j = 1, start
|
| 1287 |
+
while depth:
|
| 1288 |
+
depth += {"{": 1, "}": -1}.get(css[j], 0)
|
| 1289 |
+
j += 1
|
| 1290 |
+
body = re.sub(r"(^|[,{}])(\s*)\.gradio-container\s+(?=[.#a-zA-Z])", r"\1\2", css[start:j - 1],
|
| 1291 |
+
flags=re.M)
|
| 1292 |
+
out.append(css[i:start] + body + "}")
|
| 1293 |
+
i = j
|
| 1294 |
+
out.append(css[i:])
|
| 1295 |
+
return "".join(out)
|
| 1296 |
+
|
| 1297 |
+
|
| 1298 |
+
CSS = _fix_media_rules(CSS)
|
| 1299 |
+
|
| 1300 |
FORCE_DARK = """
|
| 1301 |
+
() => {
|
| 1302 |
+
document.documentElement.classList.add('dark'); document.body.classList.add('dark');
|
| 1303 |
+
if (window.__drBA) return; window.__drBA = true;
|
| 1304 |
+
// Before/after: switch examples
|
| 1305 |
+
document.addEventListener('click', (e) => {
|
| 1306 |
+
const chip = e.target.closest && e.target.closest('.dr-ba-chip'); if (!chip) return;
|
| 1307 |
+
const root = chip.closest('.dr-ba');
|
| 1308 |
+
root.querySelectorAll('.dr-ba-chip').forEach((c) => c.classList.toggle('is-on', c === chip));
|
| 1309 |
+
const b = root.querySelector('.dr-ba-before'), a = root.querySelector('.dr-ba-after');
|
| 1310 |
+
[b, a].forEach((v) => v.pause());
|
| 1311 |
+
b.src = chip.dataset.before; a.src = chip.dataset.after; b.load(); a.load();
|
| 1312 |
+
const cap = root.querySelector('.dr-ba-cap');
|
| 1313 |
+
cap.innerHTML = '<b></b><span></span>';
|
| 1314 |
+
cap.querySelector('b').textContent = chip.dataset.scene; cap.querySelector('span').textContent = chip.dataset.change;
|
| 1315 |
+
});
|
| 1316 |
+
// Trim timeline: preselect the first MAX seconds so the person only has to press Trim.
|
| 1317 |
+
// Gradio has no option for this, so we move the end handle the way a mouse drag would.
|
| 1318 |
+
const MAXS = __MAXSEC__;
|
| 1319 |
+
const autoTrim = () => {
|
| 1320 |
+
const root = document.getElementById('dr-in'); if (!root) return;
|
| 1321 |
+
const right = root.querySelector('.handle.right'), tl = root.querySelector('#timeline'), v = root.querySelector('video');
|
| 1322 |
+
if (!right || !tl || !v || right.dataset.drAuto) return;
|
| 1323 |
+
const d = v.duration, r = tl.getBoundingClientRect();
|
| 1324 |
+
if (!(d > 0) || !r.width) return;
|
| 1325 |
+
right.dataset.drAuto = '1';
|
| 1326 |
+
if (d <= MAXS + 0.5) return;
|
| 1327 |
+
const y = r.top + r.height / 2;
|
| 1328 |
+
right.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: r.right, clientY: y }));
|
| 1329 |
+
window.dispatchEvent(new MouseEvent('mousemove', { clientX: r.left + r.width * (MAXS / d), clientY: y }));
|
| 1330 |
+
window.dispatchEvent(new MouseEvent('mouseup', {}));
|
| 1331 |
+
v.currentTime = 0;
|
| 1332 |
+
};
|
| 1333 |
+
new MutationObserver(autoTrim).observe(document.body, { childList: true, subtree: true });
|
| 1334 |
+
// Before/after: keep the two videos playing together
|
| 1335 |
+
const partner = (v) => { const r = v.closest && v.closest('.dr-ba'); if (!r) return null;
|
| 1336 |
+
return v.classList.contains('dr-ba-before') ? r.querySelector('.dr-ba-after') : r.querySelector('.dr-ba-before'); };
|
| 1337 |
+
document.addEventListener('play', (e) => { const o = partner(e.target); if (!o) return;
|
| 1338 |
+
if (Math.abs(o.currentTime - e.target.currentTime) > 0.15) o.currentTime = e.target.currentTime;
|
| 1339 |
+
if (o.paused) o.play().catch(() => {}); }, true);
|
| 1340 |
+
document.addEventListener('pause', (e) => { const o = partner(e.target); if (o && !o.paused && !e.target.ended) o.pause(); }, true);
|
| 1341 |
+
document.addEventListener('seeked', (e) => { const o = partner(e.target);
|
| 1342 |
+
if (o && Math.abs(o.currentTime - e.target.currentTime) > 0.15) o.currentTime = e.target.currentTime; }, true);
|
| 1343 |
+
}
|
| 1344 |
+
""".replace("__MAXSEC__", str(MAX_SECONDS))
|
| 1345 |
|
| 1346 |
with gr.Blocks(title=PAGE_TITLE) as demo:
|
| 1347 |
gr.HTML('<div class="dr-glow"></div>')
|
|
|
|
| 1399 |
<div class="dr-cta-btns"><a class="dr-btn" href="{DOCS}" target="_blank">API docs</a>
|
| 1400 |
<a class="dr-btn dr-btn-primary" href="{SITE}" target="_blank">Try DRAMA Studio</a></div></div>""")
|
| 1401 |
|
| 1402 |
+
gr.HTML(samples_html())
|
| 1403 |
+
|
| 1404 |
videos = "".join(
|
| 1405 |
f'<div class="dr-video"><iframe src="https://www.youtube-nocookie.com/embed/{vid}?rel=0&modestbranding=1" '
|
| 1406 |
f'title="{title}" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" '
|
|
|
|
| 1475 |
|
| 1476 |
startup_check()
|
| 1477 |
demo.queue(max_size=20).launch(theme=THEME, css=CSS, js=FORCE_DARK, head=HEAD,
|
| 1478 |
+
allowed_paths=[os.path.abspath(SAMPLES_DIR)],
|
| 1479 |
favicon_path=favicon_file(), footer_links=[])
|