| """ |
| app.py - Blue Archive RVC dengan tampilan ala app(2).py (biru, rapi) |
| =============================================================== |
| Fitur: |
| - Download otomatis model Blue Archive dari Hugging Face (jika belum ada) |
| - Load semua karakter (42 karakter) dengan model RVC |
| - Tampilan: header biru "Library Anime", status LOADED, tabs |
| - Pilih karakter dari scroll box, masukkan teks, atur speed & pitch |
| - Generate voice menggunakan TTS (Edge TTS) + konversi suara |
| - Output audio, GIF Kurumi, expandable character info |
| """ |
|
|
| import os |
| import json |
| import traceback |
| import logging |
| import gradio as gr |
| import numpy as np |
| import librosa |
| import torch |
| import asyncio |
| import edge_tts |
| import re |
| import shutil |
| import time |
| import random |
| from datetime import datetime |
| from fairseq import checkpoint_utils |
| from fairseq.data.dictionary import Dictionary |
| from lib.infer_pack.models import ( |
| SynthesizerTrnMs256NSFsid, |
| SynthesizerTrnMs256NSFsid_nono, |
| SynthesizerTrnMs768NSFsid, |
| SynthesizerTrnMs768NSFsid_nono, |
| ) |
| from vc_infer_pipeline import VC |
| from config import Config |
|
|
| |
| |
| |
| from dotenv import load_dotenv |
| load_dotenv() |
|
|
| HF_TOKEN = os.getenv("HF_TOKEN") |
| if HF_TOKEN: |
| print("π Hugging Face token detected") |
| os.environ["HUGGINGFACE_TOKEN"] = HF_TOKEN |
| else: |
| print("β οΈ No HF_TOKEN found") |
|
|
| |
| |
| |
| def download_required_weights(): |
| print("=" * 50) |
| print("π BLUE ARCHIVE VOICE CONVERSION") |
| print("=" * 50) |
| target_dir = "weights" |
| blue_archive_dir = os.path.join(target_dir, "Blue-Archive") |
| if os.path.exists(blue_archive_dir): |
| model_files = [] |
| for root, dirs, files in os.walk(blue_archive_dir): |
| for f in files: |
| if f.endswith(".pth"): |
| model_files.append(os.path.join(root, f)) |
| if len(model_files) >= 1: |
| print(f"β
Models already exist: {len(model_files)} .pth files") |
| return True |
| try: |
| from huggingface_hub import snapshot_download |
| repo_id = "Plana-Archive/Premium-Model" |
| print(f"π₯ Downloading from: {repo_id}") |
| snapshot_download( |
| repo_id=repo_id, |
| allow_patterns=["Blue Archive - RCV/weights/**"], |
| local_dir=".", |
| local_dir_use_symlinks=False, |
| token=HF_TOKEN, |
| max_workers=2 |
| ) |
| print("β
Download completed") |
| source_dir = "Blue Archive - RCV/weights" |
| if os.path.exists(source_dir): |
| os.makedirs(target_dir, exist_ok=True) |
| for item in os.listdir(source_dir): |
| s = os.path.join(source_dir, item) |
| d = os.path.join(target_dir, item) |
| if os.path.isdir(s): |
| if os.path.exists(d): |
| shutil.rmtree(d) |
| shutil.move(s, d) |
| else: |
| shutil.move(s, d) |
| print(f"π Moved models to: {target_dir}") |
| |
| folder_info_path = os.path.join(target_dir, "folder_info.json") |
| if not os.path.exists(folder_info_path): |
| folder_info = { |
| "Blue-Archive": { |
| "title": "Blue Archive - RCV Collection", |
| "folder_path": "Blue-Archive", |
| "description": "Official RVC Weights by Plana-Archive", |
| "enable": True |
| } |
| } |
| with open(folder_info_path, "w") as f: |
| json.dump(folder_info, f, indent=2) |
| create_model_info_from_files(target_dir) |
| return True |
| except Exception as e: |
| print(f"β οΈ Download failed: {e}") |
| return False |
|
|
| def create_model_info_from_files(base_path): |
| blue_archive_dir = os.path.join(base_path, "Blue-Archive") |
| if not os.path.exists(blue_archive_dir): |
| return |
| model_info = {} |
| for char_folder in os.listdir(blue_archive_dir): |
| char_path = os.path.join(blue_archive_dir, char_folder) |
| if not os.path.isdir(char_path): |
| continue |
| pth_files = [f for f in os.listdir(char_path) if f.endswith('.pth')] |
| idx_files = [f for f in os.listdir(char_path) if f.endswith('.index')] |
| img_files = [f for f in os.listdir(char_path) if f.lower().endswith(('.png','.jpg','.jpeg'))] |
| if not pth_files: |
| continue |
| char_name_fmt = re.sub(r"([a-z])([A-Z])", r"\1 \2", char_folder) |
| model_info[char_folder] = { |
| "enable": True, |
| "model_path": pth_files[0], |
| "title": f"Blue Archive - {char_name_fmt}", |
| "cover": img_files[0] if img_files else "cover.png", |
| "feature_retrieval_library": idx_files[0] if idx_files else "", |
| "author": "Plana-Archive" |
| } |
| with open(os.path.join(blue_archive_dir, "model_info.json"), "w") as f: |
| json.dump(model_info, f, indent=2) |
| print(f"β
Created model_info.json with {len(model_info)} characters") |
|
|
| download_required_weights() |
|
|
| |
| |
| |
| config = Config() |
| logging.getLogger("numba").setLevel(logging.WARNING) |
| logging.getLogger("fairseq").setLevel(logging.WARNING) |
|
|
| model_cache = {} |
| hubert_loaded = False |
| hubert_model = None |
| vc_fn_map = {} |
|
|
| f0method_mode = ["pm", "harvest"] |
| if os.path.isfile("rmvpe.pt"): |
| f0method_mode.insert(2, "rmvpe") |
|
|
| def clean_title(title): |
| title = re.sub(r'^Blue Archive\s*-\s*', '', title, flags=re.IGNORECASE) |
| return re.sub(r'\s*-\s*\d+\s*epochs', '', title, flags=re.IGNORECASE) |
|
|
| |
| |
| |
| def _load_audio_input(vc_audio_mode, vc_input, vc_upload, tts_text): |
| temp_file = None |
| try: |
| if vc_audio_mode == "Input path" and vc_input: |
| audio, sr = librosa.load(vc_input, sr=16000, mono=True) |
| return audio.astype(np.float32), 16000, None |
| elif vc_audio_mode == "Upload audio": |
| if vc_upload is None: |
| raise ValueError("Please upload an audio file!") |
| sr, audio = vc_upload |
| if audio.dtype != np.float32: |
| audio = audio.astype(np.float32) / np.iinfo(audio.dtype).max |
| if len(audio.shape) > 1: |
| audio = np.mean(audio, axis=0) |
| if sr != 16000: |
| audio = librosa.resample(audio, orig_sr=sr, target_sr=16000) |
| return audio.astype(np.float32), 16000, None |
| elif vc_audio_mode == "TTS Audio": |
| if not tts_text or not tts_text.strip(): |
| raise ValueError("Please enter text for TTS!") |
| temp_file = f"tts_temp_{int(time.time())}.wav" |
| async def tts_task(): |
| await edge_tts.Communicate(tts_text, "ja-JP-NanamiNeural").save(temp_file) |
| asyncio.run(asyncio.wait_for(tts_task(), timeout=15)) |
| audio, sr = librosa.load(temp_file, sr=16000, mono=True) |
| return audio.astype(np.float32), 16000, temp_file |
| except Exception as e: |
| if temp_file and os.path.exists(temp_file): |
| os.remove(temp_file) |
| raise e |
| raise ValueError("Invalid audio mode") |
|
|
| def adjust_audio_speed(audio, speed): |
| if speed == 1.0: |
| return audio |
| return librosa.effects.time_stretch(audio.astype(np.float32), rate=speed) |
|
|
| def preprocess_audio(audio): |
| if np.max(np.abs(audio)) > 1.0: |
| audio = audio / np.max(np.abs(audio)) * 0.9 |
| return audio.astype(np.float32) |
|
|
| |
| |
| |
| def create_vc_fn(model_key, tgt_sr, net_g, vc, if_f0, version, file_index): |
| async def vc_fn( |
| vc_audio_mode, vc_input, vc_upload, tts_text, |
| f0_up_key, f0_method, index_rate, filter_radius, |
| resample_sr, rms_mix_rate, protect, speed, |
| ): |
| temp_audio_file = None |
| try: |
| if torch.cuda.is_available(): |
| torch.cuda.empty_cache() |
| net_g.to(config.device) |
| yield "Status: π Processing audio...", None |
| audio, sr, temp_audio_file = _load_audio_input(vc_audio_mode, vc_input, vc_upload, tts_text) |
| audio = preprocess_audio(audio) |
| audio_tensor = torch.FloatTensor(audio).to(config.device) |
| times = [0, 0, 0] |
| max_chunk_size = 16000 * 30 |
| if len(audio) > max_chunk_size: |
| chunks = [] |
| for i in range(0, len(audio), max_chunk_size): |
| chunk = audio[i:i+max_chunk_size] |
| chunk_tensor = torch.FloatTensor(chunk).to(config.device) |
| chunk_opt = vc.pipeline( |
| hubert_model, net_g, 0, chunk_tensor, |
| "chunk" if vc_input else "temp", times, |
| int(f0_up_key), f0_method, file_index, index_rate, |
| if_f0, filter_radius, tgt_sr, resample_sr, |
| rms_mix_rate, version, protect, f0_file=None, |
| ) |
| chunks.append(chunk_opt) |
| audio_opt = np.concatenate(chunks) |
| else: |
| audio_opt = vc.pipeline( |
| hubert_model, net_g, 0, audio_tensor, |
| vc_input if vc_input else "temp", times, |
| int(f0_up_key), f0_method, file_index, index_rate, |
| if_f0, filter_radius, tgt_sr, resample_sr, |
| rms_mix_rate, version, protect, f0_file=None, |
| ) |
| audio_opt = audio_opt.astype(np.float32) |
| if speed != 1.0: |
| audio_opt = adjust_audio_speed(audio_opt, speed) |
| if np.max(np.abs(audio_opt)) > 0: |
| audio_opt = (audio_opt / np.max(np.abs(audio_opt)) * 0.9).astype(np.float32) |
| yield "Status: β
Conversion completed!", (tgt_sr, audio_opt) |
| except Exception as e: |
| yield f"β Error: {str(e)}", None |
| finally: |
| if temp_audio_file and os.path.exists(temp_audio_file): |
| os.remove(temp_audio_file) |
| if torch.cuda.is_available(): |
| torch.cuda.empty_cache() |
| if model_key not in model_cache: |
| net_g.to('cpu') |
| return vc_fn |
|
|
| |
| |
| |
| def load_model(): |
| print("\n" + "=" * 50) |
| print("π΅ LOADING VOICE MODELS") |
| print("=" * 50) |
| categories = [] |
| base_path = "weights" |
| if not os.path.exists(base_path): |
| return categories |
| folder_info_path = f"{base_path}/folder_info.json" |
| if not os.path.exists(folder_info_path): |
| folder_info = { |
| "Blue-Archive": { |
| "title": "Blue Archive - RCV Collection", |
| "folder_path": "Blue-Archive", |
| "description": "Official RVC Weights", |
| "enable": True |
| } |
| } |
| with open(folder_info_path, "w") as f: |
| json.dump(folder_info, f, indent=2) |
| with open(folder_info_path, "r") as f: |
| folder_info = json.load(f) |
| for cat_name, cat_info in folder_info.items(): |
| if not cat_info.get('enable', True): |
| continue |
| cat_title = cat_info['title'] |
| cat_folder = cat_info['folder_path'] |
| models = [] |
| model_info_path = f"{base_path}/{cat_folder}/model_info.json" |
| if not os.path.exists(model_info_path): |
| create_model_info_from_files(base_path) |
| with open(model_info_path, "r") as f: |
| models_info = json.load(f) |
| for char_name, info in models_info.items(): |
| if not info.get('enable', True): |
| continue |
| cache_key = f"{cat_folder}_{char_name}" |
| char_dir = f"{base_path}/{cat_folder}/{char_name}" |
| model_path = f"{char_dir}/{info['model_path']}" |
| cover_path = f"{char_dir}/{info['cover']}" |
| index_path = f"{char_dir}/{info['feature_retrieval_library']}" |
| |
| if os.path.exists(char_dir): |
| actual = os.listdir(char_dir) |
| if not os.path.exists(model_path): |
| pths = [f for f in actual if f.endswith('.pth')] |
| if pths: |
| model_path = f"{char_dir}/{pths[0]}" |
| if not os.path.exists(cover_path): |
| imgs = [f for f in actual if f.lower().endswith(('.png','.jpg','.jpeg'))] |
| if imgs: |
| cover_path = f"{char_dir}/{imgs[0]}" |
| if not os.path.exists(index_path): |
| idxs = [f for f in actual if f.endswith('.index')] |
| if idxs: |
| index_path = f"{char_dir}/{idxs[0]}" |
| if not os.path.exists(model_path): |
| print(f"β Skipping {char_name} - model not found") |
| continue |
| if cache_key in model_cache: |
| tgt_sr, net_g, vc, if_f0, version, idx_path = model_cache[cache_key] |
| else: |
| try: |
| print(f"β³ Loading {char_name}...") |
| cpt = torch.load(model_path, map_location="cpu") |
| tgt_sr = cpt["config"][-1] |
| cpt["config"][-3] = cpt["weight"]["emb_g.weight"].shape[0] |
| if_f0 = cpt.get("f0", 1) |
| version = cpt.get("version", "v1") |
| if version == "v1": |
| if if_f0 == 1: |
| net_g = SynthesizerTrnMs256NSFsid(*cpt["config"], is_half=config.is_half) |
| else: |
| net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"]) |
| else: |
| if if_f0 == 1: |
| net_g = SynthesizerTrnMs768NSFsid(*cpt["config"], is_half=config.is_half) |
| else: |
| net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"]) |
| if hasattr(net_g, "enc_q"): |
| del net_g.enc_q |
| net_g.load_state_dict(cpt["weight"], strict=False) |
| net_g.eval().to('cpu') |
| vc = VC(tgt_sr, config) |
| model_cache[cache_key] = (tgt_sr, net_g, vc, if_f0, version, index_path) |
| except Exception as e: |
| print(f"β Error {char_name}: {e}") |
| continue |
| vc_func = create_vc_fn(cache_key, tgt_sr, net_g, vc, if_f0, version, index_path) |
| vc_fn_map[cache_key] = vc_func |
| models.append(( |
| char_name, info['title'], info['author'], |
| cover_path, version, cache_key |
| )) |
| if models: |
| categories.append([cat_title, cat_folder, models]) |
| total = sum(len(m) for _, _, m in categories) |
| print(f"π― Total models loaded: {total}") |
| return categories |
|
|
| def load_hubert(): |
| global hubert_model, hubert_loaded |
| if hubert_loaded: |
| return |
| print("π§ Loading HuBERT...") |
| torch.serialization.add_safe_globals([Dictionary]) |
| models, _, _ = checkpoint_utils.load_model_ensemble_and_task(["hubert_base.pt"], suffix="") |
| hubert_model = models[0].to(config.device) |
| hubert_model = hubert_model.half() if config.is_half else hubert_model.float() |
| hubert_model.eval() |
| hubert_loaded = True |
| print("β
HuBERT ready") |
|
|
| |
| |
| |
| css = """ |
| :root { |
| --primary-600: #1299ff !important; |
| --accent-600: #1299ff !important; |
| } |
| .gradio-container, .gradio-container * { |
| --loader-color: #A2D2FF !important; |
| } |
| .ba-header-container { |
| border: 1.5px solid #e1e8f0; |
| border-radius: 12px; |
| padding: 20px 10px; |
| margin-bottom: 12px; |
| background: white; |
| text-align: center; |
| } |
| .ba-header-container h1 { |
| color: #1299ff !important; |
| font-weight: 700 !important; |
| font-size: 42px !important; |
| margin: 0; |
| } |
| .status-container { |
| border: 1.5px solid #e1e8f0; |
| border-radius: 12px; |
| padding: 15px 22px; |
| margin-bottom: 20px; |
| background: white; |
| } |
| .status-title { |
| color: #1299ff !important; |
| font-weight: 800; |
| font-size: 16px; |
| margin-bottom: 8px; |
| } |
| .text-green-bold { color: #28a745 !important; font-weight: 900 !important; } |
| .text-blue-status { color: #1299ff !important; } |
| .slim-card { max-width: 480px; margin: 0 auto; background: transparent; padding: 10px; } |
| .tabs > .tab-nav { display: flex !important; overflow-x: auto !important; white-space: nowrap !important; } |
| .scroll-box { height: 280px; 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 #1299ff !important; text-align: left !important; padding: 8px !important; font-size: 12px !important; margin-bottom: 4px !important; width: 100%; color: #4a5568 !important; } |
| .warning-card { background: #fff9f0; border: 2px dashed #f5a623; 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: #1299ff !important; color: white !important; font-weight: 700 !important; border-radius: 12px !important; height: 45px !important; width: 100%; border: none !important; cursor: pointer; } |
| .info-header-custom { background: #1299ff !important; color: white !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 #1299ff; 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 #e1e8f0; } |
| .accordion-custom { margin-top: 12px; } |
| input[type="range"] { accent-color: #1299ff !important; } |
| """ |
|
|
| |
| def get_random_jp(): |
| return random.choice(["γγγ«γ‘γ―οΌ", "γε
ζ°γ§γγοΌ", "ε
ηγγη²γζ§γ§γοΌ", "ε€§ε₯½γγ γοΌ", "γΎγζζ₯γγ", "γγ£γγΌοΌ", "γγγγ§γγοΌ"]) |
|
|
| def char_info_html(char_name, title, author, version): |
| return f""" |
| <div class="info-content-area" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 15px; background: white; border: 1px solid #f0f4f8; border-top: none; border-radius: 0 0 10px 10px; max-height: 300px; overflow-y: auto;"> |
| <div style="border: 1px solid #f0f4f8; border-radius: 12px; padding: 10px; border-left: 5px solid #1299ff; background: #fff;"> |
| <div style="font-weight: 800; color: #2d3748; font-size: 14px; margin-bottom: 2px;">{char_name}</div> |
| <div style="color: #a0aec0; font-size: 11px;">{title}</div> |
| <div style="color: #a0aec0; font-size: 10px;">{author} β’ {version}</div> |
| </div> |
| </div> |
| """ |
|
|
| if __name__ == '__main__': |
| load_hubert() |
| categories = load_model() |
| total_models = sum(len(m) for _, _, m in categories) |
|
|
| with gr.Blocks(css=css) as demo: |
| with gr.Column(elem_classes="slim-card"): |
| |
| gr.HTML(""" |
| <div class="ba-header-container"> |
| <h1>Library Anime</h1> |
| <p>π Blue Archive - Real-time Voice Conversion π</p> |
| </div> |
| <div class="status-container"> |
| <div class="status-title">System Status</div> |
| <div class="status-item"><span style="color:#4a5568">Model :</span> <span class="text-green-bold"> LOADED β
</span></div> |
| <div class="status-item"><span style="color:#4a5568">Total Characters :</span> <span class="text-blue-status"> {total_models}</span></div> |
| </div> |
| """) |
| |
| |
| with gr.Tabs(elem_classes="tabs"): |
| for cat_title, cat_folder, models in categories: |
| with gr.Tab(f"{cat_title}"): |
| selected_state = gr.State(None) |
| cover_img = gr.Image(label="Character Cover", interactive=False, height=200, visible=True) |
| selected_name_display = gr.Markdown("π *Silakan pilih karakter...*") |
| |
| |
| gr.Markdown("### π Select Character") |
| with gr.Column(elem_classes="scroll-box"): |
| for (char_name, title, author, cover_path, version, cache_key) in models: |
| btn = gr.Button(f"π€ {char_name}", elem_classes="char-btn") |
| btn.click( |
| fn=lambda n=char_name, c=cover_path, k=cache_key, t=title, a=author, v=version: (n, c, k, t, a, v), |
| outputs=[selected_state] |
| ).then( |
| fn=lambda state: (state[1] if state else None, f"π Selected: **{state[0]}**" if state else "π *Silakan pilih karakter...*"), |
| inputs=[selected_state], |
| outputs=[cover_img, selected_name_display] |
| ) |
| |
| |
| txt_in = gr.TextArea(label="Input Text (Japanese / English)", value="γγγ«γ‘γ―γε
ηοΌ", lines=3) |
| gr.Button("π² RANDOM TEXT π²", elem_classes="jp-btn").click(get_random_jp, outputs=[txt_in]) |
| |
| |
| speed_slider = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Speed Audio") |
| pitch_slider = gr.Slider(-12, 12, value=12, step=1, label="Pitch Shift (semitones) β Recommended +12 for female, 0 for male") |
| |
| |
| with gr.Accordion("βοΈ Advanced Settings", open=False): |
| f0method = gr.Radio(label="F0 Method", choices=f0method_mode, value="rmvpe" if "rmvpe" in f0method_mode else "pm") |
| index_rate = gr.Slider(0, 1, value=0.75, label="Index Rate") |
| filter_radius = gr.Slider(0, 7, value=7, step=1, label="Filter Radius") |
| resample_sr = gr.Slider(0, 48000, value=0, label="Resample SR (0 = none)") |
| rms_mix_rate = gr.Slider(0, 1, value=0.76, label="Volume Mix") |
| protect = gr.Slider(0, 0.5, value=0.33, label="Voice Protect") |
| |
| |
| gen_btn = gr.Button("π GENERATE VOICE π", elem_classes="gen-btn") |
| status_log = gr.Textbox(label="Status", interactive=False) |
| audio_output = gr.Audio(label="Voice Output") |
| |
| |
| gr.HTML(""" |
| <div class="gif-container"> |
| <img src="https://huggingface.co/spaces/Plana-Archive/MOE-TTS/resolve/main/kurumi-tokisaki.gif" alt="Kurumi GIF"> |
| </div> |
| """) |
| |
| |
| gr.HTML(""" |
| <button onclick="document.getElementById('char-info-area').style.display = (document.getElementById('char-info-area').style.display === 'none') ? 'grid' : 'none';" class="info-header-custom"> |
| π Character Information π |
| </button> |
| """) |
| char_info_html_placeholder = gr.HTML('<div id="char-info-area" style="display: none;">Select a character first</div>') |
| |
| |
| def update_char_info(state): |
| if state is None: |
| return '<div id="char-info-area" style="display: none;">No character selected</div>' |
| name, cover, key, title, author, version = state |
| return char_info_html(name, title, author, version) |
| |
| selected_state.change( |
| fn=update_char_info, |
| inputs=[selected_state], |
| outputs=[char_info_html_placeholder] |
| ) |
| |
| |
| async def generate_voice(state, text, pitch, speed, f0m, idx_r, filt_r, resamp, rms, prot): |
| if state is None: |
| return "β Please select a character first", None |
| name, cover, key, title, author, version = state |
| vc_fn = vc_fn_map.get(key) |
| if vc_fn is None: |
| return "β Character function not found", None |
| if not text.strip(): |
| return "β Please enter some text", None |
| last_status, last_audio = None, None |
| async for status, audio in vc_fn( |
| vc_audio_mode="TTS Audio", |
| vc_input=None, |
| vc_upload=None, |
| tts_text=text, |
| f0_up_key=pitch, |
| f0_method=f0m, |
| index_rate=idx_r, |
| filter_radius=filt_r, |
| resample_sr=resamp, |
| rms_mix_rate=rms, |
| protect=prot, |
| speed=speed |
| ): |
| last_status, last_audio = status, audio |
| return last_status, last_audio |
| |
| gen_btn.click( |
| fn=generate_voice, |
| inputs=[selected_state, txt_in, pitch_slider, speed_slider, f0method, index_rate, filter_radius, resample_sr, rms_mix_rate, protect], |
| outputs=[status_log, audio_output] |
| ) |
| |
| gr.HTML("""<div class="credit-footer">π₯οΈ CREATED BY MUTSUMI π₯οΈ</div>""") |
| |
| |
| demo.queue().launch( |
| server_name="0.0.0.0" if os.getenv('SPACE_ID') else "127.0.0.1", |
| server_port=7860, |
| show_api=False |
| ) |