Spaces:
Running on Zero
Running on Zero
File size: 9,646 Bytes
faf23d3 8c2b5ab 6eb7e7c fccdbc5 faf23d3 6eb7e7c c4ae071 fa8f957 faf23d3 fa8f957 6eb7e7c b4f59a5 6eb7e7c 8c2b5ab 3d7bdf6 8c2b5ab faf23d3 8c2b5ab 956d5cb 3881111 956d5cb 3881111 b4466cb 8c2b5ab b4466cb 8c2b5ab b4466cb 8c2b5ab b4466cb 6eb7e7c b4466cb 3881111 b4466cb 6eb7e7c 8c2b5ab 3034bda 8c2b5ab 3034bda d662d8e 3034bda db8290f 956d5cb 3034bda 6eb7e7c 3034bda d662d8e 3034bda d662d8e 3034bda d662d8e 3034bda 3d7bdf6 d662d8e 8c2b5ab 3034bda 85e34fd | 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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | import spaces # imported first, as ZeroGPU requires
import torch
import os
os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
import gradio as gr
import traceback
import gc
import numpy as np
import librosa
from pydub import AudioSegment
from pydub.effects import normalize
from huggingface_hub import snapshot_download
from tts.infer_cli import MegaTTS3DiTInfer, convert_to_wav, cut_wav
from pyharp.core import ModelCard, build_endpoint
from pyharp.media.audio import save_audio
from pyharp.tags import Channels, SampleRate, Subcategory
from audiotools import AudioSignal
model_card = ModelCard(
name="MegaTTS 3",
description=(
"MegaTTS 3 is a text-to-speech model trained by ByteDance with "
"exceptional voice cloning capabilities, as 24 kHz mono speech. Please use this Space "
"responsibly and do not abuse it! This demo is for research and "
"educational purposes only. Repository: "
"https://github.com/bytedance/MegaTTS3 Paper: "
"https://arxiv.org/abs/2502.18924"
),
author="Ziyue Jiang et al.",
tags=[Subcategory.TEXT_TO_SPEECH, SampleRate(24000), Channels(1), 'voice-cloning', 'domain:speech']
)
def download_weights():
"""Download model weights from HuggingFace if not already present."""
repo_id = "mrfakename/MegaTTS3-VoiceCloning"
weights_dir = "checkpoints"
if not os.path.exists(weights_dir):
print("Downloading model weights from HuggingFace...")
snapshot_download(
repo_id=repo_id,
revision="b3a2c297eea319931ee985751f117f866085d0c5",
local_dir=weights_dir,
local_dir_use_symlinks=False
)
print("Model weights downloaded successfully!")
else:
print("Model weights already exist.")
return weights_dir
# pyharp runs process_fn in a worker, which imports this file as __mp_main__ and starts with the
# server, so the weights are downloaded and the model built there alone
if __name__ != "__main__":
download_weights()
print("Initializing MegaTTS3 model...")
infer_pipe = MegaTTS3DiTInfer()
print("Model loaded successfully!")
# PyHARP stops a job that runs past its timeout_s, 15 minutes by default, so a request whose
# reservation would exceed that is refused rather than started. A shorter one is left to
# ZeroGPU, which admits it when the caller's own allowance covers it.
MAX_GPU_SECONDS = 900
# The text is spoken in one pass the GPU holds whole, so it is limited to the longest measured to
# fit. The cost grows with its length and with the timesteps
MAX_TEXT_CHARACTERS = 2500
def gpu_seconds(wav_path, inp_text, infer_timestep, p_w, t_w):
# Measured at about 15 s for 70 characters and 35 s for 550, both at 32 timesteps, padded
return 15 + 0.002 * len(inp_text) * int(infer_timestep)
@spaces.GPU(duration=gpu_seconds)
def generate_speech(wav_path, inp_text, infer_timestep, p_w, t_w):
with open(wav_path, 'rb') as file:
file_content = file.read()
try:
print(f"Generating speech with: {inp_text}...")
resource_context = infer_pipe.preprocess(file_content)
fs, wav = infer_pipe.forward(resource_context, inp_text, time_step=infer_timestep, p_w=p_w, t_w=t_w)
except Exception as e:
traceback.print_exc()
raise gr.Error(f"Speech generation failed: {e}") from e
finally:
cleanup_memory()
return fs, wav
def process_fn(inp_audio, inp_text, infer_timestep, p_w, t_w):
# Checked and prepared before the GPU is reserved, so a request that cannot run spends none of
# the caller's ZeroGPU allowance. A failure is raised as gr.Error, since HARP never sees a
# gr.Warning.
if not inp_audio:
raise gr.Error("Provide a recording of the voice to clone.")
if not (inp_text or "").strip():
raise gr.Error("Enter the text for the cloned voice to speak.")
if len(inp_text) > MAX_TEXT_CHARACTERS:
raise gr.Error(
f"The text is {len(inp_text)} characters long. Shorten it to {MAX_TEXT_CHARACTERS} "
"or fewer."
)
if gpu_seconds(None, inp_text, infer_timestep, p_w, t_w) > MAX_GPU_SECONDS:
raise gr.Error(
"This much text at this many timesteps needs more GPU time than one request may "
"reserve. Shorten the text, or use fewer timesteps."
)
try:
wav_path = preprocess_audio_robust(inp_audio)
cut_wav(wav_path, max_len=10)
except Exception as e:
raise gr.Error(f"Could not use the reference recording: {e}") from e
fs, wav = generate_speech(wav_path, inp_text, int(infer_timestep), p_w, t_w)
sig = AudioSignal(wav, sample_rate=fs)
return save_audio(sig)
def cleanup_memory():
"""Clean up GPU and system memory."""
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.synchronize()
def preprocess_audio_robust(audio_path, target_sr=22050, max_duration=30):
"""Robustly preprocess audio to prevent CUDA errors."""
try:
# Load with pydub for robust format handling
audio = AudioSegment.from_file(audio_path)
# Convert to mono if stereo
if audio.channels > 1:
audio = audio.set_channels(1)
# Limit duration to prevent memory issues
if len(audio) > max_duration * 1000: # pydub uses milliseconds
audio = audio[:max_duration * 1000]
# Normalize audio to prevent clipping
audio = normalize(audio)
# Convert to target sample rate
audio = audio.set_frame_rate(target_sr)
# Export to temporary WAV file with specific parameters
temp_path = audio_path.replace(os.path.splitext(audio_path)[1], '_processed.wav')
audio.export(
temp_path,
format="wav",
parameters=["-acodec", "pcm_s16le", "-ac", "1", "-ar", str(target_sr)]
)
# Validate the audio with librosa
wav, sr = librosa.load(temp_path, sr=target_sr, mono=True)
# Check for invalid values
if np.any(np.isnan(wav)) or np.any(np.isinf(wav)):
raise ValueError("Audio contains NaN or infinite values")
# Ensure reasonable amplitude range
if np.max(np.abs(wav)) < 1e-6:
raise ValueError("Audio signal is too quiet")
# Re-save the validated audio
import soundfile as sf
sf.write(temp_path, wav, sr)
return temp_path
except Exception as e:
print(f"Audio preprocessing failed: {e}")
raise ValueError(f"Failed to process audio: {str(e)}")
if __name__ == "__main__":
with gr.Blocks(title="MegaTTS3 Voice Cloning") as demo:
gr.Markdown("# MegaTTS 3 Voice Cloning")
# gr.Markdown("MegaTTS 3 is a text-to-speech model trained by ByteDance with exceptional voice cloning capabilities. The original authors did not release the WavVAE encoder, so voice cloning was not publicly available; however, thanks to [@ACoderPassBy](https://modelscope.cn/models/ACoderPassBy/MegaTTS-SFT)'s WavVAE encoder, we can now clone voices with MegaTTS 3!")
# gr.Markdown("This is by no means the best voice cloning solution, but it works pretty well for some specific use-cases. Try out multiple and see which one works best for you.")
# gr.Markdown("**Please use this Space responsibly and do not abuse it!** This demo is for research and educational purposes only!")
# gr.Markdown("h/t to MysteryShack on Discord for the info about the unofficial WavVAE encoder!")
# gr.Markdown("Upload a reference audio clip and enter text to generate speech with the cloned voice.")
reference_audio = gr.Audio(
label="Reference Audio",
type="filepath"
).set_info((
"A clean recording of the voice to clone. A few seconds of speech "
"is enough, and background noise or room tone will be cloned along "
"with the voice."
))
text_input = gr.Textbox(
label="Text to Generate",
placeholder="Enter the text you want to synthesize...",
info="Text for the cloned voice to speak, up to 2500 characters."
)
infer_timestep = gr.Slider(
label="Inference Timesteps",
value=32,
minimum=1,
maximum=100,
step=1,
info=(
"Higher values (e.g., 50+) give clearer audio but take longer to "
"generate."
)
)
p_w = gr.Slider(
label="Intelligibility Weight",
value=1.4,
minimum=0.1,
maximum=5.0,
step=0.1,
info="Increase this if the speech sounds mumbled or unclear."
)
t_w = gr.Slider(
label="Similarity Weight",
value=3.0,
minimum=0.1,
maximum=10.0,
step=0.1,
info="Increase this for a stronger likeness to the reference voice."
)
output_audio = gr.Audio(type="filepath", label="Synthesized Speech").set_info((
"The text spoken in the reference voice."
))
_ = build_endpoint(
model_card=model_card,
input_components=[reference_audio, text_input, infer_timestep, p_w, t_w],
output_components=[output_audio],
process_fn=process_fn
)
demo.queue().launch(pwa=True, show_error=True, server_name='0.0.0.0', server_port=7860) |