import os import sys import logging import torch import utils import commons import gradio as gr from models import SynthesizerTrn from text import text_to_sequence from text.symbols import symbols # Konfigurasi Dasar logging.getLogger('matplotlib').setLevel(logging.WARNING) device = torch.device("cpu") def get_text(text, hps): text_norm = text_to_sequence(text, hps.data.text_cleaners) if hps.data.add_blank: text_norm = commons.intersperse(text_norm, 0) return torch.LongTensor(text_norm) def create_tts_fn(model, hps, speaker_ids): def tts_fn(text, speaker_idx, speed): if not text: return "Teks tidak boleh kosong!", None try: speaker_id = speaker_ids[speaker_idx] stn_tst = get_text(text, hps) with torch.no_grad(): x_tst = stn_tst.unsqueeze(0).to(device) x_tst_lengths = torch.LongTensor([stn_tst.size(0)]).to(device) sid = torch.LongTensor([speaker_id]).to(device) audio = model.infer( x_tst, x_tst_lengths, sid=sid, noise_scale=.667, noise_scale_w=0.8, length_scale=1.0/speed )[0][0,0].data.cpu().float().numpy() return "Berhasil Membuat ✅!", (hps.data.sampling_rate, audio) except Exception as e: return f"Error: {str(e)}", None return tts_fn # --- CSS: MILKY WHITE & PRECISION BLUE --- css = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800&display=swap'); body, .gradio-container { background-color: #fdfdfd !important; font-family: 'Inter', sans-serif !important; } footer { display: none !important; } /* Header */ .main-header { text-align: center; padding: 20px 0 5px 0; } .main-header h1 { font-weight: 800; color: #1299ff; font-size: 2.4rem; margin: 0; } .main-header p { color: #8a99af; letter-spacing: 2px; font-weight: 600; font-size: 10px; margin-top: 2px; } /* Menghilangkan Shadow & Border Halus pada Input */ .gradio-container .block, .gradio-container .form, .gradio-container input, .gradio-container textarea, .gradio-container .secondary { box-shadow: none !important; border: 1px solid #e2e8f0 !important; background-color: #ffffff !important; } /* --- Label Biru & Putih Susu (Target Semua Label Termasuk Hasil Audio) --- */ .gradio-container .label-wrap span, .gradio-container span.svelte-1gfkn6j, .gradio-container label span { background-color: #e6f4ff !important; color: #1299ff !important; border-radius: 8px !important; padding: 2px 10px !important; box-shadow: none !important; border: none !important; font-weight: 700 !important; font-size: 12px !important; } /* --- Audio Player (Wadah Putih Bersih) --- */ .gradio-container .audio-container { background-color: #ffffff !important; border: 1px solid #e2e8f0 !important; } /* Status Box */ .status-card { background: #ffffff; border: 1px solid #eef2f7; border-radius: 12px; padding: 12px 20px; margin: 0 auto 15px auto; max-width: 380px; } .status-item { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #4a5568; margin: 2px 0; } .dot { height: 6px; width: 6px; background-color: #1299ff; border-radius: 50%; display: inline-block; } /* Character Info Grid */ .char-scroll-box { max-height: 280px; overflow-y: auto; padding: 5px; } .char-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } .char-card { background: white; border-left: 4px solid #1299ff; padding: 10px; border-radius: 10px; border-top: 1px solid #f0f4f8; border-right: 1px solid #f0f4f8; border-bottom: 1px solid #f0f4f8; cursor: pointer; transition: all 0.2s; } .char-card:hover { background: #f0f7ff; transform: translateY(-2px); border-color: #0072ff; } .char-name-jp { font-weight: 700; font-size: 13px; color: #1a202c; display: block; } .char-name-en { font-size: 11px; color: #718096; } /* Tombol Generate */ .generate-btn { background: linear-gradient(135deg, #00c6ff 0%, #0072ff 100%) !important; border: none !important; color: white !important; font-weight: 700 !important; border-radius: 12px !important; height: 45px !important; } #custom-img { border-radius: 15px !important; border: 3px solid #fff; } """ if __name__ == '__main__': config_path = "saved_model/config.json" model_path = "saved_model/model.pth" cover_path = "saved_model/cover.png" hps = utils.get_hparams_from_file(config_path) posterior_channels = 80 if ("use_mel_posterior_encoder" in hps.model.keys() and hps.model.use_mel_posterior_encoder) else hps.data.filter_length // 2 + 1 model = SynthesizerTrn( len(symbols), posterior_channels, hps.train.segment_size // hps.data.hop_length, n_speakers=hps.data.n_speakers, **hps.model).to(device) utils.load_checkpoint(model_path, model, None) model.eval() speakers = [name for name in hps.speakers if name != "None"] speaker_ids = [i for i, name in enumerate(hps.speakers) if name != "None"] with gr.Blocks(css=css, theme=gr.themes.Soft()) as app: # Header gr.HTML("""

Blue Archive

PREMIUM TEXT-TO-SPEECH SERVICE

""") # Status Box gr.HTML(f"""
System Status
Model: LOADED
Characters: {len(speakers)}
v3.0 • Ready for generation
""") # --- KOMPONEN PENGHUBUNG (BRIDGE) --- # Ini textbox tersembunyi. Saat JS mengisi angka di sini, Python akan bereaksi. hidden_choice = gr.Textbox(visible=False, elem_id="hidden_choice") with gr.Row(): with gr.Column(scale=1): if os.path.exists(cover_path): gr.Image(cover_path, elem_id="custom-img", show_label=False, interactive=False) input_text = gr.TextArea(label="Input Teks (Japanese)", placeholder="先生、何をお手伝いしましょうか?", lines=3) with gr.Row(): # Dropdown Karakter speaker_sel = gr.Dropdown(label="🏷️ Pilih Karakter", choices=speakers, type="index", value=speakers[0], elem_id="spk_drop") speed_sl = gr.Slider(label="Kecepatan", minimum=0.5, maximum=2.0, value=1.0, step=0.1) btn = gr.Button("🎐 Generate Voice 🎐", variant="primary", elem_classes="generate-btn") with gr.Column(scale=1): out_msg = gr.Textbox(label="Status") out_audio = gr.Audio(label="Hasil Audio", type="numpy") with gr.Accordion("📑 Character Information 📑", open=True): # Kita gunakan onclick="selectChar(urutan_angka)" char_html = "".join([ f'
' f'{name}' f'Character {i+1}' f'
' for i, name in enumerate(speakers) ]) gr.HTML(f'
{char_html}
') gr.HTML("""
CREATED BY PLANA-CHAN
Blue Archive TTS v3.0 • Powered by VITS
""") # --- EVENT HANDLER (PYTHON) --- # Fungsi ini menerima input dari hidden_choice dan mengubah nilai Dropdown def move_selection(idx): try: # Pastikan dikonversi jadi integer agar Dropdown 'type=index' mengerti return int(idx) except: return 0 # "Jika hidden_choice berubah, maka ubah speaker_sel" hidden_choice.change(fn=move_selection, inputs=hidden_choice, outputs=speaker_sel) # Fungsi Generate Audio btn.click(create_tts_fn(model, hps, speaker_ids), inputs=[input_text, speaker_sel, speed_sl], outputs=[out_msg, out_audio]) # --- JAVASCRIPT (FIXED) --- # Script ini mencari elemen input tersembunyi dan memaksa update nilai app.load(None, None, None, js=""" () => { window.selectChar = (index) => { // Cari elemen textarea di dalam hidden_choice // Gradio versi baru kadang pakai input, kadang textarea let el = document.querySelector("#hidden_choice textarea"); if (!el) el = document.querySelector("#hidden_choice input"); if (el) { el.value = index; // Dispatch event 'input' agar Gradio mendeteksi perubahan el.dispatchEvent(new Event('input', { bubbles: true })); } else { console.error("Hidden input not found"); } } } """) app.queue().launch()