|
|
import os
|
|
|
import tempfile
|
|
|
|
|
|
import gradio as gr
|
|
|
from huggingface_hub import login
|
|
|
from cached_path import cached_path
|
|
|
from vinorm import TTSnorm
|
|
|
|
|
|
from f5_tts.model import DiT
|
|
|
from f5_tts.infer.utils_infer import (
|
|
|
preprocess_ref_audio_text,
|
|
|
load_vocoder,
|
|
|
load_model,
|
|
|
infer_process,
|
|
|
save_spectrogram,
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
user_db = {
|
|
|
"lytran": {
|
|
|
"password": "16051998",
|
|
|
"approved": True,
|
|
|
"is_admin": True
|
|
|
}
|
|
|
}
|
|
|
|
|
|
pending_db = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hf_token = os.getenv("HUGGINGFACEHUB_API_TOKEN")
|
|
|
if hf_token:
|
|
|
login(token=hf_token)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def post_process(text: str) -> str:
|
|
|
text = f" {text} "
|
|
|
for old, new in [(" . . ", " . "), (" .. ", " . "),
|
|
|
(" , , ", " , "), (" ,, ", " , ")]:
|
|
|
text = text.replace(old, new)
|
|
|
text = text.replace('"', "")
|
|
|
return " ".join(text.split())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vocoder = load_vocoder()
|
|
|
model = load_model(
|
|
|
DiT,
|
|
|
dict(dim=1024, depth=22, heads=16, ff_mult=2, text_dim=512, conv_layers=4),
|
|
|
ckpt_path=str(cached_path("hf://LTTEAM/100h-VN/model_100h.pt")),
|
|
|
vocab_file=str(cached_path("hf://LTTEAM/100h-VN/vocab.txt")),
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def infer_tts(ref_audio_path: str, gen_text: str, speed: float = 1.0):
|
|
|
if not ref_audio_path:
|
|
|
raise gr.Error("Vui lòng tải lên hoặc chọn tệp âm thanh mẫu.")
|
|
|
if not gen_text.strip():
|
|
|
raise gr.Error("Vui lòng nhập nội dung văn bản cần chuyển thành giọng nói.")
|
|
|
if len(gen_text.split()) > 1000:
|
|
|
raise gr.Error("Vui lòng nhập văn bản có ít hơn 1000 từ.")
|
|
|
try:
|
|
|
ref_audio, ref_text = preprocess_ref_audio_text(ref_audio_path, "")
|
|
|
wave, sr, spec = infer_process(
|
|
|
ref_audio,
|
|
|
ref_text.lower(),
|
|
|
post_process(TTSnorm(gen_text)).lower(),
|
|
|
model, vocoder,
|
|
|
speed=speed
|
|
|
)
|
|
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
|
|
|
save_spectrogram(spec, tmp.name)
|
|
|
spec_path = tmp.name
|
|
|
return (sr, wave), spec_path
|
|
|
except Exception as e:
|
|
|
raise gr.Error(f"Lỗi khi chuyển văn bản sang giọng nói: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register_user(username: str, password: str):
|
|
|
username = username.strip()
|
|
|
if not username or not password:
|
|
|
return "Vui lòng nhập đủ tên và mật khẩu."
|
|
|
if username in user_db:
|
|
|
return "Tên này đã tồn tại."
|
|
|
if username in pending_db:
|
|
|
return "Tên này đang chờ admin duyệt."
|
|
|
pending_db[username] = password
|
|
|
return "Đăng ký thành công! Vui lòng chờ admin duyệt."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def login_user(username: str, password: str):
|
|
|
username = username.strip()
|
|
|
if username in pending_db:
|
|
|
return False, "Tài khoản đang chờ admin duyệt."
|
|
|
u = user_db.get(username)
|
|
|
if not u or u["password"] != password:
|
|
|
return False, "Tên đăng nhập hoặc mật khẩu không đúng."
|
|
|
if not u["approved"]:
|
|
|
return False, "Tài khoản chưa được duyệt."
|
|
|
return True, ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def approve_user(username: str):
|
|
|
if username not in pending_db:
|
|
|
return gr.update(choices=list(pending_db.keys()), value=None), "Không tìm thấy user."
|
|
|
|
|
|
user_db[username] = {
|
|
|
"password": pending_db.pop(username),
|
|
|
"approved": True,
|
|
|
"is_admin": False
|
|
|
}
|
|
|
return gr.update(choices=list(pending_db.keys()), value=None), f"Đã duyệt {username}."
|
|
|
|
|
|
def reject_user(username: str):
|
|
|
if username not in pending_db:
|
|
|
return gr.update(choices=list(pending_db.keys()), value=None), "Không tìm thấy user."
|
|
|
pending_db.pop(username)
|
|
|
return gr.update(choices=list(pending_db.keys()), value=None), f"Đã từ chối {username}."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
|
|
|
|
with gr.Column(visible=True) as login_col:
|
|
|
gr.Markdown("## Đăng nhập / Đăng ký")
|
|
|
|
|
|
username_input = gr.Textbox(label="Tên đăng nhập", placeholder="Nhập tên đăng nhập...")
|
|
|
password_input = gr.Textbox(label="Mật khẩu", type="password", placeholder="Nhập mật khẩu...")
|
|
|
login_btn = gr.Button("Đăng nhập", variant="primary")
|
|
|
login_msg = gr.Textbox(interactive=False, label="", placeholder="")
|
|
|
|
|
|
gr.Markdown("---")
|
|
|
|
|
|
reg_username = gr.Textbox(label="Tên đăng ký", placeholder="Chọn tên đăng nhập mới...")
|
|
|
reg_password = gr.Textbox(label="Mật khẩu", type="password", placeholder="Chọn mật khẩu...")
|
|
|
register_btn = gr.Button("Đăng ký tài khoản")
|
|
|
register_msg = gr.Textbox(interactive=False, label="")
|
|
|
|
|
|
gr.Markdown("---")
|
|
|
|
|
|
gr.HTML("""
|
|
|
<div style="
|
|
|
display: flex; align-items: center;
|
|
|
padding: 20px; border:1px solid #ddd; border-radius:10px;
|
|
|
background:#fff;
|
|
|
">
|
|
|
<img src="https://congnghe360.com/wp-content/uploads/2025/05/c11f123eeec30f9d56d2.jpg"
|
|
|
style="width:80px;height:80px;border-radius:50%;margin-right:20px;"/>
|
|
|
<div>
|
|
|
<h4 style="margin:0;">Lý Trần (Admin)</h4>
|
|
|
<p style="margin:4px 0 0 0;color:#666;">Quản trị viên LT TEAM</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
""")
|
|
|
|
|
|
|
|
|
with gr.Column(visible=False) as main_col:
|
|
|
|
|
|
with gr.Column():
|
|
|
gr.Markdown("""
|
|
|
# LTTEAM – Chuyển văn bản sang giọng nói tiếng Việt
|
|
|
Nhập văn bản, chọn mẫu giọng, nhấn “Chuyển văn bản thành giọng nói”.
|
|
|
""")
|
|
|
with gr.Row():
|
|
|
ref_audio = gr.Audio(
|
|
|
value="assets/anhkhoiVbee.wav",
|
|
|
label="Mẫu giọng (có sẵn)",
|
|
|
type="filepath"
|
|
|
)
|
|
|
gen_text = gr.Textbox(
|
|
|
label="Nhập văn bản",
|
|
|
placeholder="Nhập nội dung cần chuyển...",
|
|
|
lines=3
|
|
|
)
|
|
|
speed = gr.Slider(0.3, 2.0, value=1.0, step=0.1, label="⚡ Tốc độ")
|
|
|
synth_btn = gr.Button("Chuyển TTS", variant="primary")
|
|
|
with gr.Row():
|
|
|
output_audio = gr.Audio(label="Kết quả", type="numpy")
|
|
|
output_spec = gr.Image(label="Spectrogram")
|
|
|
gr.Textbox(
|
|
|
value="""⚠️ Giới hạn:
|
|
|
1. Chưa xử lý tốt số, ngày tháng…
|
|
|
2. Có thể gián đoạn.
|
|
|
3. Transcription whisper có sai sót.
|
|
|
4. Huấn luyện 150h, chưa hoàn hảo.
|
|
|
5. Văn bản quá dài giảm chất lượng.""",
|
|
|
label="Lưu ý", lines=6, interactive=False
|
|
|
)
|
|
|
synth_btn.click(infer_tts, [ref_audio, gen_text, speed], [output_audio, output_spec])
|
|
|
|
|
|
|
|
|
with gr.Column(visible=False) as admin_col:
|
|
|
gr.Markdown("## Quản lý người dùng (Admin)")
|
|
|
pending_dropdown = gr.Dropdown(choices=[], label="Chọn user chờ duyệt")
|
|
|
btn_approve = gr.Button("Duyệt", variant="primary")
|
|
|
btn_reject = gr.Button("Từ chối", variant="secondary")
|
|
|
admin_msg = gr.Textbox(interactive=False, label="Thông báo")
|
|
|
|
|
|
|
|
|
register_btn.click(
|
|
|
fn=register_user,
|
|
|
inputs=[reg_username, reg_password],
|
|
|
outputs=[register_msg]
|
|
|
)
|
|
|
|
|
|
|
|
|
def on_login(username, password):
|
|
|
ok, msg = login_user(username, password)
|
|
|
if not ok:
|
|
|
return None, None, msg, None, None
|
|
|
|
|
|
is_admin = user_db[username]["is_admin"]
|
|
|
return (
|
|
|
gr.update(visible=False),
|
|
|
gr.update(visible=True),
|
|
|
"",
|
|
|
gr.update(visible=is_admin),
|
|
|
gr.update(choices=list(pending_db.keys()), value=None)
|
|
|
)
|
|
|
|
|
|
login_btn.click(
|
|
|
fn=on_login,
|
|
|
inputs=[username_input, password_input],
|
|
|
outputs=[login_col, main_col, login_msg, admin_col, pending_dropdown]
|
|
|
)
|
|
|
|
|
|
|
|
|
btn_approve.click(
|
|
|
fn=approve_user,
|
|
|
inputs=[pending_dropdown],
|
|
|
outputs=[pending_dropdown, admin_msg]
|
|
|
)
|
|
|
btn_reject.click(
|
|
|
fn=reject_user,
|
|
|
inputs=[pending_dropdown],
|
|
|
outputs=[pending_dropdown, admin_msg]
|
|
|
)
|
|
|
|
|
|
demo.queue().launch(share=True)
|
|
|
|