kezhui's picture
Force CPU inference on ZeroGPU
54968a7 verified
Raw
History Blame Contribute Delete
5.18 kB
import base64
import json
import os
import shutil
import tempfile
import threading
import time
from pathlib import Path
import gradio as gr
import spaces
VOICE_API_TOKEN = os.getenv("VOICE_API_TOKEN", "").strip()
MODEL_NAME = os.getenv("F5_MODEL", "F5TTS_v1_Base")
started_at = time.time()
model_lock = threading.Lock()
f5_model = None
def check_key(api_key: str) -> None:
if VOICE_API_TOKEN and api_key != VOICE_API_TOKEN:
raise gr.Error("invalid api_key")
def decode_audio_b64(audio_base64: str, filename: str) -> Path:
if "," in audio_base64[:128]:
audio_base64 = audio_base64.split(",", 1)[1]
suffix = Path(filename or "reference.wav").suffix or ".wav"
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
tmp_path = Path(tmp.name)
try:
tmp.write(base64.b64decode(audio_base64))
tmp.close()
return tmp_path
except Exception:
tmp.close()
tmp_path.unlink(missing_ok=True)
raise gr.Error("invalid base64 audio")
def get_model():
global f5_model
with model_lock:
if f5_model is not None:
return f5_model
from f5_tts.api import F5TTS
f5_model = F5TTS(model=MODEL_NAME, device="cpu")
return f5_model
def health(api_key: str = "") -> str:
check_key(api_key)
return json.dumps(
{
"ok": True,
"model": MODEL_NAME,
"model_loaded": f5_model is not None,
"uptime_seconds": round(time.time() - started_at, 3),
},
ensure_ascii=False,
)
@spaces.GPU(duration=10)
def zerogpu_probe() -> str:
return "ok"
def clone_b64(
api_key: str,
ref_audio_base64: str,
ref_filename: str,
ref_text: str,
gen_text: str,
speed: float = 1.0,
nfe_step: int = 32,
) -> str:
check_key(api_key)
speed = float(speed)
nfe_step = int(nfe_step)
if not ref_text.strip():
raise gr.Error("ref_text is required")
if not gen_text.strip():
raise gr.Error("gen_text is required")
if len(gen_text) > 1000:
raise gr.Error("gen_text is too long for free CPU; max 1000 characters")
if speed < 0.5 or speed > 2.0:
raise gr.Error("speed must be between 0.5 and 2.0")
if nfe_step < 8 or nfe_step > 64:
raise gr.Error("nfe_step must be between 8 and 64")
ref_path = decode_audio_b64(ref_audio_base64, ref_filename or "reference.wav")
output_dir = Path(tempfile.mkdtemp(prefix="f5_clone_"))
output_wav = output_dir / "output.wav"
output_spec = output_dir / "output.png"
try:
model = get_model()
try:
model.infer(
ref_file=str(ref_path),
ref_text=ref_text,
gen_text=gen_text,
file_wave=str(output_wav),
file_spec=str(output_spec),
speed=speed,
nfe_step=nfe_step,
seed=None,
)
except TypeError:
model.infer(
ref_file=str(ref_path),
ref_text=ref_text,
gen_text=gen_text,
file_wave=str(output_wav),
file_spec=str(output_spec),
speed=speed,
seed=None,
)
if not output_wav.exists():
raise gr.Error("voice clone produced no output file")
audio_base64 = base64.b64encode(output_wav.read_bytes()).decode("ascii")
return json.dumps(
{
"audio_base64": audio_base64,
"mime_type": "audio/wav",
"model": MODEL_NAME,
},
ensure_ascii=False,
)
finally:
ref_path.unlink(missing_ok=True)
shutil.rmtree(output_dir, ignore_errors=True)
with gr.Blocks(title="F5 Voice Clone API") as demo:
gr.Markdown("# F5 Voice Clone API")
gr.Markdown("Use `clone_b64` from n8n. Reference audio and output audio are base64 for simple JSON automation.")
api_key = gr.Textbox(label="api_key", type="password")
with gr.Tab("Health"):
health_out = gr.Textbox(label="result")
gr.Button("Check").click(health, inputs=[api_key], outputs=[health_out], api_name="health")
gr.Button("ZeroGPU probe", visible=False).click(
zerogpu_probe,
inputs=[],
outputs=[health_out],
api_name="zerogpu_probe",
)
with gr.Tab("Clone"):
ref_audio_base64 = gr.Textbox(label="ref_audio_base64", lines=5)
ref_filename = gr.Textbox(label="ref_filename", value="reference.wav")
ref_text = gr.Textbox(label="ref_text", lines=3)
gen_text = gr.Textbox(label="gen_text", lines=4)
speed = gr.Number(label="speed", value=1.0)
nfe_step = gr.Number(label="nfe_step", value=32, precision=0)
result = gr.Textbox(label="result", lines=8)
gr.Button("Clone").click(
clone_b64,
inputs=[api_key, ref_audio_base64, ref_filename, ref_text, gen_text, speed, nfe_step],
outputs=[result],
api_name="clone_b64",
)
demo.queue(default_concurrency_limit=1).launch()