akhaliq HF Staff Claude commited on
Commit
d994421
·
1 Parent(s): a6863b0

Fix 0-output inference causing dropped audio return

Browse files

Root cause: gradio.Server infers output components from the function's
return-type annotation. The endpoints had none, and
'from __future__ import annotations' stringified what little existed,
so Gradio inferred 0 outputs and discarded the return ('Output
components: []') — the JS client then got undefined.

- Remove 'from __future__ import annotations' so annotations resolve to
real type objects
- Restore FileData returns with explicit -> tuple[FileData, str] and
-> tuple[FileData, FileData, str] annotations (proven docs pattern)
- Drop the /rendered dict route; render_wav uses tempfile FileData again
- JS: accept obj.url || obj.path || string for the audio handle

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +13 -26
  2. index.html +2 -2
app.py CHANGED
@@ -1,9 +1,8 @@
1
- from __future__ import annotations
2
-
3
  import logging
4
  import os
5
  import re
6
  import sys
 
7
  import threading
8
  import time
9
  from dataclasses import dataclass
@@ -39,6 +38,7 @@ sys.path.insert(0, str(RUNTIME))
39
  sys.path.insert(0, str(ROOT))
40
 
41
  from gradio import Server
 
42
 
43
  # Register ONNX Runtime / model asset MIME types. Windows (and some containers)
44
  # do not map these by default, and FileResponse would otherwise serve the
@@ -280,22 +280,17 @@ def validate(
280
  return text, float(speed), float(variation), float(pitch_steps), int(seed)
281
 
282
 
283
- WAV_STORE = ROOT / "rendered"
284
- WAV_STORE.mkdir(exist_ok=True)
285
- WAV_URL = "/rendered"
286
-
287
-
288
- def render_wav(sample_rate: int, samples: np.ndarray) -> dict:
289
- """Persist PCM16 samples to a WAV and return a serializable file handle.
290
 
291
- @app.api() endpoints return JSON to the JS client, so we hand back a plain
292
- dict (url + filename) rather than a FileData object whose shape depends on
293
- Gradio's component serialization.
294
  """
295
- name = f"{int(time.perf_counter() * 1000)}_{os.getpid()}.wav"
296
- out_path = WAV_STORE / name
297
- sf.write(str(out_path), samples, sample_rate, subtype="PCM_16")
298
- return {"url": f"{WAV_URL}/{name}", "filename": name}
299
 
300
 
301
  @app.api(concurrency_limit=1)
@@ -307,7 +302,7 @@ def synthesize(
307
  variation: float,
308
  pitch_steps: float,
309
  seed: int,
310
- ):
311
  text, speed, variation, pitch_steps, seed = validate(
312
  text, speed, variation, pitch_steps, seed
313
  )
@@ -337,7 +332,7 @@ def compare(
337
  variation: float,
338
  pitch_steps: float,
339
  seed: int,
340
- ):
341
  text, speed, variation, pitch_steps, seed = validate(
342
  text, speed, variation, pitch_steps, seed
343
  )
@@ -374,14 +369,6 @@ async def _cross_origin_isolation(request, call_next):
374
  return response
375
 
376
 
377
- @app.get(WAV_URL + "/{name}")
378
- async def serve_rendered_wav(name: str):
379
- full = (WAV_STORE / name).resolve()
380
- if not full.is_file() or full.parent != WAV_STORE.resolve():
381
- return HTMLResponse("Not found", status_code=404)
382
- return FileResponse(str(full), media_type="audio/wav")
383
-
384
-
385
  @app.get(STATIC_URL + "/{path:path}")
386
  async def serve_web_runtime(path: str):
387
  full = (WEB_RUNTIME / path).resolve()
 
 
 
1
  import logging
2
  import os
3
  import re
4
  import sys
5
+ import tempfile
6
  import threading
7
  import time
8
  from dataclasses import dataclass
 
38
  sys.path.insert(0, str(ROOT))
39
 
40
  from gradio import Server
41
+ from gradio.data_classes import FileData
42
 
43
  # Register ONNX Runtime / model asset MIME types. Windows (and some containers)
44
  # do not map these by default, and FileResponse would otherwise serve the
 
280
  return text, float(speed), float(variation), float(pitch_steps), int(seed)
281
 
282
 
283
+ def render_wav(sample_rate: int, samples: np.ndarray) -> FileData:
284
+ """Persist PCM16 samples to a WAV and return a Gradio FileData handle.
 
 
 
 
 
285
 
286
+ The return-type annotation is what tells gradio.Server which output
287
+ component to serialize, so the JS client receives a {url, ...} blob
288
+ rather than the function's return value being dropped.
289
  """
290
+ handle, out_path = tempfile.mkstemp(suffix=".wav", prefix="inflect_")
291
+ os.close(handle)
292
+ sf.write(out_path, samples, sample_rate, subtype="PCM_16")
293
+ return FileData(path=out_path)
294
 
295
 
296
  @app.api(concurrency_limit=1)
 
302
  variation: float,
303
  pitch_steps: float,
304
  seed: int,
305
+ ) -> tuple[FileData, str]:
306
  text, speed, variation, pitch_steps, seed = validate(
307
  text, speed, variation, pitch_steps, seed
308
  )
 
332
  variation: float,
333
  pitch_steps: float,
334
  seed: int,
335
+ ) -> tuple[FileData, FileData, str]:
336
  text, speed, variation, pitch_steps, seed = validate(
337
  text, speed, variation, pitch_steps, seed
338
  )
 
369
  return response
370
 
371
 
 
 
 
 
 
 
 
 
372
  @app.get(STATIC_URL + "/{path:path}")
373
  async def serve_web_runtime(path: str):
374
  full = (WEB_RUNTIME / path).resolve()
index.html CHANGED
@@ -643,7 +643,7 @@ async function doGenerate(){
643
  try{
644
  const c=await getClient(); const res=await c.predict("/synthesize",{ ...inputs(), model_name: activeModel() });
645
  const fileObj=res.data[0]; const statusText=res.data[1]||"Done.";
646
- const url=fileObj.url; if(!url) throw new Error("server returned no audio url");
647
  const buf=await fetchArray(url); const blobUrl=wavBlobUrl(buf); const decoded=await decodeWav(buf);
648
  const text=$("text").value; const chunks=splitText(text); const timings=chunkTimings(chunks,decoded.duration);
649
  const samples=decoded.getChannelData(0);
@@ -669,7 +669,7 @@ async function doCompare(){
669
  ["micro",microObj,"micro-dur","micro-now","micro-seek","micro-dl","micro-ph",cmpCanvas.micro],
670
  ["nano",nanoObj,"nano-dur","nano-now","nano-seek","nano-dl","nano-ph",cmpCanvas.nano],
671
  ]){
672
- const url=obj.url; if(!url) throw new Error(`server returned no ${key} url`);
673
  const buf=await fetchArray(url); const blobUrl=wavBlobUrl(buf); const decoded=await decodeWav(buf); const samples=decoded.getChannelData(0);
674
  $(dlId).href=blobUrl; $(durId).textContent=fmtTime(decoded.duration); $(seekId).value="0"; $(seekId).disabled=false;
675
  drawMini(cv,samples,0);
 
643
  try{
644
  const c=await getClient(); const res=await c.predict("/synthesize",{ ...inputs(), model_name: activeModel() });
645
  const fileObj=res.data[0]; const statusText=res.data[1]||"Done.";
646
+ const url=(fileObj && (fileObj.url || fileObj.path)) || (typeof fileObj==="string"?fileObj:""); if(!url) throw new Error("server returned no audio url");
647
  const buf=await fetchArray(url); const blobUrl=wavBlobUrl(buf); const decoded=await decodeWav(buf);
648
  const text=$("text").value; const chunks=splitText(text); const timings=chunkTimings(chunks,decoded.duration);
649
  const samples=decoded.getChannelData(0);
 
669
  ["micro",microObj,"micro-dur","micro-now","micro-seek","micro-dl","micro-ph",cmpCanvas.micro],
670
  ["nano",nanoObj,"nano-dur","nano-now","nano-seek","nano-dl","nano-ph",cmpCanvas.nano],
671
  ]){
672
+ const url=(obj && (obj.url || obj.path)) || (typeof obj==="string"?obj:""); if(!url) throw new Error(`server returned no ${key} url`);
673
  const buf=await fetchArray(url); const blobUrl=wavBlobUrl(buf); const decoded=await decodeWav(buf); const samples=decoded.getChannelData(0);
674
  $(dlId).href=blobUrl; $(durId).textContent=fmtTime(decoded.duration); $(seekId).value="0"; $(seekId).disabled=false;
675
  drawMini(cv,samples,0);