BoxOfColors commited on
Commit
41d322f
Β·
1 Parent(s): fcc4220

Fix waveform: popup via postMessage, regen, playhead, Too many args

Browse files
Files changed (1) hide show
  1. app.py +123 -75
app.py CHANGED
@@ -1125,17 +1125,6 @@ def _build_waveform_html(audio_path: str, segments: list, slot_id: str,
1125
  canvas {{ display:block; }}
1126
  #cv {{ position:absolute; top:0; left:0; width:100%; height:100%; }}
1127
  #cvp {{ position:absolute; top:0; left:0; width:100%; height:100%; pointer-events:none; }}
1128
- #popup {{
1129
- display:none; position:fixed; z-index:9999;
1130
- background:#2a2a2a; border:1px solid #555; border-radius:6px;
1131
- padding:8px 12px; box-shadow:0 4px 16px rgba(0,0,0,.5);
1132
- font-family:sans-serif;
1133
- }}
1134
- #popuplbl {{ color:#ccc; font-size:11px; margin-bottom:6px; white-space:nowrap; }}
1135
- #regenbtn {{
1136
- background:#1d6fa5; color:#fff; border:none; border-radius:4px;
1137
- padding:5px 14px; font-size:12px; cursor:pointer; width:100%;
1138
- }}
1139
  </style>
1140
  </head>
1141
  <body>
@@ -1143,71 +1132,38 @@ def _build_waveform_html(audio_path: str, segments: list, slot_id: str,
1143
  <canvas id="cv"></canvas>
1144
  <canvas id="cvp"></canvas>
1145
  </div>
1146
- <div id="popup">
1147
- <div id="popuplbl"></div>
1148
- <button id="regenbtn">&#10227; Regenerate</button>
1149
- </div>
1150
  <script>
1151
  (function() {{
1152
  const SLOT_ID = '{slot_id}';
1153
  const segments = {segs_json};
1154
  const segColors = {json.dumps(seg_colors)};
1155
  let audioDuration = 0;
1156
- let pendingIdx = null;
1157
-
1158
- // ── Popup ──────────────────────────────────────────────────────────
1159
- const popup = document.getElementById('popup');
1160
- const popuplbl= document.getElementById('popuplbl');
1161
- const regenbtn= document.getElementById('regenbtn');
1162
 
 
 
 
1163
  function showPopup(idx, mx, my) {{
1164
- pendingIdx = idx;
1165
- popuplbl.textContent = 'Seg '+(idx+1)+' ('+segments[idx][0].toFixed(2)+'s \u2013 '+segments[idx][1].toFixed(2)+'s)';
1166
- popup.style.display = 'block';
1167
- popup.style.left = (mx+10)+'px';
1168
- popup.style.top = (my+10)+'px';
1169
- requestAnimationFrame(function() {{
1170
- const r=popup.getBoundingClientRect(), vw=window.innerWidth, vh=window.innerHeight;
1171
- if (r.right >vw-8) popup.style.left=(vw-r.width-8)+'px';
1172
- if (r.bottom>vh-8) popup.style.top =(vh-r.height-8)+'px';
1173
- }});
1174
- }}
1175
- function hidePopup() {{ popup.style.display='none'; pendingIdx=null; }}
1176
-
1177
- regenbtn.onclick = function(e) {{
1178
- e.stopPropagation();
1179
- if (pendingIdx !== null) {{
1180
- // Directly trigger Gradio hidden input in parent document (same-origin iframe)
1181
- try {{
1182
- const par = window.parent.document;
1183
- const el = par.getElementById('{hidden_input_id}');
1184
- if (el) {{
1185
- const input = el.querySelector('input, textarea');
1186
- if (input) {{
1187
- const desc = Object.getOwnPropertyDescriptor(
1188
- window.parent.HTMLInputElement.prototype, 'value')
1189
- || Object.getOwnPropertyDescriptor(
1190
- window.parent.HTMLTextAreaElement.prototype, 'value');
1191
- if (desc && desc.set) desc.set.call(input, SLOT_ID+'|'+pendingIdx);
1192
- else input.value = SLOT_ID+'|'+pendingIdx;
1193
- input.dispatchEvent(new window.parent.Event('input', {{bubbles:true}}));
1194
- }}
1195
- }}
1196
- // Update status label in parent
1197
- const lbl = par.getElementById('wf_seglabel_{slot_id}');
1198
- if (lbl && segments[pendingIdx])
1199
- lbl.textContent = 'Regenerating Seg '+(pendingIdx+1)+
1200
- ' ('+segments[pendingIdx][0].toFixed(2)+'s \u2013 '+
1201
- segments[pendingIdx][1].toFixed(2)+'s)\u2026';
1202
- }} catch(err) {{
1203
- console.error('[wf iframe] regen trigger failed:', err);
1204
- }}
1205
- hidePopup();
1206
  }}
1207
- }};
1208
- document.addEventListener('click', function(e) {{
1209
- if (popup.style.display!=='none' && !popup.contains(e.target)) hidePopup();
1210
- }}, true);
1211
 
1212
  // ── Canvas waveform ────────────────────────────────────────────────
1213
  const cv = document.getElementById('cv');
@@ -1289,14 +1245,29 @@ def _build_waveform_html(audio_path: str, segments: list, slot_id: str,
1289
  ctx.restore();
1290
  }}
1291
 
1292
- // Poll parent for video time
1293
- setInterval(function() {{
1294
  try {{
1295
- const vid = window.parent.document.querySelector('video');
1296
- if (vid && vid.duration && isFinite(vid.duration) && audioDuration > 0) {{
1297
- drawPlayhead(vid.currentTime / vid.duration);
 
 
 
 
 
 
 
1298
  }}
1299
- }} catch(e) {{ /* cross-origin β€” ignore */ }}
 
 
 
 
 
 
 
 
1300
  }}, 50);
1301
 
1302
  // ── Decode audio ───────────────────────────────────────────────────
@@ -1375,7 +1346,6 @@ def _make_output_slots(tab_prefix: str) -> tuple:
1375
  vids.append(gr.Video(label=f"Generation {i+1} β€” Video"))
1376
  waveforms.append(gr.HTML(
1377
  value="<p style='color:#888;font-size:12px'>Generate audio to see waveform.</p>",
1378
- label=f"Generation {i+1} β€” Waveform",
1379
  ))
1380
  # Hidden textbox: JS writes "<slot_id>|<seg_idx>" here to trigger regen
1381
  regen_triggers.append(gr.Textbox(
@@ -1449,7 +1419,85 @@ _SLOT_CSS = """
1449
  }
1450
  """
1451
 
1452
- with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1453
  gr.Markdown(
1454
  "# Generate Audio for Video\n"
1455
  "Choose a model and upload a video to generate synchronized audio.\n\n"
 
1125
  canvas {{ display:block; }}
1126
  #cv {{ position:absolute; top:0; left:0; width:100%; height:100%; }}
1127
  #cvp {{ position:absolute; top:0; left:0; width:100%; height:100%; pointer-events:none; }}
 
 
 
 
 
 
 
 
 
 
 
1128
  </style>
1129
  </head>
1130
  <body>
 
1132
  <canvas id="cv"></canvas>
1133
  <canvas id="cvp"></canvas>
1134
  </div>
 
 
 
 
1135
  <script>
1136
  (function() {{
1137
  const SLOT_ID = '{slot_id}';
1138
  const segments = {segs_json};
1139
  const segColors = {json.dumps(seg_colors)};
1140
  let audioDuration = 0;
 
 
 
 
 
 
1141
 
1142
+ // ── Popup via postMessage to parent global listener ─────────────────
1143
+ // The parent page (Gradio) has a global window.addEventListener('message',...)
1144
+ // set up via gr.Blocks(js=...) that handles popup show/hide and regen trigger.
1145
  function showPopup(idx, mx, my) {{
1146
+ // Convert iframe-local coords to parent page coords
1147
+ try {{
1148
+ const fr = window.frameElement ? window.frameElement.getBoundingClientRect() : {{left:0,top:0}};
1149
+ window.parent.postMessage({{
1150
+ type:'wf_popup', action:'show',
1151
+ slot_id: SLOT_ID, seg_idx: idx,
1152
+ t0: segments[idx][0], t1: segments[idx][1],
1153
+ x: mx + fr.left, y: my + fr.top
1154
+ }}, '*');
1155
+ }} catch(e) {{
1156
+ window.parent.postMessage({{
1157
+ type:'wf_popup', action:'show',
1158
+ slot_id: SLOT_ID, seg_idx: idx,
1159
+ t0: segments[idx][0], t1: segments[idx][1],
1160
+ x: mx, y: my
1161
+ }}, '*');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1162
  }}
1163
+ }}
1164
+ function hidePopup() {{
1165
+ window.parent.postMessage({{type:'wf_popup', action:'hide'}}, '*');
1166
+ }}
1167
 
1168
  // ── Canvas waveform ────────────────────────────────────────────────
1169
  const cv = document.getElementById('cv');
 
1245
  ctx.restore();
1246
  }}
1247
 
1248
+ // Poll parent for video time β€” find the video in the same wf_container slot
1249
+ function findSlotVideo() {{
1250
  try {{
1251
+ const par = window.parent.document;
1252
+ // Walk up from our iframe to find wf_container_{slot_id}, then find its sibling video
1253
+ const container = par.getElementById('wf_container_{slot_id}');
1254
+ if (!container) return par.querySelector('video');
1255
+ // The video is inside the same gr.Group β€” walk up to find it
1256
+ let node = container.parentElement;
1257
+ while (node && node !== par.body) {{
1258
+ const v = node.querySelector('video');
1259
+ if (v) return v;
1260
+ node = node.parentElement;
1261
  }}
1262
+ return null;
1263
+ }} catch(e) {{ return null; }}
1264
+ }}
1265
+
1266
+ setInterval(function() {{
1267
+ const vid = findSlotVideo();
1268
+ if (vid && vid.duration && isFinite(vid.duration) && audioDuration > 0) {{
1269
+ drawPlayhead(vid.currentTime / vid.duration);
1270
+ }}
1271
  }}, 50);
1272
 
1273
  // ── Decode audio ───────────────────────────────────────────────────
 
1346
  vids.append(gr.Video(label=f"Generation {i+1} β€” Video"))
1347
  waveforms.append(gr.HTML(
1348
  value="<p style='color:#888;font-size:12px'>Generate audio to see waveform.</p>",
 
1349
  ))
1350
  # Hidden textbox: JS writes "<slot_id>|<seg_idx>" here to trigger regen
1351
  regen_triggers.append(gr.Textbox(
 
1419
  }
1420
  """
1421
 
1422
+ _GLOBAL_JS = """
1423
+ () => {
1424
+ // Global postMessage handler for waveform iframe events.
1425
+ // Runs once on page load (Gradio js= parameter).
1426
+ // Handles: popup open/close relay, regen trigger.
1427
+ if (window._wf_global_listener) return; // already registered
1428
+ window._wf_global_listener = true;
1429
+
1430
+ // Shared popup element created once and reused across all slots
1431
+ let _popup = null;
1432
+ let _pendingSlot = null, _pendingIdx = null;
1433
+
1434
+ function ensurePopup() {
1435
+ if (_popup) return _popup;
1436
+ _popup = document.createElement('div');
1437
+ _popup.style.cssText = 'display:none;position:fixed;z-index:99999;' +
1438
+ 'background:#2a2a2a;border:1px solid #555;border-radius:6px;' +
1439
+ 'padding:8px 12px;box-shadow:0 4px 16px rgba(0,0,0,.5);font-family:sans-serif;';
1440
+ _popup.innerHTML =
1441
+ '<div id="_wf_popup_lbl" style="color:#ccc;font-size:11px;margin-bottom:6px;white-space:nowrap;"></div>' +
1442
+ '<button id="_wf_popup_btn" style="background:#1d6fa5;color:#fff;border:none;' +
1443
+ 'border-radius:4px;padding:5px 14px;font-size:12px;cursor:pointer;width:100%;">&#10227; Regenerate</button>';
1444
+ document.body.appendChild(_popup);
1445
+ document.getElementById('_wf_popup_btn').onclick = function(e) {
1446
+ e.stopPropagation();
1447
+ if (_pendingSlot !== null && _pendingIdx !== null) {
1448
+ fireRegen(_pendingSlot, _pendingIdx);
1449
+ }
1450
+ hidePopup();
1451
+ };
1452
+ document.addEventListener('click', function() { hidePopup(); }, true);
1453
+ return _popup;
1454
+ }
1455
+
1456
+ function hidePopup() {
1457
+ if (_popup) _popup.style.display = 'none';
1458
+ _pendingSlot = null; _pendingIdx = null;
1459
+ }
1460
+
1461
+ function fireRegen(slot_id, idx) {
1462
+ const hidden_id = 'regen_trigger_' + slot_id;
1463
+ const el = document.getElementById(hidden_id);
1464
+ if (!el) { console.warn('[wf global] regen el not found:', hidden_id); return; }
1465
+ const input = el.querySelector('input, textarea');
1466
+ if (!input) { console.warn('[wf global] regen input not found in', hidden_id); return; }
1467
+ const desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')
1468
+ || Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
1469
+ if (desc && desc.set) desc.set.call(input, slot_id + '|' + idx);
1470
+ else input.value = slot_id + '|' + idx;
1471
+ input.dispatchEvent(new Event('input', {bubbles: true}));
1472
+ // Update status label
1473
+ const lbl = document.getElementById('wf_seglabel_' + slot_id);
1474
+ if (lbl) lbl.textContent = 'Regenerating Seg ' + (idx + 1) + '...';
1475
+ }
1476
+
1477
+ window.addEventListener('message', function(e) {
1478
+ const d = e.data;
1479
+ if (!d || d.type !== 'wf_popup') return;
1480
+ const p = ensurePopup();
1481
+ if (d.action === 'hide') { hidePopup(); return; }
1482
+ // action === 'show'
1483
+ _pendingSlot = d.slot_id;
1484
+ _pendingIdx = d.seg_idx;
1485
+ const lbl = document.getElementById('_wf_popup_lbl');
1486
+ if (lbl) lbl.textContent = 'Seg ' + (d.seg_idx + 1) +
1487
+ ' (' + d.t0.toFixed(2) + 's – ' + d.t1.toFixed(2) + 's)';
1488
+ p.style.display = 'block';
1489
+ p.style.left = (d.x + 10) + 'px';
1490
+ p.style.top = (d.y + 10) + 'px';
1491
+ requestAnimationFrame(function() {
1492
+ const r = p.getBoundingClientRect();
1493
+ if (r.right > window.innerWidth - 8) p.style.left = (window.innerWidth - r.width - 8) + 'px';
1494
+ if (r.bottom > window.innerHeight - 8) p.style.top = (window.innerHeight - r.height - 8) + 'px';
1495
+ });
1496
+ });
1497
+ }
1498
+ """
1499
+
1500
+ with gr.Blocks(title="Generate Audio for Video", css=_SLOT_CSS, js=_GLOBAL_JS) as demo:
1501
  gr.Markdown(
1502
  "# Generate Audio for Video\n"
1503
  "Choose a model and upload a video to generate synchronized audio.\n\n"