trydrama-neuralgarage commited on
Commit
ba92a49
·
verified ·
1 Parent(s): 2d4a830

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -5
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, drag the handles to '
430
- f'choose up to {int(MAX_SECONDS)} seconds, then press <b>Trim</b>.</div>'))
 
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; } .gradio-container .dr-foot nav a { margin: 0 16px 0 0; }
 
1191
  }
1192
  """
1193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1194
  FORCE_DARK = """
1195
- () => { document.documentElement.classList.add('dark'); document.body.classList.add('dark'); }
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=[])