Spaces:
Running on Zero
Running on Zero
Fix 0-output inference causing dropped audio return
Browse filesRoot 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>
- app.py +13 -26
- 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 |
-
|
| 284 |
-
|
| 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 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
"""
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
sf.write(
|
| 298 |
-
return
|
| 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);
|