File size: 5,062 Bytes
ad3a84d 8b96d5e a83d850 8b96d5e 9131697 8b96d5e 9131697 ad3a84d 8b96d5e ad3a84d 8b96d5e 9131697 8b96d5e 9131697 8b96d5e 94a096e 9131697 94a096e ad3a84d 8b96d5e 9131697 8b96d5e ad3a84d a83d850 94a096e a093279 a83d850 a093279 94a096e a093279 a83d850 a093279 a83d850 a093279 a83d850 a093279 a83d850 94a096e 8b96d5e a83d850 8b96d5e 9131697 8b96d5e cddc8cc 8b96d5e ad3a84d 8b96d5e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | import os
import uuid
from pathlib import Path
import numpy as np
import gradio as gr
import soundfile as sf
import spaces
from voxcpm import VoxCPM
MODEL_ID = "openbmb/VoxCPM2"
OUTPUT_DIR = Path("outputs")
OUTPUT_DIR.mkdir(exist_ok=True)
_model = None
def get_model():
global _model
if _model is None:
try:
_model = VoxCPM.from_pretrained(
MODEL_ID,
load_denoiser=False,
optimize=False,
)
except TypeError:
_model = VoxCPM.from_pretrained(
MODEL_ID,
load_denoiser=False,
)
return _model
def clean_text(text: str) -> str:
return (
(text or "")
.replace("\u200b", "")
.replace("\u200c", "")
.replace("\u200d", "")
.replace("\ufeff", "")
.replace("\r\n", "\n")
.replace("\r", "\n")
.strip()
)
@spaces.GPU(duration=120)
def synthesize(target_text: str, reference_audio: str, reference_text: str):
target_text = clean_text(target_text)
reference_text = clean_text(reference_text)
if not target_text:
raise gr.Error("Target text is required.")
if not reference_audio:
raise gr.Error("Reference audio is required.")
if not reference_text:
raise gr.Error("Reference transcript is required.")
model = get_model()
sample_rate = model.tts_model.sample_rate
# VoxCPM2 Core Generation
wav = model.generate(
text=target_text,
prompt_wav_path=reference_audio,
prompt_text=reference_text,
reference_wav_path=reference_audio,
cfg_value=2.0,
inference_timesteps=10,
normalize=True,
denoise=False,
)
# Safe convert to numpy array
if hasattr(wav, "detach"):
wav = wav.detach().cpu().numpy()
wav = np.asarray(wav, dtype=np.float32).squeeze()
# 🔥 အစဦးပိုင်း ကလစ်သံ (Click/Pop Artifact) သီးသန့်ကို စစ်ထုတ်ဖျက်ဆီးမည့် Logic အသစ်
search_limit = int(0.35 * sample_rate) # ထိပ်ဆုံး 350ms အတွင်း ရှာဖွေပါမည်
if len(wav) > search_limit:
abs_wav = np.abs(wav[:search_limit])
max_val = np.max(abs_wav) if len(abs_wav) > 0 else 1.0
threshold = max(0.012, max_val * 0.05)
# ၁၀ မီလီစက္ကန့်စာ Frame ကွက်များဖြင့် စကားသံ အစစ်အမှန်ကို ရှာဖွေခြင်း
frame_len = int(0.01 * sample_rate)
start_idx = 0
for i in range(0, search_limit - (frame_len * 2), frame_len):
current_frame = abs_wav[i : i + frame_len]
# တစ်ချက်တည်းထွက်တဲ့ ကလစ်သံမဟုတ်ကြောင်း သေချာစေရန် နောက်ထပ် Frame ကိုပါ ပူးတွဲစစ်ဆေးပါသည်
next_frame = abs_wav[i + frame_len : i + (frame_len * 2)]
if np.mean(current_frame) > threshold and np.mean(next_frame) > threshold:
start_idx = i
break
if start_idx > 0:
# စကားသံ အစဦးပိုင်း ပြတ်မသွားစေရန် 5ms မျှ Cushion ချန်လှပ်၍ ညှပ်ထုတ်ပါသည်
cushion = int(0.005 * sample_rate)
start_idx = max(0, start_idx - cushion)
print(f"[Info] Successfully stripped initial pop/click artifact at index: {start_idx}")
wav = wav[start_idx:]
out_path = OUTPUT_DIR / f"voxcpm2_{uuid.uuid4().hex}.wav"
sf.write(str(out_path), wav, sample_rate)
return str(out_path)
with gr.Blocks(title="Myanmar VoxCPM2 Voice Clone") as demo:
gr.Markdown("## Myanmar VoxCPM2 Voice Clone")
target_text = gr.Textbox(
label="Target text",
lines=5,
placeholder="ဒီမှာ ပြောစေချင်တဲ့ မြန်မာစာသားကို ရိုက်ထည့်ပါ။",
)
reference_audio = gr.Audio(
label="Reference audio",
sources=["upload", "microphone"],
type="filepath",
)
reference_text = gr.Textbox(
label="Reference transcript",
lines=3,
placeholder="Reference audio ထဲမှာ ပြောထားတဲ့ စကားကို တိတိကျကျ ရိုက်ထည့်ပါ။",
)
generate_btn = gr.Button("Generate", variant="primary")
output_audio = gr.Audio(
label="Generated voice",
type="filepath",
)
generate_btn.click(
fn=synthesize,
inputs=[target_text, reference_audio, reference_text],
outputs=output_audio,
api_name="synthesize",
)
if __name__ == "__main__":
demo.queue(max_size=10).launch()
|