Commit ·
782f393
1
Parent(s): 1cf1eb7
app.py
CHANGED
|
@@ -1328,31 +1328,45 @@ def _build_waveform_html(audio_path: str, segments: list, slot_id: str,
|
|
| 1328 |
const buf = new Uint8Array(bin.length);
|
| 1329 |
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
| 1330 |
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
-
|
| 1334 |
-
|
| 1335 |
-
|
| 1336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1337 |
const channelData = audioBuffer.getChannelData(0);
|
| 1338 |
const duration = audioBuffer.duration;
|
| 1339 |
-
tmpCtx.close();
|
|
|
|
| 1340 |
|
| 1341 |
-
// Now wait for the canvas to have layout width before drawing
|
| 1342 |
function tryDraw() {{
|
| 1343 |
const canvas = document.getElementById('wf_canvas_{slot_id}');
|
| 1344 |
-
if (!canvas) {{ setTimeout(tryDraw, 100); return; }}
|
| 1345 |
-
// Force a width by walking up to a sized ancestor if needed
|
| 1346 |
let W = canvas.getBoundingClientRect().width;
|
| 1347 |
-
if (W <= 0
|
| 1348 |
-
if (W <= 0
|
|
|
|
|
|
|
|
|
|
| 1349 |
drawWaveform(channelData, duration);
|
| 1350 |
attachVideoSync();
|
| 1351 |
}}
|
| 1352 |
tryDraw();
|
| 1353 |
-
}}
|
|
|
|
|
|
|
|
|
|
| 1354 |
console.error('[waveform {slot_id}] decodeAudioData error:', err);
|
| 1355 |
}});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1356 |
}}
|
| 1357 |
}})();
|
| 1358 |
</script>
|
|
@@ -1437,19 +1451,11 @@ def _update_slot_visibility(n):
|
|
| 1437 |
# ================================================================== #
|
| 1438 |
|
| 1439 |
_SLOT_CSS = """
|
| 1440 |
-
/*
|
| 1441 |
-
aspect-ratio container so they match at any window size. */
|
| 1442 |
-
.gradio-video,
|
| 1443 |
-
.gradio-video > div {
|
| 1444 |
-
aspect-ratio: 16 / 9;
|
| 1445 |
-
width: 100%;
|
| 1446 |
-
height: auto !important;
|
| 1447 |
-
min-height: unset !important;
|
| 1448 |
-
max-height: unset !important;
|
| 1449 |
-
}
|
| 1450 |
.gradio-video video {
|
| 1451 |
-
width: 100%;
|
| 1452 |
-
height:
|
|
|
|
| 1453 |
object-fit: contain;
|
| 1454 |
}
|
| 1455 |
"""
|
|
|
|
| 1328 |
const buf = new Uint8Array(bin.length);
|
| 1329 |
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
| 1330 |
|
| 1331 |
+
// OfflineAudioContext for decoding — works without user gesture, no playback
|
| 1332 |
+
const OfflineCtx = window.OfflineAudioContext || window.webkitOfflineAudioContext;
|
| 1333 |
+
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
| 1334 |
+
if (!OfflineCtx && !AudioCtx) {{
|
| 1335 |
+
console.warn('[waveform {slot_id}] No AudioContext available');
|
| 1336 |
+
}} else {{
|
| 1337 |
+
// Decode using a throwaway AudioContext (OfflineAudioContext.decodeAudioData
|
| 1338 |
+
// has the same user-gesture restriction in some browsers, so use regular one)
|
| 1339 |
+
const tmpCtx = new (AudioCtx || OfflineCtx)({{sampleRate: 44100}});
|
| 1340 |
+
const doRender = function(audioBuffer) {{
|
| 1341 |
const channelData = audioBuffer.getChannelData(0);
|
| 1342 |
const duration = audioBuffer.duration;
|
| 1343 |
+
try {{ tmpCtx.close(); }} catch(e) {{}}
|
| 1344 |
+
console.log('[waveform {slot_id}] decoded OK, duration=' + duration + 's, samples=' + channelData.length);
|
| 1345 |
|
|
|
|
| 1346 |
function tryDraw() {{
|
| 1347 |
const canvas = document.getElementById('wf_canvas_{slot_id}');
|
| 1348 |
+
if (!canvas) {{ console.log('[waveform {slot_id}] canvas not in DOM yet'); setTimeout(tryDraw, 100); return; }}
|
|
|
|
| 1349 |
let W = canvas.getBoundingClientRect().width;
|
| 1350 |
+
if (W <= 0 && canvas.parentElement) W = canvas.parentElement.getBoundingClientRect().width;
|
| 1351 |
+
if (W <= 0 && canvas.parentElement && canvas.parentElement.parentElement)
|
| 1352 |
+
W = canvas.parentElement.parentElement.getBoundingClientRect().width;
|
| 1353 |
+
console.log('[waveform {slot_id}] tryDraw W=' + W);
|
| 1354 |
+
if (W <= 0) {{ setTimeout(tryDraw, 150); return; }}
|
| 1355 |
drawWaveform(channelData, duration);
|
| 1356 |
attachVideoSync();
|
| 1357 |
}}
|
| 1358 |
tryDraw();
|
| 1359 |
+
}};
|
| 1360 |
+
|
| 1361 |
+
// decodeAudioData with promise fallback
|
| 1362 |
+
const decodePromise = tmpCtx.decodeAudioData(buf.buffer.slice(0), doRender, function(err) {{
|
| 1363 |
console.error('[waveform {slot_id}] decodeAudioData error:', err);
|
| 1364 |
}});
|
| 1365 |
+
if (decodePromise && typeof decodePromise.then === 'function') {{
|
| 1366 |
+
decodePromise.then(doRender).catch(function(err) {{
|
| 1367 |
+
console.error('[waveform {slot_id}] decodeAudioData promise error:', err);
|
| 1368 |
+
}});
|
| 1369 |
+
}}
|
| 1370 |
}}
|
| 1371 |
}})();
|
| 1372 |
</script>
|
|
|
|
| 1451 |
# ================================================================== #
|
| 1452 |
|
| 1453 |
_SLOT_CSS = """
|
| 1454 |
+
/* Responsive video: fills column width, height auto from aspect ratio */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1455 |
.gradio-video video {
|
| 1456 |
+
width: 100% !important;
|
| 1457 |
+
height: auto !important;
|
| 1458 |
+
max-height: 60vh !important;
|
| 1459 |
object-fit: contain;
|
| 1460 |
}
|
| 1461 |
"""
|