- HunyuanFoley: extract visual features only per segment (reuse
text_feats from single initial extraction) — saves ~2-5s GPU/segment
- MMAudio/Hunyuan: add torch.cuda.empty_cache() between samples to
prevent VRAM fragmentation (TARO already had this)
- Regen duration floor lowered from 60s to 30s — single-segment regen
takes ~16-20s actual GPU, saving ~30s wasted ZeroGPU quota per call
- Register regen temp dirs with _register_tmp_dir for cleanup (was
leaking tmpfiles indefinitely)
- TARO: save CAVP/onset features once instead of per-sample (identical
data, depends only on video)
- Remove redundant import time in _splice_and_save
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use Gradio's postMessage zerogpu-headers for regen quota attribution
Instead of the unreliable x-ip-token relay, now use the same mechanism
Gradio's own JS client uses: postMessage("zerogpu-headers") to the HF
parent frame, which responds with x-zerogpu-token and x-zerogpu-uuid.
These are the actual headers that ZeroGPU uses for Pro quota attribution.
The HF token input in Settings is kept as a fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix crossfade loudness bump: apply db_boost to overlap as a whole
Applying gain to each side independently (old code) caused a ~+3 dB
loudness spike at every segment seam. Equal-power crossfade (cos/sin)
already sums to 1.0 at the midpoint; the boost should be applied to
the blended overlap as a unit, not to each fade arm separately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add HF token input for ZeroGPU Pro quota on regen
Users can paste their HF token in the Settings accordion so that
segment regen calls use Authorization: Bearer <token> — the documented
way for ZeroGPU to attribute quota to a Pro account. Falls back to
x-ip-token relay if no token is provided. Quota now correctly debits
the logged-in user's Pro pool instead of the shared anonymous pool.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix ZeroGPU Pro quota: fetch fresh x-ip-token before each regen call
The JWT token expires in ~170s. Previously we fetched once on page load
and cached it, so by the time the user triggers regen the token was
already expired. Now _fetchIpToken() returns a Promise and fireRegen()
awaits a fresh token immediately before every queue/join call, ensuring
quota is always attributed to the logged-in Pro user.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: relay x-ip-token from HF proxy to JS for ZeroGPU Pro quota attribution
ZeroGPU identifies the user via the x-ip-token header injected by HF's
proxy on browser requests, but NOT on raw JS fetch() calls.
Solution:
- Add get_ip_token Gradio endpoint that reads x-ip-token from gr.Request
- On page load, JS calls this endpoint to retrieve the token and stores
it in window.__hf_ip_token
- All regen fetch() calls now include x-ip-token as a request header,
so ZeroGPU correctly attributes quota to the logged-in Pro account
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: add gr.Request to xregen_* for proper ZeroGPU quota attribution
Gradio automatically injects gr.Request into the server-side handler
when the function signature includes it. This gives @spaces.GPU access
to the user's auth token via the request context, so ZeroGPU attributes
GPU usage to the logged-in Pro account instead of the anonymous pool.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
revert: back to queue/join — call endpoint gives 404 Session not found
/gradio_api/call/{api_name} requires a different session registration
that our queue/join session_hash doesn't satisfy, causing 404 errors.
Revert to the working queue/join + queue/data SSE approach.
The quota attribution issue (Pro vs anon) remains a known limitation —
the queue/join path with credentials:include is the best we can do
without server-side gr.Request changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: switch regen to /gradio_api/call/{api_name} for proper ZeroGPU quota attribution
The old /gradio_api/queue/join endpoint bypasses HF's proxy auth injection,
causing ZeroGPU to treat regen calls as unauthenticated (2min quota) even
for Pro users. The newer /gradio_api/call/{api_name} endpoint is what
Gradio's own Svelte client uses and correctly carries the session through
HF's proxy so quota is attributed to the logged-in account.
Also updates _listenAndApply to use the call-based SSE stream
(/gradio_api/call/{api_name}/{event_id}) with named events (generating,
complete, error) instead of the queue/data message format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: restore waveform HTML before writing error to statusbar
The pending spinner (first yield) replaces the whole wf_container HTML
including the statusbar span. On quota error, we were trying to write
to wf_statusbar_* after it had been wiped. Fix: restore preRegenWaveHtml
first, then getElementById finds the span and error message shows correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ZeroGPU quota is attributed via the HF session cookie. Without
credentials:'include', the fetch to /gradio_api/queue/join omits
cookies because the Space origin (*.hf.space) differs from the HF
auth origin (huggingface.co), causing all regen calls to be treated
as unauthenticated (2min quota) even for PRO users.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: show quota error in status bar instead of toast
Toast approach was unreliable due to Gradio's iframe/document context.
Instead write the error directly into the wf_statusbar_<slot_id> span
which is always in our own DOM. Text turns red with the full ZeroGPU
quota message, then resets to normal after 15s so user can retry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: revert to document.body for toast — window.top blocked cross-origin
When the Space runs embedded on huggingface.co, window.top is the HF
parent page (different origin), so accessing window.top.document throws
a SecurityError. The _GLOBAL_JS already runs in the Space's own iframe
context so plain document.body is correct and always accessible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: use window.top.document for toast and max z-index
Gradio js= callbacks may run in a context where document.body is not
the visible page body. Use window.top.document to guarantee the toast
lands on the real page, and set z-index to INT_MAX so it clears all
Gradio overlays.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: show quota/error toast and restore UI state on failed regen
- Snapshot waveform HTML and video src before firing regen
- On process_completed with error (e.g. ZeroGPU quota exceeded):
- Show red toast with full error message including countdown timer
- Restore waveform to pre-regen HTML
- Restore video src to pre-regen value
- Set segment label to 'Regen failed — quota exceeded'
- Add _showRegenToast() helper styled like ZeroGPU warnings
- Remove debug SSE data[0]/data[1] console.log
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
debug: log SSE data shape and fix misleading success:false log
msg.success doesn't exist in Gradio 5 SSE protocol — completion/error
is signalled via msg.output.error. Log the actual error field and dump
out.data[0]/[1] shape so we can verify the video URL extraction path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: replace render=False xregen outputs with real rendered slot components
Gradio's postprocess_data looks up output component IDs in session state.
Components created with render=False are not registered there, causing
KeyError: 153 (or whichever block ID was assigned).
Replace _xr_vid_out / _xr_wave_out (both render=False) with
taro_slot_vids[0] / taro_slot_waves[0] as dummy sinks for all three
xregen_* event handlers. The JS _listenAndApply listener applies the
returned video/HTML directly to the correct slot's DOM elements and
ignores Gradio's output routing, so these slot-0 components are never
visibly affected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs fixed:
1. ValueError 'arrays must have same number of dimensions':
TARO outputs mono (T,) while MMAudio/Hunyuan output stereo (C, T).
_resample_to_slot_sr now takes a slot_wav_ref and matches channel
layout after resampling — stereo→mono averages channels, mono→stereo
duplicates the channel — so _cf_join always receives matching shapes.
2. No loading indicator on cross-model regen buttons:
xregen_* functions were plain return functions; the pending waveform
spinner only appeared via the Python yield in same-model regen. All
three xregen_* are now generators that yield the pending HTML
immediately before calling the GPU, matching the same-model behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat: cross-model segment regen — regenerate any slot with TARO, MMAudio, or HunyuanFoley
Popup now shows three model buttons (TARO / MMAudio / Hunyuan) instead of
one generic Regenerate. Clicking a different model's button fires one of
three new shared xregen_* Gradio endpoints (xregen_taro, xregen_mmaudio,
xregen_hunyuan) that accept slot_id as a plain string so results are
spliced back into the correct slot regardless of which tab generated it.
New _resample_to_slot_sr() helper resamples the incoming wav to match
the slot's original SR (TARO=16kHz, MMAudio=44.1kHz, Hunyuan=48kHz)
before _splice_and_save stitches it in, so any model can replace any
segment without sample-rate mismatch.
All new Gradio components/buttons use render=False to avoid adding to
the SSR component tree and triggering the 'Too many arguments' warning.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: free GPU memory between samples to prevent VRAM fragmentation
All samples within a single generate call share one @spaces.GPU
reservation. Without explicit cleanup, each sample's intermediate
tensors accumulate in the CUDA allocator cache, fragmenting VRAM and
causing progressive quality degradation on samples 2, 3, 4+.
torch.cuda.empty_cache() after each sample flushes the allocator so
every sample starts from a clean memory state, making quality
consistent across all generations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: use render=False on regen buttons to suppress Gradio 5 SSR 'Too many arguments' error
gr.Button(visible=False) renders a DOM element that Gradio's SSR validator
counts when checking event argument cardinality, causing 'Too many arguments'
warnings at startup and breaking output delivery for the Generate button.
render=False removes the button from the Svelte component tree entirely while
keeping the event handler registered in gradio_config.dependencies (required
for the JS api_name -> fn_index lookup used by the queue/join regen calls).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: move MMAUDIO_WINDOW and HUNYUAN_MAX_DUR before MODEL_CONFIGS
Both constants were defined after MODEL_CONFIGS referenced them,
causing NameError on startup. Moved them up alongside the other
per-model constants and removed the now-duplicate definitions.
fix: allow Gradio to serve /tmp files so waveform audio loads (403 fix)
Gradio 5 on HF Spaces blocks file serving from outside its own managed
temp dir by default. WAV files written to /tmp/tmpXXX/... returned 403
when the waveform iframe fetched them via /gradio_api/file=...
Fix: pass allowed_paths=["/tmp"] to demo.launch() so Gradio serves any
path under /tmp, resolving the 403 and unblocking the canvas waveform.
refactor: consolidate duplicated code via MODEL_CONFIGS registry
- Add MODEL_CONFIGS dict as single source of truth for per-model constants
(window_s, sr, secs_per_step, load_overhead, tab_prefix, regen_fn, label)
- Replace 6 nearly-identical duration estimators with 2 generic functions:
_estimate_gpu_duration() and _estimate_regen_duration()
- Replace 3 duplicated regen button factory loops (~35 lines each, 40%
duplication) with single _register_regen_handlers() function
- Fix import redundancy: consolidate threading/shutil imports at top of file,
remove duplicate `import threading` at line 310 and inline `import shutil`
Net effect: ~170 lines of duplicated boilerplate eliminated, all model-specific
behavior now parameterized through the registry. Future model additions only need
a new MODEL_CONFIGS entry + model-specific GPU/regen functions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
perf: move CPU work outside @spaces.GPU to reduce ZeroGPU cost
Split all generate and regen functions into CPU wrapper + GPU-only
inner function pattern. CPU pre/post-processing (ffmpeg, torchaudio,
numpy stitching, muxing) now runs outside @spaces.GPU boundary.
Saves ~5-12s of GPU reservation time per call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Complete .npy migration: segment wavs stored as file paths instead of
serialized arrays, removing all .tolist() calls
- Cache CAVP+onset features (TARO) and text features (HunyuanFoley) in
seg_meta so regen skips re-extraction (~5-7s saved per TARO regen,
~2-3s per HunyuanFoley regen)
- Extract shared model loading into helper functions (_load_taro_models,
_load_taro_feature_extractors, _load_mmaudio_models, _load_hunyuan_model)
to deduplicate generate/regen code
- Batch ffmpeg segment extraction before sample loop (MMAudio, HunyuanFoley)
so clips are extracted once and reused across samples
- Add temp directory registry with automatic cleanup of old dirs (keeps
last 10, removes older ones on new generation)
- Make TARO inference cache thread-safe with threading.Lock
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: also hide video download button via CSS as belt-and-suspenders
The Gradio gr.Video download button always points to the originally generated
file and doesn't update after regen. Replaced by the Video download link in
the waveform panel which always reflects the latest mux.
fix: add video download link to waveform panel so it always reflects latest regen
Gradio's built-in gr.Video download button is tied to Svelte state and stays
pointing at the originally generated file even after regen. Added a dedicated
Video download link inside _build_waveform_html (served via /gradio_api/file=)
alongside the existing audio download link. Since the waveform HTML is rebuilt
with the fresh video_path on every regen, this link always downloads the most
recent muxed video. Renamed the existing download button label to Audio for
clarity.
fix: draw and hit-test segments by unique region to prevent overlap highlight bleed
Segment color blocks and click targets were drawn using each segment's full
[start, end] span. Since windows overlap (e.g. seg0=0-8s, seg1=6-14s), this
caused adjacent segment colors to visually bleed into each other, and clicking
in an overlap zone would ambiguously match multiple segments.
Fix: each segment now owns only [start, nextSeg.start) visually and for click
detection, with the final segment owning [start, end]. This means exactly one
colored block is highlighted during regen and clicks always target precisely
the visible segment the user clicked on.
fix: derive total_dur_s from ffprobe not CAVP frame count to prevent audio truncation
CAVP feature extractor can drop the last partial window, causing total_dur_s
to be shorter than the actual video (e.g. 10s instead of 25s). This caused
generate_taro() to build too few segments and produce audio that cuts off early.
Fix: use get_video_duration(video_file) as the canonical source for total_dur_s.
fix: load only AudioLDM2 VAE+vocoder subcomponents instead of full pipeline to prevent GPU OOM on long videos
Root cause: document addEventListener with capture=true fired BEFORE
button onclick, so _pendingSlot was already null when fireRegen ran.
Fix: save slot/idx to locals first, use bubble phase on doc listener.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix regen: render=False on hidden Textboxes to stop 'Too many arguments' error
Gradio's Svelte client throws api_info.ts:423 'Too many arguments provided
for the endpoint' when gr.Textbox(visible=False) components are in the
inputs list — the client counts them as DOM components and rejects the call.
Switching to render=False prevents any DOM element from being created, so
Gradio's internal validation no longer sees them as extra inputs. The JS
fireRegen() still passes values at the correct positional index via the
direct queue/join fetch, which bypasses Svelte validation entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Debug: console.log in waveform click and global message listener
gr.State stores values server-side and ignores data passed via queue/join API.
gr.Textbox(visible=False) is client-side and correctly receives seg_idx and
state_json from the JS direct queue API call.
Fix blank iframe: set height=900 in launch() to override iFrameResizer height:0px
gr.Group(visible=False) was inside gr.Row, acting as a third column
and breaking the two-column layout. Moved outside the Row block,
at Tab level, so they have zero layout impact.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix UI layout: move hidden regen textboxes into invisible gr.Group
visible=False on gr.Textbox inside a Column still renders empty DOM
blocks that break the layout. Wrapping them in gr.Group(visible=False)
collapses the entire group with zero visual footprint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix UI crash: replace hidden gr.Number with gr.Textbox for regen_seg inputs
gr.Number with visible=False triggers a Gradio 5 internal .split() error
on the number format string, breaking the UI layout. Use gr.Textbox('0')
instead — handlers already call int(seg_idx) so the cast is safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix regen duration functions: add slot_id=None param to match regen function signatures
@spaces.GPU(duration=fn) passes all args to the duration function,
so it must accept the same signature as the decorated function.
All three regen functions take slot_id as the last arg.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix regen: use properly-typed hidden Number/Textbox inputs for seg_idx and state_json
Previously seg_idx and state_json used taro_seed (gr.Number) as placeholder
for both — Gradio rejected a JSON string passed to a Number-type component,
causing process_completed success=false immediately. Now each tab has dedicated
hidden gr.Number (regen_seg) and gr.Textbox (regen_state) components with
matching elem_ids, so queue/join type validation passes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>