diff --git a/server/Exceptions.py b/server/Exceptions.py index 9a2104b4..0ad8fc49 100644 --- a/server/Exceptions.py +++ b/server/Exceptions.py @@ -3,9 +3,7 @@ class NoModeLoadedException(Exception): self.framework = framework def __str__(self): - return repr( - f"No model for {self.framework} loaded. Please confirm the model uploaded." - ) + return repr(f"No model for {self.framework} loaded. Please confirm the model uploaded.") class HalfPrecisionChangingException(Exception): @@ -36,3 +34,8 @@ class DeviceCannotSupportHalfPrecisionException(Exception): class VoiceChangerIsNotSelectedException(Exception): def __str__(self): return repr("Voice Changer is not selected.") + + +class WeightDownladException(Exception): + def __str__(self): + return repr("Failed to download weight.") diff --git a/server/MMVCServerSIO.py b/server/MMVCServerSIO.py index bbf3ac99..26c79ce4 100755 --- a/server/MMVCServerSIO.py +++ b/server/MMVCServerSIO.py @@ -1,4 +1,3 @@ -from concurrent.futures import ThreadPoolExecutor import sys from distutils.util import strtobool @@ -7,11 +6,9 @@ import socket import platform import os import argparse -from Downloader import download, download_no_tqdm -from voice_changer.RVC.SampleDownloader import ( - checkRvcModelExist, - downloadInitialSampleModels, -) +from Exceptions import WeightDownladException +from utils.downloader.SampleDownloader import downloadInitialSamples +from utils.downloader.WeightDownloader import downloadWeight from voice_changer.utils.VoiceChangerParams import VoiceChangerParams @@ -24,7 +21,6 @@ from const import ( NATIVE_CLIENT_FILE_MAC, NATIVE_CLIENT_FILE_WIN, SSL_KEY_DIR, - getRVCSampleJsonAndModelIds, ) import subprocess import multiprocessing as mp @@ -35,56 +31,23 @@ setup_loggers() def setupArgParser(): parser = argparse.ArgumentParser() - parser.add_argument( - "--logLevel", - type=str, - default="critical", - help="Log level info|critical. (default: critical)", - ) + parser.add_argument("--logLevel", type=str, default="critical", help="Log level info|critical. (default: critical)") parser.add_argument("-p", type=int, default=18888, help="port") parser.add_argument("--https", type=strtobool, default=False, help="use https") - parser.add_argument( - "--httpsKey", type=str, default="ssl.key", help="path for the key of https" - ) - parser.add_argument( - "--httpsCert", type=str, default="ssl.cert", help="path for the cert of https" - ) - parser.add_argument( - "--httpsSelfSigned", - type=strtobool, - default=True, - help="generate self-signed certificate", - ) + parser.add_argument("--httpsKey", type=str, default="ssl.key", help="path for the key of https") + parser.add_argument("--httpsCert", type=str, default="ssl.cert", help="path for the cert of https") + parser.add_argument("--httpsSelfSigned", type=strtobool, default=True, help="generate self-signed certificate") parser.add_argument("--model_dir", type=str, help="path to model files") - parser.add_argument( - "--rvc_sample_mode", type=str, default="production", help="rvc_sample_mode" - ) + parser.add_argument("--sample_mode", type=str, default="production", help="rvc_sample_mode") - parser.add_argument( - "--content_vec_500", type=str, help="path to content_vec_500 model(pytorch)" - ) - parser.add_argument( - "--content_vec_500_onnx", type=str, help="path to content_vec_500 model(onnx)" - ) - parser.add_argument( - "--content_vec_500_onnx_on", - type=strtobool, - default=False, - help="use or not onnx for content_vec_500", - ) - parser.add_argument( - "--hubert_base", type=str, help="path to hubert_base model(pytorch)" - ) - parser.add_argument( - "--hubert_base_jp", type=str, help="path to hubert_base_jp model(pytorch)" - ) - parser.add_argument( - "--hubert_soft", type=str, help="path to hubert_soft model(pytorch)" - ) - parser.add_argument( - "--nsf_hifigan", type=str, help="path to nsf_hifigan model(pytorch)" - ) + parser.add_argument("--content_vec_500", type=str, help="path to content_vec_500 model(pytorch)") + parser.add_argument("--content_vec_500_onnx", type=str, help="path to content_vec_500 model(onnx)") + parser.add_argument("--content_vec_500_onnx_on", type=strtobool, default=False, help="use or not onnx for content_vec_500") + parser.add_argument("--hubert_base", type=str, help="path to hubert_base model(pytorch)") + parser.add_argument("--hubert_base_jp", type=str, help="path to hubert_base_jp model(pytorch)") + parser.add_argument("--hubert_soft", type=str, help="path to hubert_soft model(pytorch)") + parser.add_argument("--nsf_hifigan", type=str, help="path to nsf_hifigan model(pytorch)") return parser @@ -111,76 +74,19 @@ def printMessage(message, level=0): print(f"\033[47m {message}\033[0m") -def downloadWeight(): - # content_vec_500 = (args.content_vec_500,) - # content_vec_500_onnx = (args.content_vec_500_onnx,) - # content_vec_500_onnx_on = (args.content_vec_500_onnx_on,) - hubert_base = args.hubert_base - hubert_base_jp = args.hubert_base_jp - hubert_soft = args.hubert_soft - nsf_hifigan = args.nsf_hifigan - - # file exists check (currently only for rvc) - downloadParams = [] - if os.path.exists(hubert_base) is False: - downloadParams.append( - { - "url": "https://huggingface.co/ddPn08/rvc-webui-models/resolve/main/embeddings/hubert_base.pt", - "saveTo": hubert_base, - "position": 0, - } - ) - if os.path.exists(hubert_base_jp) is False: - downloadParams.append( - { - "url": "https://huggingface.co/rinna/japanese-hubert-base/resolve/main/fairseq/model.pt", - "saveTo": hubert_base_jp, - "position": 1, - } - ) - if os.path.exists(hubert_soft) is False: - downloadParams.append( - { - "url": "https://huggingface.co/wok000/weights/resolve/main/ddsp-svc30/embedder/hubert-soft-0d54a1f4.pt", - "saveTo": hubert_soft, - "position": 2, - } - ) - if os.path.exists(nsf_hifigan) is False: - downloadParams.append( - { - "url": "https://huggingface.co/wok000/weights/resolve/main/ddsp-svc30/nsf_hifigan_20221211/model.bin", - "saveTo": nsf_hifigan, - "position": 3, - } - ) - nsf_hifigan_config = os.path.join(os.path.dirname(nsf_hifigan), "config.json") - - if os.path.exists(nsf_hifigan_config) is False: - downloadParams.append( - { - "url": "https://huggingface.co/wok000/weights/raw/main/ddsp-svc30/nsf_hifigan_20221211/config.json", - "saveTo": nsf_hifigan_config, - "position": 4, - } - ) - - with ThreadPoolExecutor() as pool: - pool.map(download, downloadParams) - - if ( - os.path.exists(hubert_base) is False - or os.path.exists(hubert_base_jp) is False - or os.path.exists(hubert_soft) is False - or os.path.exists(nsf_hifigan) is False - or os.path.exists(nsf_hifigan_config) is False - ): - printMessage("RVC用のモデルファイルのダウンロードに失敗しました。", level=2) - printMessage("failed to download weight for rvc", level=2) - - parser = setupArgParser() args, unknown = parser.parse_known_args() +voiceChangerParams = VoiceChangerParams( + model_dir=args.model_dir, + content_vec_500=args.content_vec_500, + content_vec_500_onnx=args.content_vec_500_onnx, + content_vec_500_onnx_on=args.content_vec_500_onnx_on, + hubert_base=args.hubert_base, + hubert_base_jp=args.hubert_base_jp, + hubert_soft=args.hubert_soft, + nsf_hifigan=args.nsf_hifigan, + sample_mode=args.sample_mode, +) printMessage(f"Booting PHASE :{__name__}", level=2) @@ -199,24 +105,6 @@ def localServer(logLevel: str = "critical"): if __name__ == "MMVCServerSIO": mp.freeze_support() - voiceChangerParams = VoiceChangerParams( - model_dir=args.model_dir, - content_vec_500=args.content_vec_500, - content_vec_500_onnx=args.content_vec_500_onnx, - content_vec_500_onnx_on=args.content_vec_500_onnx_on, - hubert_base=args.hubert_base, - hubert_base_jp=args.hubert_base_jp, - hubert_soft=args.hubert_soft, - nsf_hifigan=args.nsf_hifigan, - rvc_sample_mode=args.rvc_sample_mode, - ) - - if ( - os.path.exists(voiceChangerParams.hubert_base) is False - or os.path.exists(voiceChangerParams.hubert_base_jp) is False - ): - printMessage("RVC用のモデルファイルのダウンロードに失敗しました。", level=2) - printMessage("failed to download weight for rvc", level=2) voiceChangerManager = VoiceChangerManager.get_instance(voiceChangerParams) app_fastapi = MMVC_Rest.get_instance(voiceChangerManager, voiceChangerParams) @@ -230,24 +118,20 @@ if __name__ == "__main__": mp.freeze_support() printMessage("Voice Changerを起動しています。", level=2) - - # ダウンロード - downloadWeight() - os.makedirs(args.model_dir, exist_ok=True) - + # ダウンロード(Weight) try: - sampleJsons = [] - sampleJsonUrls, sampleModels = getRVCSampleJsonAndModelIds(args.rvc_sample_mode) - for url in sampleJsonUrls: - filename = os.path.basename(url) - download_no_tqdm({"url": url, "saveTo": filename, "position": 0}) - sampleJsons.append(filename) - if checkRvcModelExist(args.model_dir) is False: - downloadInitialSampleModels(sampleJsons, sampleModels, args.model_dir) + downloadWeight(voiceChangerParams) + except WeightDownladException: + printMessage("RVC用のモデルファイルのダウンロードに失敗しました。", level=2) + printMessage("failed to download weight for rvc", level=2) + + # ダウンロード(Sample) + try: + downloadInitialSamples(args.sample_mode, args.model_dir) except Exception as e: print("[Voice Changer] loading sample failed", e) - PORT = args.p + # PORT = args.p if os.getenv("EX_PORT"): EX_PORT = os.environ["EX_PORT"] @@ -280,9 +164,7 @@ if __name__ == "__main__": ) key_path = os.path.join(SSL_KEY_DIR, keyname) cert_path = os.path.join(SSL_KEY_DIR, certname) - printMessage( - f"protocol: HTTPS(self-signed), key:{key_path}, cert:{cert_path}", level=1 - ) + printMessage(f"protocol: HTTPS(self-signed), key:{key_path}, cert:{cert_path}", level=1) elif args.https and args.httpsSelfSigned == 0: # HTTPS @@ -336,16 +218,12 @@ if __name__ == "__main__": p.start() try: if sys.platform.startswith("win"): - process = subprocess.Popen( - [NATIVE_CLIENT_FILE_WIN, "-u", f"http://localhost:{PORT}/"] - ) + process = subprocess.Popen([NATIVE_CLIENT_FILE_WIN, "-u", f"http://localhost:{PORT}/"]) return_code = process.wait() print("client closed.") p.terminate() elif sys.platform.startswith("darwin"): - process = subprocess.Popen( - [NATIVE_CLIENT_FILE_MAC, "-u", f"http://localhost:{PORT}/"] - ) + process = subprocess.Popen([NATIVE_CLIENT_FILE_MAC, "-u", f"http://localhost:{PORT}/"]) return_code = process.wait() print("client closed.") p.terminate() diff --git a/server/const.py b/server/const.py index 63d3bc69..c5c18cc7 100644 --- a/server/const.py +++ b/server/const.py @@ -5,6 +5,14 @@ import tempfile from typing import Literal, TypeAlias +VoiceChangerType: TypeAlias = Literal[ + "MMVCv13", + "MMVCv15", + "so-vits-svc-40", + "DDSP-SVC", + "RVC", +] + ModelType: TypeAlias = Literal[ "MMVCv15", "MMVCv13", @@ -22,16 +30,8 @@ tmpdir = tempfile.TemporaryDirectory() # print("generate tmpdir:::",tmpdir) SSL_KEY_DIR = os.path.join(tmpdir.name, "keys") if hasattr(sys, "_MEIPASS") else "keys" MODEL_DIR = os.path.join(tmpdir.name, "logs") if hasattr(sys, "_MEIPASS") else "logs" -UPLOAD_DIR = ( - os.path.join(tmpdir.name, "upload_dir") - if hasattr(sys, "_MEIPASS") - else "upload_dir" -) -NATIVE_CLIENT_FILE_WIN = ( - os.path.join(sys._MEIPASS, "voice-changer-native-client.exe") # type: ignore - if hasattr(sys, "_MEIPASS") - else "voice-changer-native-client" -) +UPLOAD_DIR = os.path.join(tmpdir.name, "upload_dir") if hasattr(sys, "_MEIPASS") else "upload_dir" +NATIVE_CLIENT_FILE_WIN = os.path.join(sys._MEIPASS, "voice-changer-native-client.exe") if hasattr(sys, "_MEIPASS") else "voice-changer-native-client" # type: ignore NATIVE_CLIENT_FILE_MAC = ( os.path.join( sys._MEIPASS, # type: ignore @@ -44,25 +44,15 @@ NATIVE_CLIENT_FILE_MAC = ( else "voice-changer-native-client" ) -HUBERT_ONNX_MODEL_PATH = ( - os.path.join(sys._MEIPASS, "model_hubert/hubert_simple.onnx") # type: ignore - if hasattr(sys, "_MEIPASS") - else "model_hubert/hubert_simple.onnx" -) +HUBERT_ONNX_MODEL_PATH = os.path.join(sys._MEIPASS, "model_hubert/hubert_simple.onnx") if hasattr(sys, "_MEIPASS") else "model_hubert/hubert_simple.onnx" # type: ignore -TMP_DIR = ( - os.path.join(tmpdir.name, "tmp_dir") if hasattr(sys, "_MEIPASS") else "tmp_dir" -) +TMP_DIR = os.path.join(tmpdir.name, "tmp_dir") if hasattr(sys, "_MEIPASS") else "tmp_dir" os.makedirs(TMP_DIR, exist_ok=True) def getFrontendPath(): - frontend_path = ( - os.path.join(sys._MEIPASS, "dist") - if hasattr(sys, "_MEIPASS") - else "../client/demo/dist" - ) + frontend_path = os.path.join(sys._MEIPASS, "dist") if hasattr(sys, "_MEIPASS") else "../client/demo/dist" return frontend_path @@ -100,80 +90,81 @@ class ServerAudioDeviceTypes(Enum): audiooutput = "audiooutput" -class RVCSampleMode(Enum): - production = "production" - testOfficial = "testOfficial" - testDDPNTorch = "testDDPNTorch" - testDDPNONNX = "testDDPNONNX" - testONNXFull = "testONNXFull" +RVCSampleMode: TypeAlias = Literal[ + "production", + "testOfficial", + "testDDPNTorch", + "testDDPNONNX", + "testONNXFull", +] -def getRVCSampleJsonAndModelIds(mode: RVCSampleMode): - if mode == RVCSampleMode.production.value: +def getSampleJsonAndModelIds(mode: RVCSampleMode): + if mode == "production": return [ # "https://huggingface.co/wok000/vcclient_model/raw/main/samples_0001.json", # "https://huggingface.co/wok000/vcclient_model/raw/main/samples_0002.json", - "https://huggingface.co/wok000/vcclient_model/raw/main/samples_0003_t.json", - "https://huggingface.co/wok000/vcclient_model/raw/main/samples_0003_o.json", + "https://huggingface.co/wok000/vcclient_model/raw/main/samples_0003_t2.json", + "https://huggingface.co/wok000/vcclient_model/raw/main/samples_0003_o2.json", ], [ - ("TokinaShigure_o", True), - ("KikotoMahiro_o", False), - ("Amitaro_o", False), - ("Tsukuyomi-chan_o", False), + ("TokinaShigure_o", {"useIndex": True}), + ("KikotoMahiro_o", {"useIndex": False}), + ("Amitaro_o", {"useIndex": False}), + ("Tsukuyomi-chan_o", {"useIndex": False}), ] - elif mode == RVCSampleMode.testOfficial.value: + elif mode == "testOfficial": return [ "https://huggingface.co/wok000/vcclient_model/raw/main/test/test_official_v1_v2.json", "https://huggingface.co/wok000/vcclient_model/raw/main/test/test_ddpn_v1_v2.json", ], [ - ("test-official-v1-f0-48k-l9-hubert_t", True), - ("test-official-v1-nof0-48k-l9-hubert_t", False), - ("test-official-v2-f0-40k-l12-hubert_t", False), - ("test-official-v2-nof0-40k-l12-hubert_t", False), - ("test-official-v1-f0-48k-l9-hubert_o", True), - ("test-official-v1-nof0-48k-l9-hubert_o", False), - ("test-official-v2-f0-40k-l12-hubert_o", False), - ("test-official-v2-nof0-40k-l12-hubert_o", False), + ("test-official-v1-f0-48k-l9-hubert_t", {"useIndex": True}), + ("test-official-v1-nof0-48k-l9-hubert_t", {"useIndex": False}), + ("test-official-v2-f0-40k-l12-hubert_t", {"useIndex": False}), + ("test-official-v2-nof0-40k-l12-hubert_t", {"useIndex": False}), + ("test-official-v1-f0-48k-l9-hubert_o", {"useIndex": True}), + ("test-official-v1-nof0-48k-l9-hubert_o", {"useIndex": False}), + ("test-official-v2-f0-40k-l12-hubert_o", {"useIndex": False}), + ("test-official-v2-nof0-40k-l12-hubert_o", {"useIndex": False}), ] - elif mode == RVCSampleMode.testDDPNTorch.value: + elif mode == "testDDPNTorch": return [ "https://huggingface.co/wok000/vcclient_model/raw/main/test/test_official_v1_v2.json", "https://huggingface.co/wok000/vcclient_model/raw/main/test/test_ddpn_v1_v2.json", ], [ - ("test-ddpn-v1-f0-48k-l9-hubert_t", False), - ("test-ddpn-v1-nof0-48k-l9-hubert_t", False), - ("test-ddpn-v2-f0-40k-l12-hubert_t", False), - ("test-ddpn-v2-nof0-40k-l12-hubert_t", False), - ("test-ddpn-v2-f0-40k-l12-hubert_jp_t", False), - ("test-ddpn-v2-nof0-40k-l12-hubert_jp_t", False), + ("test-ddpn-v1-f0-48k-l9-hubert_t", {"useIndex": False}), + ("test-ddpn-v1-nof0-48k-l9-hubert_t", {"useIndex": False}), + ("test-ddpn-v2-f0-40k-l12-hubert_t", {"useIndex": False}), + ("test-ddpn-v2-nof0-40k-l12-hubert_t", {"useIndex": False}), + ("test-ddpn-v2-f0-40k-l12-hubert_jp_t", {"useIndex": False}), + ("test-ddpn-v2-nof0-40k-l12-hubert_jp_t", {"useIndex": False}), ] - elif mode == RVCSampleMode.testDDPNONNX.value: + elif mode == "testDDPNONNX": return [ "https://huggingface.co/wok000/vcclient_model/raw/main/test/test_official_v1_v2.json", "https://huggingface.co/wok000/vcclient_model/raw/main/test/test_ddpn_v1_v2.json", ], [ - ("test-ddpn-v1-f0-48k-l9-hubert_o", False), - ("test-ddpn-v1-nof0-48k-l9-hubert_o", False), - ("test-ddpn-v2-f0-40k-l12-hubert_o", False), - ("test-ddpn-v2-nof0-40k-l12-hubert_o", False), - ("test-ddpn-v2-f0-40k-l12-hubert_jp_o", False), - ("test-ddpn-v2-nof0-40k-l12-hubert_jp_o", False), + ("test-ddpn-v1-f0-48k-l9-hubert_o", {"useIndex": False}), + ("test-ddpn-v1-nof0-48k-l9-hubert_o", {"useIndex": False}), + ("test-ddpn-v2-f0-40k-l12-hubert_o", {"useIndex": False}), + ("test-ddpn-v2-nof0-40k-l12-hubert_o", {"useIndex": False}), + ("test-ddpn-v2-f0-40k-l12-hubert_jp_o", {"useIndex": False}), + ("test-ddpn-v2-nof0-40k-l12-hubert_jp_o", {"useIndex": False}), ] - elif mode == RVCSampleMode.testONNXFull.value: + elif mode == "testONNXFull": return [ "https://huggingface.co/wok000/vcclient_model/raw/main/test/test_official_v1_v2.json", "https://huggingface.co/wok000/vcclient_model/raw/main/test/test_ddpn_v1_v2.json", ], [ - ("test-official-v1-f0-48k-l9-hubert_o_full", False), - ("test-official-v1-nof0-48k-l9-hubert_o_full", False), - ("test-official-v2-f0-40k-l12-hubert_o_full", False), - ("test-official-v2-nof0-40k-l12-hubert_o_full", False), - ("test-ddpn-v1-f0-48k-l9-hubert_o_full", False), - ("test-ddpn-v1-nof0-48k-l9-hubert_o_full", False), - ("test-ddpn-v2-f0-40k-l12-hubert_o_full", False), - ("test-ddpn-v2-nof0-40k-l12-hubert_o_full", False), - ("test-ddpn-v2-f0-40k-l12-hubert_jp_o_full", False), - ("test-ddpn-v2-nof0-40k-l12-hubert_jp_o_full", False), + ("test-official-v1-f0-48k-l9-hubert_o_full", {"useIndex": False}), + ("test-official-v1-nof0-48k-l9-hubert_o_full", {"useIndex": False}), + ("test-official-v2-f0-40k-l12-hubert_o_full", {"useIndex": False}), + ("test-official-v2-nof0-40k-l12-hubert_o_full", {"useIndex": False}), + ("test-ddpn-v1-f0-48k-l9-hubert_o_full", {"useIndex": False}), + ("test-ddpn-v1-nof0-48k-l9-hubert_o_full", {"useIndex": False}), + ("test-ddpn-v2-f0-40k-l12-hubert_o_full", {"useIndex": False}), + ("test-ddpn-v2-nof0-40k-l12-hubert_o_full", {"useIndex": False}), + ("test-ddpn-v2-f0-40k-l12-hubert_jp_o_full", {"useIndex": False}), + ("test-ddpn-v2-nof0-40k-l12-hubert_jp_o_full", {"useIndex": False}), ] else: return [], [] diff --git a/server/data/ModelSample.py b/server/data/ModelSample.py new file mode 100644 index 00000000..b7df3b4c --- /dev/null +++ b/server/data/ModelSample.py @@ -0,0 +1,37 @@ +from dataclasses import dataclass, field +from typing import TypeAlias, Union, Any +from const import VoiceChangerType + + +@dataclass +class ModelSample: + id: str = "" + voiceChangerType: VoiceChangerType | None = None + + +@dataclass +class RVCModelSample(ModelSample): + voiceChangerType: VoiceChangerType = "RVC" + lang: str = "" + tag: list[str] = field(default_factory=lambda: []) + name: str = "" + modelUrl: str = "" + indexUrl: str = "" + termsOfUseUrl: str = "" + icon: str = "" + credit: str = "" + description: str = "" + + sampleRate: int = 48000 + modelType: str = "" + f0: bool = True + + +ModelSamples: TypeAlias = Union[ModelSample, RVCModelSample] + + +def generateModelSample(params: Any) -> ModelSamples: + if params["voiceChangerType"] == "RVC": + return RVCModelSample(**params) + else: + return ModelSample(**{k: v for k, v in params.items() if k in ModelSample.__annotations__}) diff --git a/server/data/ModelSlot.py b/server/data/ModelSlot.py new file mode 100644 index 00000000..1087d90c --- /dev/null +++ b/server/data/ModelSlot.py @@ -0,0 +1,59 @@ +from typing import TypeAlias, Union +from const import EnumInferenceTypes, EnumEmbedderTypes, VoiceChangerType + +from dataclasses import dataclass, asdict + +import os +import json + + +@dataclass +class ModelSlot: + voiceChangerType: VoiceChangerType | None = None + + +@dataclass +class RVCModelSlot(ModelSlot): + voiceChangerType: VoiceChangerType = "RVC" + modelFile: str = "" + indexFile: str = "" + defaultTune: int = 0 + defaultIndexRatio: int = 1 + defaultProtect: float = 0.5 + isONNX: bool = False + modelType: str = EnumInferenceTypes.pyTorchRVC.value + samplingRate: int = -1 + f0: bool = True + embChannels: int = 256 + embOutputLayer: int = 9 + useFinalProj: bool = True + deprecated: bool = False + embedder: str = EnumEmbedderTypes.hubert.value + + name: str = "" + description: str = "" + credit: str = "" + termsOfUseUrl: str = "" + sampleId: str = "" + iconFile: str = "" + + +ModelSlots: TypeAlias = Union[ModelSlot, RVCModelSlot] + + +def loadSlotInfo(model_dir: str, slotIndex: int) -> ModelSlots: + slotDir = os.path.join(model_dir, str(slotIndex)) + jsonFile = os.path.join(slotDir, "params.json") + if not os.path.exists(jsonFile): + return ModelSlot() + jsonDict = json.load(open(os.path.join(slotDir, "params.json"))) + slotInfo = ModelSlot(**{k: v for k, v in jsonDict.items() if k in ModelSlot.__annotations__}) + if slotInfo.voiceChangerType == "RVC": + return RVCModelSlot(**jsonDict) + else: + return ModelSlot() + + +def saveSlotInfo(model_dir: str, slotIndex: int, slotInfo: ModelSlots): + slotDir = os.path.join(model_dir, str(slotIndex)) + json.dump(asdict(slotInfo), open(os.path.join(slotDir, "params.json"), "w")) diff --git a/server/samples_0003_o.json b/server/samples_0003_o.json deleted file mode 100644 index 18a7e967..00000000 --- a/server/samples_0003_o.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "RVC": [ - { - "id": "KikotoKurage_o", - "lang": "ja-JP", - "tag": ["v2", "onnx"], - "name": "黄琴海月(onnx)", - "modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/kikoto_kurage/kikoto_kurage_v2_40k_e100_simple.onnx", - "indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/kikoto_kurage/added_IVF5181_Flat_nprobe_1_v2.index.bin", - "termsOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc_v2_alpha/kikoto_kurage/terms_of_use.txt", - "icon": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/kikoto_kurage/kikoto_kurage.png", - "credit": "黄琴海月", - "description": "", - "sampleRate": 40000, - "modelType": "rvc_v2", - "f0": true - }, - { - "id": "KikotoMahiro_o", - "lang": "ja-JP", - "tag": ["v2", "onnx"], - "name": "黄琴まひろ(onnx)", - "modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/kikoto_mahiro/kikoto_mahiro_v2_40k_simple.onnx", - "indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/kikoto_mahiro/added_IVF6881_Flat_nprobe_1_v2.index.bin", - "termsOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc_v2_alpha/kikoto_mahiro/terms_of_use.txt", - "icon": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/kikoto_mahiro/kikoto_mahiro.png", - "credit": "黄琴まひろ", - "description": "", - "sampleRate": 40000, - "modelType": "rvc_v2", - "f0": true - }, - { - "id": "TokinaShigure_o", - "lang": "ja-JP", - "tag": ["v2", "onnx"], - "name": "刻鳴時雨(onnx)", - "modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tokina_shigure/tokina_shigure_v2_40k_e100_simple.onnx", - "indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tokina_shigure/added_IVF2736_Flat_nprobe_1_v2.index.bin", - "termsOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc_v2_alpha/tokina_shigure/terms_of_use.txt", - "icon": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tokina_shigure/tokina_shigure.png", - "credit": "刻鳴時雨", - "description": "", - "sampleRate": 40000, - "modelType": "rvc_v2", - "f0": true - }, - { - "id": "Amitaro_o", - "lang": "ja-JP", - "tag": ["v2", "onnx"], - "name": "あみたろ(onnx)", - "modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/amitaro/amitaro_v2_40k_e100_simple.onnx", - "indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/amitaro/added_IVF3139_Flat_nprobe_1_v2.index.bin", - "termsOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc_v2_alpha/amitaro/terms_of_use.txt", - "icon": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/amitaro/amitaro.png", - "credit": "あみたろ", - "description": "", - "sampleRate": 40000, - "modelType": "rvc_v2", - "f0": true - }, - { - "id": "Tsukuyomi-chan_o", - "lang": "ja-JP", - "tag": ["v2", "onnx"], - "name": "つくよみちゃん(onnx)", - "modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tsukuyomi-chan/tsukuyomi_v2_40k_e100_simple.onnx", - "indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tsukuyomi-chan/added_IVF7852_Flat_nprobe_1_v2.index.bin", - "termsOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc_v2_alpha/tsukuyomi-chan/terms_of_use.txt", - "icon": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc_v2_alpha/tsukuyomi-chan/tsukuyomi-chan.png", - "credit": "つくよみちゃん", - "description": "", - "sampleRate": 40000, - "modelType": "rvc_v2", - "f0": true - } - ] -} diff --git a/server/utils/downloader/Downloader.py b/server/utils/downloader/Downloader.py new file mode 100644 index 00000000..1b346f90 --- /dev/null +++ b/server/utils/downloader/Downloader.py @@ -0,0 +1,56 @@ +import requests # type: ignore +import os +from tqdm import tqdm + + +def download(params): + url = params["url"] + saveTo = params["saveTo"] + position = params["position"] + dirname = os.path.dirname(saveTo) + if dirname != "": + os.makedirs(dirname, exist_ok=True) + + try: + req = requests.get(url, stream=True, allow_redirects=True) + content_length = req.headers.get("content-length") + progress_bar = tqdm( + total=int(content_length) if content_length is not None else None, + leave=False, + unit="B", + unit_scale=True, + unit_divisor=1024, + position=position, + ) + + # with tqdm + with open(saveTo, "wb") as f: + for chunk in req.iter_content(chunk_size=1024): + if chunk: + progress_bar.update(len(chunk)) + f.write(chunk) + + except Exception as e: + print(e) + + +def download_no_tqdm(params): + url = params["url"] + saveTo = params["saveTo"] + dirname = os.path.dirname(saveTo) + if dirname != "": + os.makedirs(dirname, exist_ok=True) + try: + req = requests.get(url, stream=True, allow_redirects=True) + with open(saveTo, "wb") as f: + countToDot = 0 + for chunk in req.iter_content(chunk_size=1024): + if chunk: + f.write(chunk) + countToDot += 1 + if countToDot % 1024 == 0: + print(".", end="", flush=True) + + print("+", end="", flush=True) + except Exception as e: + print(e) diff --git a/server/utils/downloader/SampleDownloader.py b/server/utils/downloader/SampleDownloader.py new file mode 100644 index 00000000..12e77696 --- /dev/null +++ b/server/utils/downloader/SampleDownloader.py @@ -0,0 +1,166 @@ +import json +import os +from concurrent.futures import ThreadPoolExecutor +from typing import Any, Tuple + +from const import RVCSampleMode, getSampleJsonAndModelIds +from data.ModelSample import ModelSamples, generateModelSample +from data.ModelSlot import RVCModelSlot, loadSlotInfo, saveSlotInfo +from voice_changer.RVC.ModelSlotGenerator import _setInfoByONNX, _setInfoByPytorch +from utils.downloader.Downloader import download, download_no_tqdm + + +def downloadInitialSamples(mode: RVCSampleMode, model_dir: str): + sampleJsonUrls, sampleModels = getSampleJsonAndModelIds(mode) + sampleJsons = _downloadSampleJsons(sampleJsonUrls) + if os.path.exists(model_dir): + print("[Voice Changer] model_dir is already exists. skil download samples.") + return + samples = _generateSampleList(sampleJsons) + slotIndex = list(range(len(sampleModels))) + _downloadSamples(samples, sampleModels, model_dir, slotIndex) + + pass + + +def downloadSample(mode: RVCSampleMode, modelId: str, model_dir: str, slotIndex: int, params: Any): + sampleJsonUrls, _sampleModels = getSampleJsonAndModelIds(mode) + sampleJsons = _generateSampleJsons(sampleJsonUrls) + samples = _generateSampleList(sampleJsons) + _downloadSamples(samples, [(modelId, params)], model_dir, [slotIndex]) + pass + + +def getSampleInfos(mode: RVCSampleMode): + sampleJsonUrls, _sampleModels = getSampleJsonAndModelIds(mode) + sampleJsons = _generateSampleJsons(sampleJsonUrls) + samples = _generateSampleList(sampleJsons) + return samples + + +def _downloadSampleJsons(sampleJsonUrls: list[str]): + sampleJsons = [] + for url in sampleJsonUrls: + filename = os.path.basename(url) + download_no_tqdm({"url": url, "saveTo": filename, "position": 0}) + sampleJsons.append(filename) + return sampleJsons + + +def _generateSampleJsons(sampleJsonUrls: list[str]): + sampleJsons = [] + for url in sampleJsonUrls: + filename = os.path.basename(url) + sampleJsons.append(filename) + return sampleJsons + + +def _generateSampleList(sampleJsons: list[str]): + samples: list[ModelSamples] = [] + for file in sampleJsons: + with open(file, "r", encoding="utf-8") as f: + jsonDict = json.load(f) + for vcType in jsonDict: + for sampleParams in jsonDict[vcType]: + sample = generateModelSample(sampleParams) + samples.append(sample) + return samples + + +def _downloadSamples(samples: list[ModelSamples], sampleModelIds: list[Tuple[str, Any]], model_dir: str, slotIndex: list[int]): + downloadParams = [] + line_num = 0 + + for i, initSampleId in enumerate(sampleModelIds): + targetSampleId = initSampleId[0] + targetSampleParams = initSampleId[1] + tagetSlotIndex = slotIndex[i] + + # 初期サンプルをサーチ + match = False + for sample in samples: + if sample.id == targetSampleId: + match = True + break + if match is False: + print(f"[Voice Changer] initiail sample not found. {targetSampleId}") + continue + + # 検出されたら、、、 + slotDir = os.path.join(model_dir, str(tagetSlotIndex)) + if sample.voiceChangerType == "RVC": + slotInfo: RVCModelSlot = RVCModelSlot() + + os.makedirs(slotDir, exist_ok=True) + modelFilePath = os.path.join( + slotDir, + os.path.basename(sample.modelUrl), + ) + downloadParams.append( + { + "url": sample.modelUrl, + "saveTo": modelFilePath, + "position": line_num, + } + ) + slotInfo.modelFile = modelFilePath + line_num += 1 + + if targetSampleParams["useIndex"] is True and hasattr(sample, "indexUrl") and sample.indexUrl != "": + indexPath = os.path.join( + slotDir, + os.path.basename(sample.indexUrl), + ) + downloadParams.append( + { + "url": sample.indexUrl, + "saveTo": indexPath, + "position": line_num, + } + ) + slotInfo.indexFile = indexPath + line_num += 1 + + if hasattr(sample, "icon") and sample.icon != "": + iconPath = os.path.join( + slotDir, + os.path.basename(sample.icon), + ) + downloadParams.append( + { + "url": sample.icon, + "saveTo": iconPath, + "position": line_num, + } + ) + slotInfo.iconFile = iconPath + line_num += 1 + + slotInfo.sampleId = sample.id + slotInfo.credit = sample.credit + slotInfo.description = sample.description + slotInfo.name = sample.name + slotInfo.termsOfUseUrl = sample.termsOfUseUrl + slotInfo.defaultTune = 0 + slotInfo.defaultIndexRatio = 1 + slotInfo.defaultProtect = 0.5 + slotInfo.isONNX = slotInfo.modelFile.endswith(".onnx") + saveSlotInfo(model_dir, tagetSlotIndex, slotInfo) + else: + print(f"[Voice Changer] {sample.voiceChangerType} is not supported.") + + # ダウンロード + print("[Voice Changer] Downloading model files...") + with ThreadPoolExecutor() as pool: + pool.map(download, downloadParams) + + # メタデータ作成 + print("[Voice Changer] Generating metadata...") + for targetSlotIndex in slotIndex: + slotInfo = loadSlotInfo(model_dir, targetSlotIndex) + if slotInfo.voiceChangerType == "RVC": + if slotInfo.isONNX: + _setInfoByONNX(slotInfo) + else: + _setInfoByPytorch(slotInfo) + saveSlotInfo(model_dir, targetSlotIndex, slotInfo) diff --git a/server/utils/downloader/WeightDownloader.py b/server/utils/downloader/WeightDownloader.py new file mode 100644 index 00000000..1bb42ef8 --- /dev/null +++ b/server/utils/downloader/WeightDownloader.py @@ -0,0 +1,64 @@ +import os +from concurrent.futures import ThreadPoolExecutor + +from utils.downloader.Downloader import download +from voice_changer.utils.VoiceChangerParams import VoiceChangerParams +from Exceptions import WeightDownladException + + +def downloadWeight(voiceChangerParams: VoiceChangerParams): + hubert_base = voiceChangerParams.hubert_base + hubert_base_jp = voiceChangerParams.hubert_base_jp + hubert_soft = voiceChangerParams.hubert_soft + nsf_hifigan = voiceChangerParams.nsf_hifigan + + # file exists check (currently only for rvc) + downloadParams = [] + if os.path.exists(hubert_base) is False: + downloadParams.append( + { + "url": "https://huggingface.co/ddPn08/rvc-webui-models/resolve/main/embeddings/hubert_base.pt", + "saveTo": hubert_base, + "position": 0, + } + ) + if os.path.exists(hubert_base_jp) is False: + downloadParams.append( + { + "url": "https://huggingface.co/rinna/japanese-hubert-base/resolve/main/fairseq/model.pt", + "saveTo": hubert_base_jp, + "position": 1, + } + ) + if os.path.exists(hubert_soft) is False: + downloadParams.append( + { + "url": "https://huggingface.co/wok000/weights/resolve/main/ddsp-svc30/embedder/hubert-soft-0d54a1f4.pt", + "saveTo": hubert_soft, + "position": 2, + } + ) + if os.path.exists(nsf_hifigan) is False: + downloadParams.append( + { + "url": "https://huggingface.co/wok000/weights/resolve/main/ddsp-svc30/nsf_hifigan_20221211/model.bin", + "saveTo": nsf_hifigan, + "position": 3, + } + ) + nsf_hifigan_config = os.path.join(os.path.dirname(nsf_hifigan), "config.json") + + if os.path.exists(nsf_hifigan_config) is False: + downloadParams.append( + { + "url": "https://huggingface.co/wok000/weights/raw/main/ddsp-svc30/nsf_hifigan_20221211/config.json", + "saveTo": nsf_hifigan_config, + "position": 4, + } + ) + + with ThreadPoolExecutor() as pool: + pool.map(download, downloadParams) + + if os.path.exists(hubert_base) is False or os.path.exists(hubert_base_jp) is False or os.path.exists(hubert_soft) is False or os.path.exists(nsf_hifigan) is False or os.path.exists(nsf_hifigan_config) is False: + raise WeightDownladException() diff --git a/server/voice_changer/utils/VoiceChangerParams.py b/server/voice_changer/utils/VoiceChangerParams.py index 47e4855a..51f83995 100644 --- a/server/voice_changer/utils/VoiceChangerParams.py +++ b/server/voice_changer/utils/VoiceChangerParams.py @@ -11,4 +11,4 @@ class VoiceChangerParams: hubert_base_jp: str hubert_soft: str nsf_hifigan: str - rvc_sample_mode: str + sample_mode: str