TUHs/CN-SambertPersonalTTS
0
1import subprocess2import random3import os4from pathlib import Path5import librosa6from scipy.io import wavfile7import numpy as np8import torch9import csv10import whisper11import gradio as gr12import soundfile as sf13 14os.system("pip install --upgrade Cython==0.29.35")15os.system("pip install pysptk --no-build-isolation")16os.system("pip install kantts -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html")17os.system("pip install tts-autolabel -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html")18 19import sox20 21def split_long_audio(model, filepaths, save_dir="data_dir", out_sr=44100):22 if isinstance(filepaths, str):23 filepaths = [filepaths]24 25 for file_idx, filepath in enumerate(filepaths):26 27 save_path = Path(save_dir)28 save_path.mkdir(exist_ok=True, parents=True)29 30 print(f"Transcribing file {file_idx}: '{filepath}' to segments...")31 result = model.transcribe(filepath, word_timestamps=True, task="transcribe", beam_size=5, best_of=5)32 segments = result['segments']33 34 wav, sr = librosa.load(filepath, sr=None, offset=0, duration=None, mono=True)35 wav, _ = librosa.effects.trim(wav, top_db=20)36 peak = np.abs(wav).max()37 if peak > 1.0:38 wav = 0.98 * wav / peak39 wav2 = librosa.resample(wav, orig_sr=sr, target_sr=out_sr)40 wav2 /= max(wav2.max(), -wav2.min())41 42 for i, seg in enumerate(segments):43 start_time = seg['start']44 end_time = seg['end']45 wav_seg = wav2[int(start_time * out_sr):int(end_time * out_sr)]46 wav_seg_name = f"{file_idx}_{i}.wav"47 out_fpath = save_path / wav_seg_name48 wavfile.write(out_fpath, rate=out_sr, data=(wav_seg * np.iinfo(np.int16).max).astype(np.int16))49 50device = 'cuda' if torch.cuda.is_available() else 'cpu'51whisper_size = "medium"52whisper_model = whisper.load_model(whisper_size).to(device)53 54from modelscope.tools import run_auto_label55 56from modelscope.models.audio.tts import SambertHifigan57from modelscope.pipelines import pipeline58from modelscope.utils.constant import Tasks59 60from modelscope.metainfo import Trainers61from modelscope.trainers import build_trainer62from modelscope.utils.audio.audio_utils import TtsTrainType63 64pretrained_model_id = 'damo/speech_personal_sambert-hifigan_nsf_tts_zh-cn_pretrain_16k'65 66dataset_id = "/home/user/app/output_training_data/"67pretrain_work_dir = "/home/user/app/pretrain_work_dir/"68 69 70def auto_label(Voicetoclone, VoiceMicrophone):71 if VoiceMicrophone is not None:72 audio = VoiceMicrophone73 else:74 audio = Voicetoclone75 76 try:77 split_long_audio(whisper_model, audio, "/home/user/app/test_wavs/")78 input_wav = "/home/user/app/test_wavs/"79 output_data = "/home/user/app/output_training_data/"80 ret, report = run_auto_label(input_wav=input_wav, work_dir=output_data, resource_revision="v1.0.7")81 82 except Exception as e:83 print(e)84 return "标注成功"85 86 87 88def train(train_step):89 try:90 91 train_info = {92 TtsTrainType.TRAIN_TYPE_SAMBERT: { # 配置训练AM(sambert)模型93 'train_steps': int(train_step / 20) * 20 + 2, # 训练多少个step94 'save_interval_steps': int(train_step / 20) * 20, # 每训练多少个step保存一次checkpoint95 'log_interval': int(train_step / 20) * 20 # 每训练多少个step打印一次训练日志96 }97 }98 99 kwargs = dict(100 model=pretrained_model_id, # 指定要finetune的模型101 model_revision = "v1.0.6",102 work_dir=pretrain_work_dir, # 指定临时工作目录103 train_dataset=dataset_id, # 指定数据集id104 train_type=train_info # 指定要训练类型及参数105 )106 107 trainer = build_trainer(Trainers.speech_kantts_trainer,108 default_args=kwargs)109 110 trainer.train()111 112 except Exception as e:113 print(e)114 115 return "训练完成"116 117 118# 保存模型119 120import shutil121 122import datetime123 124def save_model(worked_dir,dest_dir):125 worked_dir = "/home/user/app/pretrain_work_dir"126 dest_dir = "/home/user/app/trained_model"127 128 if os.listdir(worked_dir): 129 130 now = datetime.datetime.now()131 132 date_str = now.strftime("%Y%m%d%H%M%S")133 134 dest_folder = os.path.join(dest_dir, date_str)135 136 shutil.copytree(worked_dir, dest_folder)137 138 # List of files and directories to delete139 files_to_delete = [140 "tmp_voc",141 "tmp_am/ckpt/checkpoint_2400000.pth",142 "orig_model/description",143 "orig_model/.mdl",144 "orig_model/.msc",145 "orig_model/README.md",146 "orig_model/resource",147 "orig_model/description",148 "orig_model/basemodel_16k/sambert",149 "orig_model/basemodel_16k/speaker_embedding",150 "data/duration",151 "data/energy",152 "data/f0",153 "data/frame_energy",154 "data/frame_f0",155 "data/frame_uv",156 "data/mel",157 "data/raw_duration",158 "data/wav",159 "data/am_train.lst",160 "data/am_valid.lst",161 "data/badlist.txt",162 "data/raw_metafile.txt",163 "data/Script.xml",164 "data/train.lst",165 "data/valid.lst",166 "data/se/0_*"167 ]168 169 for item in files_to_delete:170 item_path = os.path.join(dest_folder, item)171 if os.path.exists(item_path):172 if os.path.isdir(item_path):173 shutil.rmtree(item_path)174 else:175 os.remove(item_path)176 177 shutil.rmtree("/home/user/app/output_training_data")178 shutil.rmtree("/home/user/app/pretrain_work_dir")179 shutil.rmtree("/home/user/app/test_wavs")180 181 os.mkdir("/home/user/app/output_training_data")182 os.mkdir("/home/user/app/pretrain_work_dir")183 os.mkdir("/home/user/app/test_wavs")184 185 return f"模型已成功保存为 {date_str}"186 else: 187 return "保存失败,模型已保存或已被清除"188 189 190import random191 192def infer(text):193 194 model_dir = "/home/user/app/pretrain_work_dir/"195 196 test_infer_abs = {197 'voice_name':198 'F7',199 'am_ckpt':200 os.path.join(model_dir, 'tmp_am', 'ckpt'),201 'am_config':202 os.path.join(model_dir, 'tmp_am', 'config.yaml'),203 'voc_ckpt':204 os.path.join(model_dir, 'orig_model', 'basemodel_16k', 'hifigan', 'ckpt'),205 'voc_config':206 os.path.join(model_dir, 'orig_model', 'basemodel_16k', 'hifigan',207 'config.yaml'),208 'audio_config':209 os.path.join(model_dir, 'data', 'audio_config.yaml'),210 'se_file':211 os.path.join(model_dir, 'data', 'se', 'se.npy')212 }213 kwargs = {'custom_ckpt': test_infer_abs}214 215 model_id = SambertHifigan(os.path.join(model_dir, "orig_model"), **kwargs)216 217 inference = pipeline(task=Tasks.text_to_speech, model=model_id)218 output = inference(input=text) 219 220 221 now = datetime.datetime.now()222 date_str = now.strftime("%Y%m%d%H%M%S")223 rand_num = random.randint(1000, 9999)224 filename = date_str + str(rand_num)225 226 227 with open(filename + "0.wav", mode='bx') as f:228 f.write(output["output_wav"])229 230 231 y, sr = librosa.load(filename + "0.wav")232 233 S = librosa.stft(y)234 235 noise = S[np.abs(S) < np.percentile(S, 95)]236 noise_mean, noise_std = np.mean(noise), np.std(noise)237 238 filter_ = np.ones_like(S)239 filter_[np.abs(S) < noise_mean + 2 * noise_std] = 0240 241 filtered_S = filter_ * S242 243 filtered_y = librosa.istft(filtered_S)244 245 sf.write(filename + "testfile.wav", filtered_y, sr)246 247 248 os.remove(filename + "0.wav")249 250 251 return filename + "testfile.wav"252 253 254def infer_custom(model_name, text, noise_level): 255 256 custom_model_dir = os.path.join("/home/user/app/trained_model/", model_name) 257 258 custom_infer_abs = {259 'voice_name':260 'F7', 261 'am_ckpt':262 os.path.join(custom_model_dir, 'tmp_am', 'ckpt'),263 'am_config':264 os.path.join(custom_model_dir, 'tmp_am', 'config.yaml'),265 'voc_ckpt':266 os.path.join(custom_model_dir, 'orig_model', 'basemodel_16k', 'hifigan', 'ckpt'),267 'voc_config':268 os.path.join(custom_model_dir, 'orig_model', 'basemodel_16k', 'hifigan',269 'config.yaml'),270 'audio_config':271 os.path.join(custom_model_dir, 'data', 'audio_config.yaml'),272 'se_file':273 os.path.join(custom_model_dir, 'data', 'se', 'se.npy')274 }275 kwargs = {'custom_ckpt': custom_infer_abs}276 277 model_id = SambertHifigan(os.path.join(custom_model_dir, "orig_model"), **kwargs)278 279 inference = pipeline(task=Tasks.text_to_speech, model=model_id)280 output = inference(input=text)281 282 283 now = datetime.datetime.now()284 date_str = now.strftime("%Y%m%d%H%M%S")285 rand_num = random.randint(1000, 9999)286 filename = date_str + str(rand_num)287 288 289 with open(filename + ".wav", mode='bx') as f:290 f.write(output["output_wav"])291 292 293 294 295 y, sr = librosa.load(filename + ".wav")296 297 S = librosa.stft(y)298 299 noise = S[np.abs(S) < np.percentile(S, 95)]300 noise_mean, noise_std = np.mean(noise), np.std(noise)301 302 filter_ = np.ones_like(S)303 filter_[np.abs(S) < noise_mean + noise_level * noise_std] = 0304 305 filtered_S = filter_ * S306 307 filtered_y = librosa.istft(filtered_S)308 309 sf.write(filename + "customfile.wav", filtered_y, sr)310 311 os.remove(filename + ".wav")312 313 return filename + "customfile.wav"314 315 316 317trained_model = "/home/user/app/trained_model/"318 319 320def update_model_dropdown(inp3):321 322 model_list = os.listdir(trained_model)323 324 return gr.Dropdown(choices=model_list, value=inp3)325 326 327def rename_model(old_name, new_name):328 329 if not os.path.isdir(os.path.join(trained_model, old_name)):330 return "模型名称不存在,请重新输入!"331 else:332 try:333 os.rename(os.path.join(trained_model, old_name), os.path.join(trained_model, new_name))334 return "模型重命名成功!"335 except OSError:336 return "新名称已经存在,请重新输入!"337 338 339# 清除训练缓存340def clear_cache(a):341 shutil.rmtree("/home/user/app/output_training_data")342 shutil.rmtree("/home/user/app/pretrain_work_dir")343 shutil.rmtree("/home/user/app/test_wavs")344 345 os.mkdir("/home/user/app/output_training_data")346 os.mkdir("/home/user/app/pretrain_work_dir")347 os.mkdir("/home/user/app/test_wavs")348 return "已清除缓存,请返回训练页面重新训练"349 350 351from textwrap import dedent352 353 354 355def FRCRN_De_Noise(noise_wav, noisemic_wav):356 357 if noisemic_wav is not None:358 noise_audio = noisemic_wav359 else:360 noise_audio = noise_wav361 362 ans = pipeline(363 Tasks.acoustic_noise_suppression,364 model='damo/speech_frcrn_ans_cirm_16k')365 366 now = datetime.datetime.now()367 date_str = now.strftime("%Y%m%d%H%M%S")368 rand_num = random.randint(1000, 9999)369 filename = date_str + str(rand_num)370 371 result = ans(372 noise_audio,373 output_path= filename + "AIdenoise.wav" )374 375 return filename + "AIdenoise.wav"376 377def Normal_De_Noise(noise_wav, noisemic_wav, noise_level):378 if noisemic_wav is not None:379 noise_audio = noisemic_wav380 else:381 noise_audio = noise_wav382 383 now = datetime.datetime.now()384 date_str = now.strftime("%Y%m%d%H%M%S")385 rand_num = random.randint(1000, 9999)386 filename = date_str + str(rand_num)387 388 389 y, sr = librosa.load(noise_audio)390 391 S = librosa.stft(y)392 393 noise = S[np.abs(S) < np.percentile(S, 95)]394 noise_mean, noise_std = np.mean(noise), np.std(noise)395 396 filter_ = np.ones_like(S)397 filter_[np.abs(S) < noise_mean + noise_level * noise_std] = 0398 399 filtered_S = filter_ * S400 401 filtered_y = librosa.istft(filtered_S)402 403 sf.write(filename + "denoise.wav", filtered_y, sr)404 405 return filename + "denoise.wav"406 407 408app = gr.Blocks()409 410with app:411 gr.Markdown("# <center>🥳🎶🎡 - Sambert中文声音克隆</center>")412 gr.Markdown("## <center>🌟 - 训练3分钟,推理10秒钟,中英真实拟声 </center>")413 gr.Markdown("### <center>🌊 - 基于SambertHifiGan项目修改而来,添加两种降噪功能、模型管理功能等")414 415 with gr.Tabs(): 416 with gr.TabItem("一键训练"): 417 with gr.Row():418 with gr.Column():419 inp1 = gr.Audio(type="filepath", sources="upload", label="方案一:请从本地上传一段语音")420 inp_micro = gr.Audio(type="filepath", sources="microphone", label="方案二:请用麦克风录制您的声音")421 with gr.Column():422 out1 = gr.Textbox(label="标注情况", lines=1, interactive=False)423 out2 = gr.Textbox(label="训练情况", lines=1, interactive=False)424 inp2 = gr.Slider(label="训练步数(需要为20的倍数)", minimum=200, maximum=4000, value=400, min_width=40)425 inp3 = gr.Textbox(label="请在这里填写您想合成的文本", placeholder="想说却还没说的 还很多...", lines=3, interactive=True)426 with gr.Column():427 out3 = gr.Audio(type="filepath", label="为您合成的专属音频")428 out4 = gr.Textbox(label="保存情况", lines=1, interactive=False)429 with gr.Row():430 btn1 = gr.Button("1.标注数据")431 btn2 = gr.Button("2.开始训练")432 btn3 = gr.Button("3.一键推理", variant="primary")433 btn4 = gr.Button("4.保存模型", variant="primary") 434 435 btn1.click(auto_label, [inp1, inp_micro], out1)436 btn2.click(train, inp2, out2)437 btn3.click(infer, inp3, out3)438 btn4.click(save_model, out1, out4) 439 with gr.Accordion("📒 训练教程", open=True):440 _ = f""" 如何开始训练: 441 * 第一步,选择 [方案一] 或 [方案二] 上传一分钟左右的音频,注意要吐字清晰、感情饱满、音色纯净不含杂音442 * 第二步,点击“标注数据”,等到提示标注成功后,选择合适的训练步数,点击“开始训练”等待训练完成443 * 第三步,耐心等待训练成功后,在文本框内输入想要生成的文字,点击“一键生成”按钮,生成克隆后的语音444 * !!注意!! 不要生成会对个人以及组织造成侵害的内容445 * 如果您的训练素材比较嘈杂,您可以在[AI降噪]选项卡上传或录制训练音频,降噪后再上传到训练界面446 * 如果您需要用方案二录制您的声音,以下是一段长度合适的文本,供您朗读并录制:447 448 记得春天的时候,小草就转出地面,树上的叶子也抽出来了,大地一片绿色,就像穿上了一件绿衣裳。我就与小孩子一起到田野去捉蜻蜓,玩游戏,比如老鹰合作小鸡或是捉迷藏,又或是跳格子。到了夏天,天气热了,我就会与小孩子到水库里面游泳,那时候水库的安全系数还不是很高,几乎每年都会有事故发生,所以父母都不会让我去游泳的,被发现之后当然就是处罚或是责骂了。可是那时候自己真的很叛逆,也不知道什么是危险,被处罚之后下一次还是回去的。到了秋天,田野一片金黄,山上的野果也成熟了,我就会与自己的伙伴拿着篮子到上山去采,采回来了还要跟自己的好朋友一起分享。449 450 """451 gr.Markdown(dedent(_))452 453 with gr.TabItem("声音合成"): 454 with gr.Row():455 with gr.Column():456 inp21 = gr.Dropdown(label="请选择一个模型", choices=os.listdir(trained_model)) 457 inp22 = gr.Slider(label="降噪强度(为0时不降噪)", minimum=0, maximum=3, value=2)458 with gr.Column():459 inp23 = gr.Textbox(label="请在这里填写您想合成的文本", placeholder="想说却还没说的 还很多...", lines=3, interactive=True)460 with gr.Column():461 out21 = gr.Audio(type="filepath", label="为您合成的专属音频", interactive=False)462 with gr.Row():463 btn21 = gr.Button("刷新模型列表") 464 btn22 = gr.Button("一键推理", variant="primary") 465 466 btn21.click(update_model_dropdown, inp21, inp21)467 btn22.click(infer_custom, [inp21, inp23, inp22], out21) 468 with gr.Accordion("📒 推理教程", open=True):469 _ = f""" 如何推理声音: 470 * 第一步,选择一个你想要使用的模型,如果训练后保存的模型无法找到请点击“刷新模型列表”471 * 第二步,在文本框处输入你想要生成的文本,选择降噪强度,如果无需降噪请将强度设为0472 * 第三步,点击“一键生成”按钮,生成克隆后的语音473 * !!注意!! 不要生成会对个人以及组织造成侵害的内容474 * 此处使用的降噪算法为机械降噪,非AI降噪,如需AI降噪可以将生成的音频下载后转到“AI降噪”选项卡进行AI降噪475 476 """477 gr.Markdown(dedent(_))478 479 with gr.TabItem("模型修改"): 480 with gr.Row():481 with gr.Column():482 inp31 = gr.Dropdown(label="选择重命名的模型", choices=os.listdir(trained_model)) 483 with gr.Column():484 inp32 = gr.Textbox(label="输入模型命名", placeholder="新名称", lines=1, interactive=True)485 with gr.Column(): 486 out31 = gr.Textbox(label="保存情况", lines=1, interactive=False)487 with gr.Row():488 btn31 = gr.Button("刷新模型列表") 489 btn32 = gr.Button("重命名", variant="primary") 490 491 btn31.click(update_model_dropdown, inp31, inp31)492 btn32.click(rename_model, [inp31, inp32], out31)493 with gr.Accordion("📒 推理教程", open=True):494 _ = f""" 如何修改模型名称: 495 * 第一步,选择一个你想要修改的模型,如果训练后保存的模型无法找到请点击“刷新模型列表”496 * 第二步,在文本框处输入你想要修改的模型名称,推荐以“[训练步数]时间-名称”来命名497 * 第三步,点击“重命名”按钮对模型重命名498 499 """500 gr.Markdown(dedent(_)) 501 502 with gr.TabItem("AI降噪"): 503 with gr.Row():504 with gr.Column():505 inp41 = gr.Audio(type="filepath", sources="upload", label="方案一:请从本地上传一段语音")506 inp_micro42 = gr.Audio(type="filepath", sources="microphone", label="方案二:请用麦克风录制您的声音")507 with gr.Column():508 out41 = gr.Audio(type="filepath", label="降噪后的音频", interactive=False)509 inp43 = gr.Slider(label="机械降噪强度(非AI降噪)", minimum=0, maximum=3, value=2) 510 btn41 = gr.Button("机械降噪")511 btn42 = gr.Button("一键AI降噪", variant="primary")512 513 btn41.click(Normal_De_Noise, [inp41, inp_micro42, inp43], out41)514 btn42.click(FRCRN_De_Noise, [inp41, inp_micro42], out41)515 with gr.Accordion("📒 AI降噪", open=True):516 _ = f""" 如何使用AI降噪: 517 * 第一步,在[方案一]上传你想要降噪的音频,或者在[方案二]录制音频518 * 第二步,点击“一键AI降噪”进行降噪519 * 第三步,下载降噪后的音频520 * 如果您的训练素材比较嘈杂,您可以在此处上传或录制训练音频,降噪后再上传到训练界面521 * 如果您需要用方案二录制您的声音,以下是一段长度合适的文本,供您朗读并录制:522 523 记得春天的时候,小草就转出地面,树上的叶子也抽出来了,大地一片绿色,就像穿上了一件绿衣裳。我就与小孩子一起到田野去捉蜻蜓,玩游戏,比如老鹰合作小鸡或是捉迷藏,又或是跳格子。到了夏天,天气热了,我就会与小孩子到水库里面游泳,那时候水库的安全系数还不是很高,几乎每年都会有事故发生,所以父母都不会让我去游泳的,被发现之后当然就是处罚或是责骂了。可是那时候自己真的很叛逆,也不知道什么是危险,被处罚之后下一次还是回去的。到了秋天,田野一片金黄,山上的野果也成熟了,我就会与自己的伙伴拿着篮子到上山去采,采回来了还要跟自己的好朋友一起分享。524 525 * AI降噪与机械降噪的不同:机械降噪主要是移除声音的激波,会对人声造成一定的破坏,而AI降噪主要是移除声音中的非人声部分,可以处理复杂的背景音频环境,但是对人声本身质量问题处理的效果一般 526 """527 gr.Markdown(dedent(_)) 528 529 with gr.TabItem("缓存清理"): 530 with gr.Row():531 with gr.Column():532 gr.Markdown("### <center>注意,这会清除[一键训练]界面生成的所有数据")533 gr.Markdown("### <center>包括标注数据,训练数据,及最终模型")534 gr.Markdown("### <center>如需保存模型请点击保存当前模型按钮")535 with gr.Column():536 out97 = gr.Textbox(label="", lines=1, interactive=False)537 btn91 = gr.Button("保存当前模型", ) 538 btn92 = gr.Button("清空缓存数据", variant="primary") 539 540 btn91.click(save_model, out1, out97) 541 btn92.click(clear_cache, out1, out97)542 543 544 545 546 547 548 with gr.Accordion("📒 使用指南", open=False):549 _ = f""" 如何使用此程序: 550 * [一键训练] : 上传或录制音频,程序会自动标注音频,一键训练模型,支持训练后推理试听,支持模型保存551 * [声音合成] : 在这里可以选择已保存的模型进行推理,自带可调机械降噪,可以任意选择已训练的音频进行推理552 * [模型修改] : 在这里可以选择已保存的模型进行重命名,方便日后推理使用553 * [ AI降噪 ] : 在这里可以上传音频进行AI降噪,一键去除噪音杂声554 * [缓存清理] : 如果训练时出现报错可以尝试缓存清理,每次保存模型会自动清理缓存,如果未保存就重新开始训练需要清理缓存555 * !!注意!! 不要生成会对个人以及组织造成侵害的内容556 * 如果您需要录制您的声音,以下是一段长度合适的文本,供您朗读并录制:557 558 记得春天的时候,小草就转出地面,树上的叶子也抽出来了,大地一片绿色,就像穿上了一件绿衣裳。我就与小孩子一起到田野去捉蜻蜓,玩游戏,比如老鹰合作小鸡或是捉迷藏,又或是跳格子。到了夏天,天气热了,我就会与小孩子到水库里面游泳,那时候水库的安全系数还不是很高,几乎每年都会有事故发生,所以父母都不会让我去游泳的,被发现之后当然就是处罚或是责骂了。可是那时候自己真的很叛逆,也不知道什么是危险,被处罚之后下一次还是回去的。到了秋天,田野一片金黄,山上的野果也成熟了,我就会与自己的伙伴拿着篮子到上山去采,采回来了还要跟自己的好朋友一起分享。559 560 """561 gr.Markdown(dedent(_))562 563 564 gr.Markdown("### <center>注意❗:请不要生成会对个人以及组织造成侵害的内容,此程序仅供科研、学习及个人娱乐使用。</center>")565 gr.HTML('''566 <div class="footer">567 <p>🌊🏞️🎶 - 江水东流急,滔滔无尽声。 明·顾璘568 </p>569 </div>570 ''')571 572 573app.launch(show_error=True, share=False)574 