Audio Wave: correct model — 721 groups are hard splits, segments inside; range select; mirrored render; fix reload explosion
Rebuilt around the real model: group_frames (721) are HARD splits; segments = user
splits UNION the group lines, so a segment never crosses a group boundary (last segment
in a group ends exactly on frame 721). segment_select is now a RANGE string ('A-B' /
'N' / '' = all) that crops waveform_image + audio + summary to segments A..B. Render
rewritten: mirrored waveform (uses top+bottom), bold group grid, thin segment lines,
labels along the top and notes along the bottom, selected range shaded. JS: guards
frame values (0 -> default, fixing the 400+-segments-on-reload explosion), writes only
USER splits to segments_json, dblclick=add split / shift-click=remove / click=seek.
group number fixed (boundary rounding). Workflows + README updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+29
-22
@@ -131,43 +131,50 @@ def _segments(rms_n, times, duration, fps, max_segments, beats):
|
||||
|
||||
|
||||
def _render(rms_n, times, duration, beats, bounds, segs,
|
||||
fps=None, frames_marker=0, window=None):
|
||||
"""Render the envelope + beats + segment boundaries + time labels to a ComfyUI IMAGE.
|
||||
window=(t0,t1) crops to that time span (for a selected segment). frames_marker draws
|
||||
a green line every N frames (N/fps seconds) — LTX clip-length grid."""
|
||||
fps=None, group_frames=0, frames_marker=0, window=None, sel=None):
|
||||
"""Render a mirrored waveform + the 721-frame group grid (bold) + segment lines (thin),
|
||||
labels along the top and per-segment notes along the bottom. window=(t0,t1) crops to a
|
||||
range; sel=(a,b) shades segments a..b."""
|
||||
W, H = 1024, 256
|
||||
img = Image.new("RGB", (W, H), (18, 18, 22))
|
||||
d = ImageDraw.Draw(img)
|
||||
t0, t1 = window if window else (0.0, duration)
|
||||
span = max(t1 - t0, 1e-6)
|
||||
mid = H // 2
|
||||
|
||||
def X(t):
|
||||
return int(max(0, min(W - 1, (t - t0) / span * (W - 1))))
|
||||
|
||||
pts = [(0, H)] + [(X(times[i]), H - int(rms_n[i] * (H - 30)))
|
||||
for i in range(len(rms_n)) if t0 <= times[i] <= t1] + [(W - 1, H)]
|
||||
if len(pts) > 2:
|
||||
d.polygon(pts, fill=(60, 140, 220))
|
||||
if fps and frames_marker: # frame grid (green)
|
||||
step = frames_marker / fps
|
||||
if sel and not window: # shade the selected segment range
|
||||
chosen = [s for s in segs if sel[0] <= s["segment"] <= sel[1]]
|
||||
if chosen:
|
||||
xa = X(chosen[0]["start_s"])
|
||||
xb = X(chosen[-1]["start_s"] + chosen[-1]["duration_s"])
|
||||
d.rectangle([xa, 0, xb, H], fill=(38, 54, 82))
|
||||
amp = H * 0.44 # mirrored waveform around the centre line
|
||||
for i in range(len(rms_n)):
|
||||
if t0 <= times[i] <= t1:
|
||||
x = X(times[i]); h = int(rms_n[i] * amp)
|
||||
d.line([(x, mid - h), (x, mid + h)], fill=(60, 140, 220))
|
||||
if fps and group_frames: # bold 721-frame group grid
|
||||
gstep = group_frames / fps
|
||||
k = 1
|
||||
while k * step < duration + 1e-6:
|
||||
mt = k * step
|
||||
if t0 <= mt <= t1:
|
||||
d.line([(X(mt), 0), (X(mt), H)], fill=(70, 200, 120), width=1)
|
||||
d.text((X(mt) + 2, H - 13), f"{frames_marker * k}f", fill=(70, 200, 120))
|
||||
while k * gstep < duration + 1e-6:
|
||||
gt = k * gstep
|
||||
if t0 <= gt <= t1:
|
||||
d.line([(X(gt), 0), (X(gt), H)], fill=(235, 235, 242), width=2)
|
||||
k += 1
|
||||
for b in beats: # beat markers (orange)
|
||||
for b in beats: # beat ticks (faint, centre band)
|
||||
if t0 <= b <= t1:
|
||||
d.line([(X(b), 0), (X(b), H)], fill=(230, 110, 60), width=1)
|
||||
for s in segs: # boundaries + label + time + note
|
||||
d.line([(X(b), mid - 4), (X(b), mid + 4)], fill=(150, 90, 60), width=1)
|
||||
for s in segs: # segment line + label top + note bottom
|
||||
if not (t0 <= s["start_s"] <= t1):
|
||||
continue
|
||||
x = X(s["start_s"])
|
||||
d.line([(x, 0), (x, H)], fill=(240, 240, 240), width=1)
|
||||
d.text((x + 4, 4), f"S{s['segment']} {s['energy']} {s['start_s']}s/{s['frames']}f", fill=(255, 255, 255))
|
||||
if s.get("note"): # per-segment note (amber)
|
||||
d.text((x + 4, 18), s["note"][:30], fill=(255, 210, 110))
|
||||
d.line([(x, 0), (x, H)], fill=(120, 190, 150), width=1)
|
||||
d.text((x + 3, 3), f"S{s['segment']} {s['start_s']}s/{s['frames']}f", fill=(240, 240, 240))
|
||||
if s.get("note"):
|
||||
d.text((x + 3, H - 13), s["note"][:28], fill=(255, 210, 110))
|
||||
arr = np.asarray(img, dtype=np.float32) / 255.0
|
||||
return torch.from_numpy(arr)[None, ...] # [1, H, W, 3]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user