# ========================================================= # PROJECT SEKAI TTS # BABY GREEN THEME UI WITH ORIGINAL DOWNLOAD FEATURE # ========================================================= import os import random import logging import re import numpy as np import torch import shutil from torch import no_grad, LongTensor import commons import utils import gradio as gr from models import SynthesizerTrn from text import text_to_sequence, _clean_text from huggingface_hub import hf_hub_download # ========================================================= # 1. LOGGING # ========================================================= logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) # ========================================================= # 2. CONFIG & ROMAJI UTILS # ========================================================= HF_TOKEN = os.getenv("HF_TOKEN") REPO_ID = "Plana-Archive/Anime-Models" SUBFOLDER = "Anime-VITS/Prosekai-TTS/saved_model" device = torch.device("cpu") try: import pykakasi kks = pykakasi.kakasi() def to_romaji(text): if not text or text == "None": return "" try: result = kks.convert(str(text)) return "".join([item["hepburn"].capitalize() for item in result]) except: return str(text) except: def to_romaji(text): return str(text) # ========================================================= # 3. FITUR DOWNLOAD ASLI BAWAAN APP (6).PY (TIDAK DIUBAH) # ========================================================= logger.info("[*] Downloading model assets...") config_path = hf_hub_download( repo_id=REPO_ID, filename="config.json", subfolder=SUBFOLDER, token=HF_TOKEN ) model_path = hf_hub_download( repo_id=REPO_ID, filename="model.pth", subfolder=SUBFOLDER, token=HF_TOKEN ) cover_path = hf_hub_download( repo_id=REPO_ID, filename="cover.png", subfolder=SUBFOLDER, token=HF_TOKEN ) # ========================================================= # 4. ENGINE LOAD MODEL # ========================================================= hps = utils.get_hparams_from_file(config_path) model = SynthesizerTrn( len(hps.symbols), hps.data.filter_length // 2 + 1, 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() speaker_names = [name for name in hps.speakers if name != "None"] display_names = [to_romaji(name) for name in speaker_names] speaker_map = {romaji: original for romaji, original in zip(display_names, speaker_names)} # ========================================================= # 5. HELPER FUNCTIONS # ========================================================= def get_text(text, hps, is_phoneme): text_norm = text_to_sequence( text, hps.symbols, [] if is_phoneme else hps.data.text_cleaners ) if hps.data.add_blank: text_norm = commons.intersperse(text_norm, 0) return LongTensor(text_norm) def tts_execute(text, speaker_romaji, speed, noise_scale, noise_scale_w, is_phoneme): if not speaker_romaji: return "Pilih karakter terlebih dahulu.", None try: original_name = speaker_map[speaker_romaji] speaker_id = hps.speakers.index(original_name) stn_tst = get_text(text, hps, is_phoneme) with no_grad(): x_tst = stn_tst.unsqueeze(0).to(device) x_tst_lengths = LongTensor([stn_tst.size(0)]).to(device) sid = LongTensor([speaker_id]).to(device) audio = model.infer( x_tst, x_tst_lengths, sid=sid, noise_scale=noise_scale, noise_scale_w=noise_scale_w, length_scale=1.0 / speed )[0][0, 0].data.cpu().float().numpy() return "Voice berhasil dibuat.", (hps.data.sampling_rate, (audio * 32767).astype(np.int16)) except Exception as e: return f"Error: {e}", None def get_random_jp(): return random.choice([ "こんにちは、今日はいい天気ですね。", "みんなで一緒に歌おう!", "ワンダショ、最高のステージにしよう。", "練習、頑張ろうね。きっと上手くなるよ。", "また明日も会えるかな?楽しみにしてる。" ]) # --- MENCONTEK TAMPILAN REFINED CARD GRID LAYOUT DARI APP.PY --- def get_char_info_html(): html = """
""" for i, name in enumerate(display_names): html += f"""
{name}
Character {i+1}
""" html += "
" return html # ========================================================= # 6. UI STYLE (BABY GREEN THEME FROM APP (7).PY) # ========================================================= css = """ :root { --primary-600: #a8e6cf !important; --accent-600: #a8e6cf !important; } .gradio-container, .gradio-container * { --loader-color: #dcedc1 !important; } .ba-header-container { border: 1.5px solid #e1f0e5; border-radius: 12px; margin-bottom: 12px; background: white; overflow: hidden; line-height: 0; } .ba-header-container img { width: 100%; height: auto; } .status-container { border: 1.5px solid #e1f0e5; border-radius: 12px; padding: 15px 22px; margin-bottom: 20px; background: white; } .status-title { color: #88d498 !important; font-weight: 800; font-size: 16px; margin-bottom: 8px; } .text-green-bold { color: #88d498 !important; font-weight: 900 !important; } /* Pulsing Dot Effect */ .dot { height: 8px; width: 8px; background-color: #88d498; border-radius: 50%; display: inline-block; margin-right: 5px; box-shadow: 0 0 0 0 rgba(136, 212, 152, 1); animation: pulse-green 2s infinite; } @keyframes pulse-green { 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(136, 212, 152, 0.7); } 70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(136, 212, 152, 0); } 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(136, 212, 152, 0); } } .slim-card { max-width: 480px; margin: 0 auto; background: transparent; padding: 10px; } .scroll-box { height: 200px; overflow-y: auto; border: 1px solid #f0f4f8; border-radius: 12px; padding: 10px; background: #fafbfc; margin-bottom: 10px; } .char-btn { background: white !important; border: 1px solid #e2e8f0 !important; border-left: 5px solid #a8e6cf !important; text-align: left !important; padding: 8px !important; font-size: 12px !important; margin-bottom: 4px !important; width: 100%; color: #4a5568 !important; } .warning-card { background: #fffcf0; border: 2px dashed #ffd3b6; border-radius: 10px; padding: 12px; margin-bottom: 15px; text-align: center; } .jp-btn { background: #f8fafc !important; border: 1px solid #cbd5e1 !important; color: #475569 !important; font-weight: 700 !important; border-radius: 10px !important; margin-bottom: 10px; font-size: 12px !important; width: 100%; } .gen-btn { background: #a8e6cf !important; color: #4a5568 !important; font-weight: 700 !important; border-radius: 12px !important; height: 45px !important; width: 100%; border: none !important; cursor: pointer; } .info-header-custom { background: #dcedc1 !important; color: #4a5568 !important; border: none !important; border-radius: 8px 8px 0 0 !important; padding: 12px 15px !important; width: 100% !important; cursor: pointer; font-weight: 800 !important; margin-top: 5px; } .credit-footer { margin-top: 25px; padding: 15px; background: white; border-radius: 12px; text-align: center; border-bottom: 4px solid #a8e6cf; color: #94a3b8; font-weight: 700; font-size: 12px; letter-spacing: 2px; } .gif-container { display: flex; justify-content: center; margin-top: 15px; } .gif-container img { border-radius: 12px; max-width: 100%; height: auto; border: 1px solid #e1f0e5; } """ # ========================================================= # 7. GRADIO UI LAYOUT # ========================================================= with gr.Blocks() as demo: with gr.Column(elem_classes="slim-card"): # Header Banner & System Status gr.HTML("""
Mutsumi Banner
System Status
Model :  LOADED ✅
System : Online
""") gr.Markdown(f"### 📂 Project SEKAI - Milk Pastel TTS") # Tampilkan cover image yang sudah terdownload asli jika ada if os.path.exists(cover_path): gr.Image(cover_path, show_label=False, interactive=False, height=140) sel_name = gr.State("") char_display = gr.Markdown("📍 *Silakan pilih karakter...*") # Scroll box penampung daftar karakter (menggunakan display_names hasil download asli) with gr.Column(elem_classes="scroll-box"): if display_names: for name in display_names: btn = gr.Button(f"👤 {name}", elem_classes="char-btn") btn.click(fn=lambda n=name: (n, f"📍 Selected: **{n}**"), outputs=[sel_name, char_display]) gr.HTML("""
🔖 PERINGATAN MINNA 🔖
Cara pakai: klik character di atas, masukkan text, lalu Generate! ✨
""") # Input Teks & Fitur Random Text txt_in = gr.TextArea(label="Input Text", value="こんにちは。みんなで楽しく歌いましょう。", lines=3) gr.Button("🎲 INPUTS RANDOM TEXT 🎲", elem_classes="jp-btn").click(get_random_jp, outputs=[txt_in]) # Kecepatan bicara spd = gr.Slider(0.6, 1.8, value=1.0, step=0.05, label="Kecepatan bicara") # Parameter Advanced (Sembunyi dalam Accordion) with gr.Accordion("🎛️ Advanced (noise & expressivity)", open=False): noise_scale = gr.Slider(0.3, 1.2, value=0.667, step=0.01, label="Noise Scale (tone randomness)") noise_scale_w = gr.Slider(0.5, 1.3, value=0.8, step=0.01, label="Duration Variation (w)") phoneme_mode = gr.Checkbox(label="Mode input fonem (langsung ke fonem)") # Tombol utama eksekusi suara btn_gen = gr.Button("🫒 GENERATE VOICE 🫒", elem_classes="gen-btn") status_out = gr.Textbox(label="Status", interactive=False) aud_out = gr.Audio(label="Voice Output", type="numpy") # Animasi GIF tambahan & Informasi Karakter gr.HTML(f"""
Kurumi GIF
""") gr.HTML(get_char_info_html()) # Logika tombol saat di-klik btn_gen.click( fn=tts_execute, inputs=[txt_in, sel_name, spd, noise_scale, noise_scale_w, phoneme_mode], outputs=[status_out, aud_out] ) gr.HTML("""""") # ========================================================= # 8. LAUNCH RUN # ========================================================= if __name__ == "__main__": demo.queue().launch( server_name="0.0.0.0", server_port=7860, css=css )