mehdi999 commited on
Commit
4af42e5
·
1 Parent(s): ef11c21

Demo Lina-speech (pardi-speech) on Spaces

Browse files
Files changed (3) hide show
  1. app.py +157 -0
  2. readme.md +11 -0
  3. requirements.txt +10 -0
app.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import os
3
+ import gradio as gr
4
+ import numpy as np
5
+ import torch
6
+ import soundfile as sf
7
+ import spaces
8
+
9
+ from huggingface_hub import login
10
+ from pardi_speech import PardiSpeech, VelocityHeadSamplingParams # présent dans ce repo
11
+ # Les sous-modules requis se trouvent dans tts/ et codec/
12
+
13
+ MODEL_REPO_ID = os.environ.get("MODEL_REPO_ID", "theodorr/pardi-speech-enfr-forbidden")
14
+
15
+ # Auth HF (le secret est défini dans les Settings du Space)
16
+ HF_TOKEN = os.environ.get("HF_TOKEN")
17
+ if HF_TOKEN:
18
+ try:
19
+ login(token=HF_TOKEN)
20
+ print("✅ Logged to Hugging Face Hub.")
21
+ except Exception as e:
22
+ print("⚠️ HF login failed:", e)
23
+
24
+ # Chargement lazy pour ZeroGPU (et pour réduire le temps de cold-start)
25
+ _pardi = None
26
+ _sampling_rate = 24000
27
+
28
+ def _normalize_text(s: str, lang_hint: str = "fr") -> str:
29
+ """Normalisation légère façon Whisper: lowercase + chiffres en lettres (si possible)."""
30
+ s = (s or "").strip().lower()
31
+ try:
32
+ import re
33
+ from num2words import num2words
34
+ def repl(m): return num2words(int(m.group()), lang=lang_hint)
35
+ s = re.sub(r"\d+", repl, s)
36
+ except Exception:
37
+ pass
38
+ return s
39
+
40
+ def _load_model(device: str = "cuda"):
41
+ global _pardi, _sampling_rate
42
+ if _pardi is None:
43
+ _pardi = PardiSpeech.from_pretrained(MODEL_REPO_ID, map_location=device)
44
+ _sampling_rate = getattr(_pardi, "sampling_rate", 24000)
45
+ print(f"✅ PardiSpeech loaded on {device} (sr={_sampling_rate}).")
46
+ return _pardi
47
+
48
+ def _to_mono_float32(arr: np.ndarray) -> np.ndarray:
49
+ arr = arr.astype(np.float32)
50
+ if arr.ndim == 2: # stereo -> mono
51
+ arr = arr.mean(axis=1)
52
+ return arr
53
+
54
+ @spaces.GPU(duration=120) # ZeroGPU: alloue un GPU pendant l'appel (noop ailleurs)
55
+ def synthesize(
56
+ text: str,
57
+ ref_audio, # tuple (sr, np.ndarray) ou chemin
58
+ ref_text: str,
59
+ steps: int,
60
+ cfg: float,
61
+ cfg_ref: float,
62
+ temperature: float,
63
+ max_seq_len: int,
64
+ seed: int,
65
+ lang_hint: str
66
+ ):
67
+ device = "cuda" if torch.cuda.is_available() else "cpu"
68
+ torch.manual_seed(int(seed))
69
+
70
+ pardi = _load_model(device)
71
+ txt = _normalize_text(text, lang_hint=lang_hint)
72
+
73
+ # Prépare cache décodage
74
+ cache = pardi.tts.audio_decoder.init_cache(int(max_seq_len), device)
75
+
76
+ # Paramètres de sampling (cf. notebook inference & tes notes)
77
+ vel_params = VelocityHeadSamplingParams(
78
+ cfg_ref=float(cfg_ref),
79
+ cfg=float(cfg),
80
+ num_steps=int(steps),
81
+ temperature=float(temperature)
82
+ )
83
+
84
+ # Gestion du prefix (optionnel)
85
+ prefix = None
86
+ if ref_audio is not None:
87
+ # ref_audio peut être un chemin ou (sr, wav)
88
+ if isinstance(ref_audio, str):
89
+ wav, sr = sf.read(ref_audio)
90
+ else:
91
+ sr, wav = ref_audio
92
+ wav = _to_mono_float32(np.array(wav))
93
+ wav_t = torch.from_numpy(wav).to(device)
94
+ # Resample sur le sr attendu par le codec/ modèle
95
+ import torchaudio
96
+ if sr != pardi.sampling_rate:
97
+ wav_t = torchaudio.functional.resample(wav_t, sr, pardi.sampling_rate)
98
+ wav_t = wav_t.unsqueeze(0) # [1, T]
99
+
100
+ with torch.inference_mode():
101
+ # Encode prefix en tokens via PatchVAE (comme dans inference.ipynb)
102
+ prefix_tokens = pardi.patchvae.encode(wav_t) # [1, ...]
103
+ # ref_text est optionnel ; s’il est vide, on passe une chaîne vide
104
+ prefix = (ref_text or "", prefix_tokens[0])
105
+
106
+ # Synthèse
107
+ with torch.inference_mode():
108
+ wavs, _ = pardi.text_to_speech(
109
+ [txt],
110
+ prefix,
111
+ max_seq_len=int(max_seq_len),
112
+ velocity_head_sampling_params=vel_params,
113
+ cache=cache
114
+ )
115
+ wav = wavs[0].detach().cpu().numpy() # float32 [-1,1]
116
+ return (_sampling_rate, wav)
117
+
118
+ def build_demo():
119
+ with gr.Blocks(title="Lina‑speech / pardi‑speech Demo") as demo:
120
+ gr.Markdown(
121
+ "## Lina‑speech (pardi‑speech) – Démo TTS\n"
122
+ "Génère de l'audio à partir de texte, avec ou sans *prefix* (audio de référence).\n"
123
+ "Paramètres avancés: *num_steps*, *CFG*, *température*, *max_seq_len*, *seed*."
124
+ )
125
+
126
+ with gr.Row():
127
+ text = gr.Textbox(label="Texte à synthétiser", lines=4, placeholder="Tape ton texte ici…")
128
+ with gr.Accordion("Prefix (optionnel)", open=False):
129
+ ref_audio = gr.Audio(sources=["upload", "microphone"], type="numpy", label="Audio de référence")
130
+ ref_text = gr.Textbox(label="Texte du prefix (si connu)", placeholder="Transcription du prefix (optionnel)")
131
+ with gr.Accordion("Options avancées", open=False):
132
+ with gr.Row():
133
+ steps = gr.Slider(1, 50, value=10, step=1, label="num_steps")
134
+ cfg = gr.Slider(0.5, 3.0, value=1.4, step=0.05, label="CFG (guidance)")
135
+ cfg_ref = gr.Slider(0.5, 3.0, value=1.0, step=0.05, label="CFG (réf.)")
136
+ with gr.Row():
137
+ temperature = gr.Slider(0.1, 2.0, value=1.0, step=0.05, label="Température")
138
+ max_seq_len = gr.Slider(50, 1200, value=300, step=10, label="max_seq_len (tokens audio)")
139
+ seed = gr.Number(value=0, precision=0, label="Seed (reproductibilité)")
140
+ lang_hint = gr.Dropdown(choices=["fr", "en"], value="fr", label="Langue (normalisation)")
141
+
142
+ btn = gr.Button("Synthétiser")
143
+ out_audio = gr.Audio(label="Sortie audio", type="numpy")
144
+
145
+ # File d'attente pour GPU (gestion du débit)
146
+ demo.queue(default_concurrency_limit=1, max_size=32)
147
+
148
+ btn.click(
149
+ fn=synthesize,
150
+ inputs=[text, ref_audio, ref_text, steps, cfg, cfg_ref, temperature, max_seq_len, seed, lang_hint],
151
+ outputs=[out_audio]
152
+ )
153
+ return demo
154
+
155
+ if __name__ == "__main__":
156
+ demo = build_demo()
157
+ demo.launch()
readme.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Lina-speech (pardi-speech) — Demo Gradio
2
+
3
+ - Charge un checkpoint privé: `${MODEL_REPO_ID}` (par défaut `theodorr/pardi-speech-enfr-forbidden`)
4
+ - Nécessite un secret `HF_TOKEN` (Settings ▸ Secrets)
5
+
6
+ ## Paramètres
7
+ - num_steps, CFG, CFG_ref, température, max_seq_len, seed
8
+ - Prefix optionnel: audio + texte (si disponible)
9
+
10
+ ## Matériel
11
+ - ZeroGPU (PRO requis pour héberger) ou GPU T4/L4/L40S/A10G…
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.44.0
2
+ spaces>=0.20.0
3
+ huggingface_hub>=0.24.0
4
+ torch>=2.2.0
5
+ torchaudio>=2.2.0
6
+ numpy
7
+ soundfile
8
+ librosa
9
+ num2words
10
+ tqdm