Spaces:
Running on Zero
Running on Zero
Commit Β·
41cd582
1
Parent(s): 5cf7a39
feat: add crossfade overlap indicators to waveform display
Browse filesDraws diagonal hatch pattern with 'CF' label over overlap zones between
adjacent segments so users can visually identify where crossfade blending
occurs. Helps correlate audio artifacts with crossfade regions during testing.
app.py
CHANGED
|
@@ -1066,7 +1066,8 @@ def _splice_and_save(new_wav, seg_idx, meta, slot_id):
|
|
| 1066 |
|
| 1067 |
waveform_html = _build_waveform_html(audio_path, segments, slot_id, "",
|
| 1068 |
state_json=state_json_new,
|
| 1069 |
-
video_path=video_path
|
|
|
|
| 1070 |
return video_path, audio_path, updated_meta, waveform_html
|
| 1071 |
|
| 1072 |
|
|
@@ -1612,7 +1613,8 @@ def _build_regen_pending_html(segments: list, regen_seg_idx: int, slot_id: str,
|
|
| 1612 |
|
| 1613 |
def _build_waveform_html(audio_path: str, segments: list, slot_id: str,
|
| 1614 |
hidden_input_id: str, state_json: str = "",
|
| 1615 |
-
fn_index: int = -1, video_path: str = ""
|
|
|
|
| 1616 |
"""Return a self-contained HTML block with a Canvas waveform (display only),
|
| 1617 |
segment boundary markers, and a download link.
|
| 1618 |
|
|
@@ -1659,6 +1661,7 @@ def _build_waveform_html(audio_path: str, segments: list, slot_id: str,
|
|
| 1659 |
const SLOT_ID = '{slot_id}';
|
| 1660 |
const segments = {segs_json};
|
| 1661 |
const segColors = {json.dumps(seg_colors)};
|
|
|
|
| 1662 |
let audioDuration = 0;
|
| 1663 |
|
| 1664 |
// ββ Popup via postMessage to parent global listener βββββββββββββββββ
|
|
@@ -1746,6 +1749,39 @@ def _build_waveform_html(audio_path: str, segments: list, slot_id: str,
|
|
| 1746 |
}});
|
| 1747 |
}});
|
| 1748 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1749 |
cv.onclick = function(e) {{
|
| 1750 |
const r=cv.getBoundingClientRect();
|
| 1751 |
const xRel=(e.clientX-r.left)/r.width;
|
|
@@ -1912,7 +1948,8 @@ def _unpack_outputs(flat: list, n: int, tab_prefix: str) -> list:
|
|
| 1912 |
state_json = json.dumps(meta)
|
| 1913 |
html = _build_waveform_html(aud_path, meta["segments"], slot_id,
|
| 1914 |
"", state_json=state_json,
|
| 1915 |
-
video_path=meta.get("video_path", "")
|
|
|
|
| 1916 |
wave_updates.append(gr.update(value=html))
|
| 1917 |
else:
|
| 1918 |
wave_updates.append(gr.update(
|
|
|
|
| 1066 |
|
| 1067 |
waveform_html = _build_waveform_html(audio_path, segments, slot_id, "",
|
| 1068 |
state_json=state_json_new,
|
| 1069 |
+
video_path=video_path,
|
| 1070 |
+
crossfade_s=crossfade_s)
|
| 1071 |
return video_path, audio_path, updated_meta, waveform_html
|
| 1072 |
|
| 1073 |
|
|
|
|
| 1613 |
|
| 1614 |
def _build_waveform_html(audio_path: str, segments: list, slot_id: str,
|
| 1615 |
hidden_input_id: str, state_json: str = "",
|
| 1616 |
+
fn_index: int = -1, video_path: str = "",
|
| 1617 |
+
crossfade_s: float = 0.0) -> str:
|
| 1618 |
"""Return a self-contained HTML block with a Canvas waveform (display only),
|
| 1619 |
segment boundary markers, and a download link.
|
| 1620 |
|
|
|
|
| 1661 |
const SLOT_ID = '{slot_id}';
|
| 1662 |
const segments = {segs_json};
|
| 1663 |
const segColors = {json.dumps(seg_colors)};
|
| 1664 |
+
const crossfadeSec = {crossfade_s};
|
| 1665 |
let audioDuration = 0;
|
| 1666 |
|
| 1667 |
// ββ Popup via postMessage to parent global listener βββββββββββββββββ
|
|
|
|
| 1749 |
}});
|
| 1750 |
}});
|
| 1751 |
|
| 1752 |
+
// ββ Crossfade overlap indicators ββ
|
| 1753 |
+
if (crossfadeSec > 0 && segments.length > 1) {{
|
| 1754 |
+
for (let i = 0; i < segments.length - 1; i++) {{
|
| 1755 |
+
// Overlap zone: where segment i ends and segment i+1 starts
|
| 1756 |
+
const overlapStart = segments[i+1][0];
|
| 1757 |
+
const overlapEnd = Math.min(segments[i][1], overlapStart + crossfadeSec);
|
| 1758 |
+
const xL = (overlapStart / duration) * W;
|
| 1759 |
+
const xR = (overlapEnd / duration) * W;
|
| 1760 |
+
// Diagonal hatch pattern over the overlap zone
|
| 1761 |
+
ctx.save();
|
| 1762 |
+
ctx.beginPath();
|
| 1763 |
+
ctx.rect(xL, 0, xR - xL, H);
|
| 1764 |
+
ctx.clip();
|
| 1765 |
+
ctx.strokeStyle = 'rgba(255,255,255,0.35)';
|
| 1766 |
+
ctx.lineWidth = 1;
|
| 1767 |
+
const spacing = 6;
|
| 1768 |
+
for (let lx = xL - H; lx < xR + H; lx += spacing) {{
|
| 1769 |
+
ctx.beginPath();
|
| 1770 |
+
ctx.moveTo(lx, H);
|
| 1771 |
+
ctx.lineTo(lx + H, 0);
|
| 1772 |
+
ctx.stroke();
|
| 1773 |
+
}}
|
| 1774 |
+
ctx.restore();
|
| 1775 |
+
// Label
|
| 1776 |
+
ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
| 1777 |
+
ctx.font = '9px sans-serif';
|
| 1778 |
+
const cfW = xR - xL;
|
| 1779 |
+
if (cfW > 20) {{
|
| 1780 |
+
ctx.fillText('CF', xL + (cfW - ctx.measureText('CF').width) / 2, H - 3);
|
| 1781 |
+
}}
|
| 1782 |
+
}}
|
| 1783 |
+
}}
|
| 1784 |
+
|
| 1785 |
cv.onclick = function(e) {{
|
| 1786 |
const r=cv.getBoundingClientRect();
|
| 1787 |
const xRel=(e.clientX-r.left)/r.width;
|
|
|
|
| 1948 |
state_json = json.dumps(meta)
|
| 1949 |
html = _build_waveform_html(aud_path, meta["segments"], slot_id,
|
| 1950 |
"", state_json=state_json,
|
| 1951 |
+
video_path=meta.get("video_path", ""),
|
| 1952 |
+
crossfade_s=float(meta.get("crossfade_s", 0)))
|
| 1953 |
wave_updates.append(gr.update(value=html))
|
| 1954 |
else:
|
| 1955 |
wave_updates.append(gr.update(
|