infer_rvc_python 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infer_rvc_python/__init__.py +1 -0
- infer_rvc_python/lib/__init__.py +0 -0
- infer_rvc_python/lib/audio.py +21 -0
- infer_rvc_python/lib/infer_pack/__init__.py +0 -0
- infer_rvc_python/lib/infer_pack/attentions.py +417 -0
- infer_rvc_python/lib/infer_pack/commons.py +166 -0
- infer_rvc_python/lib/infer_pack/models.py +1142 -0
- infer_rvc_python/lib/infer_pack/modules.py +522 -0
- infer_rvc_python/lib/infer_pack/transforms.py +209 -0
- infer_rvc_python/lib/log_config.py +48 -0
- infer_rvc_python/lib/rmvpe.py +422 -0
- infer_rvc_python/main.py +967 -0
- infer_rvc_python/root_pipe.py +454 -0
- infer_rvc_python-1.3.0.dist-info/LICENSE +21 -0
- infer_rvc_python-1.3.0.dist-info/METADATA +175 -0
- infer_rvc_python-1.3.0.dist-info/RECORD +17 -0
- infer_rvc_python-1.3.0.dist-info/WHEEL +4 -0
infer_rvc_python/main.py
ADDED
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
from infer_rvc_python.lib.log_config import logger
|
|
2
|
+
import torch
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
import gc
|
|
5
|
+
import numpy as np
|
|
6
|
+
import os
|
|
7
|
+
import warnings
|
|
8
|
+
import threading
|
|
9
|
+
from tqdm import tqdm
|
|
10
|
+
from transformers import HubertConfig, HubertModel
|
|
11
|
+
from infer_rvc_python.lib.infer_pack.models import (
|
|
12
|
+
SynthesizerTrnMs256NSFsid,
|
|
13
|
+
SynthesizerTrnMs256NSFsid_nono,
|
|
14
|
+
SynthesizerTrnMs768NSFsid,
|
|
15
|
+
SynthesizerTrnMs768NSFsid_nono,
|
|
16
|
+
)
|
|
17
|
+
from infer_rvc_python.lib.audio import load_audio
|
|
18
|
+
import soundfile as sf
|
|
19
|
+
from scipy import signal
|
|
20
|
+
from time import time as ttime
|
|
21
|
+
import faiss
|
|
22
|
+
from infer_rvc_python.root_pipe import VC, change_rms, bh, ah
|
|
23
|
+
import librosa
|
|
24
|
+
from urllib.parse import urlparse
|
|
25
|
+
import copy
|
|
26
|
+
|
|
27
|
+
warnings.filterwarnings("ignore")
|
|
28
|
+
SUPPORTED_SAMPLE_RATES = [8000, 12000, 16000, 22050, 24000, 32000, 44100, 48000] # 96000
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Config:
|
|
32
|
+
def __init__(self, only_cpu=False):
|
|
33
|
+
self.device = "cuda:0"
|
|
34
|
+
self.is_half = True
|
|
35
|
+
self.n_cpu = 0
|
|
36
|
+
self.gpu_name = None
|
|
37
|
+
self.gpu_mem = None
|
|
38
|
+
(
|
|
39
|
+
self.x_pad,
|
|
40
|
+
self.x_query,
|
|
41
|
+
self.x_center,
|
|
42
|
+
self.x_max
|
|
43
|
+
) = self.device_config(only_cpu)
|
|
44
|
+
|
|
45
|
+
def device_config(self, only_cpu) -> tuple:
|
|
46
|
+
if torch.cuda.is_available() and not only_cpu:
|
|
47
|
+
i_device = int(self.device.split(":")[-1])
|
|
48
|
+
self.gpu_name = torch.cuda.get_device_name(i_device)
|
|
49
|
+
if (
|
|
50
|
+
("16" in self.gpu_name and "V100" not in self.gpu_name.upper())
|
|
51
|
+
or "P40" in self.gpu_name.upper()
|
|
52
|
+
or "1060" in self.gpu_name
|
|
53
|
+
or "1070" in self.gpu_name
|
|
54
|
+
or "1080" in self.gpu_name
|
|
55
|
+
):
|
|
56
|
+
logger.info(
|
|
57
|
+
"16/10 Series GPUs and P40 excel "
|
|
58
|
+
"in single-precision tasks."
|
|
59
|
+
)
|
|
60
|
+
self.is_half = False
|
|
61
|
+
else:
|
|
62
|
+
self.gpu_name = None
|
|
63
|
+
self.gpu_mem = int(
|
|
64
|
+
torch.cuda.get_device_properties(i_device).total_memory
|
|
65
|
+
/ 1024
|
|
66
|
+
/ 1024
|
|
67
|
+
/ 1024
|
|
68
|
+
+ 0.4
|
|
69
|
+
)
|
|
70
|
+
elif torch.backends.mps.is_available() and not only_cpu:
|
|
71
|
+
logger.info("Supported N-card not found, using MPS for inference")
|
|
72
|
+
self.device = "mps"
|
|
73
|
+
else:
|
|
74
|
+
logger.info("No supported N-card found, using CPU for inference")
|
|
75
|
+
self.device = "cpu"
|
|
76
|
+
self.is_half = False
|
|
77
|
+
|
|
78
|
+
if self.n_cpu == 0:
|
|
79
|
+
self.n_cpu = os.cpu_count()
|
|
80
|
+
|
|
81
|
+
if self.is_half:
|
|
82
|
+
# 6GB VRAM configuration
|
|
83
|
+
x_pad = 3
|
|
84
|
+
x_query = 10
|
|
85
|
+
x_center = 60
|
|
86
|
+
x_max = 65
|
|
87
|
+
else:
|
|
88
|
+
# 5GB VRAM configuration
|
|
89
|
+
x_pad = 1
|
|
90
|
+
x_query = 6
|
|
91
|
+
x_center = 38
|
|
92
|
+
x_max = 41
|
|
93
|
+
|
|
94
|
+
if self.gpu_mem is not None and self.gpu_mem <= 4:
|
|
95
|
+
x_pad = 1
|
|
96
|
+
x_query = 5
|
|
97
|
+
x_center = 30
|
|
98
|
+
x_max = 32
|
|
99
|
+
|
|
100
|
+
logger.info(
|
|
101
|
+
f"Config: Device is {self.device}, "
|
|
102
|
+
f"half precision is {self.is_half}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return x_pad, x_query, x_center, x_max
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
BASE_DOWNLOAD_LINK = "https://huggingface.co/r3gm/sonitranslate_voice_models/resolve/main/"
|
|
109
|
+
BASE_MODELS = [
|
|
110
|
+
"rmvpe.pt"
|
|
111
|
+
]
|
|
112
|
+
BASE_DIR = "."
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def load_file_from_url(
|
|
116
|
+
url: str,
|
|
117
|
+
model_dir: str,
|
|
118
|
+
file_name: str | None = None,
|
|
119
|
+
overwrite: bool = False,
|
|
120
|
+
progress: bool = True,
|
|
121
|
+
) -> str:
|
|
122
|
+
"""Download a file from `url` into `model_dir`,
|
|
123
|
+
using the file present if possible.
|
|
124
|
+
|
|
125
|
+
Returns the path to the downloaded file.
|
|
126
|
+
"""
|
|
127
|
+
os.makedirs(model_dir, exist_ok=True)
|
|
128
|
+
if not file_name:
|
|
129
|
+
parts = urlparse(url)
|
|
130
|
+
file_name = os.path.basename(parts.path)
|
|
131
|
+
cached_file = os.path.abspath(os.path.join(model_dir, file_name))
|
|
132
|
+
|
|
133
|
+
# Overwrite
|
|
134
|
+
if os.path.exists(cached_file):
|
|
135
|
+
if overwrite or os.path.getsize(cached_file) == 0:
|
|
136
|
+
os.remove(cached_file)
|
|
137
|
+
|
|
138
|
+
# Download
|
|
139
|
+
if not os.path.exists(cached_file):
|
|
140
|
+
logger.info(f'Downloading: "{url}" to {cached_file}\n')
|
|
141
|
+
from torch.hub import download_url_to_file
|
|
142
|
+
|
|
143
|
+
download_url_to_file(url, cached_file, progress=progress)
|
|
144
|
+
else:
|
|
145
|
+
logger.debug(cached_file)
|
|
146
|
+
|
|
147
|
+
return cached_file
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def friendly_name(file: str):
|
|
151
|
+
if file.startswith("http"):
|
|
152
|
+
file = urlparse(file).path
|
|
153
|
+
|
|
154
|
+
file = os.path.basename(file)
|
|
155
|
+
model_name, extension = os.path.splitext(file)
|
|
156
|
+
return model_name, extension
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def download_manager(
|
|
160
|
+
url: str,
|
|
161
|
+
path: str,
|
|
162
|
+
extension: str = "",
|
|
163
|
+
overwrite: bool = False,
|
|
164
|
+
progress: bool = True,
|
|
165
|
+
):
|
|
166
|
+
url = url.strip()
|
|
167
|
+
|
|
168
|
+
name, ext = friendly_name(url)
|
|
169
|
+
name += ext if not extension else f".{extension}"
|
|
170
|
+
|
|
171
|
+
if url.startswith("http"):
|
|
172
|
+
filename = load_file_from_url(
|
|
173
|
+
url=url,
|
|
174
|
+
model_dir=path,
|
|
175
|
+
file_name=name,
|
|
176
|
+
overwrite=overwrite,
|
|
177
|
+
progress=progress,
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
filename = path
|
|
181
|
+
|
|
182
|
+
return filename
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class HubertModelWithFinalProj(HubertModel):
|
|
186
|
+
def __init__(self, config):
|
|
187
|
+
super().__init__(config)
|
|
188
|
+
self.final_proj = nn.Linear(config.hidden_size, config.classifier_proj_size)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class FairseqHubertWrapper(nn.Module):
|
|
192
|
+
def __init__(self, model_path_or_name="r3gm/hubert_base"):
|
|
193
|
+
super().__init__()
|
|
194
|
+
try:
|
|
195
|
+
self.model = HubertModelWithFinalProj.from_pretrained(model_path_or_name)
|
|
196
|
+
except Exception:
|
|
197
|
+
self.model = HubertModel.from_pretrained(model_path_or_name)
|
|
198
|
+
|
|
199
|
+
def extract_features(self, source, padding_mask=None, output_layer=12, **kwargs):
|
|
200
|
+
param_dtype = next(self.model.parameters()).dtype
|
|
201
|
+
if source.dtype != param_dtype:
|
|
202
|
+
source = source.to(param_dtype)
|
|
203
|
+
|
|
204
|
+
if source.dim() == 1:
|
|
205
|
+
source = source.unsqueeze(0)
|
|
206
|
+
|
|
207
|
+
with torch.no_grad():
|
|
208
|
+
outputs = self.model(source, output_hidden_states=True)
|
|
209
|
+
|
|
210
|
+
# 9 for v1, 12 for v2/ContentVec
|
|
211
|
+
if output_layer is None or output_layer >= len(outputs.hidden_states):
|
|
212
|
+
hidden_state = outputs.hidden_states[-1]
|
|
213
|
+
else:
|
|
214
|
+
hidden_state = outputs.hidden_states[output_layer]
|
|
215
|
+
|
|
216
|
+
return (hidden_state, None)
|
|
217
|
+
|
|
218
|
+
def final_proj(self, x):
|
|
219
|
+
if hasattr(self.model, "final_proj"):
|
|
220
|
+
return self.model.final_proj(x)
|
|
221
|
+
return x
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def load_hu_bert(config, hubert_path=None):
|
|
225
|
+
if hubert_path and (os.path.exists(hubert_path) or os.path.isdir(hubert_path)):
|
|
226
|
+
target_path = hubert_path
|
|
227
|
+
else:
|
|
228
|
+
target_path = "r3gm/hubert_base"
|
|
229
|
+
|
|
230
|
+
hubert_model = FairseqHubertWrapper(target_path)
|
|
231
|
+
hubert_model = hubert_model.to(config.device)
|
|
232
|
+
|
|
233
|
+
if config.is_half and torch.device(config.device).type != "cpu":
|
|
234
|
+
hubert_model = hubert_model.half()
|
|
235
|
+
else:
|
|
236
|
+
hubert_model = hubert_model.float()
|
|
237
|
+
|
|
238
|
+
hubert_model.eval()
|
|
239
|
+
return hubert_model
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def load_trained_model(model_path, config):
|
|
243
|
+
|
|
244
|
+
if not model_path:
|
|
245
|
+
raise ValueError("No model found")
|
|
246
|
+
|
|
247
|
+
logger.info("Loading %s" % model_path)
|
|
248
|
+
cpt = torch.load(model_path, map_location="cpu")
|
|
249
|
+
tgt_sr = cpt["config"][-1]
|
|
250
|
+
cpt["config"][-3] = cpt["weight"]["emb_g.weight"].shape[0] # n_spk
|
|
251
|
+
if_f0 = cpt.get("f0", 1)
|
|
252
|
+
if if_f0 == 0:
|
|
253
|
+
# protect to 0.5 need?
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
version = cpt.get("version", "v1")
|
|
257
|
+
if version == "v1":
|
|
258
|
+
if if_f0 == 1:
|
|
259
|
+
net_g = SynthesizerTrnMs256NSFsid(
|
|
260
|
+
*cpt["config"], is_half=config.is_half
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
net_g = SynthesizerTrnMs256NSFsid_nono(*cpt["config"])
|
|
264
|
+
elif version == "v2":
|
|
265
|
+
if if_f0 == 1:
|
|
266
|
+
net_g = SynthesizerTrnMs768NSFsid(
|
|
267
|
+
*cpt["config"], is_half=config.is_half
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
net_g = SynthesizerTrnMs768NSFsid_nono(*cpt["config"])
|
|
271
|
+
del net_g.enc_q
|
|
272
|
+
|
|
273
|
+
net_g.load_state_dict(cpt["weight"], strict=False)
|
|
274
|
+
net_g.eval().to(config.device)
|
|
275
|
+
|
|
276
|
+
if config.is_half:
|
|
277
|
+
net_g = net_g.half()
|
|
278
|
+
else:
|
|
279
|
+
net_g = net_g.float()
|
|
280
|
+
|
|
281
|
+
vc = VC(tgt_sr, config)
|
|
282
|
+
n_spk = cpt["config"][-3]
|
|
283
|
+
|
|
284
|
+
return n_spk, tgt_sr, net_g, vc, cpt, version
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class BaseLoader:
|
|
288
|
+
def __init__(self, only_cpu=False, hubert_path=None, rmvpe_path=None):
|
|
289
|
+
self.model_config = {}
|
|
290
|
+
self.config = None
|
|
291
|
+
self.cache_model = {}
|
|
292
|
+
self.only_cpu = only_cpu
|
|
293
|
+
self.hubert_path = hubert_path
|
|
294
|
+
self.rmvpe_path = rmvpe_path
|
|
295
|
+
|
|
296
|
+
def apply_conf(
|
|
297
|
+
self,
|
|
298
|
+
tag="base_model",
|
|
299
|
+
file_model="",
|
|
300
|
+
pitch_algo="pm",
|
|
301
|
+
pitch_lvl=0,
|
|
302
|
+
file_index="",
|
|
303
|
+
index_influence=0.66,
|
|
304
|
+
respiration_median_filtering=3,
|
|
305
|
+
envelope_ratio=0.25,
|
|
306
|
+
consonant_breath_protection=0.33,
|
|
307
|
+
resample_sr=0,
|
|
308
|
+
file_pitch_algo="",
|
|
309
|
+
):
|
|
310
|
+
|
|
311
|
+
if not file_model:
|
|
312
|
+
raise ValueError("Model not found")
|
|
313
|
+
|
|
314
|
+
if file_index is None:
|
|
315
|
+
file_index = ""
|
|
316
|
+
|
|
317
|
+
if file_pitch_algo is None:
|
|
318
|
+
file_pitch_algo = ""
|
|
319
|
+
|
|
320
|
+
if not self.config:
|
|
321
|
+
self.config = Config(self.only_cpu)
|
|
322
|
+
self.hu_bert_model = None
|
|
323
|
+
self.model_pitch_estimator = None
|
|
324
|
+
|
|
325
|
+
self.model_config[tag] = {
|
|
326
|
+
"file_model": file_model,
|
|
327
|
+
"pitch_algo": pitch_algo,
|
|
328
|
+
"pitch_lvl": pitch_lvl, # no decimal
|
|
329
|
+
"file_index": file_index,
|
|
330
|
+
"index_influence": index_influence,
|
|
331
|
+
"respiration_median_filtering": respiration_median_filtering,
|
|
332
|
+
"envelope_ratio": envelope_ratio,
|
|
333
|
+
"consonant_breath_protection": consonant_breath_protection,
|
|
334
|
+
"resample_sr": resample_sr,
|
|
335
|
+
"file_pitch_algo": file_pitch_algo,
|
|
336
|
+
}
|
|
337
|
+
return f"CONFIGURATION APPLIED FOR {tag}: {file_model}"
|
|
338
|
+
|
|
339
|
+
def infer(
|
|
340
|
+
self,
|
|
341
|
+
task_id,
|
|
342
|
+
params,
|
|
343
|
+
# load model
|
|
344
|
+
n_spk,
|
|
345
|
+
tgt_sr,
|
|
346
|
+
net_g,
|
|
347
|
+
pipe,
|
|
348
|
+
cpt,
|
|
349
|
+
version,
|
|
350
|
+
if_f0,
|
|
351
|
+
# load index
|
|
352
|
+
index_rate,
|
|
353
|
+
index,
|
|
354
|
+
big_npy,
|
|
355
|
+
# load f0 file
|
|
356
|
+
inp_f0,
|
|
357
|
+
# audio file
|
|
358
|
+
input_audio_path,
|
|
359
|
+
overwrite,
|
|
360
|
+
type_output,
|
|
361
|
+
):
|
|
362
|
+
|
|
363
|
+
f0_method = params["pitch_algo"]
|
|
364
|
+
f0_up_key = params["pitch_lvl"]
|
|
365
|
+
filter_radius = params["respiration_median_filtering"]
|
|
366
|
+
resample_sr = params["resample_sr"]
|
|
367
|
+
rms_mix_rate = params["envelope_ratio"]
|
|
368
|
+
protect = params["consonant_breath_protection"]
|
|
369
|
+
base_sr = 16000
|
|
370
|
+
|
|
371
|
+
if isinstance(input_audio_path, tuple):
|
|
372
|
+
if f0_method == "harvest":
|
|
373
|
+
raise ValueError("Harvest not support from array")
|
|
374
|
+
audio = input_audio_path[0]
|
|
375
|
+
source_sr = input_audio_path[1]
|
|
376
|
+
if source_sr != base_sr:
|
|
377
|
+
audio = librosa.resample(
|
|
378
|
+
audio.astype(np.float32),
|
|
379
|
+
orig_sr=source_sr,
|
|
380
|
+
target_sr=base_sr
|
|
381
|
+
)
|
|
382
|
+
audio = audio.astype(np.float32).flatten()
|
|
383
|
+
elif not os.path.exists(input_audio_path):
|
|
384
|
+
raise ValueError(
|
|
385
|
+
"The audio file was not found or is not "
|
|
386
|
+
f"a valid file: {input_audio_path}"
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
audio = load_audio(input_audio_path, base_sr)
|
|
390
|
+
|
|
391
|
+
f0_up_key = int(f0_up_key)
|
|
392
|
+
|
|
393
|
+
# Normalize audio
|
|
394
|
+
audio_max = np.abs(audio).max() / 0.95
|
|
395
|
+
if audio_max > 1:
|
|
396
|
+
audio /= audio_max
|
|
397
|
+
|
|
398
|
+
times = [0, 0, 0]
|
|
399
|
+
|
|
400
|
+
# filters audio signal, pads it, computes sliding window sums,
|
|
401
|
+
# and extracts optimized time indices
|
|
402
|
+
audio = signal.filtfilt(bh, ah, audio)
|
|
403
|
+
audio_pad = np.pad(
|
|
404
|
+
audio, (pipe.window // 2, pipe.window // 2), mode="reflect"
|
|
405
|
+
)
|
|
406
|
+
opt_ts = []
|
|
407
|
+
if audio_pad.shape[0] > pipe.t_max:
|
|
408
|
+
audio_sum = np.zeros_like(audio)
|
|
409
|
+
for i in range(pipe.window):
|
|
410
|
+
audio_sum += audio_pad[i:i - pipe.window]
|
|
411
|
+
for t in range(pipe.t_center, audio.shape[0], pipe.t_center):
|
|
412
|
+
opt_ts.append(
|
|
413
|
+
t
|
|
414
|
+
- pipe.t_query
|
|
415
|
+
+ np.where(
|
|
416
|
+
np.abs(audio_sum[t - pipe.t_query: t + pipe.t_query])
|
|
417
|
+
== np.abs(audio_sum[t - pipe.t_query: t + pipe.t_query]).min()
|
|
418
|
+
)[0][0]
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
s = 0
|
|
422
|
+
audio_opt = []
|
|
423
|
+
t = None
|
|
424
|
+
t1 = ttime()
|
|
425
|
+
|
|
426
|
+
sid_value = 0
|
|
427
|
+
sid = torch.tensor(sid_value, device=pipe.device).unsqueeze(0).long()
|
|
428
|
+
|
|
429
|
+
# Pads audio symmetrically, calculates length divided by window size.
|
|
430
|
+
audio_pad = np.pad(audio, (pipe.t_pad, pipe.t_pad), mode="reflect")
|
|
431
|
+
p_len = audio_pad.shape[0] // pipe.window
|
|
432
|
+
|
|
433
|
+
# Estimates pitch from audio signal
|
|
434
|
+
pitch, pitchf = None, None
|
|
435
|
+
if if_f0 == 1:
|
|
436
|
+
pitch, pitchf = pipe.get_f0(
|
|
437
|
+
input_audio_path,
|
|
438
|
+
audio_pad,
|
|
439
|
+
p_len,
|
|
440
|
+
f0_up_key,
|
|
441
|
+
f0_method,
|
|
442
|
+
filter_radius,
|
|
443
|
+
inp_f0,
|
|
444
|
+
)
|
|
445
|
+
pitch = pitch[:p_len]
|
|
446
|
+
pitchf = pitchf[:p_len]
|
|
447
|
+
if pipe.device == "mps":
|
|
448
|
+
pitchf = pitchf.astype(np.float32)
|
|
449
|
+
pitch = torch.tensor(
|
|
450
|
+
pitch, device=pipe.device
|
|
451
|
+
).unsqueeze(0).long()
|
|
452
|
+
pitchf = torch.tensor(
|
|
453
|
+
pitchf, device=pipe.device
|
|
454
|
+
).unsqueeze(0).float()
|
|
455
|
+
|
|
456
|
+
t2 = ttime()
|
|
457
|
+
times[1] += t2 - t1
|
|
458
|
+
for t in opt_ts:
|
|
459
|
+
t = t // pipe.window * pipe.window
|
|
460
|
+
if if_f0 == 1:
|
|
461
|
+
pitch_slice = pitch[
|
|
462
|
+
:, s // pipe.window: (t + pipe.t_pad2) // pipe.window
|
|
463
|
+
]
|
|
464
|
+
pitchf_slice = pitchf[
|
|
465
|
+
:, s // pipe.window: (t + pipe.t_pad2) // pipe.window
|
|
466
|
+
]
|
|
467
|
+
else:
|
|
468
|
+
pitch_slice = None
|
|
469
|
+
pitchf_slice = None
|
|
470
|
+
|
|
471
|
+
audio_slice = audio_pad[s:t + pipe.t_pad2 + pipe.window]
|
|
472
|
+
audio_opt.append(
|
|
473
|
+
pipe.vc(
|
|
474
|
+
self.hu_bert_model,
|
|
475
|
+
net_g,
|
|
476
|
+
sid,
|
|
477
|
+
audio_slice,
|
|
478
|
+
pitch_slice,
|
|
479
|
+
pitchf_slice,
|
|
480
|
+
times,
|
|
481
|
+
index,
|
|
482
|
+
big_npy,
|
|
483
|
+
index_rate,
|
|
484
|
+
version,
|
|
485
|
+
protect,
|
|
486
|
+
)[pipe.t_pad_tgt:-pipe.t_pad_tgt]
|
|
487
|
+
)
|
|
488
|
+
s = t
|
|
489
|
+
|
|
490
|
+
pitch_end_slice = pitch[
|
|
491
|
+
:, t // pipe.window:
|
|
492
|
+
] if t is not None else pitch
|
|
493
|
+
pitchf_end_slice = pitchf[
|
|
494
|
+
:, t // pipe.window:
|
|
495
|
+
] if t is not None else pitchf
|
|
496
|
+
|
|
497
|
+
audio_opt.append(
|
|
498
|
+
pipe.vc(
|
|
499
|
+
self.hu_bert_model,
|
|
500
|
+
net_g,
|
|
501
|
+
sid,
|
|
502
|
+
audio_pad[t:],
|
|
503
|
+
pitch_end_slice,
|
|
504
|
+
pitchf_end_slice,
|
|
505
|
+
times,
|
|
506
|
+
index,
|
|
507
|
+
big_npy,
|
|
508
|
+
index_rate,
|
|
509
|
+
version,
|
|
510
|
+
protect,
|
|
511
|
+
)[pipe.t_pad_tgt:-pipe.t_pad_tgt]
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
audio_opt = np.concatenate(audio_opt)
|
|
515
|
+
if rms_mix_rate != 1:
|
|
516
|
+
audio_opt = change_rms(
|
|
517
|
+
audio, 16000, audio_opt, tgt_sr, rms_mix_rate
|
|
518
|
+
)
|
|
519
|
+
if resample_sr >= 16000 and tgt_sr != resample_sr:
|
|
520
|
+
audio_opt = librosa.resample(
|
|
521
|
+
audio_opt, orig_sr=tgt_sr, target_sr=resample_sr
|
|
522
|
+
)
|
|
523
|
+
audio_max = np.abs(audio_opt).max() / 0.99
|
|
524
|
+
max_int16 = 32768
|
|
525
|
+
if audio_max > 1:
|
|
526
|
+
max_int16 /= audio_max
|
|
527
|
+
audio_opt = (audio_opt * max_int16).astype(np.int16)
|
|
528
|
+
del pitch, pitchf, sid
|
|
529
|
+
if torch.cuda.is_available():
|
|
530
|
+
torch.cuda.empty_cache()
|
|
531
|
+
|
|
532
|
+
if tgt_sr != resample_sr >= 16000:
|
|
533
|
+
final_sr = resample_sr
|
|
534
|
+
else:
|
|
535
|
+
final_sr = tgt_sr
|
|
536
|
+
|
|
537
|
+
"""
|
|
538
|
+
"Success.\n %s\nTime:\n npy:%ss, f0:%ss, infer:%ss" % (
|
|
539
|
+
times[0],
|
|
540
|
+
times[1],
|
|
541
|
+
times[2],
|
|
542
|
+
), (final_sr, audio_opt)
|
|
543
|
+
|
|
544
|
+
"""
|
|
545
|
+
|
|
546
|
+
if type_output == "array":
|
|
547
|
+
return audio_opt, final_sr
|
|
548
|
+
|
|
549
|
+
if overwrite:
|
|
550
|
+
output_audio_path = input_audio_path # Overwrite
|
|
551
|
+
type_output = os.path.splitext(output_audio_path)[1].lstrip(".")
|
|
552
|
+
else:
|
|
553
|
+
basename = os.path.basename(input_audio_path)
|
|
554
|
+
dirname = os.path.dirname(input_audio_path)
|
|
555
|
+
|
|
556
|
+
name_, ext_ = os.path.splitext(basename)
|
|
557
|
+
new_basename = f"{name_}_edited{ext_}"
|
|
558
|
+
new_path = os.path.join(dirname, new_basename)
|
|
559
|
+
|
|
560
|
+
output_audio_path = new_path
|
|
561
|
+
|
|
562
|
+
# Save file
|
|
563
|
+
if not type_output:
|
|
564
|
+
type_output = "wav"
|
|
565
|
+
output_audio_path = os.path.splitext(
|
|
566
|
+
output_audio_path
|
|
567
|
+
)[0]+f".{type_output}"
|
|
568
|
+
|
|
569
|
+
try:
|
|
570
|
+
if type_output.lower() == "wav":
|
|
571
|
+
sf.write(file=output_audio_path, samplerate=final_sr, data=audio_opt)
|
|
572
|
+
else:
|
|
573
|
+
target_sr = min(SUPPORTED_SAMPLE_RATES, key=lambda altsr: abs(altsr - final_sr))
|
|
574
|
+
if target_sr != final_sr:
|
|
575
|
+
logger.debug(f"Resampling from {final_sr} -> {target_sr} for {type_output}")
|
|
576
|
+
audio_opt = signal.resample_poly(audio_opt, target_sr, final_sr, axis=0).astype(np.int16)
|
|
577
|
+
sf.write(file=output_audio_path, samplerate=target_sr, data=audio_opt, format=type_output)
|
|
578
|
+
except Exception as e:
|
|
579
|
+
logger.error(e)
|
|
580
|
+
logger.error("Error saving file, trying with WAV format")
|
|
581
|
+
output_audio_path = os.path.splitext(output_audio_path)[0]+".wav"
|
|
582
|
+
sf.write(
|
|
583
|
+
file=output_audio_path,
|
|
584
|
+
samplerate=final_sr,
|
|
585
|
+
data=audio_opt
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
logger.info(str(output_audio_path))
|
|
589
|
+
|
|
590
|
+
self.model_config[task_id]["result"].append(output_audio_path)
|
|
591
|
+
self.output_list.append(output_audio_path)
|
|
592
|
+
|
|
593
|
+
def run_threads(self, threads):
|
|
594
|
+
# Start threads
|
|
595
|
+
for thread in threads:
|
|
596
|
+
thread.start()
|
|
597
|
+
|
|
598
|
+
# Wait for all threads to finish
|
|
599
|
+
for thread in threads:
|
|
600
|
+
thread.join()
|
|
601
|
+
|
|
602
|
+
gc.collect()
|
|
603
|
+
torch.cuda.empty_cache()
|
|
604
|
+
|
|
605
|
+
def unload_models(self):
|
|
606
|
+
self.hu_bert_model = None
|
|
607
|
+
self.model_pitch_estimator = None
|
|
608
|
+
self.model_vc = {}
|
|
609
|
+
self.cache_model = {}
|
|
610
|
+
gc.collect()
|
|
611
|
+
torch.cuda.empty_cache()
|
|
612
|
+
|
|
613
|
+
def __call__(
|
|
614
|
+
self,
|
|
615
|
+
audio_files=[],
|
|
616
|
+
tag_list=[],
|
|
617
|
+
overwrite=False,
|
|
618
|
+
parallel_workers=1,
|
|
619
|
+
type_output=None, # ["mp3", "wav", "flac"]
|
|
620
|
+
):
|
|
621
|
+
logger.info(f"Parallel workers: {str(parallel_workers)}")
|
|
622
|
+
|
|
623
|
+
self.output_list = []
|
|
624
|
+
|
|
625
|
+
if not self.model_config:
|
|
626
|
+
raise ValueError("No model has been configured for inference")
|
|
627
|
+
|
|
628
|
+
if isinstance(audio_files, str):
|
|
629
|
+
audio_files = [audio_files]
|
|
630
|
+
if isinstance(tag_list, str):
|
|
631
|
+
tag_list = [tag_list]
|
|
632
|
+
|
|
633
|
+
if not audio_files:
|
|
634
|
+
raise ValueError("No audio found to convert")
|
|
635
|
+
if not tag_list:
|
|
636
|
+
tag_list = [list(self.model_config.keys())[-1]] * len(audio_files)
|
|
637
|
+
|
|
638
|
+
if len(audio_files) > len(tag_list):
|
|
639
|
+
logger.info("Extend tag list to match audio files")
|
|
640
|
+
extend_number = len(audio_files) - len(tag_list)
|
|
641
|
+
tag_list.extend([tag_list[0]] * extend_number)
|
|
642
|
+
|
|
643
|
+
if len(audio_files) < len(tag_list):
|
|
644
|
+
logger.info("Cut list tags")
|
|
645
|
+
tag_list = tag_list[:len(audio_files)]
|
|
646
|
+
|
|
647
|
+
tag_file_pairs = list(zip(tag_list, audio_files))
|
|
648
|
+
sorted_tag_file = sorted(tag_file_pairs, key=lambda x: x[0])
|
|
649
|
+
|
|
650
|
+
# Base params
|
|
651
|
+
if not self.hu_bert_model:
|
|
652
|
+
self.hu_bert_model = load_hu_bert(self.config, self.hubert_path)
|
|
653
|
+
|
|
654
|
+
cache_params = None
|
|
655
|
+
threads = []
|
|
656
|
+
progress_bar = tqdm(total=len(tag_list), desc="Progress")
|
|
657
|
+
for i, (id_tag, input_audio_path) in enumerate(sorted_tag_file):
|
|
658
|
+
|
|
659
|
+
if id_tag not in self.model_config.keys():
|
|
660
|
+
logger.info(
|
|
661
|
+
f"No configured model for {id_tag} with {input_audio_path}"
|
|
662
|
+
)
|
|
663
|
+
continue
|
|
664
|
+
|
|
665
|
+
if (
|
|
666
|
+
len(threads) >= parallel_workers
|
|
667
|
+
or cache_params != id_tag
|
|
668
|
+
and cache_params is not None
|
|
669
|
+
):
|
|
670
|
+
|
|
671
|
+
self.run_threads(threads)
|
|
672
|
+
progress_bar.update(len(threads))
|
|
673
|
+
|
|
674
|
+
threads = []
|
|
675
|
+
|
|
676
|
+
if cache_params != id_tag:
|
|
677
|
+
|
|
678
|
+
self.model_config[id_tag]["result"] = []
|
|
679
|
+
|
|
680
|
+
# Unload previous
|
|
681
|
+
(
|
|
682
|
+
n_spk,
|
|
683
|
+
tgt_sr,
|
|
684
|
+
net_g,
|
|
685
|
+
pipe,
|
|
686
|
+
cpt,
|
|
687
|
+
version,
|
|
688
|
+
if_f0,
|
|
689
|
+
index_rate,
|
|
690
|
+
index,
|
|
691
|
+
big_npy,
|
|
692
|
+
inp_f0,
|
|
693
|
+
) = [None] * 11
|
|
694
|
+
gc.collect()
|
|
695
|
+
torch.cuda.empty_cache()
|
|
696
|
+
|
|
697
|
+
# Model params
|
|
698
|
+
params = self.model_config[id_tag]
|
|
699
|
+
|
|
700
|
+
model_path = params["file_model"]
|
|
701
|
+
f0_method = params["pitch_algo"]
|
|
702
|
+
file_index = params["file_index"]
|
|
703
|
+
index_rate = params["index_influence"]
|
|
704
|
+
f0_file = params["file_pitch_algo"]
|
|
705
|
+
|
|
706
|
+
# Load model
|
|
707
|
+
(
|
|
708
|
+
n_spk,
|
|
709
|
+
tgt_sr,
|
|
710
|
+
net_g,
|
|
711
|
+
pipe,
|
|
712
|
+
cpt,
|
|
713
|
+
version
|
|
714
|
+
) = load_trained_model(model_path, self.config)
|
|
715
|
+
if_f0 = cpt.get("f0", 1) # pitch data
|
|
716
|
+
|
|
717
|
+
# Load index
|
|
718
|
+
if os.path.exists(file_index) and index_rate != 0:
|
|
719
|
+
try:
|
|
720
|
+
index = faiss.read_index(file_index)
|
|
721
|
+
big_npy = index.reconstruct_n(0, index.ntotal)
|
|
722
|
+
except Exception as error:
|
|
723
|
+
logger.error(f"Index: {str(error)}")
|
|
724
|
+
index_rate = 0
|
|
725
|
+
index = big_npy = None
|
|
726
|
+
else:
|
|
727
|
+
logger.warning("File index not found")
|
|
728
|
+
index_rate = 0
|
|
729
|
+
index = big_npy = None
|
|
730
|
+
|
|
731
|
+
# Load f0 file
|
|
732
|
+
inp_f0 = None
|
|
733
|
+
if os.path.exists(f0_file):
|
|
734
|
+
try:
|
|
735
|
+
with open(f0_file, "r") as f:
|
|
736
|
+
lines = f.read().strip("\n").split("\n")
|
|
737
|
+
inp_f0 = []
|
|
738
|
+
for line in lines:
|
|
739
|
+
inp_f0.append([float(i) for i in line.split(",")])
|
|
740
|
+
inp_f0 = np.array(inp_f0, dtype="float32")
|
|
741
|
+
except Exception as error:
|
|
742
|
+
logger.error(f"f0 file: {str(error)}")
|
|
743
|
+
|
|
744
|
+
if "rmvpe" in f0_method:
|
|
745
|
+
if not self.model_pitch_estimator:
|
|
746
|
+
from infer_rvc_python.lib.rmvpe import RMVPE
|
|
747
|
+
|
|
748
|
+
logger.info("Loading vocal pitch estimator model")
|
|
749
|
+
if self.rmvpe_path is None:
|
|
750
|
+
self.rmvpe_path = ""
|
|
751
|
+
rm_local_path = "rmvpe.pt"
|
|
752
|
+
if os.path.exists(self.rmvpe_path):
|
|
753
|
+
rm_local_path = self.rmvpe_path
|
|
754
|
+
else:
|
|
755
|
+
download_manager(
|
|
756
|
+
os.path.join(BASE_DOWNLOAD_LINK, "rmvpe.pt"), BASE_DIR
|
|
757
|
+
)
|
|
758
|
+
self.model_pitch_estimator = RMVPE(
|
|
759
|
+
rm_local_path,
|
|
760
|
+
is_half=self.config.is_half,
|
|
761
|
+
device=self.config.device
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
pipe.model_rmvpe = self.model_pitch_estimator
|
|
765
|
+
|
|
766
|
+
cache_params = id_tag
|
|
767
|
+
|
|
768
|
+
# self.infer(
|
|
769
|
+
# id_tag,
|
|
770
|
+
# params,
|
|
771
|
+
# # load model
|
|
772
|
+
# n_spk,
|
|
773
|
+
# tgt_sr,
|
|
774
|
+
# net_g,
|
|
775
|
+
# pipe,
|
|
776
|
+
# cpt,
|
|
777
|
+
# version,
|
|
778
|
+
# if_f0,
|
|
779
|
+
# # load index
|
|
780
|
+
# index_rate,
|
|
781
|
+
# index,
|
|
782
|
+
# big_npy,
|
|
783
|
+
# # load f0 file
|
|
784
|
+
# inp_f0,
|
|
785
|
+
# # output file
|
|
786
|
+
# input_audio_path,
|
|
787
|
+
# overwrite,
|
|
788
|
+
# type_output,
|
|
789
|
+
# )
|
|
790
|
+
|
|
791
|
+
thread = threading.Thread(
|
|
792
|
+
target=self.infer,
|
|
793
|
+
args=(
|
|
794
|
+
id_tag,
|
|
795
|
+
params,
|
|
796
|
+
# loaded model
|
|
797
|
+
n_spk,
|
|
798
|
+
tgt_sr,
|
|
799
|
+
net_g,
|
|
800
|
+
pipe,
|
|
801
|
+
cpt,
|
|
802
|
+
version,
|
|
803
|
+
if_f0,
|
|
804
|
+
# loaded index
|
|
805
|
+
index_rate,
|
|
806
|
+
index,
|
|
807
|
+
big_npy,
|
|
808
|
+
# loaded f0 file
|
|
809
|
+
inp_f0,
|
|
810
|
+
# audio file
|
|
811
|
+
input_audio_path,
|
|
812
|
+
overwrite,
|
|
813
|
+
type_output,
|
|
814
|
+
)
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
threads.append(thread)
|
|
818
|
+
|
|
819
|
+
# Run last
|
|
820
|
+
if threads:
|
|
821
|
+
self.run_threads(threads)
|
|
822
|
+
|
|
823
|
+
progress_bar.update(len(threads))
|
|
824
|
+
progress_bar.close()
|
|
825
|
+
|
|
826
|
+
final_result = []
|
|
827
|
+
valid_tags = set(tag_list)
|
|
828
|
+
for tag in valid_tags:
|
|
829
|
+
if (
|
|
830
|
+
tag in self.model_config.keys()
|
|
831
|
+
and "result" in self.model_config[tag].keys()
|
|
832
|
+
):
|
|
833
|
+
final_result.extend(self.model_config[tag]["result"])
|
|
834
|
+
|
|
835
|
+
return final_result
|
|
836
|
+
|
|
837
|
+
def generate_from_cache(
|
|
838
|
+
self,
|
|
839
|
+
audio_data=None, # str or tuple (<array data>,<int sampling rate>)
|
|
840
|
+
tag=None,
|
|
841
|
+
reload=False,
|
|
842
|
+
):
|
|
843
|
+
|
|
844
|
+
if not self.model_config:
|
|
845
|
+
raise ValueError("No model has been configured for inference")
|
|
846
|
+
|
|
847
|
+
if not audio_data:
|
|
848
|
+
raise ValueError(
|
|
849
|
+
"An audio file or tuple with "
|
|
850
|
+
"(<numpy data audio>,<sampling rate>) is needed"
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
# Base params
|
|
854
|
+
if not self.hu_bert_model:
|
|
855
|
+
self.hu_bert_model = load_hu_bert(self.config, self.hubert_path)
|
|
856
|
+
|
|
857
|
+
if tag not in self.model_config.keys():
|
|
858
|
+
raise ValueError(
|
|
859
|
+
f"No configured model for {tag}"
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
now_data = self.model_config[tag]
|
|
863
|
+
now_data["tag"] = tag
|
|
864
|
+
|
|
865
|
+
if self.cache_model != now_data and not reload:
|
|
866
|
+
|
|
867
|
+
# Unload previous
|
|
868
|
+
self.model_vc = {}
|
|
869
|
+
gc.collect()
|
|
870
|
+
torch.cuda.empty_cache()
|
|
871
|
+
|
|
872
|
+
model_path = now_data["file_model"]
|
|
873
|
+
f0_method = now_data["pitch_algo"]
|
|
874
|
+
file_index = now_data["file_index"]
|
|
875
|
+
index_rate = now_data["index_influence"]
|
|
876
|
+
f0_file = now_data["file_pitch_algo"]
|
|
877
|
+
|
|
878
|
+
# Load model
|
|
879
|
+
(
|
|
880
|
+
self.model_vc["n_spk"],
|
|
881
|
+
self.model_vc["tgt_sr"],
|
|
882
|
+
self.model_vc["net_g"],
|
|
883
|
+
self.model_vc["pipe"],
|
|
884
|
+
self.model_vc["cpt"],
|
|
885
|
+
self.model_vc["version"]
|
|
886
|
+
) = load_trained_model(model_path, self.config)
|
|
887
|
+
self.model_vc["if_f0"] = self.model_vc["cpt"].get("f0", 1)
|
|
888
|
+
|
|
889
|
+
# Load index
|
|
890
|
+
if os.path.exists(file_index) and index_rate != 0:
|
|
891
|
+
try:
|
|
892
|
+
index = faiss.read_index(file_index)
|
|
893
|
+
big_npy = index.reconstruct_n(0, index.ntotal)
|
|
894
|
+
except Exception as error:
|
|
895
|
+
logger.error(f"Index: {str(error)}")
|
|
896
|
+
index_rate = 0
|
|
897
|
+
index = big_npy = None
|
|
898
|
+
else:
|
|
899
|
+
logger.warning("File index not found")
|
|
900
|
+
index_rate = 0
|
|
901
|
+
index = big_npy = None
|
|
902
|
+
|
|
903
|
+
self.model_vc["index_rate"] = index_rate
|
|
904
|
+
self.model_vc["index"] = index
|
|
905
|
+
self.model_vc["big_npy"] = big_npy
|
|
906
|
+
|
|
907
|
+
# Load f0 file
|
|
908
|
+
inp_f0 = None
|
|
909
|
+
if os.path.exists(f0_file):
|
|
910
|
+
try:
|
|
911
|
+
with open(f0_file, "r") as f:
|
|
912
|
+
lines = f.read().strip("\n").split("\n")
|
|
913
|
+
inp_f0 = []
|
|
914
|
+
for line in lines:
|
|
915
|
+
inp_f0.append([float(i) for i in line.split(",")])
|
|
916
|
+
inp_f0 = np.array(inp_f0, dtype="float32")
|
|
917
|
+
except Exception as error:
|
|
918
|
+
logger.error(f"f0 file: {str(error)}")
|
|
919
|
+
|
|
920
|
+
self.model_vc["inp_f0"] = inp_f0
|
|
921
|
+
|
|
922
|
+
if "rmvpe" in f0_method:
|
|
923
|
+
if not self.model_pitch_estimator:
|
|
924
|
+
from infer_rvc_python.lib.rmvpe import RMVPE
|
|
925
|
+
|
|
926
|
+
logger.info("Loading vocal pitch estimator model")
|
|
927
|
+
if self.rmvpe_path is None:
|
|
928
|
+
self.rmvpe_path = ""
|
|
929
|
+
rm_local_path = "rmvpe.pt"
|
|
930
|
+
if os.path.exists(self.rmvpe_path):
|
|
931
|
+
rm_local_path = self.rmvpe_path
|
|
932
|
+
else:
|
|
933
|
+
download_manager(
|
|
934
|
+
os.path.join(BASE_DOWNLOAD_LINK, "rmvpe.pt"), BASE_DIR
|
|
935
|
+
)
|
|
936
|
+
self.model_pitch_estimator = RMVPE(
|
|
937
|
+
rm_local_path,
|
|
938
|
+
is_half=self.config.is_half,
|
|
939
|
+
device=self.config.device
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
self.model_vc["pipe"].model_rmvpe = self.model_pitch_estimator
|
|
943
|
+
|
|
944
|
+
self.cache_model = copy.deepcopy(now_data)
|
|
945
|
+
|
|
946
|
+
return self.infer(
|
|
947
|
+
tag,
|
|
948
|
+
now_data,
|
|
949
|
+
# load model
|
|
950
|
+
self.model_vc["n_spk"],
|
|
951
|
+
self.model_vc["tgt_sr"],
|
|
952
|
+
self.model_vc["net_g"],
|
|
953
|
+
self.model_vc["pipe"],
|
|
954
|
+
self.model_vc["cpt"],
|
|
955
|
+
self.model_vc["version"],
|
|
956
|
+
self.model_vc["if_f0"],
|
|
957
|
+
# load index
|
|
958
|
+
self.model_vc["index_rate"],
|
|
959
|
+
self.model_vc["index"],
|
|
960
|
+
self.model_vc["big_npy"],
|
|
961
|
+
# load f0 file
|
|
962
|
+
self.model_vc["inp_f0"],
|
|
963
|
+
# output file
|
|
964
|
+
audio_data,
|
|
965
|
+
False,
|
|
966
|
+
"array",
|
|
967
|
+
)
|