From 34aec2b98ee4ef82ef488129b61a7952af5226a3 Mon Sep 17 00:00:00 2001 From: 34j <55338215+34j@users.noreply.github.com> Date: Fri, 17 Mar 2023 21:55:37 +0900 Subject: [PATCH] feat: add gui (#3) --- README.md | 27 ++- docs/_static/gui.png | Bin 0 -> 28253 bytes poetry.lock | 26 ++- pyproject.toml | 6 + src/so_vits_svc_fork/__main__.py | 29 ++- src/so_vits_svc_fork/gui.py | 234 +++++++++++++++++++ src/so_vits_svc_fork/inference/infer_tool.py | 36 ++- 7 files changed, 328 insertions(+), 30 deletions(-) create mode 100644 docs/_static/gui.png create mode 100644 src/so_vits_svc_fork/gui.py diff --git a/README.md b/README.md index 711a6ac3..769cfc62 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ pip install so-vits-svc-fork ## Features not available in the original repo - **Realtime voice conversion** +- GUI available - Unified command-line interface (no need to run Python scripts) - Ready to use just by installing with `pip`. - Automatically download pretrained base model and HuBERT model @@ -51,7 +52,25 @@ pip install so-vits-svc-fork ## Usage -### Realtime Voice conversion +### Inference + +#### GUI + +![GUI](https://raw.githubusercontent.com/34j/so-vits-svc-fork/main/docs/_static/gui.png) + +```shell +svcg +``` + +#### CLI + +- Realtime (from microphone) + +```shell +svc --model-path source.wav +``` + +- File ```shell svc vc --model-path @@ -70,11 +89,7 @@ svc pre-hubert svc train ``` -### Inference - -```shell -svc --model-path source.wav -``` +### Further help For more details, run `svc -h` or `svc -h`. diff --git a/docs/_static/gui.png b/docs/_static/gui.png new file mode 100644 index 0000000000000000000000000000000000000000..dbc93c0bcba9cd2b36d0b75e878b474a9f1d7e10 GIT binary patch literal 28253 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.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] +[[package]] +name = "pebble" +version = "5.0.3" +description = "Threading and multiprocessing eye-candy." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pebble-5.0.3-py3-none-any.whl", hash = "sha256:8274aa0959f387b368ede47666129cbe5d123f276a1bd9cafe77e020194b2141"}, + {file = "Pebble-5.0.3.tar.gz", hash = "sha256:bdcfd9ea7e0aedb895b204177c19e6d6543d9962f4e3402ebab2175004863da8"}, +] + [[package]] name = "pillow" version = "9.4.0" @@ -3118,6 +3130,18 @@ files = [ {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, ] +[[package]] +name = "pysimplegui" +version = "4.60.4" +description = "Python GUIs for Humans. Launched in 2018. It's 2022 & PySimpleGUI is an ACTIVE & supported project. Super-simple to create custom GUI's. 325+ Demo programs & Cookbook for rapid start. Extensive documentation. Main docs at www.PySimpleGUI.org. Fun & your success are the focus. Examples using Machine Learning (GUI, OpenCV Integration), Rainmeter Style Desktop Widgets, Matplotlib + Pyplot, PIL support, add GUI to command line scripts, PDF & Image Viewers. Great for beginners & advanced GUI programmers." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "PySimpleGUI-4.60.4-py3-none-any.whl", hash = "sha256:e133fbd21779f0f125cebbc2a4e1f5a931a383738661013ff33ad525d5611eda"}, + {file = "PySimpleGUI-4.60.4.tar.gz", hash = "sha256:f88c82c301a51aea35be605dc060bcceb0dcb6682e16280544884701ab4b23ba"}, +] + [[package]] name = "pysimplevalidate" version = "0.2.12" @@ -4670,4 +4694,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.11" -content-hash = "bd0a5148f6634dc9b2df2d30a8752d0de8dc72d509827ea6b4245e12bfb34060" +content-hash = "c484b8f4456aa9c2c6964b1173b94cfed86b84643fe10adf55fd49714bfc8a16" diff --git a/pyproject.toml b/pyproject.toml index 560ab76a..ecf11490 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,10 @@ packages = [ [tool.poetry.scripts] so-vits-svc-fork = "so_vits_svc_fork.__main__:cli" svc = "so_vits_svc_fork.__main__:cli" +svcf = "so_vits_svc_fork.__main__:cli" +svcg = "so_vits_svc_fork.gui:main" +svc-gui = "so_vits_svc_fork.gui:main" +svcf-gui = "so_vits_svc_fork.gui:main" [tool.poetry.urls] "Bug Tracker" = "https://github.com/34j/so-vits-svc-fork/issues" @@ -55,6 +59,8 @@ tqdm-joblib = "*" tensorboardx = "*" pyinputplus = "*" cm-time = "^0.1.2" +pysimplegui = ">=4.6" +pebble = "^5.0.3" [tool.poetry.group.dev.dependencies] pre-commit = ">=3" diff --git a/src/so_vits_svc_fork/__main__.py b/src/so_vits_svc_fork/__main__.py index 4f4b57ed..a07c7532 100644 --- a/src/so_vits_svc_fork/__main__.py +++ b/src/so_vits_svc_fork/__main__.py @@ -17,18 +17,22 @@ import torch from rich.logging import RichHandler -IN_COLAB = os.getenv("COLAB_RELEASE_TAG") - -basicConfig( - level=INFO, - format="%(asctime)s %(message)s", - datefmt="[%X]", - handlers=[ - RichHandler() if not IN_COLAB else StreamHandler(), - FileHandler(f"{__name__.split('.')[0]}.log"), - ], -) -captureWarnings(True) + +def init_logger() -> None: + IN_COLAB = os.getenv("COLAB_RELEASE_TAG") + + basicConfig( + level=INFO, + format="%(asctime)s %(message)s", + datefmt="[%X]", + handlers=[ + RichHandler() if not IN_COLAB else StreamHandler(), + FileHandler(f"{__name__.split('.')[0]}.log"), + ], + ) + captureWarnings(True) + + LOG = getLogger(__name__) @@ -48,6 +52,7 @@ def cli(): To train a model, run pre-resample, pre-config, pre-hubert, train. To infer a model, run infer. """ + init_logger() @click.help_option("--help", "-h") diff --git a/src/so_vits_svc_fork/gui.py b/src/so_vits_svc_fork/gui.py new file mode 100644 index 00000000..d09ca2b8 --- /dev/null +++ b/src/so_vits_svc_fork/gui.py @@ -0,0 +1,234 @@ +from logging import getLogger +from pathlib import Path + +import PySimpleGUI as sg +import sounddevice as sd +import soundfile as sf +from pebble import ProcessPool + +from .__main__ import init_logger + +LOG = getLogger(__name__) + +init_logger() + + +def play_audio(path: Path | str): + if isinstance(path, Path): + path = path.as_posix() + data, sr = sf.read(path) + sd.play(data, sr) + + +def main(): + sg.theme("Dark") + model_candidates = list(sorted(Path("./logs/44k/").glob("G_*.pth"))) + layout = [ + [ + sg.Text("Model path: "), + sg.InputText( + key="model_path", + default_text=model_candidates[-1].as_posix() + if model_candidates + else "", + ), + sg.FileBrowse( + initial_folder="./logs/44k/" if Path("./logs/44k/").exists() else "." + ), + ], + [ + sg.Text("Config path: "), + sg.InputText( + key="config_path", + default_text="./configs/44k/config.json", + enable_events=True, + ), + sg.FileBrowse( + initial_folder="./configs/44k/" + if Path("./configs/44k/").exists() + else "." + ), + ], + [sg.Text("Speaker"), sg.Combo(values=[], key="speaker", size=(20, 1))], + [ + sg.Text("Input audio path:"), + sg.InputText(key="input_path"), + sg.FileBrowse(initial_folder="."), + sg.Button("Play", key="play_input"), + ], + [ + sg.Text("Silence threshold: "), + sg.Slider( + range=(-60.0, 0), + orientation="h", + key="silence_threshold", + default_value=-20, + resolution=0.1, + ), + ], + [ + sg.Checkbox( + key="auto_predict_f0", + default=True, + text="Auto predict F0 (Pitch may become unstable when turned on in real-time inference.)", + ) + ], + [ + sg.Text("Pitch: "), + sg.Slider( + range=(-20, 20), orientation="h", key="transpose", default_value=0 + ), + ], + [ + sg.Text("Cluster infer ratio: "), + sg.Slider( + range=(0, 1.0), + orientation="h", + key="cluster_infer_ratio", + default_value=0, + resolution=0.01, + ), + ], + [ + sg.Text("Cluster model path: "), + sg.InputText(key="cluster_model_path"), + sg.FileBrowse(), + ], + [ + sg.Text("Noise scale: "), + sg.Slider( + range=(0.0, 1.0), + orientation="h", + key="noise_scale", + default_value=0.4, + resolution=0.01, + ), + ], + [ + sg.Text("Pad seconds"), + sg.Slider( + range=(0.0, 1.0), + orientation="h", + key="pad_seconds", + default_value=0.1, + resolution=0.01, + ), + ], + [ + sg.Text("Crossfade seconds"), + sg.Slider( + range=(0, 0.6), + orientation="h", + key="crossfade_seconds", + default_value=0.1, + resolution=0.001, + ), + ], + [ + sg.Text("Block seconds"), + sg.Slider( + range=(0, 3.0), + orientation="h", + key="block_seconds", + default_value=1, + resolution=0.01, + ), + ], + [sg.Checkbox(key="use_gpu", default=True, text="Use GPU")], + [sg.Checkbox(key="auto_play", default=True, text="Auto play")], + [ + sg.Button("Infer", key="infer"), + sg.Button("(Re)Start Voice Changer", key="start_vc"), + sg.Button("Stop Voice Changer", key="stop_vc"), + ], + ] + + window = sg.Window( + f"{__name__.split('.')[0]}", layout + ) # , use_custom_titlebar=True) + with ProcessPool(max_workers=1) as pool: + future = None + while True: + event, values = window.read(100) + if event == sg.WIN_CLOSED: + break + + def update_combo() -> None: + from . import utils + + if Path(values["config_path"]).exists(): + hp = utils.get_hparams_from_file(values["config_path"]) + LOG.info(f"Loaded config from {values['config_path']}") + window["speaker"].update( + values=list(hp.__dict__["spk"].keys()), set_to_index=0 + ) + + if not event == sg.EVENT_TIMEOUT: + LOG.info(f"Event: {event}, values: {values}") + if values["speaker"] == "": + update_combo() + + if event == "config_path": + update_combo() + elif event == "infer": + from .inference_main import infer + + input_path = Path(values["input_path"]) + output_path = ( + input_path.parent / f"{input_path.stem}.out{input_path.suffix}" + ) + infer( + model_path=Path(values["model_path"]), + config_path=Path(values["config_path"]), + input_path=input_path, + output_path=output_path, + speaker=values["speaker"], + cluster_model_path=Path(values["cluster_model_path"]) + if values["cluster_model_path"] + else None, + transpose=values["transpose"], + auto_predict_f0=values["auto_predict_f0"], + cluster_infer_ratio=values["cluster_infer_ratio"], + noise_scale=values["noise_scale"], + db_thresh=values["silence_threshold"], + pad_seconds=values["pad_seconds"], + device="cuda" if values["use_gpu"] else "cpu", + ) + if values["auto_play"]: + pool.schedule(play_audio, args=[output_path]) + elif event == "play_input": + if Path(values["input_path"]).exists(): + pool.schedule(play_audio, args=[Path(values["input_path"])]) + elif event == "start_vc": + from .inference_main import realtime + + if future: + LOG.info("Canceling previous task") + future.cancel() + future = pool.schedule( + realtime, + kwargs=dict( + model_path=Path(values["model_path"]), + config_path=Path(values["config_path"]), + speaker=values["speaker"], + cluster_model_path=Path(values["cluster_model_path"]) + if values["cluster_model_path"] + else None, + transpose=values["transpose"], + auto_predict_f0=values["auto_predict_f0"], + cluster_infer_ratio=values["cluster_infer_ratio"], + noise_scale=values["noise_scale"], + crossfade_seconds=values["crossfade_seconds"], + db_thresh=values["silence_threshold"], + pad_seconds=values["pad_seconds"], + device="cuda" if values["use_gpu"] else "cpu", + block_seconds=values["block_seconds"], + ), + ) + elif event == "stop_vc": + if future: + future.cancel() + future = None + if future: + future.cancel() + window.close() diff --git a/src/so_vits_svc_fork/inference/infer_tool.py b/src/so_vits_svc_fork/inference/infer_tool.py index 0458af2e..1217f00a 100644 --- a/src/so_vits_svc_fork/inference/infer_tool.py +++ b/src/so_vits_svc_fork/inference/infer_tool.py @@ -119,13 +119,19 @@ def infer( ): audio = audio.astype(np.float32) # get speaker id - speaker_id = self.spk2id.__dict__.get(speaker) - if not speaker_id and isinstance(speaker, int): + if isinstance(speaker, int): if len(self.spk2id.__dict__) >= speaker: speaker_id = speaker + else: + raise ValueError( + f"Speaker id {speaker} >= number of speakers {len(self.spk2id.__dict__)}" + ) else: - LOG.warning(f"Speaker {speaker} is not found. Use speaker 0 instead.") - speaker_id = 0 + if speaker in self.spk2id.__dict__: + speaker_id = self.spk2id.__dict__[speaker] + else: + LOG.warning(f"Speaker {speaker} is not found. Use speaker 0 instead.") + speaker_id = 0 sid = torch.LongTensor([int(speaker_id)]).to(self.dev).unsqueeze(0) # get unit f0 @@ -167,7 +173,7 @@ def infer_silence( # slice config db_thresh: int = -40, pad_seconds: float = 0.5, - fade_seconds: float = 0.04, + # fade_seconds: float = 0.0, ) -> np.ndarray[Any, np.dtype[np.float32]]: chunks = slicer.cut(audio, self.target_sample, db_thresh=db_thresh) LOG.info(f"Cut audio into chunks {chunks}") @@ -197,9 +203,9 @@ def infer_silence( _audio = _audio[pad_len:-pad_len] # add fade - fade_len = int(self.target_sample * fade_seconds) - _audio[:fade_len] = _audio[:fade_len] * np.linspace(0, 1, fade_len) - _audio[-fade_len:] = _audio[-fade_len:] * np.linspace(1, 0, fade_len) + # fade_len = int(self.target_sample * fade_seconds) + # _audio[:fade_len] = _audio[:fade_len] * np.linspace(0, 1, fade_len) + # _audio[-fade_len:] = _audio[-fade_len:] * np.linspace(1, 0, fade_len) result_audio = np.concatenate([result_audio, pad_array(_audio, length)]) result_audio = result_audio[: audio.shape[0]] return result_audio @@ -238,6 +244,15 @@ def process( db_thresh: int = -40, pad_seconds: float = 0.5, ): + """ + chunks : ■■■■■■□□□□□□ + add last input:□■■■■■■ + ■□□□□□□ + infer :□■■■■■■ + ■□□□□□□ + crossfade :▲■■■■■ + ▲□□□□□ + """ if input_audio.ndim != 1: raise ValueError("Input audio must be 1-dimensional.") if input_audio.shape[0] < self.crossfade_len: @@ -286,15 +301,14 @@ def process( noise_scale=noise_scale, ) infered_audio_c = infered_audio_c.cpu().numpy() - infered_audio_c = infered_audio_c LOG.info(f"Concentrated Inferred shape: {infered_audio_c.shape}") assert infered_audio_c.shape[0] == input_audio_c.shape[0] # crossfade result = maad.util.crossfade( self.last_infered, infered_audio_c, 1, self.crossfade_len - )[: input_audio.shape[0]] + )[-(input_audio.shape[0] + self.crossfade_len) : -self.crossfade_len] LOG.info(f"Result shape: {result.shape}") assert result.shape[0] == input_audio.shape[0] - self.last_infered = infered_audio_c + self.last_infered = infered_audio_c[-self.crossfade_len - 1 :].copy() return result