Spaces:
Runtime error
Runtime error
import sys | |
from pathlib import Path | |
import os | |
import torch | |
import openvino as ov | |
import gradio as gr | |
import langid | |
import ipywidgets as widgets | |
from IPython.display import Audio | |
# from openvoice.api import BaseSpeakerTTS, ToneColorConverter, OpenVoiceBaseClass | |
# import openvoice.se_extractor as se_extractor | |
import nncf | |
import subprocess | |
# Clone the repo and set up the environment | |
repo_dir = Path("OpenVoice") | |
if not repo_dir.exists(): | |
subprocess.run(["git", "clone", "https://github.com/myshell-ai/OpenVoice"]) | |
orig_english_path = Path("OpenVoice/openvoice/text/_orig_english.py") | |
english_path = Path("OpenVoice/openvoice/text/english.py") | |
english_path.rename(orig_english_path) | |
with orig_english_path.open("r") as f: | |
data = f.read() | |
data = data.replace("unidecode", "anyascii") | |
with english_path.open("w") as out_f: | |
out_f.write(data) | |
sys.path.append(str(repo_dir)) | |
# Install the required packages | |
# %pip install -q "librosa>=0.8.1" "wavmark>=0.0.3" "faster-whisper>=0.9.0" "pydub>=0.25.1" "whisper-timestamped>=1.14.2" "tqdm" "inflect>=7.0.0" "eng_to_ipa>=0.0.2" "pypinyin>=0.50.0" \ | |
# "cn2an>=0.5.22" "jieba>=0.42.1" "langid>=1.1.6" "gradio>=4.15" "ipywebrtc" "anyascii" "openvino>=2023.3" "torch>=2.1" "nncf>=2.11.0" | |
from openvoice.api import BaseSpeakerTTS, ToneColorConverter, OpenVoiceBaseClass | |
import openvoice.se_extractor as se_extractor | |
packages = [ | |
"librosa>=0.8.1", | |
"wavmark>=0.0.3", | |
"faster-whisper>=0.9.0", | |
"pydub>=0.25.1", | |
"whisper-timestamped>=1.14.2", | |
"tqdm", | |
"inflect>=7.0.0", | |
"eng_to_ipa>=0.0.2", | |
"pypinyin>=0.50.0", | |
"ipywidgets" | |
] | |
subprocess.run(["pip", "install"] + packages, check=True) | |
core = ov.Core() | |
CKPT_BASE_PATH = "checkpoints" | |
en_suffix = f"{CKPT_BASE_PATH}/base_speakers/EN" | |
zh_suffix = f"{CKPT_BASE_PATH}/base_speakers/ZH" | |
converter_suffix = f"{CKPT_BASE_PATH}/converter" | |
enable_chinese_lang = False | |
def download_from_hf_hub(filename, local_dir="./"): | |
from huggingface_hub import hf_hub_download | |
os.makedirs(local_dir, exist_ok=True) | |
hf_hub_download(repo_id="myshell-ai/OpenVoice", filename=filename, local_dir=local_dir) | |
download_from_hf_hub(f"{converter_suffix}/checkpoint.pth") | |
download_from_hf_hub(f"{converter_suffix}/config.json") | |
download_from_hf_hub(f"{en_suffix}/checkpoint.pth") | |
download_from_hf_hub(f"{en_suffix}/config.json") | |
download_from_hf_hub(f"{en_suffix}/en_default_se.pth") | |
download_from_hf_hub(f"{en_suffix}/en_style_se.pth") | |
if enable_chinese_lang: | |
download_from_hf_hub(f"{zh_suffix}/checkpoint.pth") | |
download_from_hf_hub(f"{zh_suffix}/config.json") | |
download_from_hf_hub(f"{zh_suffix}/zh_default_se.pth") | |
pt_device = "cpu" | |
en_base_speaker_tts = BaseSpeakerTTS(f"{en_suffix}/config.json", device=pt_device) | |
en_base_speaker_tts.load_ckpt(f"{en_suffix}/checkpoint.pth") | |
tone_color_converter = ToneColorConverter(f"{converter_suffix}/config.json", device=pt_device) | |
tone_color_converter.load_ckpt(f"{converter_suffix}/checkpoint.pth") | |
if enable_chinese_lang: | |
zh_base_speaker_tts = BaseSpeakerTTS(f"{zh_suffix}/config.json", device=pt_device) | |
zh_base_speaker_tts.load_ckpt(f"{zh_suffix}/checkpoint.pth") | |
else: | |
zh_base_speaker_tts = None | |
class OVOpenVoiceBase(torch.nn.Module): | |
def __init__(self, voice_model: OpenVoiceBaseClass): | |
super().__init__() | |
self.voice_model = voice_model | |
for par in voice_model.model.parameters(): | |
par.requires_grad = False | |
class OVOpenVoiceTTS(OVOpenVoiceBase): | |
def get_example_input(self): | |
stn_tst = self.voice_model.get_text("this is original text", self.voice_model.hps, False) | |
x_tst = stn_tst.unsqueeze(0) | |
x_tst_lengths = torch.LongTensor([stn_tst.size(0)]) | |
speaker_id = torch.LongTensor([1]) | |
noise_scale = torch.tensor(0.667) | |
length_scale = torch.tensor(1.0) | |
noise_scale_w = torch.tensor(0.6) | |
return ( | |
x_tst, | |
x_tst_lengths, | |
speaker_id, | |
noise_scale, | |
length_scale, | |
noise_scale_w, | |
) | |
def forward(self, x, x_lengths, sid, noise_scale, length_scale, noise_scale_w): | |
return self.voice_model.model.infer(x, x_lengths, sid, noise_scale, length_scale, noise_scale_w) | |
class OVOpenVoiceConverter(OVOpenVoiceBase): | |
def get_example_input(self): | |
y = torch.randn([1, 513, 238], dtype=torch.float32) | |
y_lengths = torch.LongTensor([y.size(-1)]) | |
target_se = torch.randn(*(1, 256, 1)) | |
source_se = torch.randn(*(1, 256, 1)) | |
tau = torch.tensor(0.3) | |
return (y, y_lengths, source_se, target_se, tau) | |
def forward(self, y, y_lengths, sid_src, sid_tgt, tau): | |
return self.voice_model.model.voice_conversion(y, y_lengths, sid_src, sid_tgt, tau) | |
IRS_PATH = "openvino_irs/" | |
EN_TTS_IR = f"{IRS_PATH}/openvoice_en_tts.xml" | |
ZH_TTS_IR = f"{IRS_PATH}/openvoice_zh_tts.xml" | |
VOICE_CONVERTER_IR = f"{IRS_PATH}/openvoice_tone_conversion.xml" | |
paths = [EN_TTS_IR, VOICE_CONVERTER_IR] | |
models = [ | |
OVOpenVoiceTTS(en_base_speaker_tts), | |
OVOpenVoiceConverter(tone_color_converter), | |
] | |
if enable_chinese_lang: | |
models.append(OVOpenVoiceTTS(zh_base_speaker_tts)) | |
paths.append(ZH_TTS_IR) | |
ov_models = [] | |
for model, path in zip(models, paths): | |
if not os.path.exists(path): | |
ov_model = ov.convert_model(model, example_input=model.get_example_input()) | |
ov_model = nncf.compress_weights(ov_model) | |
ov.save_model(ov_model, path) | |
else: | |
ov_model = core.read_model(path) | |
ov_models.append(ov_model) | |
ov_en_tts, ov_voice_conversion = ov_models[:2] | |
if enable_chinese_lang: | |
ov_zh_tts = ov_models[-1] | |
REFERENCE_VOICES_PATH = f"{repo_dir}/resources/" | |
reference_speakers = [ | |
*[path for path in os.listdir(REFERENCE_VOICES_PATH) if os.path.splitext(path)[-1] == ".mp3"], | |
"record_manually", | |
"load_manually", | |
] | |
ref_speaker = widgets.Dropdown( | |
options=reference_speakers, | |
value=reference_speakers[0], | |
description="reference voice from which tone color will be copied", | |
disabled=False, | |
) | |
ref_speaker | |
OUTPUT_DIR = "outputs/" | |
os.makedirs(OUTPUT_DIR, exist_ok=True) | |
ref_speaker_path = f"{REFERENCE_VOICES_PATH}/{ref_speaker.value}" | |
allowed_audio_types = ".mp4,.mp3,.wav,.wma,.aac,.m4a,.m4b,.webm" | |
if ref_speaker.value == "record_manually": | |
ref_speaker_path = f"{OUTPUT_DIR}/custom_example_sample.webm" | |
from ipywebrtc import AudioRecorder, CameraStream | |
camera = CameraStream(constraints={"audio": True, "video": False}) | |
recorder = AudioRecorder(stream=camera, filename=ref_speaker_path, autosave=True) | |
display(recorder) | |
elif ref_speaker.value == "load_manually": | |
upload_ref = widgets.FileUpload( | |
accept=allowed_audio_types, | |
multiple=False, | |
description="Select audio with reference voice", | |
) | |
display(upload_ref) | |
def save_audio(voice_source: widgets.FileUpload, out_path: str): | |
with open(out_path, "wb") as output_file: | |
assert len(voice_source.value) > 0, "Please select audio file" | |
output_file.write(voice_source.value[0]["content"]) | |
en_source_default_se = torch.load(f"{en_suffix}/en_default_se.pth") | |
en_source_style_se = torch.load(f"{en_suffix}/en_style_se.pth") | |
zh_source_se = torch.load(f"{zh_suffix}/zh_default_se.pth") if enable_chinese_lang else None | |
target_se, audio_name = se_extractor.get_se(ref_speaker_path, tone_color_converter, target_dir=OUTPUT_DIR, vad=True) | |
def get_pathched_infer(ov_model: ov.Model, device: str) -> callable: | |
compiled_model = core.compile_model(ov_model, device) | |
def infer_impl(x, x_lengths, sid, noise_scale, length_scale, noise_scale_w): | |
ov_output = compiled_model((x, x_lengths, sid, noise_scale, length_scale, noise_scale_w)) | |
return (torch.tensor(ov_output[0]),) | |
return infer_impl | |
def get_patched_voice_conversion(ov_model: ov.Model, device: str) -> callable: | |
compiled_model = core.compile_model(ov_model, device) | |
def voice_conversion_impl(y, y_lengths, sid_src, sid_tgt, tau): | |
ov_output = compiled_model((y, y_lengths, sid_src, sid_tgt, tau)) | |
return (torch.tensor(ov_output[0]),) | |
return voice_conversion_impl | |
core = ov.Core() | |
device = widgets.Dropdown( | |
options=core.available_devices + ["AUTO"], | |
value="AUTO", | |
description="Device:", | |
disabled=False, | |
) | |
device | |
en_base_speaker_tts.model.infer = get_pathched_infer(ov_en_tts, device.value) | |
tone_color_converter.model.voice_conversion = get_patched_voice_conversion(ov_voice_conversion, device.value) | |
if enable_chinese_lang: | |
zh_base_speaker_tts.model.infer = get_pathched_infer(ov_zh_tts, device.value) | |
supported_languages = ["zh", "en"] | |
def build_predict( | |
output_dir, | |
tone_color_converter, | |
en_tts_model, | |
zh_tts_model, | |
en_source_default_se, | |
en_source_style_se, | |
zh_source_se, | |
supported_languages, | |
): | |
def predict( | |
input_text, | |
reference_audio, | |
speaker, | |
noise_scale=0.667, | |
length_scale=1.0, | |
noise_scale_w=0.8, | |
tone_color=False, | |
): | |
if reference_audio: | |
ref_audio_path = f"{output_dir}/input_audio.wav" | |
save_audio(reference_audio, ref_audio_path) | |
target_se, _ = se_extractor.get_se(ref_audio_path, tone_color_converter, target_dir=output_dir, vad=True) | |
else: | |
if speaker == "record_manually": | |
raise ValueError("Manual recording is not implemented in this example.") | |
elif speaker == "load_manually": | |
raise ValueError("Loading a manual audio file is not implemented in this example.") | |
else: | |
ref_audio_path = f"{REFERENCE_VOICES_PATH}/{speaker}" | |
target_se, _ = se_extractor.get_se(ref_audio_path, tone_color_converter, target_dir=output_dir, vad=True) | |
lang = langid.classify(input_text)[0] | |
if lang not in supported_languages: | |
return f"Unsupported language: {lang}" | |
tts_model = en_tts_model if lang == "en" else zh_tts_model | |
stn_tst = tts_model.get_text(input_text, tts_model.hps, False) | |
x_tst = stn_tst.unsqueeze(0) | |
x_tst_lengths = torch.LongTensor([stn_tst.size(0)]) | |
speaker_id = torch.LongTensor([1]) | |
noise_scale = torch.tensor(noise_scale) | |
length_scale = torch.tensor(length_scale) | |
noise_scale_w = torch.tensor(noise_scale_w) | |
with torch.no_grad(): | |
audio = tts_model.model.infer(x_tst, x_tst_lengths, speaker_id, noise_scale, length_scale, noise_scale_w)[0] | |
if tone_color: | |
source_se = en_source_style_se if lang == "en" else zh_source_se | |
audio = tone_color_converter.model.voice_conversion(audio, x_tst_lengths, source_se, target_se, torch.tensor(0.3))[0] | |
audio = audio.squeeze().cpu().numpy() | |
output_path = f"{output_dir}/output_audio.wav" | |
Audio(audio, rate=tts_model.hps.data.sampling_rate).save(output_path) | |
return output_path | |
return predict | |
OUTPUT_DIR = "output_audio" | |
os.makedirs(OUTPUT_DIR, exist_ok=True) | |
predict_fn = build_predict( | |
OUTPUT_DIR, | |
tone_color_converter, | |
en_base_speaker_tts, | |
zh_base_speaker_tts, | |
en_source_default_se, | |
en_source_style_se, | |
zh_source_se, | |
supported_languages, | |
) | |
def gradio_interface(): | |
input_text = gr.Textbox(lines=2, placeholder="Enter text here...") | |
reference_audio = gr.Audio(type="filepath", label="Reference Audio") | |
speaker = gr.Dropdown(choices=reference_speakers, value="record_manually", label="Select Speaker") | |
noise_scale = gr.Slider(minimum=0.1, maximum=1.0, value=0.667, label="Noise Scale") | |
length_scale = gr.Slider(minimum=0.1, maximum=2.0, value=1.0, label="Length Scale") | |
noise_scale_w = gr.Slider(minimum=0.1, maximum=1.0, value=0.8, label="Noise Scale W") | |
tone_color = gr.Checkbox(value=False, label="Enable Tone Color Conversion") | |
gr.Interface( | |
fn=predict_fn, | |
inputs=[input_text, reference_audio, speaker, noise_scale, length_scale, noise_scale_w, tone_color], | |
outputs=gr.Audio(type="filepath", label="Generated Audio"), | |
title="Speech Generation and Tone Conversion", | |
description="Generate speech and convert tone using the OpenVoice model.", | |
).launch() | |
# end | |