voxpolish / app.py
jeevav62's picture
Disable SSR to fix No API found error
ef61de9 verified
Raw
History Blame Contribute Delete
6.53 kB
"""VoxPolish β€” TTS Dataset Cleaner Β· HuggingFace Space"""
import time
import tempfile
import zipfile
from pathlib import Path
import gradio as gr
import torch
import numpy as np
from cleaner.enhance import load_model, enhance_audio, sample_rate
from cleaner.audio_utils import trim_silence, normalize_loudness, tensor_to_numpy, numpy_to_tensor
from cleaner.dataset_io import detect_dataset_type, discover_audio_files, output_path_for, copy_metadata
from cleaner.pipeline import process_file, PipelineConfig
from df.io import load_audio, save_audio
# Load model once (CPU on HF free tier, GPU if available)
load_model(post_filter=True)
MODEL_SR = 48000
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
# ── Quick Clean: single file ─────────────────────────────────────────────────
def quick_clean(audio_path, do_trim, do_norm, target_lufs):
if audio_path is None:
return None, "Upload an audio file first."
audio, meta = load_audio(audio_path, sr=MODEL_SR)
orig_sr = meta.sample_rate
duration_s = audio.shape[-1] / MODEL_SR
if DEVICE == "cuda":
torch.cuda.reset_peak_memory_stats()
t0 = time.perf_counter()
enhanced = enhance_audio(audio)
if DEVICE == "cuda":
torch.cuda.synchronize()
latency_ms = (time.perf_counter() - t0) * 1000
if orig_sr != MODEL_SR:
import torchaudio
enhanced = torchaudio.functional.resample(enhanced, MODEL_SR, orig_sr)
audio_np = tensor_to_numpy(enhanced)
if do_trim:
audio_np = trim_silence(audio_np, orig_sr)
if do_norm:
audio_np = normalize_loudness(audio_np, orig_sr, target_lufs)
out_path = tempfile.mktemp(suffix="_cleaned.wav")
save_audio(out_path, numpy_to_tensor(audio_np), orig_sr)
rtf = (latency_ms / 1000) / duration_s if duration_s > 0 else 0
vram = f"{torch.cuda.max_memory_allocated()/1e6:.1f} MB" if DEVICE == "cuda" else "n/a (CPU)"
stats = f"""
## Stats
| | |
|---|---|
| **Device** | {DEVICE.upper()} |
| **Latency** | {latency_ms:.0f} ms |
| **RTF** | {rtf:.3f}x {'βœ… faster than real-time' if rtf < 1 else ''} |
| **Output sample rate** | {orig_sr} Hz |
| **VRAM** | {vram} |
""".strip()
return out_path, stats
# ── Dataset Cleaner: ZIP ──────────────────────────────────────────────────────
def clean_zip(zip_file, do_trim, do_norm, target_lufs, progress=gr.Progress()):
if zip_file is None:
return None, "Upload a ZIP file first."
with tempfile.TemporaryDirectory() as tmp:
in_dir = Path(tmp) / "in"
out_dir = Path(tmp) / "out"
in_dir.mkdir(); out_dir.mkdir()
with zipfile.ZipFile(zip_file, "r") as z:
z.extractall(in_dir)
contents = list(in_dir.iterdir())
if len(contents) == 1 and contents[0].is_dir():
in_dir = contents[0]
dtype = detect_dataset_type(in_dir)
files = discover_audio_files(in_dir, dtype)
if not files:
return None, "No audio files found in ZIP."
copy_metadata(in_dir, out_dir, dtype)
cfg = PipelineConfig(trim=do_trim, normalize=do_norm, target_lufs=target_lufs)
results = []
for i, f in enumerate(files):
progress((i + 1) / len(files), desc=f"Cleaning {f.name}")
results.append(process_file(f, output_path_for(f, in_dir, out_dir), cfg))
ok = [r for r in results if r.success]
avg_ms = sum(r.latency_ms for r in ok) / len(ok) if ok else 0
zip_bytes_path = Path(tmp) / "cleaned.zip"
with zipfile.ZipFile(zip_bytes_path, "w", zipfile.ZIP_DEFLATED) as zout:
for f in out_dir.rglob("*"):
if f.is_file():
zout.write(f, f.relative_to(out_dir))
data = zip_bytes_path.read_bytes()
out_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".zip")
out_tmp.write(data); out_tmp.close()
stats = f"""
## Results
| | |
|---|---|
| **Dataset type** | {dtype} |
| **Files cleaned** | {len(ok)} / {len(files)} |
| **Avg latency/file** | {avg_ms:.0f} ms |
""".strip()
return out_tmp.name, stats
# ── UI ───────────────────────────────────────────────────────────────────────
with gr.Blocks(title="VoxPolish β€” TTS Dataset Cleaner", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# πŸŽ™οΈ VoxPolish β€” TTS Dataset Cleaner
Clean noisy speech for text-to-speech training: **neural denoise β†’ silence trim β†’ loudness normalize (-23 LUFS).**
[GitHub repo](https://github.com/Jeevav62/voxpolish) Β· built by [Jeeva](https://huggingface.co/jeevav62)
""")
with gr.Tab("⚑ Quick Clean (single file)"):
with gr.Row():
with gr.Column():
a_in = gr.Audio(label="Noisy audio", type="filepath", sources=["upload", "microphone"])
q_trim = gr.Checkbox(label="Trim silence", value=True)
q_norm = gr.Checkbox(label="Loudness normalize", value=True)
q_lufs = gr.Slider(-35, -14, value=-23, step=0.5, label="Target LUFS")
q_btn = gr.Button("Clean Audio", variant="primary", size="lg")
with gr.Column():
a_out = gr.Audio(label="Cleaned audio", type="filepath")
q_stats = gr.Markdown()
q_btn.click(quick_clean, [a_in, q_trim, q_norm, q_lufs], [a_out, q_stats])
with gr.Tab("πŸ“¦ Dataset Cleaner (ZIP)"):
with gr.Row():
with gr.Column():
z_in = gr.File(label="Dataset ZIP", file_types=[".zip"])
z_trim = gr.Checkbox(label="Trim silence", value=True)
z_norm = gr.Checkbox(label="Loudness normalize", value=True)
z_lufs = gr.Slider(-35, -14, value=-23, step=0.5, label="Target LUFS")
z_btn = gr.Button("Clean Dataset", variant="primary", size="lg")
with gr.Column():
z_out = gr.File(label="Cleaned dataset ZIP")
z_stats = gr.Markdown()
z_btn.click(clean_zip, [z_in, z_trim, z_norm, z_lufs], [z_out, z_stats])
if __name__ == "__main__":
# ssr_mode=False avoids the gradio 5 "No API found" routing bug on HF Spaces
demo.queue().launch(ssr_mode=False)