Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -15,13 +15,13 @@ import torch
|
|
| 15 |
import torchaudio as ta
|
| 16 |
import gradio as gr
|
| 17 |
|
| 18 |
-
# Optimasi Core CPU shared Hugging Face agar fokus
|
| 19 |
torch.set_num_threads(2)
|
| 20 |
torch.set_num_interop_threads(2)
|
| 21 |
torch.set_grad_enabled(False)
|
| 22 |
torch.cuda.is_available = lambda: False # noqa: E731
|
| 23 |
|
| 24 |
-
# Override global torch load untuk keamanan CPU
|
| 25 |
_original_torch_load = torch.load
|
| 26 |
def _torch_load_cpu(*args, **kwargs):
|
| 27 |
kwargs["map_location"] = torch.device("cpu")
|
|
@@ -41,13 +41,13 @@ MAX_CHUNKS = int(os.getenv("MAX_CHUNKS", "12"))
|
|
| 41 |
PAUSE_SECONDS = float(os.getenv("PAUSE_SECONDS", "0.15"))
|
| 42 |
DOWNLOAD_TIMEOUT = int(os.getenv("DOWNLOAD_TIMEOUT", "90"))
|
| 43 |
|
| 44 |
-
# Jalur untuk suara default bawaan sistem (
|
| 45 |
ROOT_DIR = Path(__file__).parent
|
| 46 |
ASSETS_DIR = ROOT_DIR / "assets"
|
| 47 |
ASSETS_DIR.mkdir(exist_ok=True)
|
| 48 |
DEFAULT_SPEAKER_PATH = ASSETS_DIR / "default_speaker.wav"
|
| 49 |
|
| 50 |
-
# Global Cache untuk mengunci embedding suara
|
| 51 |
CACHED_EMBEDDINGS = {}
|
| 52 |
|
| 53 |
from chatterbox.tts import ChatterboxTTS
|
|
@@ -98,7 +98,7 @@ def _resolve_audio_input(audio_file, audio_url: str):
|
|
| 98 |
except Exception:
|
| 99 |
pass
|
| 100 |
|
| 101 |
-
#
|
| 102 |
if DEFAULT_SPEAKER_PATH.exists():
|
| 103 |
return str(DEFAULT_SPEAKER_PATH)
|
| 104 |
|
|
@@ -147,7 +147,7 @@ def _split_text_safely(text: str, max_chars: int = MAX_CHARS_PER_CHUNK):
|
|
| 147 |
return chunks
|
| 148 |
|
| 149 |
# =====================================================================
|
| 150 |
-
# ENGINE
|
| 151 |
# =====================================================================
|
| 152 |
def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(track_tqdm=False)):
|
| 153 |
global CACHED_EMBEDDINGS
|
|
@@ -156,10 +156,10 @@ def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(trac
|
|
| 156 |
if not raw_text:
|
| 157 |
raise gr.Error("Text prompt tidak boleh kosong.")
|
| 158 |
|
| 159 |
-
#
|
| 160 |
prompt_path = _resolve_audio_input(audio_file, audio_url)
|
| 161 |
if not prompt_path:
|
| 162 |
-
raise gr.Error("Suara
|
| 163 |
|
| 164 |
chunks = _split_text_safely(raw_text, max_chars=MAX_CHARS_PER_CHUNK)
|
| 165 |
chunks = [ch for ch in chunks if re.search(r'[a-zA-Z0-9]', ch)]
|
|
@@ -168,9 +168,9 @@ def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(trac
|
|
| 168 |
sr = getattr(model, "sr", 24000)
|
| 169 |
torch.manual_seed(42)
|
| 170 |
|
| 171 |
-
#
|
| 172 |
if prompt_path not in CACHED_EMBEDDINGS:
|
| 173 |
-
progress(0.0, desc="Mengekstrak
|
| 174 |
|
| 175 |
if hasattr(model, "extract_conditioning") or hasattr(model, "get_speaker_embedding"):
|
| 176 |
extract_fn = getattr(model, "extract_conditioning", getattr(model, "get_speaker_embedding", None))
|
|
@@ -178,7 +178,7 @@ def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(trac
|
|
| 178 |
else:
|
| 179 |
CACHED_EMBEDDINGS[prompt_path] = prompt_path
|
| 180 |
|
| 181 |
-
print(f"[CACHE]
|
| 182 |
|
| 183 |
speaker_embedding = CACHED_EMBEDDINGS[prompt_path]
|
| 184 |
|
|
@@ -186,6 +186,7 @@ def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(trac
|
|
| 186 |
pause = torch.zeros(1, int(sr * PAUSE_SECONDS))
|
| 187 |
total = len(chunks)
|
| 188 |
|
|
|
|
| 189 |
sig = inspect.signature(model.generate)
|
| 190 |
params = sig.parameters
|
| 191 |
|
|
@@ -197,21 +198,31 @@ def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(trac
|
|
| 197 |
if not re.search(r"[.!?…]$", ch):
|
| 198 |
ch += "."
|
| 199 |
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
"top_p": 0.7,
|
| 203 |
-
"exaggeration": 0.25,
|
| 204 |
-
"cfg_weight": 0.3,
|
| 205 |
-
"max_new_tokens": 260
|
| 206 |
-
}
|
| 207 |
|
| 208 |
if "audio_prompt_path" in params:
|
| 209 |
kwargs["audio_prompt_path"] = speaker_embedding
|
| 210 |
elif "speaker_embedding" in params:
|
| 211 |
kwargs["speaker_embedding"] = speaker_embedding
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
try:
|
| 216 |
wav = model.generate(ch, **kwargs)
|
| 217 |
except TypeError:
|
|
@@ -219,6 +230,7 @@ def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(trac
|
|
| 219 |
kwargs["text"] = ch
|
| 220 |
wav = model.generate(**kwargs)
|
| 221 |
else:
|
|
|
|
| 222 |
wav = model.generate(ch)
|
| 223 |
|
| 224 |
if wav.dim() == 1:
|
|
@@ -230,12 +242,13 @@ def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(trac
|
|
| 230 |
if wav_parts:
|
| 231 |
wav_parts = wav_parts[:-1]
|
| 232 |
|
| 233 |
-
progress(0.95, desc="Menggabungkan
|
| 234 |
full_wav = torch.cat(wav_parts, dim=1)
|
| 235 |
|
| 236 |
out_path = tempfile.NamedTemporaryFile(delete=False, suffix=".wav").name
|
| 237 |
ta.save(out_path, full_wav, sr)
|
| 238 |
|
|
|
|
| 239 |
del wav_parts
|
| 240 |
del full_wav
|
| 241 |
gc.collect()
|
|
@@ -251,16 +264,16 @@ def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(trac
|
|
| 251 |
# =====================================================================
|
| 252 |
# INTERFACE DESIGN
|
| 253 |
# =====================================================================
|
| 254 |
-
with gr.Blocks(title="Chatterbox Dual Voice
|
| 255 |
-
gr.Markdown("## EduScanner AI
|
| 256 |
-
gr.Markdown("
|
| 257 |
|
| 258 |
-
text_in = gr.Textbox(label="Teks Rangkuman", lines=8, placeholder="
|
| 259 |
-
wav_in = gr.Audio(label="Opsi Custom Voice (Kosongkan jika ingin pakai suara default Syahid)", type="filepath")
|
| 260 |
-
url_in = gr.Textbox(label="Opsi
|
| 261 |
|
| 262 |
-
btn = gr.Button("Sintesis Audio", variant="primary")
|
| 263 |
-
out_audio = gr.Audio(label="Hasil Audio (WAV)", type="filepath")
|
| 264 |
|
| 265 |
btn.click(
|
| 266 |
fn=clone_voice,
|
|
|
|
| 15 |
import torchaudio as ta
|
| 16 |
import gradio as gr
|
| 17 |
|
| 18 |
+
# Optimasi Core CPU shared Hugging Face agar fokus dan stabil
|
| 19 |
torch.set_num_threads(2)
|
| 20 |
torch.set_num_interop_threads(2)
|
| 21 |
torch.set_grad_enabled(False)
|
| 22 |
torch.cuda.is_available = lambda: False # noqa: E731
|
| 23 |
|
| 24 |
+
# Override global torch load untuk keamanan jalur CPU
|
| 25 |
_original_torch_load = torch.load
|
| 26 |
def _torch_load_cpu(*args, **kwargs):
|
| 27 |
kwargs["map_location"] = torch.device("cpu")
|
|
|
|
| 41 |
PAUSE_SECONDS = float(os.getenv("PAUSE_SECONDS", "0.15"))
|
| 42 |
DOWNLOAD_TIMEOUT = int(os.getenv("DOWNLOAD_TIMEOUT", "90"))
|
| 43 |
|
| 44 |
+
# Jalur untuk suara default bawaan sistem (Simpan file 1:15 kamu di folder assets)
|
| 45 |
ROOT_DIR = Path(__file__).parent
|
| 46 |
ASSETS_DIR = ROOT_DIR / "assets"
|
| 47 |
ASSETS_DIR.mkdir(exist_ok=True)
|
| 48 |
DEFAULT_SPEAKER_PATH = ASSETS_DIR / "default_speaker.wav"
|
| 49 |
|
| 50 |
+
# Global Cache untuk mengunci embedding karakteristik suara ke dalam RAM
|
| 51 |
CACHED_EMBEDDINGS = {}
|
| 52 |
|
| 53 |
from chatterbox.tts import ChatterboxTTS
|
|
|
|
| 98 |
except Exception:
|
| 99 |
pass
|
| 100 |
|
| 101 |
+
# Jika parameter audio kosong, otomatis gunakan file suara default 1:15 di folder assets
|
| 102 |
if DEFAULT_SPEAKER_PATH.exists():
|
| 103 |
return str(DEFAULT_SPEAKER_PATH)
|
| 104 |
|
|
|
|
| 147 |
return chunks
|
| 148 |
|
| 149 |
# =====================================================================
|
| 150 |
+
# ENGINE UTAMA: SUDAH FIX TYPEERROR ARGUMEN & CACHE EMBEDDING
|
| 151 |
# =====================================================================
|
| 152 |
def clone_voice(text: str, audio_file, audio_url: str, progress=gr.Progress(track_tqdm=False)):
|
| 153 |
global CACHED_EMBEDDINGS
|
|
|
|
| 156 |
if not raw_text:
|
| 157 |
raise gr.Error("Text prompt tidak boleh kosong.")
|
| 158 |
|
| 159 |
+
# Ambil jalur audio (Custom atau Default)
|
| 160 |
prompt_path = _resolve_audio_input(audio_file, audio_url)
|
| 161 |
if not prompt_path:
|
| 162 |
+
raise gr.Error("Suara acuan tidak ditemukan. Sediakan file audio default atau upload sampel.")
|
| 163 |
|
| 164 |
chunks = _split_text_safely(raw_text, max_chars=MAX_CHARS_PER_CHUNK)
|
| 165 |
chunks = [ch for ch in chunks if re.search(r'[a-zA-Z0-9]', ch)]
|
|
|
|
| 168 |
sr = getattr(model, "sr", 24000)
|
| 169 |
torch.manual_seed(42)
|
| 170 |
|
| 171 |
+
# Proses pembuatan cache embedding jika file audio terdeteksi baru
|
| 172 |
if prompt_path not in CACHED_EMBEDDINGS:
|
| 173 |
+
progress(0.0, desc="Mengekstrak karakteristik gelombang audio ke RAM (Hanya sekali)...")
|
| 174 |
|
| 175 |
if hasattr(model, "extract_conditioning") or hasattr(model, "get_speaker_embedding"):
|
| 176 |
extract_fn = getattr(model, "extract_conditioning", getattr(model, "get_speaker_embedding", None))
|
|
|
|
| 178 |
else:
|
| 179 |
CACHED_EMBEDDINGS[prompt_path] = prompt_path
|
| 180 |
|
| 181 |
+
print(f"[CACHE] Fitur karakteristik untuk {prompt_path} berhasil dikunci di RAM.")
|
| 182 |
|
| 183 |
speaker_embedding = CACHED_EMBEDDINGS[prompt_path]
|
| 184 |
|
|
|
|
| 186 |
pause = torch.zeros(1, int(sr * PAUSE_SECONDS))
|
| 187 |
total = len(chunks)
|
| 188 |
|
| 189 |
+
# Bedah blueprint fungsi model asli untuk memvalidasi parameter masuk
|
| 190 |
sig = inspect.signature(model.generate)
|
| 191 |
params = sig.parameters
|
| 192 |
|
|
|
|
| 198 |
if not re.search(r"[.!?…]$", ch):
|
| 199 |
ch += "."
|
| 200 |
|
| 201 |
+
# Saring parameter agar hanya memasukkan key yang dikenal oleh library model
|
| 202 |
+
kwargs = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
if "audio_prompt_path" in params:
|
| 205 |
kwargs["audio_prompt_path"] = speaker_embedding
|
| 206 |
elif "speaker_embedding" in params:
|
| 207 |
kwargs["speaker_embedding"] = speaker_embedding
|
| 208 |
+
elif "prompt" in params and len(params) > 1:
|
| 209 |
+
list_keys = list(params.keys())
|
| 210 |
+
if len(list_keys) > 1:
|
| 211 |
+
kwargs[list_keys[1]] = speaker_embedding
|
| 212 |
+
|
| 213 |
+
# Parameter opsional tambahan (Hanya disuntikkan jika disupport oleh versi model)
|
| 214 |
+
if "temperature" in params:
|
| 215 |
+
kwargs["temperature"] = 0.05
|
| 216 |
+
if "top_p" in params:
|
| 217 |
+
kwargs["top_p"] = 0.7
|
| 218 |
+
if "exaggeration" in params:
|
| 219 |
+
kwargs["exaggeration"] = 0.25
|
| 220 |
+
if "cfg_weight" in params:
|
| 221 |
+
kwargs["cfg_weight"] = 0.3
|
| 222 |
+
if "max_new_tokens" in params:
|
| 223 |
+
kwargs["max_new_tokens"] = 260
|
| 224 |
+
|
| 225 |
+
# Eksekusi aman bebas dari bug TypeError
|
| 226 |
try:
|
| 227 |
wav = model.generate(ch, **kwargs)
|
| 228 |
except TypeError:
|
|
|
|
| 230 |
kwargs["text"] = ch
|
| 231 |
wav = model.generate(**kwargs)
|
| 232 |
else:
|
| 233 |
+
# Jalur teraman jika custom kwargs ditolak total oleh internal model
|
| 234 |
wav = model.generate(ch)
|
| 235 |
|
| 236 |
if wav.dim() == 1:
|
|
|
|
| 242 |
if wav_parts:
|
| 243 |
wav_parts = wav_parts[:-1]
|
| 244 |
|
| 245 |
+
progress(0.95, desc="Menggabungkan kompilasi audio...")
|
| 246 |
full_wav = torch.cat(wav_parts, dim=1)
|
| 247 |
|
| 248 |
out_path = tempfile.NamedTemporaryFile(delete=False, suffix=".wav").name
|
| 249 |
ta.save(out_path, full_wav, sr)
|
| 250 |
|
| 251 |
+
# Bersihkan memori RAM kontainer secara agresif
|
| 252 |
del wav_parts
|
| 253 |
del full_wav
|
| 254 |
gc.collect()
|
|
|
|
| 264 |
# =====================================================================
|
| 265 |
# INTERFACE DESIGN
|
| 266 |
# =====================================================================
|
| 267 |
+
with gr.Blocks(title="Chatterbox Dual Voice Safe Engine") as demo:
|
| 268 |
+
gr.Markdown("## EduScanner AI Voice Backend - Anti-Error Parameter Generation")
|
| 269 |
+
gr.Markdown("Kode ini menyaring argumen secara dinamis untuk mencegah error crash sistem.")
|
| 270 |
|
| 271 |
+
text_in = gr.Textbox(label="Teks Rangkuman Materi Kuliah", lines=8, placeholder="Ketik teks di sini...")
|
| 272 |
+
wav_in = gr.Audio(label="Opsi Custom Voice (Kosongkan jika ingin pakai suara default Syahid 1:15)", type="filepath")
|
| 273 |
+
url_in = gr.Textbox(label="Opsi URL File Audio Custom", placeholder="https://domain-kamu.com/audio.wav")
|
| 274 |
|
| 275 |
+
btn = gr.Button("Sintesis Audio Kloning Suara", variant="primary")
|
| 276 |
+
out_audio = gr.Audio(label="Hasil Audio Akhir (WAV)", type="filepath")
|
| 277 |
|
| 278 |
btn.click(
|
| 279 |
fn=clone_voice,
|