Update app.py
Browse files
app.py
CHANGED
|
@@ -354,40 +354,63 @@ def decode_audio(codes_str, codec):
|
|
| 354 |
return recon[0, 0, :]
|
| 355 |
|
| 356 |
# --- MODEL LOADING ---
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
repo_id=BACKBONE_REPO,
|
| 365 |
-
filename="*.gguf",
|
| 366 |
-
verbose=False,
|
| 367 |
-
n_gpu_layers=-1,
|
| 368 |
-
n_ctx=2048,
|
| 369 |
-
mlock=True,
|
| 370 |
-
flash_attn=True,
|
| 371 |
-
)
|
| 372 |
-
|
| 373 |
-
codec = NeuCodecOnnxDecoder.from_pretrained(CODEC_REPO)
|
| 374 |
-
|
| 375 |
-
print("✅ Model đã tải thành công!")
|
| 376 |
-
model_loaded = True
|
| 377 |
-
except Exception as e:
|
| 378 |
-
import traceback
|
| 379 |
-
traceback.print_exc()
|
| 380 |
-
print(f"❌ Lỗi khi tải model: {e}")
|
| 381 |
-
model_loaded = False
|
| 382 |
|
| 383 |
# --- SYNTHESIS FUNCTION (Internal) ---
|
| 384 |
def synthesize_speech_internal(text, voice_choice):
|
| 385 |
"""Internal synthesis function không phụ thuộc UI"""
|
| 386 |
-
global backbone, codec, model_loaded
|
| 387 |
|
| 388 |
-
if not
|
| 389 |
-
print("❌ Model chưa được tải")
|
| 390 |
-
|
|
|
|
|
|
|
| 391 |
|
| 392 |
if not text or text.strip() == "":
|
| 393 |
print("❌ Text rỗng")
|
|
@@ -451,7 +474,7 @@ def synthesize_speech_internal(text, voice_choice):
|
|
| 451 |
)
|
| 452 |
|
| 453 |
# Generate
|
| 454 |
-
output = backbone(
|
| 455 |
prompt,
|
| 456 |
max_tokens=2048,
|
| 457 |
temperature=1.0,
|
|
@@ -461,7 +484,7 @@ def synthesize_speech_internal(text, voice_choice):
|
|
| 461 |
output_str = output["choices"][0]["text"]
|
| 462 |
|
| 463 |
# Decode
|
| 464 |
-
chunk_wav = decode_audio(output_str, codec)
|
| 465 |
|
| 466 |
if chunk_wav is not None and len(chunk_wav) > 0:
|
| 467 |
all_audio_segments.append(chunk_wav)
|
|
@@ -495,11 +518,13 @@ def synthesize_speech_internal(text, voice_choice):
|
|
| 495 |
# --- SYNTHESIS FUNCTION (UI) ---
|
| 496 |
def synthesize_speech(text, voice_choice):
|
| 497 |
"""Main synthesis function với UI feedback"""
|
| 498 |
-
global backbone, codec, model_loaded
|
| 499 |
|
| 500 |
-
if not
|
| 501 |
-
yield None, "⚠️ Model chưa tải.
|
| 502 |
-
|
|
|
|
|
|
|
|
|
|
| 503 |
|
| 504 |
if not text or text.strip() == "":
|
| 505 |
yield None, "⚠️ Vui lòng nhập văn bản!"
|
|
@@ -553,7 +578,8 @@ def synthesize_speech(text, voice_choice):
|
|
| 553 |
|
| 554 |
try:
|
| 555 |
for i, chunk in enumerate(text_chunks):
|
| 556 |
-
|
|
|
|
| 557 |
|
| 558 |
# Phonemize
|
| 559 |
ref_text_phoneme = phonemize_with_dict(ref_text_raw)
|
|
@@ -568,7 +594,7 @@ def synthesize_speech(text, voice_choice):
|
|
| 568 |
)
|
| 569 |
|
| 570 |
# Generate
|
| 571 |
-
output = backbone(
|
| 572 |
prompt,
|
| 573 |
max_tokens=2048,
|
| 574 |
temperature=1.0,
|
|
@@ -578,7 +604,7 @@ def synthesize_speech(text, voice_choice):
|
|
| 578 |
output_str = output["choices"][0]["text"]
|
| 579 |
|
| 580 |
# Decode
|
| 581 |
-
chunk_wav = decode_audio(output_str, codec)
|
| 582 |
|
| 583 |
if chunk_wav is not None and len(chunk_wav) > 0:
|
| 584 |
all_audio_segments.append(chunk_wav)
|
|
@@ -598,11 +624,13 @@ def synthesize_speech(text, voice_choice):
|
|
| 598 |
output_path = tmp.name
|
| 599 |
|
| 600 |
process_time = time.time() - start_time
|
|
|
|
|
|
|
| 601 |
|
| 602 |
# Lưu vào lịch sử
|
| 603 |
permanent_path = add_to_history(raw_text, voice_choice, output_path, process_time, "Thành công")
|
| 604 |
|
| 605 |
-
yield permanent_path, f"✅ Hoàn tất! (
|
| 606 |
|
| 607 |
except Exception as e:
|
| 608 |
import traceback
|
|
@@ -731,7 +759,7 @@ EXAMPLES_LIST = [
|
|
| 731 |
["Thành phố Hồ Chí Minh luôn chuyển mình không ngừng với nhịp sống hối hả, năng động.", "Dung (nữ miền Nam)"],
|
| 732 |
]
|
| 733 |
|
| 734 |
-
initial_status = f"✅ Model đã tải thành công! (Chạy trên **{DEVICE_INFO}**). Hỗ trợ xử lý background và lưu lịch sử." if
|
| 735 |
|
| 736 |
with gr.Blocks(title="VieNeu-TTS", theme=theme, css=css) as demo:
|
| 737 |
with gr.Column(elem_classes="container"):
|
|
@@ -757,6 +785,22 @@ with gr.Blocks(title="VieNeu-TTS", theme=theme, css=css) as demo:
|
|
| 757 |
""")
|
| 758 |
|
| 759 |
status_banner = gr.Markdown(initial_status)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 760 |
|
| 761 |
# --- TABS ---
|
| 762 |
with gr.Tabs():
|
|
@@ -778,7 +822,7 @@ with gr.Blocks(title="VieNeu-TTS", theme=theme, css=css) as demo:
|
|
| 778 |
)
|
| 779 |
|
| 780 |
with gr.Row():
|
| 781 |
-
btn_generate = gr.Button("🎵 Bắt đầu tổng hợp", variant="primary", size="lg"
|
| 782 |
btn_clear = gr.Button("🗑️ Xóa", variant="secondary", size="lg")
|
| 783 |
|
| 784 |
with gr.Column(scale=2):
|
|
@@ -905,6 +949,7 @@ with gr.Blocks(title="VieNeu-TTS", theme=theme, css=css) as demo:
|
|
| 905 |
- ✅ Chia chunk thông minh
|
| 906 |
- ✅ Thread-safe operations
|
| 907 |
- ✅ Tự động xóa file cũ khi vượt quá 100 bản ghi
|
|
|
|
| 908 |
|
| 909 |
### 📊 Thống kê
|
| 910 |
{get_processing_stats()}
|
|
@@ -969,6 +1014,7 @@ if __name__ == "__main__":
|
|
| 969 |
print(f"📂 Lịch sử lưu tại: {HISTORY_DIR}")
|
| 970 |
print(f"🎭 Số giọng mẫu: {len(VOICE_SAMPLES)}")
|
| 971 |
print(f"⚙️ Chế độ: {DEVICE_INFO}")
|
|
|
|
| 972 |
print(f"{'='*60}\n")
|
| 973 |
|
| 974 |
demo.queue().launch(
|
|
|
|
| 354 |
return recon[0, 0, :]
|
| 355 |
|
| 356 |
# --- MODEL LOADING ---
|
| 357 |
+
# Sử dụng class để giữ state của model
|
| 358 |
+
class ModelManager:
|
| 359 |
+
def __init__(self):
|
| 360 |
+
self.backbone = None
|
| 361 |
+
self.codec = None
|
| 362 |
+
self.model_loaded = False
|
| 363 |
+
self.loading_lock = threading.Lock()
|
| 364 |
+
self.load_models()
|
| 365 |
+
|
| 366 |
+
def load_models(self):
|
| 367 |
+
"""Tải models với thread safety"""
|
| 368 |
+
with self.loading_lock:
|
| 369 |
+
if self.model_loaded:
|
| 370 |
+
print("✅ Models đã được tải trước đó")
|
| 371 |
+
return True
|
| 372 |
+
|
| 373 |
+
print("📦 Đang tải model Q4 GGUF và Codec ONNX...")
|
| 374 |
+
try:
|
| 375 |
+
self.backbone = Llama.from_pretrained(
|
| 376 |
+
repo_id=BACKBONE_REPO,
|
| 377 |
+
filename="*.gguf",
|
| 378 |
+
verbose=False,
|
| 379 |
+
n_gpu_layers=-1,
|
| 380 |
+
n_ctx=2048,
|
| 381 |
+
mlock=True,
|
| 382 |
+
flash_attn=True,
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
+
self.codec = NeuCodecOnnxDecoder.from_pretrained(CODEC_REPO)
|
| 386 |
+
|
| 387 |
+
self.model_loaded = True
|
| 388 |
+
print("✅ Model đã tải thành công!")
|
| 389 |
+
return True
|
| 390 |
+
|
| 391 |
+
except Exception as e:
|
| 392 |
+
import traceback
|
| 393 |
+
traceback.print_exc()
|
| 394 |
+
print(f"❌ Lỗi khi tải model: {e}")
|
| 395 |
+
self.model_loaded = False
|
| 396 |
+
return False
|
| 397 |
+
|
| 398 |
+
def is_ready(self):
|
| 399 |
+
"""Kiểm tra xem model đã sẵn sàng chưa"""
|
| 400 |
+
return self.model_loaded and self.backbone is not None and self.codec is not None
|
| 401 |
|
| 402 |
+
# Khởi tạo ModelManager singleton
|
| 403 |
+
model_manager = ModelManager()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
|
| 405 |
# --- SYNTHESIS FUNCTION (Internal) ---
|
| 406 |
def synthesize_speech_internal(text, voice_choice):
|
| 407 |
"""Internal synthesis function không phụ thuộc UI"""
|
|
|
|
| 408 |
|
| 409 |
+
if not model_manager.is_ready():
|
| 410 |
+
print("❌ Model chưa được tải, đang thử tải lại...")
|
| 411 |
+
if not model_manager.load_models():
|
| 412 |
+
print("❌ Không thể tải model")
|
| 413 |
+
return None
|
| 414 |
|
| 415 |
if not text or text.strip() == "":
|
| 416 |
print("❌ Text rỗng")
|
|
|
|
| 474 |
)
|
| 475 |
|
| 476 |
# Generate
|
| 477 |
+
output = model_manager.backbone(
|
| 478 |
prompt,
|
| 479 |
max_tokens=2048,
|
| 480 |
temperature=1.0,
|
|
|
|
| 484 |
output_str = output["choices"][0]["text"]
|
| 485 |
|
| 486 |
# Decode
|
| 487 |
+
chunk_wav = decode_audio(output_str, model_manager.codec)
|
| 488 |
|
| 489 |
if chunk_wav is not None and len(chunk_wav) > 0:
|
| 490 |
all_audio_segments.append(chunk_wav)
|
|
|
|
| 518 |
# --- SYNTHESIS FUNCTION (UI) ---
|
| 519 |
def synthesize_speech(text, voice_choice):
|
| 520 |
"""Main synthesis function với UI feedback"""
|
|
|
|
| 521 |
|
| 522 |
+
if not model_manager.is_ready():
|
| 523 |
+
yield None, "⚠️ Model chưa tải. Đang thử tải lại..."
|
| 524 |
+
if not model_manager.load_models():
|
| 525 |
+
yield None, "❌ Không thể tải model. Vui lòng kiểm tra console!"
|
| 526 |
+
return
|
| 527 |
+
yield None, "✅ Model đã tải thành công!"
|
| 528 |
|
| 529 |
if not text or text.strip() == "":
|
| 530 |
yield None, "⚠️ Vui lòng nhập văn bản!"
|
|
|
|
| 578 |
|
| 579 |
try:
|
| 580 |
for i, chunk in enumerate(text_chunks):
|
| 581 |
+
progress = int((i/total_chunks)*100)
|
| 582 |
+
yield None, f"⏳ Đang xử lý đoạn {i+1}/{total_chunks}... ({progress}%)"
|
| 583 |
|
| 584 |
# Phonemize
|
| 585 |
ref_text_phoneme = phonemize_with_dict(ref_text_raw)
|
|
|
|
| 594 |
)
|
| 595 |
|
| 596 |
# Generate
|
| 597 |
+
output = model_manager.backbone(
|
| 598 |
prompt,
|
| 599 |
max_tokens=2048,
|
| 600 |
temperature=1.0,
|
|
|
|
| 604 |
output_str = output["choices"][0]["text"]
|
| 605 |
|
| 606 |
# Decode
|
| 607 |
+
chunk_wav = decode_audio(output_str, model_manager.codec)
|
| 608 |
|
| 609 |
if chunk_wav is not None and len(chunk_wav) > 0:
|
| 610 |
all_audio_segments.append(chunk_wav)
|
|
|
|
| 624 |
output_path = tmp.name
|
| 625 |
|
| 626 |
process_time = time.time() - start_time
|
| 627 |
+
audio_duration = len(final_wav) / SAMPLE_RATE
|
| 628 |
+
rtf = process_time / audio_duration
|
| 629 |
|
| 630 |
# Lưu vào lịch sử
|
| 631 |
permanent_path = add_to_history(raw_text, voice_choice, output_path, process_time, "Thành công")
|
| 632 |
|
| 633 |
+
yield permanent_path, f"✅ Hoàn tất! (Thời gian: {process_time:.2f}s | Audio: {audio_duration:.2f}s | RTF: {rtf:.3f})"
|
| 634 |
|
| 635 |
except Exception as e:
|
| 636 |
import traceback
|
|
|
|
| 759 |
["Thành phố Hồ Chí Minh luôn chuyển mình không ngừng với nhịp sống hối hả, năng động.", "Dung (nữ miền Nam)"],
|
| 760 |
]
|
| 761 |
|
| 762 |
+
initial_status = f"✅ Model đã tải thành công! (Chạy trên **{DEVICE_INFO}**). Hỗ trợ xử lý background và lưu lịch sử." if model_manager.is_ready() else "⚠️ Model đang được tải hoặc chưa sẵn sàng..."
|
| 763 |
|
| 764 |
with gr.Blocks(title="VieNeu-TTS", theme=theme, css=css) as demo:
|
| 765 |
with gr.Column(elem_classes="container"):
|
|
|
|
| 785 |
""")
|
| 786 |
|
| 787 |
status_banner = gr.Markdown(initial_status)
|
| 788 |
+
|
| 789 |
+
# Thêm nút kiểm tra model status
|
| 790 |
+
with gr.Row():
|
| 791 |
+
btn_check_model = gr.Button("🔍 Kiểm tra trạng thái Model", size="sm", variant="secondary")
|
| 792 |
+
model_status_output = gr.Textbox(label="", show_label=False, interactive=False, container=False)
|
| 793 |
+
|
| 794 |
+
def check_model_status():
|
| 795 |
+
if model_manager.is_ready():
|
| 796 |
+
return f"✅ Model sẵn sàng | Backbone: {'Loaded' if model_manager.backbone else 'Not loaded'} | Codec: {'Loaded' if model_manager.codec else 'Not loaded'}"
|
| 797 |
+
else:
|
| 798 |
+
return "⚠️ Model chưa sẵn sàng. Nhấn nút tổng hợp để tự động tải model."
|
| 799 |
+
|
| 800 |
+
btn_check_model.click(
|
| 801 |
+
fn=check_model_status,
|
| 802 |
+
outputs=model_status_output
|
| 803 |
+
)
|
| 804 |
|
| 805 |
# --- TABS ---
|
| 806 |
with gr.Tabs():
|
|
|
|
| 822 |
)
|
| 823 |
|
| 824 |
with gr.Row():
|
| 825 |
+
btn_generate = gr.Button("🎵 Bắt đầu tổng hợp", variant="primary", size="lg")
|
| 826 |
btn_clear = gr.Button("🗑️ Xóa", variant="secondary", size="lg")
|
| 827 |
|
| 828 |
with gr.Column(scale=2):
|
|
|
|
| 949 |
- ✅ Chia chunk thông minh
|
| 950 |
- ✅ Thread-safe operations
|
| 951 |
- ✅ Tự động xóa file cũ khi vượt quá 100 bản ghi
|
| 952 |
+
- ✅ Auto-reload model sau khi reload trang
|
| 953 |
|
| 954 |
### 📊 Thống kê
|
| 955 |
{get_processing_stats()}
|
|
|
|
| 1014 |
print(f"📂 Lịch sử lưu tại: {HISTORY_DIR}")
|
| 1015 |
print(f"🎭 Số giọng mẫu: {len(VOICE_SAMPLES)}")
|
| 1016 |
print(f"⚙️ Chế độ: {DEVICE_INFO}")
|
| 1017 |
+
print(f"✅ Model status: {'Ready' if model_manager.is_ready() else 'Not ready'}")
|
| 1018 |
print(f"{'='*60}\n")
|
| 1019 |
|
| 1020 |
demo.queue().launch(
|