Blane187 commited on
Commit
2b7fd6e
1 Parent(s): 7372340

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +13 -0
  2. README.md +44 -13
  3. api/base.py +32 -0
  4. api/main.py +15 -0
  5. api/svc.py +338 -0
  6. api/tool.py +39 -0
  7. api/vm.py +87 -0
  8. cluster/__init__.py +33 -0
  9. config.py +77 -0
  10. config/config.ini +3 -0
  11. frontend/.eslintrc.cjs +15 -0
  12. frontend/.gitignore +28 -0
  13. frontend/.prettierrc.json +8 -0
  14. frontend/README.md +57 -0
  15. frontend/babel.config.js +13 -0
  16. frontend/env.d.ts +1 -0
  17. frontend/index.html +13 -0
  18. frontend/package-lock.json +0 -0
  19. frontend/package.json +44 -0
  20. frontend/public/favicon.ico +0 -0
  21. frontend/src/App.vue +72 -0
  22. frontend/src/assets/base.css +74 -0
  23. frontend/src/assets/logo.svg +1 -0
  24. frontend/src/assets/main.css +35 -0
  25. frontend/src/components/GreetingAudio.vue +47 -0
  26. frontend/src/components/TheWelcome.vue +86 -0
  27. frontend/src/components/WelcomeItem.vue +86 -0
  28. frontend/src/components/icons/IconCommunity.vue +7 -0
  29. frontend/src/components/icons/IconDocumentation.vue +7 -0
  30. frontend/src/components/icons/IconEcosystem.vue +7 -0
  31. frontend/src/components/icons/IconSupport.vue +7 -0
  32. frontend/src/components/icons/IconTooling.vue +19 -0
  33. frontend/src/layout/LayoutFrame.vue +151 -0
  34. frontend/src/layout/SideBar.vue +40 -0
  35. frontend/src/main.ts +27 -0
  36. frontend/src/router/index.ts +30 -0
  37. frontend/src/stores/counter.ts +12 -0
  38. frontend/src/views/AboutView.vue +15 -0
  39. frontend/src/views/HomeView.vue +10 -0
  40. frontend/src/views/RMView.vue +134 -0
  41. frontend/src/views/SVCView.vue +320 -0
  42. frontend/tsconfig.app.json +14 -0
  43. frontend/tsconfig.json +14 -0
  44. frontend/tsconfig.node.json +9 -0
  45. frontend/tsconfig.vitest.json +10 -0
  46. frontend/vite.config.ts +14 -0
  47. frontend/vitest.config.ts +15 -0
  48. hubert/__init__.py +0 -0
  49. hubert/hubert_model.py +222 -0
  50. inference/__init__.py +0 -0
.gitignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ checkpoints/*
2
+ hubert/*.pt
3
+ *.wav
4
+
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ pretrained_models/
8
+ *.py[cod]
9
+ *$py.class
10
+
11
+ .idea/
12
+
13
+ temp/
README.md CHANGED
@@ -1,13 +1,44 @@
1
- ---
2
- title: VIST UI
3
- emoji:
4
- colorFrom: indigo
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 4.40.0
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SO-VITS-SVC 4.0 WEBUI
2
+
3
+ ## Features
4
+ - 支持so-vits-svc 4.0,添加4.0的一些参数;
5
+ - 支持批量转换,输出文件名暂定为原文件名,执行打包下载;
6
+ - 添加人声提取Tab,需要Spleeter;
7
+ - Python 3.8。
8
+ - Gradio框架:python main.py 简单快捷
9
+ - Tornado框架可以python maint.py 鲁棒性强点
10
+ - Tornado框架目前api only
11
+ - TBD:a frontend webpage for tornado framework
12
+
13
+
14
+
15
+ ## Install & Run
16
+ ### 预先下载的模型文件
17
+ + contentvec :[checkpoint_best_legacy_500.pt](https://ibm.box.com/s/z1wgl1stco8ffooyatzdwsqn2psd9lrr)
18
+ + 放在`hubert`目录下
19
+ ```shell
20
+ # 一键下载
21
+ # contentvec
22
+ http://obs.cstcloud.cn/share/obs/sankagenkeshi/checkpoint_best_legacy_500.pt
23
+ # 也可手动下载放在hubert目录
24
+ ```
25
+
26
+ ### 配置
27
+ 0. 新建"checkpoints"文件夹,并在文件夹下面新建角色名字的子文件夹
28
+ 1. 将模型文件命名为“model.pth”,并和kmeans_10000.pt与config.json一同拷贝到角色名的子文件夹中
29
+ 2. 运行
30
+ ```bash
31
+ pip install -r requirements.txt
32
+ pip install setuptools==59.5.0
33
+ ```
34
+ 安装依赖
35
+
36
+ 3. 运行
37
+ ```bash
38
+ python main.py
39
+ ```
40
+ 运行程序
41
+
42
+
43
+ ## Known Issue
44
+ - Gradio在本地运行OK,但是在服务端由于需要经过Nginx/Apache的反向代理以及Gradio的queue机制(websocket)经常reset导致不能正常运行。
api/base.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import tornado.web
4
+
5
+
6
+ # noinspection PyAbstractClass
7
+ class ApiHandler(tornado.web.RequestHandler):
8
+ def __init__(self, *args, **kwargs):
9
+ super().__init__(*args, **kwargs)
10
+ self.json_args = None
11
+
12
+ def set_default_headers(self):
13
+ # 跨域测试
14
+ if not self.application.settings['debug']:
15
+ return
16
+ self.set_header('Access-Control-Allow-Origin', '*')
17
+ self.set_header('Access-Control-Allow-Methods', 'OPTIONS, PUT, POST, GET, DELETE')
18
+ if 'Access-Control-Request-Headers' in self.request.headers:
19
+ self.set_header('Access-Control-Allow-Headers',
20
+ self.request.headers['Access-Control-Allow-Headers'])
21
+
22
+ def prepare(self):
23
+ if not self.request.headers.get('Content-Type', '').startswith('application/json'):
24
+ return
25
+ try:
26
+ self.json_args = json.loads(self.request.body)
27
+ except json.JSONDecodeError:
28
+ pass
29
+
30
+ async def options(self, *_args, **_kwargs):
31
+ # 跨域测试
32
+ self.set_status(204 if self.application.settings['debug'] else 405)
api/main.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import tornado.web
3
+
4
+
5
+ # noinspection PyAbstractClass
6
+ class MainHandler(tornado.web.StaticFileHandler):
7
+ """为了使用Vue Router的history模式,把不存在的文件请求转发到index.html"""
8
+ async def get(self, path, include_body=True):
9
+ try:
10
+ await super().get(path, include_body)
11
+ except tornado.web.HTTPError as e:
12
+ if e.status_code != 404:
13
+ raise
14
+ # 不存在的文件请求转发到index.html,交给前段路由
15
+ await super().get('index.html', include_body)
api/svc.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ import tempfile
4
+
5
+ from inference.infer_tool import Svc
6
+ from typing import *
7
+ import api.base
8
+ import os
9
+ import io
10
+ import wave
11
+ import numpy as np
12
+ from service.tool import audio_normalize, read_wav_file_to_numpy_array
13
+ from utils import get_hparams_from_file
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ _svc: Optional[Svc] = None
18
+ _model_paths: Optional[List] = None
19
+
20
+
21
+ def init():
22
+ global _svc, _model_paths
23
+ _svc = Svc()
24
+ _model_paths = []
25
+
26
+ # get the path to the parent directory
27
+ parent_dir = os.path.abspath(os.path.join(os.getcwd(), os.curdir))
28
+
29
+ # construct the path to the "checkpoints" directory
30
+ checkpoints_dir = os.path.join(parent_dir, "checkpoints")
31
+
32
+ logger.debug(f"CkPoints Dir: {checkpoints_dir}")
33
+
34
+ for root, dirs, files in os.walk(checkpoints_dir):
35
+ for dir in dirs:
36
+ _model_paths.append(dir)
37
+
38
+
39
+ # noinspection PyAbstractClass
40
+ class ModelListHandler(api.base.ApiHandler):
41
+ async def get(self):
42
+ self.write({
43
+ "code": 200,
44
+ "msg": "ok",
45
+ "data": _model_paths
46
+ })
47
+
48
+
49
+ # noinspection PyAbstractClass
50
+ class SwitchHandler(api.base.ApiHandler):
51
+ async def post(self):
52
+ model_name = self.get_argument("model", "") # model name
53
+ mode = self.get_argument("mode", "single") # running mode: single or batch
54
+ device = self.get_argument("device", "cuda") # "cpu" or "cuda"
55
+
56
+ if model_name == "":
57
+ self.set_status(400)
58
+ self.write({
59
+ "code": 400,
60
+ "msg": "未选择模型!",
61
+ "data": None
62
+ })
63
+ return
64
+
65
+ if mode not in ("single", "batch"):
66
+ self.set_status(400)
67
+ self.write({
68
+ "code": 400,
69
+ "msg": "运行模式选择错误!",
70
+ "data": None
71
+ })
72
+ return
73
+
74
+ if device not in ("cpu", "cuda"):
75
+ self.set_status(400)
76
+ self.write({
77
+ "code": 400,
78
+ "msg": "设备选择错误!",
79
+ "data": None
80
+ })
81
+ return
82
+
83
+ logger.debug(f"modelname: {model_name}\n"
84
+ f"mode: {mode}\n"
85
+ f"device: {device}\n")
86
+ try:
87
+ _svc.set_device(device=device)
88
+ logger.debug(f"Device set.")
89
+ _svc.load_checkpoint(path=model_name)
90
+ logger.debug(f"Model set.")
91
+ except Exception as e:
92
+ logger.exception(e)
93
+ self.set_status(500)
94
+ self.write({
95
+ "code": 500,
96
+ "msg": "system_error",
97
+ "data": None
98
+ })
99
+ return
100
+
101
+ self.write({
102
+ "code": 200,
103
+ "msg": "ok",
104
+ "data": {
105
+ "mode": mode
106
+ }
107
+ })
108
+
109
+
110
+ # noinspection PyAbstractClass
111
+ class SingleInferenceHandler(api.base.ApiHandler):
112
+ async def post(self):
113
+ try:
114
+ from scipy.io import wavfile
115
+
116
+ dsid = self.get_argument("dsid", "")
117
+ tran = self.get_argument("tran", "0")
118
+ th = self.get_argument("th", "-40.0")
119
+ ns = self.get_argument("ns", "0.4")
120
+ audiofile_dict = self.request.files.get("srcaudio", [])
121
+
122
+ if not audiofile_dict:
123
+ self.set_status(400)
124
+ self.write({
125
+ "code": 400,
126
+ "msg": "未上传文件!",
127
+ "data": None
128
+ })
129
+ return
130
+
131
+ if dsid == "":
132
+ self.set_status(400)
133
+ self.write({
134
+ "code": 400,
135
+ "msg": "未选择模型!",
136
+ "data": None
137
+ })
138
+ return
139
+
140
+ audiofile = audiofile_dict[0]
141
+ audio_filename = audiofile['filename']
142
+ audio_filebody = audiofile['body']
143
+ audio_fileext = os.path.splitext(audio_filename)[-1].lower()
144
+
145
+ with tempfile.NamedTemporaryFile(suffix=audio_fileext, delete=False) as temp_file:
146
+ temp_file.write(audio_filebody)
147
+ temp_file.close()
148
+
149
+ converted_file = await audio_normalize(full_filename=audio_filename, file_data=audio_filebody)
150
+ # if audio_fileext != ".wav":
151
+ # logger.debug(f"file format is {audio_fileext}, not wav\n"
152
+ # f"converting to standard wav data...")
153
+ # converted_file = await audio_normalize(full_filename=audio_filename, file_data=audio_filebody)
154
+ # logger.debug(f"wav conversion completed.")
155
+ # else:
156
+ # converted_file = temp_file.name
157
+
158
+ sampling_rate, audio_array = read_wav_file_to_numpy_array(converted_file)
159
+ os.remove(converted_file)
160
+
161
+ scraudio = (sampling_rate, audio_array)
162
+
163
+ logger.debug(f"read file {audio_filename}\n"
164
+ f"sampling rate: {sampling_rate}")
165
+
166
+ tran = float(tran)
167
+ th = float(th)
168
+ ns = float(ns)
169
+
170
+ hparams = get_hparams_from_file(f"checkpoints/{dsid}/config.json")
171
+ spk = hparams.spk
172
+ real_dsid = ""
173
+ for k, v in spk.items():
174
+ if v == 0:
175
+ real_dsid = k
176
+ logger.debug(f"read dsid is: {real_dsid}")
177
+
178
+ output_audio_sr, output_audio_array = _svc.inference(srcaudio=scraudio,
179
+ chara=real_dsid,
180
+ tran=tran,
181
+ slice_db=th,
182
+ ns=ns)
183
+
184
+ logger.debug(f"svc for {audio_filename} succeed. \n"
185
+ f"audio data type: {type(output_audio_array)}\n"
186
+ f"audio data sr: {output_audio_sr}")
187
+
188
+ logger.debug(f"start output data.")
189
+
190
+ # Convert the NumPy array to WAV format
191
+ with io.BytesIO() as wav_file:
192
+ wavfile.write(wav_file, sampling_rate, output_audio_array)
193
+ wav_data = wav_file.getvalue()
194
+
195
+ # set the response headers and body
196
+ self.set_header('Content-Type', 'audio/wav')
197
+ self.set_header('Content-Disposition', f'attachment; filename="svc_output.wav"')
198
+ self.write(wav_data)
199
+ await self.flush()
200
+ logger.debug(f"response completed.")
201
+ except Exception as e:
202
+ logger.exception(e)
203
+ self.set_status(500)
204
+ self.write({
205
+ "code": 500,
206
+ "msg": "system_error",
207
+ "data": None
208
+ })
209
+ return
210
+
211
+
212
+ # noinspection PyAbstractClass
213
+ class BatchInferenceHandler(api.base.ApiHandler):
214
+ async def post(self):
215
+ try:
216
+ from zipfile import ZipFile
217
+ from scipy.io import wavfile
218
+ import uuid
219
+
220
+ dsid = self.get_argument("dsid", "")
221
+ tran = self.get_argument("tran", "0")
222
+ th = self.get_argument("th", "-40.0")
223
+ ns = self.get_argument("ns", "0.4")
224
+ audiofile_dict = self.request.files.get("srcaudio", [])
225
+
226
+ logger.debug(len(self.request.files))
227
+
228
+ if not audiofile_dict:
229
+ self.set_status(400)
230
+ self.write({
231
+ "code": 400,
232
+ "msg": "未上传文件!",
233
+ "data": None
234
+ })
235
+ return
236
+
237
+ if dsid == "":
238
+ self.set_status(400)
239
+ self.write({
240
+ "code": 400,
241
+ "msg": "未选择模型!",
242
+ "data": None
243
+ })
244
+ return
245
+
246
+ temp_dir_name = "temp"
247
+
248
+ # get the path to the parent directory
249
+ parent_dir = os.path.abspath(os.path.join(os.getcwd(), os.curdir))
250
+
251
+ # construct the path to the "temp" directory
252
+ temp_dir = os.path.join(parent_dir, temp_dir_name)
253
+
254
+ logger.debug(f"TempDir: {temp_dir}")
255
+
256
+ if not os.path.exists(temp_dir):
257
+ os.mkdir(temp_dir)
258
+
259
+ tmp_workdir_name = f"{temp_dir}/batch_{uuid.uuid4()}"
260
+ if not os.path.exists(tmp_workdir_name):
261
+ os.mkdir(tmp_workdir_name)
262
+
263
+ output_files = []
264
+
265
+ tran = float(tran)
266
+ th = float(th)
267
+ ns = float(ns)
268
+
269
+ hparams = get_hparams_from_file(f"checkpoints/{dsid}/config.json")
270
+ spk = hparams.spk
271
+ real_dsid = ""
272
+ for k, v in spk.items():
273
+ if v == 0:
274
+ real_dsid = k
275
+ logger.debug(f"read dsid is: {real_dsid}")
276
+
277
+ for idx, file in enumerate(audiofile_dict):
278
+ audio_filename = file["filename"]
279
+ audio_filebody = file["body"]
280
+ filename = os.path.basename(audio_filename)
281
+ audio_fileext = os.path.splitext(audio_filename)[-1].lower()
282
+
283
+ with tempfile.NamedTemporaryFile(suffix=audio_fileext, delete=False) as temp_file:
284
+ temp_file.write(audio_filebody)
285
+ temp_file.close()
286
+
287
+ converted_file = await audio_normalize(full_filename=audio_filename, file_data=audio_filebody)
288
+
289
+ # if audio_fileext != ".wav":
290
+ # logger.debug(f"file format is {audio_fileext}, not wav\n"
291
+ # f"converting to standard wav data...")
292
+ # converted_file = await audio_normalize(full_filename=audio_filename, file_data=audio_filebody)
293
+ # logger.debug(f"wav conversion completed.")
294
+ # else:
295
+ # converted_file = temp_file.name
296
+
297
+ sampling_rate, audio_array = read_wav_file_to_numpy_array(converted_file)
298
+ os.remove(converted_file)
299
+
300
+ scraudio = (sampling_rate, audio_array)
301
+
302
+ print(f"{idx}, {len(audio_filebody)}, {filename}")
303
+
304
+ output_sampling_rate, output_audio = _svc.inference(scraudio, chara=real_dsid, tran=tran,
305
+ slice_db=th, ns=ns)
306
+ new_filepath = f"{tmp_workdir_name}/{filename}"
307
+ wavfile.write(filename=new_filepath, rate=output_sampling_rate, data=output_audio)
308
+ output_files.append(new_filepath)
309
+
310
+ zipfilename = f"{tmp_workdir_name}/output.zip"
311
+ with ZipFile(zipfilename, "w") as zip_obj:
312
+ for idx, filepath in enumerate(output_files):
313
+ zip_obj.write(filepath, os.path.basename(filepath))
314
+
315
+ # todo: remove data
316
+
317
+ logger.debug(f"start output data.")
318
+ # set response header and body
319
+ self.set_header("Content-Type", "application/zip")
320
+ self.set_header("Content-Disposition", "attachment; filename=output.zip")
321
+ with open(zipfilename, "rb") as file:
322
+ self.write(file.read())
323
+ await self.flush()
324
+ logger.debug(f"response completed.")
325
+ except Exception as e:
326
+ logger.exception(e)
327
+ self.set_status(500)
328
+ self.write({
329
+ "code": 500,
330
+ "msg": "system_error",
331
+ "data": None
332
+ })
333
+ return
334
+
335
+
336
+ if __name__ == "__main__":
337
+ init()
338
+ print(_model_paths)
api/tool.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os.path
3
+ import subprocess
4
+ import tempfile
5
+
6
+ import api.base
7
+ from service.tool import audio_normalize
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ # noinspection PyAbstractClass
13
+ class AudioNormalizerHandler(api.base.ApiHandler):
14
+ async def post(self):
15
+ try:
16
+ import uuid
17
+ uploaded_file = self.request.files['srcaudio'][0]
18
+ file_data = uploaded_file['body']
19
+ full_filename = uploaded_file['filename']
20
+
21
+ output_filename = await audio_normalize(full_filename=full_filename, file_data=file_data)
22
+
23
+ if not output_filename:
24
+ raise SystemError()
25
+
26
+ with open(output_filename, 'rb') as f:
27
+ self.set_header('Content-Type', 'audio/wav')
28
+ self.set_header('Content-Disposition', 'attachment; filename="converted_audio.wav"')
29
+ self.write(f.read())
30
+ await self.flush()
31
+ os.remove(output_filename)
32
+ except Exception as e:
33
+ logger.exception(e)
34
+ self.set_status(500)
35
+ self.write({
36
+ "code": 500,
37
+ "msg": "system_error",
38
+ "data": None
39
+ })
api/vm.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import logging
3
+ import os
4
+ import tempfile
5
+ import wave
6
+ import uuid
7
+ import zipfile
8
+ from zipfile import ZipFile
9
+
10
+ import numpy as np
11
+ from scipy.io import wavfile
12
+
13
+ import api.base
14
+ from service.tool import audio_normalize, read_wav_file_to_numpy_array
15
+ from vextract.vocal_extract import VEX
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ # noinspection PyAbstractClass
21
+ class VocalRemoverHandler(api.base.ApiHandler):
22
+ async def post(self):
23
+ try:
24
+ uploaded_file = self.request.files['srcaudio'][0]
25
+ audio_filebody = uploaded_file['body']
26
+ audio_filename = uploaded_file['filename']
27
+ audio_fileext = os.path.splitext(audio_filename)[-1].lower()
28
+
29
+ if audio_fileext != ".wav":
30
+ logger.debug(f"file format is {audio_fileext}, not wav\n"
31
+ f"converting to standard wav data...")
32
+ converted_file = await audio_normalize(full_filename=audio_filename, file_data=audio_filebody)
33
+ with wave.open(converted_file, 'rb') as wav_file:
34
+ num_frames = wav_file.getnframes()
35
+ audiofile_body = wav_file.readframes(num_frames)
36
+ logger.debug(f"wav conversion completed.")
37
+ os.remove(converted_file)
38
+
39
+ with tempfile.NamedTemporaryFile(suffix=audio_fileext, delete=False) as temp_wav:
40
+ temp_wav.write(audiofile_body)
41
+ temp_wav.close()
42
+
43
+ converted_file = await audio_normalize(full_filename=audio_filename, file_data=audio_filebody)
44
+
45
+ sampling_rate, audio_array = read_wav_file_to_numpy_array(converted_file)
46
+ os.remove(converted_file)
47
+
48
+ print(f"Input Audio Shape: {audio_array.shape}\n"
49
+ f"Input Audio Data Type: {audio_array.dtype}")
50
+
51
+ v = VEX()
52
+ [(vocal_sampling_rate, vocal_audio), (accompaniment_sampling_rate, accompaniment_audio)] = v.separate((sampling_rate, audio_array))
53
+
54
+ output_dirname = f"{uuid.uuid4()}"
55
+ output_dir = f"output/{output_dirname}"
56
+ if not os.path.exists(output_dir):
57
+ os.mkdir(output_dir)
58
+ wavfile.write(f"{output_dir}/vocals.wav", vocal_sampling_rate, vocal_audio)
59
+ wavfile.write(f"{output_dir}/accompaniment.wav", accompaniment_sampling_rate, accompaniment_audio)
60
+
61
+ zipfilename = f"{output_dir}/output.zip"
62
+ with ZipFile(zipfilename, 'w', zipfile.ZIP_DEFLATED) as zip_obj:
63
+ zip_obj.write(f"{output_dir}/vocals.wav")
64
+ zip_obj.write(f"{output_dir}/accompaniment.wav")
65
+
66
+ logger.debug(f"start output data.")
67
+ # set response header and body
68
+ self.set_header("Content-Type", "application/zip")
69
+ self.set_header("Content-Disposition", "attachment; filename=output.zip")
70
+
71
+ with open(zipfilename, "rb") as file:
72
+ self.write(file.read())
73
+ await self.flush()
74
+ logger.debug(f"response completed.")
75
+
76
+ os.remove(f"{output_dir}/vocals.wav")
77
+ os.remove(f"{output_dir}/accompaniment.wav")
78
+ os.remove(f"{output_dir}/output.zip")
79
+ os.rmdir(output_dir)
80
+ except Exception as e:
81
+ logger.exception(e)
82
+ self.set_status(500)
83
+ self.write({
84
+ "code": 500,
85
+ "msg": "system_error",
86
+ "data": None
87
+ })
cluster/__init__.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import torch
3
+ from sklearn.cluster import KMeans
4
+
5
+
6
+ def get_cluster_model(ckpt_path):
7
+ checkpoint = torch.load(ckpt_path)
8
+ kmeans_dict = {}
9
+ for spk, ckpt in checkpoint.items():
10
+ km = KMeans(ckpt["n_features_in_"])
11
+ km.__dict__["n_features_in_"] = ckpt["n_features_in_"]
12
+ km.__dict__["_n_threads"] = ckpt["_n_threads"]
13
+ km.__dict__["cluster_centers_"] = ckpt["cluster_centers_"]
14
+ kmeans_dict[spk] = km
15
+ return kmeans_dict
16
+
17
+
18
+ def get_cluster_result(model, x, speaker):
19
+ """
20
+ x: np.array [t, 256]
21
+ return cluster class result
22
+ """
23
+ return model[speaker].predict(x)
24
+
25
+
26
+ def get_cluster_center_result(model, x,speaker):
27
+ """x: np.array [t, 256]"""
28
+ predict = model[speaker].predict(x)
29
+ return model[speaker].cluster_centers_[predict]
30
+
31
+
32
+ def get_center(model, x,speaker):
33
+ return model[speaker].cluster_centers_[x]
config.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import configparser
4
+ import logging
5
+ import os
6
+ from typing import *
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ CONFIG_PATH_LIST = [
11
+ os.path.join('config', 'config.ini'),
12
+ os.path.join('config', 'config.example.ini')
13
+ ]
14
+
15
+ _config: Optional['AppConfig'] = None
16
+
17
+
18
+ def init():
19
+ if reload():
20
+ return
21
+ logger.warning('Using default config')
22
+ global _config
23
+ _config = AppConfig()
24
+
25
+
26
+ def reload():
27
+ config_path = ''
28
+ for path in CONFIG_PATH_LIST:
29
+ if os.path.exists(path):
30
+ config_path = path
31
+ break
32
+ if config_path == '':
33
+ return False
34
+
35
+ config = AppConfig()
36
+ if not config.load(config_path):
37
+ return False
38
+ global _config
39
+ _config = config
40
+ return True
41
+
42
+
43
+ def get_config():
44
+ return _config
45
+
46
+
47
+ class AppConfig:
48
+ def __init__(self):
49
+ self.database_url = 'sqlite:///data/database.db'
50
+ self.tornado_xheaders = False
51
+
52
+ def load(self, path):
53
+ try:
54
+ config = configparser.ConfigParser()
55
+ config.read(path, 'utf-8-sig')
56
+
57
+ self._load_app_config(config)
58
+ except Exception as e:
59
+ logger.exception(f'Failed to load config: {e}')
60
+ return False
61
+ return True
62
+
63
+ def _load_app_config(self, config):
64
+ app_section = config['app']
65
+ self.database_url = app_section['database_url']
66
+ self.tornado_xheaders = app_section.getboolean('tornado_xheaders')
67
+
68
+
69
+ def _str_to_list(value, item_type: Type = str, container_type: Type = list):
70
+ value = value.strip()
71
+ if value == '':
72
+ return container_type
73
+ items = value.split(',')
74
+ items = map(lambda item: item.strip(), items)
75
+ if item_type is not str:
76
+ items = map(lambda item: item_type(item), items)
77
+ return container_type(items)
config/config.ini ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [app]
2
+ database_url = sqlite:///data/database.db
3
+ tornado_xheaders = false
frontend/.eslintrc.cjs ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-env node */
2
+ require('@rushstack/eslint-patch/modern-module-resolution')
3
+
4
+ module.exports = {
5
+ root: true,
6
+ 'extends': [
7
+ 'plugin:vue/vue3-essential',
8
+ 'eslint:recommended',
9
+ '@vue/eslint-config-typescript',
10
+ '@vue/eslint-config-prettier/skip-formatting'
11
+ ],
12
+ parserOptions: {
13
+ ecmaVersion: 'latest'
14
+ }
15
+ }
frontend/.gitignore ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ .DS_Store
12
+ dist
13
+ dist-ssr
14
+ coverage
15
+ *.local
16
+
17
+ /cypress/videos/
18
+ /cypress/screenshots/
19
+
20
+ # Editor directories and files
21
+ lilyn-svs/.vscode/extensions.json
22
+ !.vscode/extensions.json
23
+ .idea
24
+ *.suo
25
+ *.ntvs*
26
+ *.njsproj
27
+ *.sln
28
+ *.sw?
frontend/.prettierrc.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://json.schemastore.org/prettierrc",
3
+ "semi": false,
4
+ "tabWidth": 2,
5
+ "singleQuote": true,
6
+ "printWidth": 100,
7
+ "trailingComma": "none"
8
+ }
frontend/README.md ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SVS SYSTEM
2
+
3
+ ## Features
4
+ - 支持SVC模式切换
5
+ - 支持批量/单个处理
6
+
7
+ ## Todos
8
+ - 音频清洗(需要ffmpeg)
9
+ - VocalRemover
10
+ - DiffSinger support coming soon
11
+
12
+ ## Install & Run
13
+ ### 预先下载的模型文件
14
+ + 需要装有Node以及npm
15
+ + 在frontend文件夹下
16
+ ```shell
17
+ npm i
18
+ ```
19
+
20
+ ### 配置
21
+ 1. 修改/frontend/src/main.ts文件,将axios.defaults.baseURL修改为服务器运行的IP地址与端口号
22
+ 2. 运行
23
+ ```bash
24
+ npm run buid
25
+ ```
26
+ 执行构建
27
+
28
+
29
+ ## Some npm helpers
30
+
31
+ ```sh
32
+ npm install
33
+ ```
34
+
35
+ ### Compile and Hot-Reload for Development
36
+
37
+ ```sh
38
+ npm run dev
39
+ ```
40
+
41
+ ### Type-Check, Compile and Minify for Production
42
+
43
+ ```sh
44
+ npm run build
45
+ ```
46
+
47
+ ### Run Unit Tests with [Vitest](https://vitest.dev/)
48
+
49
+ ```sh
50
+ npm run test:unit
51
+ ```
52
+
53
+ ### Lint with [ESLint](https://eslint.org/)
54
+
55
+ ```sh
56
+ npm run lint
57
+ ```
frontend/babel.config.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ presets: [
3
+ '@vue/cli-plugin-babel/preset'
4
+ ],
5
+ plugins: [
6
+ [
7
+ 'component', {
8
+ 'libraryName': 'element-ui',
9
+ 'styleLibraryName': 'theme-chalk'
10
+ }
11
+ ]
12
+ ]
13
+ }
frontend/env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="vite/client" />
frontend/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <link rel="icon" href="/favicon.ico">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>SVS SYSTEM</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "sys-system",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "run-p type-check build-only",
8
+ "preview": "vite preview",
9
+ "test:unit": "vitest",
10
+ "build-only": "vite build",
11
+ "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
12
+ "lint": "eslint lilyn-svs --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
13
+ "format": "prettier --write src"
14
+ },
15
+ "dependencies": {
16
+ "@element-plus/icons-vue": "^2.1.0",
17
+ "axios": "^1.3.4",
18
+ "downloadjs": "^1.4.7",
19
+ "element-plus": "^2.3.1",
20
+ "jszip": "^3.10.1",
21
+ "pinia": "^2.0.32",
22
+ "vue": "^3.2.47",
23
+ "vue-router": "^4.1.6"
24
+ },
25
+ "devDependencies": {
26
+ "@rushstack/eslint-patch": "^1.2.0",
27
+ "@types/jsdom": "^21.1.0",
28
+ "@types/node": "^18.14.2",
29
+ "@vitejs/plugin-vue": "^4.0.0",
30
+ "@vue/eslint-config-prettier": "^7.1.0",
31
+ "@vue/eslint-config-typescript": "^11.0.2",
32
+ "@vue/test-utils": "^2.3.0",
33
+ "@vue/tsconfig": "^0.1.3",
34
+ "eslint": "^8.34.0",
35
+ "eslint-plugin-vue": "^9.9.0",
36
+ "jsdom": "^21.1.0",
37
+ "npm-run-all": "^4.1.5",
38
+ "prettier": "^2.8.4",
39
+ "typescript": "~4.8.4",
40
+ "vite": "^4.1.4",
41
+ "vitest": "^0.29.1",
42
+ "vue-tsc": "^1.2.0"
43
+ }
44
+ }
frontend/public/favicon.ico ADDED
frontend/src/App.vue ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div id="app">
3
+ <router-view></router-view>
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ </script>
9
+
10
+ <style scoped>
11
+ header {
12
+ line-height: 1.5;
13
+ max-height: 100vh;
14
+ }
15
+
16
+ .logo {
17
+ display: block;
18
+ margin: 0 auto 2rem;
19
+ }
20
+
21
+ nav {
22
+ width: 100%;
23
+ font-size: 12px;
24
+ text-align: center;
25
+ margin-top: 2rem;
26
+ }
27
+
28
+ nav a.router-link-exact-active {
29
+ color: var(--color-text);
30
+ }
31
+
32
+ nav a.router-link-exact-active:hover {
33
+ background-color: transparent;
34
+ }
35
+
36
+ nav a {
37
+ display: inline-block;
38
+ padding: 0 1rem;
39
+ border-left: 1px solid var(--color-border);
40
+ }
41
+
42
+ nav a:first-of-type {
43
+ border: 0;
44
+ }
45
+
46
+ @media (min-width: 1024px) {
47
+ header {
48
+ display: flex;
49
+ place-items: center;
50
+ padding-right: calc(var(--section-gap) / 2);
51
+ }
52
+
53
+ .logo {
54
+ margin: 0 2rem 0 0;
55
+ }
56
+
57
+ header .wrapper {
58
+ display: flex;
59
+ place-items: flex-start;
60
+ flex-wrap: wrap;
61
+ }
62
+
63
+ nav {
64
+ text-align: left;
65
+ margin-left: -1rem;
66
+ font-size: 1rem;
67
+
68
+ padding: 1rem 0;
69
+ margin-top: 1rem;
70
+ }
71
+ }
72
+ </style>
frontend/src/assets/base.css ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* color palette from <https://github.com/vuejs/theme> */
2
+ :root {
3
+ --vt-c-white: #ffffff;
4
+ --vt-c-white-soft: #f8f8f8;
5
+ --vt-c-white-mute: #f2f2f2;
6
+
7
+ --vt-c-black: #181818;
8
+ --vt-c-black-soft: #222222;
9
+ --vt-c-black-mute: #282828;
10
+
11
+ --vt-c-indigo: #2c3e50;
12
+
13
+ --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14
+ --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15
+ --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16
+ --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17
+
18
+ --vt-c-text-light-1: var(--vt-c-indigo);
19
+ --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20
+ --vt-c-text-dark-1: var(--vt-c-white);
21
+ --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22
+ }
23
+
24
+ /* semantic color variables for this project */
25
+ :root {
26
+ --color-background: var(--vt-c-white);
27
+ --color-background-soft: var(--vt-c-white-soft);
28
+ --color-background-mute: var(--vt-c-white-mute);
29
+
30
+ --color-border: var(--vt-c-divider-light-2);
31
+ --color-border-hover: var(--vt-c-divider-light-1);
32
+
33
+ --color-heading: var(--vt-c-text-light-1);
34
+ --color-text: var(--vt-c-text-light-1);
35
+
36
+ --section-gap: 160px;
37
+ }
38
+
39
+ @media (prefers-color-scheme: dark) {
40
+ :root {
41
+ --color-background: var(--vt-c-black);
42
+ --color-background-soft: var(--vt-c-black-soft);
43
+ --color-background-mute: var(--vt-c-black-mute);
44
+
45
+ --color-border: var(--vt-c-divider-dark-2);
46
+ --color-border-hover: var(--vt-c-divider-dark-1);
47
+
48
+ --color-heading: var(--vt-c-text-dark-1);
49
+ --color-text: var(--vt-c-text-dark-2);
50
+ }
51
+ }
52
+
53
+ *,
54
+ *::before,
55
+ *::after {
56
+ box-sizing: border-box;
57
+ margin: 0;
58
+ position: relative;
59
+ font-weight: normal;
60
+ }
61
+
62
+ body {
63
+ min-height: 100vh;
64
+ color: var(--color-text);
65
+ background: var(--color-background);
66
+ transition: color 0.5s, background-color 0.5s;
67
+ line-height: 1.6;
68
+ font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
69
+ Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
70
+ font-size: 15px;
71
+ text-rendering: optimizeLegibility;
72
+ -webkit-font-smoothing: antialiased;
73
+ -moz-osx-font-smoothing: grayscale;
74
+ }
frontend/src/assets/logo.svg ADDED
frontend/src/assets/main.css ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import 'base.css';
2
+
3
+ #app {
4
+ max-width: 1280px;
5
+ margin: 0 auto;
6
+ padding: 2rem;
7
+
8
+ font-weight: normal;
9
+ }
10
+
11
+ a,
12
+ .green {
13
+ text-decoration: none;
14
+ color: hsla(160, 100%, 37%, 1);
15
+ transition: 0.4s;
16
+ }
17
+
18
+ @media (hover: hover) {
19
+ a:hover {
20
+ background-color: hsla(160, 100%, 37%, 0.2);
21
+ }
22
+ }
23
+
24
+ @media (min-width: 1024px) {
25
+ body {
26
+ display: flex;
27
+ place-items: center;
28
+ }
29
+
30
+ #app {
31
+ display: contents;
32
+ grid-template-columns: 1fr 1fr;
33
+ padding: 0 2rem;
34
+ }
35
+ }
frontend/src/components/GreetingAudio.vue ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ src: string
4
+ title: string
5
+ }>()
6
+ </script>
7
+
8
+ <template>
9
+ <div class="greetings">
10
+ <h3 style="color: #2b2f3a">{{ title }}</h3>
11
+ <audio class="greetings-audio" controls :src="src"></audio>
12
+ </div>
13
+ </template>
14
+
15
+ <style scoped>
16
+ h1 {
17
+ font-weight: 500;
18
+ font-size: 2.6rem;
19
+ top: -10px;
20
+ }
21
+
22
+ h3 {
23
+ font-size: 1.2rem;
24
+ }
25
+
26
+ .greetings {
27
+ position: fixed;
28
+ bottom: 0;
29
+ right: 30px;
30
+ background-color: #3498db;
31
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
32
+ border-radius: 10px;
33
+ color: #fff;
34
+ padding: 10px;
35
+ }
36
+
37
+ .greetings h3 {
38
+ text-align: left;
39
+ margin: 10px;
40
+ }
41
+
42
+ @media (min-width: 1024px) {
43
+ .greetings h3 {
44
+ text-align: left;
45
+ }
46
+ }
47
+ </style>
frontend/src/components/TheWelcome.vue ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup lang="ts">
2
+ import WelcomeItem from './WelcomeItem.vue'
3
+ import DocumentationIcon from './icons/IconDocumentation.vue'
4
+ import ToolingIcon from './icons/IconTooling.vue'
5
+ import EcosystemIcon from './icons/IconEcosystem.vue'
6
+ import CommunityIcon from './icons/IconCommunity.vue'
7
+ import SupportIcon from './icons/IconSupport.vue'
8
+ </script>
9
+
10
+ <template>
11
+ <WelcomeItem>
12
+ <template #icon>
13
+ <DocumentationIcon />
14
+ </template>
15
+ <template #heading>Documentation</template>
16
+
17
+ Vue’s
18
+ <a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
19
+ provides you with all information you need to get started.
20
+ </WelcomeItem>
21
+
22
+ <WelcomeItem>
23
+ <template #icon>
24
+ <ToolingIcon />
25
+ </template>
26
+ <template #heading>Tooling</template>
27
+
28
+ This project is served and bundled with
29
+ <a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
30
+ recommended IDE setup is
31
+ <a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
32
+ <a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
33
+ you need to test your components and web pages, check out
34
+ <a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
35
+ <a href="https://on.cypress.io/component" target="_blank">Cypress Component Testing</a>.
36
+
37
+ <br />
38
+
39
+ More instructions are available in <code>README.md</code>.
40
+ </WelcomeItem>
41
+
42
+ <WelcomeItem>
43
+ <template #icon>
44
+ <EcosystemIcon />
45
+ </template>
46
+ <template #heading>Ecosystem</template>
47
+
48
+ Get official tools and libraries for your project:
49
+ <a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
50
+ <a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
51
+ <a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
52
+ <a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
53
+ you need more resources, we suggest paying
54
+ <a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
55
+ a visit.
56
+ </WelcomeItem>
57
+
58
+ <WelcomeItem>
59
+ <template #icon>
60
+ <CommunityIcon />
61
+ </template>
62
+ <template #heading>Community</template>
63
+
64
+ Got stuck? Ask your question on
65
+ <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
66
+ Discord server, or
67
+ <a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
68
+ >StackOverflow</a
69
+ >. You should also subscribe to
70
+ <a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
71
+ the official
72
+ <a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
73
+ twitter account for latest news in the Vue world.
74
+ </WelcomeItem>
75
+
76
+ <WelcomeItem>
77
+ <template #icon>
78
+ <SupportIcon />
79
+ </template>
80
+ <template #heading>Support Vue</template>
81
+
82
+ As an independent project, Vue relies on community backing for its sustainability. You can help
83
+ us by
84
+ <a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
85
+ </WelcomeItem>
86
+ </template>
frontend/src/components/WelcomeItem.vue ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="item">
3
+ <i>
4
+ <slot name="icon"></slot>
5
+ </i>
6
+ <div class="details">
7
+ <h3>
8
+ <slot name="heading"></slot>
9
+ </h3>
10
+ <slot></slot>
11
+ </div>
12
+ </div>
13
+ </template>
14
+
15
+ <style scoped>
16
+ .item {
17
+ margin-top: 2rem;
18
+ display: flex;
19
+ }
20
+
21
+ .details {
22
+ flex: 1;
23
+ margin-left: 1rem;
24
+ }
25
+
26
+ i {
27
+ display: flex;
28
+ place-items: center;
29
+ place-content: center;
30
+ width: 32px;
31
+ height: 32px;
32
+
33
+ color: var(--color-text);
34
+ }
35
+
36
+ h3 {
37
+ font-size: 1.2rem;
38
+ font-weight: 500;
39
+ margin-bottom: 0.4rem;
40
+ color: var(--color-heading);
41
+ }
42
+
43
+ @media (min-width: 1024px) {
44
+ .item {
45
+ margin-top: 0;
46
+ padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
47
+ }
48
+
49
+ i {
50
+ top: calc(50% - 25px);
51
+ left: -26px;
52
+ position: absolute;
53
+ border: 1px solid var(--color-border);
54
+ background: var(--color-background);
55
+ border-radius: 8px;
56
+ width: 50px;
57
+ height: 50px;
58
+ }
59
+
60
+ .item:before {
61
+ content: ' ';
62
+ border-left: 1px solid var(--color-border);
63
+ position: absolute;
64
+ left: 0;
65
+ bottom: calc(50% + 25px);
66
+ height: calc(50% - 25px);
67
+ }
68
+
69
+ .item:after {
70
+ content: ' ';
71
+ border-left: 1px solid var(--color-border);
72
+ position: absolute;
73
+ left: 0;
74
+ top: calc(50% + 25px);
75
+ height: calc(50% - 25px);
76
+ }
77
+
78
+ .item:first-of-type:before {
79
+ display: none;
80
+ }
81
+
82
+ .item:last-of-type:after {
83
+ display: none;
84
+ }
85
+ }
86
+ </style>
frontend/src/components/icons/IconCommunity.vue ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
3
+ <path
4
+ d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
5
+ />
6
+ </svg>
7
+ </template>
frontend/src/components/icons/IconDocumentation.vue ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
3
+ <path
4
+ d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
5
+ />
6
+ </svg>
7
+ </template>
frontend/src/components/icons/IconEcosystem.vue ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
3
+ <path
4
+ d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
5
+ />
6
+ </svg>
7
+ </template>
frontend/src/components/icons/IconSupport.vue ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
3
+ <path
4
+ d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
5
+ />
6
+ </svg>
7
+ </template>
frontend/src/components/icons/IconTooling.vue ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
2
+ <template>
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ xmlns:xlink="http://www.w3.org/1999/xlink"
6
+ aria-hidden="true"
7
+ role="img"
8
+ class="iconify iconify--mdi"
9
+ width="24"
10
+ height="24"
11
+ preserveAspectRatio="xMidYMid meet"
12
+ viewBox="0 0 24 24"
13
+ >
14
+ <path
15
+ d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
16
+ fill="currentColor"
17
+ ></path>
18
+ </svg>
19
+ </template>
frontend/src/layout/LayoutFrame.vue ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <el-container class="app-wrapper" :class="{ mobile: isMobile }">
3
+ <div v-show="isMobile && !hideSidebar" class="drawer-bg" @click="hideSidebar = true"></div>
4
+ <el-aside width="230px" class="sidebar-container" :class="{ 'hide-sidebar': hideSidebar }">
5
+ <div class="logo-container">
6
+ <router-link to="/">
7
+ <img class="sidebar-logo" src="../assets/logo.svg" width="100" alt="logo"/>
8
+ <h1 class="sidebar-title">SVS SYSTEM</h1>
9
+ </router-link>
10
+ </div>
11
+ <div class="version">v1.0.0</div>
12
+
13
+ <SideBar></SideBar>
14
+ </el-aside>
15
+ <el-main>
16
+ <el-button
17
+ v-show="isMobile"
18
+ class="menu-button"
19
+ icon="Fold"
20
+ @click="hideSidebar = false"
21
+ ></el-button>
22
+ <keep-alive>
23
+ <router-view></router-view>
24
+ </keep-alive>
25
+ </el-main>
26
+ </el-container>
27
+ </template>
28
+
29
+ <script>
30
+ import SideBar from './SideBar.vue'
31
+ export default {
32
+ name: 'LayoutFrame',
33
+ components: {
34
+ SideBar
35
+ },
36
+ data() {
37
+ return {
38
+ isMobile: false,
39
+ hideSidebar: true
40
+ }
41
+ },
42
+ mounted() {
43
+ window.addEventListener('resize', this.onResize)
44
+ this.onResize()
45
+ },
46
+ beforeUnmount() {
47
+ window.removeEventListener('resize', this.onResize)
48
+ },
49
+ methods: {
50
+ onResize() {
51
+ this.isMobile = document.body.clientWidth <= 992
52
+ }
53
+ }
54
+ }
55
+ </script>
56
+
57
+ <style>
58
+ html {
59
+ font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
60
+ '\5FAE \8F6F \96C5 \9ED1 ', '微软雅黑', Arial, sans-serif;
61
+ }
62
+
63
+ html,
64
+ body,
65
+ #app,
66
+ .app-wrapper,
67
+ .sidebar-container {
68
+ height: 100%;
69
+ }
70
+
71
+ body {
72
+ margin: 0;
73
+ background-color: #f6f8fa;
74
+ }
75
+
76
+ a,
77
+ a:focus,
78
+ a:hover {
79
+ text-decoration: none;
80
+ }
81
+
82
+ .drawer-bg {
83
+ background: #000;
84
+ opacity: 0.3;
85
+ width: 100%;
86
+ top: 0;
87
+ height: 100%;
88
+ position: absolute;
89
+ z-index: 999;
90
+ }
91
+
92
+ .sidebar-container {
93
+ background-color: #304156;
94
+ overflow: hidden;
95
+ }
96
+
97
+ .app-wrapper.mobile .sidebar-container {
98
+ position: fixed;
99
+ top: 0;
100
+ left: 0;
101
+ transition-duration: 0.3s;
102
+ z-index: 1001;
103
+ }
104
+
105
+ .app-wrapper.mobile .sidebar-container.hide-sidebar {
106
+ pointer-events: none;
107
+ transition-duration: 0.3s;
108
+ transform: translate3d(-230px, 0, 0);
109
+ }
110
+
111
+ .logo-container {
112
+ width: 100%;
113
+ height: 50px;
114
+ line-height: 50px;
115
+ background: #2b2f3a;
116
+ text-align: center;
117
+ }
118
+
119
+ .sidebar-logo {
120
+ width: 32px;
121
+ height: 32px;
122
+ vertical-align: middle;
123
+ margin-right: 12px;
124
+ }
125
+
126
+ .sidebar-title {
127
+ display: inline-block;
128
+ margin: 0;
129
+ color: #fff;
130
+ font-weight: 600;
131
+ line-height: 50px;
132
+ font-size: 14px;
133
+ font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
134
+ vertical-align: middle;
135
+ }
136
+
137
+ .sidebar-container .version {
138
+ height: 30px;
139
+ background: #2b2f3a;
140
+ color: #aaa;
141
+ font-weight: 600;
142
+ line-height: 30px;
143
+ font-size: 14px;
144
+ vertical-align: middle;
145
+ text-align: center;
146
+ }
147
+
148
+ .sidebar-container .is-horizontal {
149
+ display: none;
150
+ }
151
+ </style>
frontend/src/layout/SideBar.vue ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <el-scrollbar wrap-class="scrollbar-wrapper">
3
+ <el-menu
4
+ router
5
+ text-color="#bfcbd9"
6
+ active-text-color="rgb(64,158,255)"
7
+ :default-active="$route.path"
8
+ background-color="transparent"
9
+ >
10
+ <el-menu-item index="/">
11
+ <el-icon><HomeFilled /></el-icon>首页
12
+ </el-menu-item>
13
+ <el-menu-item :index="$router.resolve({ name: 'svc' }).href">
14
+ <el-icon><Refresh /></el-icon>SVC转换
15
+ </el-menu-item>
16
+ <el-menu-item index="/">
17
+ <el-icon><RefreshRight /></el-icon>DiffSinger生成
18
+ </el-menu-item>
19
+ <el-menu-item :index="$router.resolve({ name: 'vm' }).href">
20
+ <el-icon><EditPen /></el-icon>歌曲音声分离
21
+ </el-menu-item>
22
+ <a href="https://github.com/realerikk0/so-vits-svc-webui" target="_blank">
23
+ <el-menu-item>
24
+ <el-icon><Share /></el-icon>项目地址
25
+ </el-menu-item>
26
+ </a>
27
+ </el-menu>
28
+ </el-scrollbar>
29
+ </template>
30
+
31
+ <script>
32
+ export default {
33
+ name: 'SideBar'
34
+ }
35
+ </script>
36
+ <style>
37
+ .scrollbar-wrapper .el-menu {
38
+ border: none;
39
+ }
40
+ </style>
frontend/src/main.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createApp } from 'vue'
2
+ import { createPinia } from 'pinia'
3
+ import ElementPlus from 'element-plus'
4
+ import 'element-plus/dist/index.css'
5
+ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
6
+ import App from './App.vue'
7
+ import router from './router'
8
+ import axios from "axios";
9
+
10
+ import './assets/main.css'
11
+
12
+
13
+ axios.defaults.baseURL = 'http://127.0.0.1:7870' // 这里填写服务器的地址
14
+ axios.defaults.timeout = 10 * 60 * 1000
15
+
16
+ const app = createApp(App)
17
+
18
+ let name, comp;
19
+ for ([name, comp] of Object.entries(ElementPlusIconsVue)) {
20
+ app.component(name, comp);
21
+ }
22
+
23
+ app.use(createPinia())
24
+ app.use(router)
25
+ app.use(ElementPlus)
26
+
27
+ app.mount('#app')
frontend/src/router/index.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+ import LayoutFrame from '../layout/LayoutFrame.vue'
3
+ import SVCView from "../views/SVCView.vue";
4
+ import HomeView from "../views/HomeView.vue";
5
+ import RMView from "../views/RMView.vue";
6
+
7
+ const router = createRouter({
8
+ history: createWebHistory(import.meta.env.BASE_URL),
9
+ routes: [
10
+ {
11
+ path: '/',
12
+ component: LayoutFrame,
13
+ children: [
14
+ { path: '', component: HomeView },
15
+ { path: 'svc', name:'svc', component: SVCView},
16
+ { path: 'vm', name: 'vm', component: RMView },
17
+ ]
18
+ },
19
+ {
20
+ path: '/about',
21
+ name: 'about',
22
+ // route level code-splitting
23
+ // this generates a separate chunk (About.[hash].js) for this route
24
+ // which is lazy-loaded when the route is visited.
25
+ component: () => import('../views/AboutView.vue')
26
+ }
27
+ ]
28
+ })
29
+
30
+ export default router
frontend/src/stores/counter.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ref, computed } from 'vue'
2
+ import { defineStore } from 'pinia'
3
+
4
+ export const useCounterStore = defineStore('counter', () => {
5
+ const count = ref(0)
6
+ const doubleCount = computed(() => count.value * 2)
7
+ function increment() {
8
+ count.value++
9
+ }
10
+
11
+ return { count, doubleCount, increment }
12
+ })
frontend/src/views/AboutView.vue ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="about">
3
+ <h1>This is an about page</h1>
4
+ </div>
5
+ </template>
6
+
7
+ <style>
8
+ @media (min-width: 1024px) {
9
+ .about {
10
+ min-height: 100vh;
11
+ display: flex;
12
+ align-items: center;
13
+ }
14
+ }
15
+ </style>
frontend/src/views/HomeView.vue ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup lang="ts">
2
+ // import TheWelcome from '../components/TheWelcome.vue'
3
+ </script>
4
+
5
+ <template>
6
+ <main>
7
+ <!-- <TheWelcome />-->
8
+ Hi from Lilyn
9
+ </main>
10
+ </template>
frontend/src/views/RMView.vue ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="svc-container">
3
+ <div class="svc-wrapper">
4
+ <div class="form-panel single-run">
5
+ <h1 class="form-title">歌曲音声分离</h1>
6
+ <h3 class="model-tips">上传歌曲,运行可下载人声(vocals.wav)与背景音乐(accompaniment.wav)</h3>
7
+ <el-form class="svc-form single-run-form" :model="singleForm" label-width="150px">
8
+ <el-upload
9
+ ref="audioSingleUpload"
10
+ class="audio-upload"
11
+ action=""
12
+ accept=".wav,.mp3,.flac"
13
+ :http-request="handleSingleUpload"
14
+ >
15
+ <template #trigger>
16
+ <el-button type="primary" v-loading="isRuningSingle">选择音频文件并开始转换!</el-button>
17
+ </template>
18
+
19
+ <template #tip>
20
+ <div class="el-upload__tip">
21
+ 需要是wav或者mp3文件哦
22
+ </div>
23
+ </template>
24
+ </el-upload>
25
+ </el-form>
26
+ </div>
27
+
28
+ </div>
29
+
30
+ <!-- <GreetingAudio :title="audioTitle" :src="audioSrc" v-if="audioLoaded"></GreetingAudio>-->
31
+ </div>
32
+ </template>
33
+
34
+ <script>
35
+ import axios from "axios";
36
+ import JSZip from "jszip";
37
+ import GreetingAudio from "@/components/GreetingAudio.vue";
38
+
39
+ export default {
40
+ name: 'RMView',
41
+ components: {GreetingAudio},
42
+ data() {
43
+ return {
44
+ singleForm: {
45
+ srcaudio: null,
46
+ },
47
+ isRuningSingle: false,
48
+ }
49
+ },
50
+ mounted() {
51
+ },
52
+ methods: {
53
+ async handleSingleUpload(file) {
54
+ try {
55
+ this.isRuningSingle = true
56
+ console.log("HANDLE UPLOAD")
57
+ console.log(file)
58
+ let srcaudio = file.file
59
+ if (srcaudio) {
60
+ console.log(srcaudio)
61
+ let runData = new FormData()
62
+ runData.append("srcaudio", srcaudio)
63
+
64
+ let resp = await axios.post('/api/vm/run', runData, {
65
+ responseType: 'arraybuffer'
66
+ })
67
+ if (resp.status === 200) {
68
+ const zipBlob = new Blob([resp.data], { type: 'application/zip' });
69
+
70
+ // Create a temporary link element to trigger the download
71
+ const link = document.createElement('a');
72
+ link.href = URL.createObjectURL(zipBlob);
73
+ link.download = 'output.zip';
74
+
75
+ // Trigger the download
76
+ document.body.appendChild(link);
77
+ link.click();
78
+ document.body.removeChild(link);
79
+
80
+ this.isRuningSingle = false
81
+ } else {
82
+ this.$message.error('Failed to load model: ' + resp.data.msg)
83
+ this.isRuningSingle = false
84
+ }
85
+ } else {
86
+ this.$message.error('Error unable to get audio files')
87
+ this.isRuningSingle = false
88
+ }
89
+ } catch (e) {
90
+ this.$message.error('Failed to run: ' + e)
91
+ } finally {
92
+ this.isRuningSingle = false
93
+ }
94
+ },
95
+ getFileURL(file) {
96
+ // Create a blob URL for the file
97
+ const blob = new Blob([file], { type: 'audio/wav' })
98
+ return URL.createObjectURL(blob)
99
+ }
100
+ }
101
+ }
102
+ </script>
103
+
104
+ <style scoped>
105
+ .svc-container {
106
+ height: 100%;
107
+ width: 100%;
108
+ }
109
+
110
+ .svc-wrapper {
111
+ top: 60px;
112
+ display: flex;
113
+ flex-direction: column;
114
+ justify-content: center;
115
+ align-items: center;
116
+ /*background-color: #ccc;*/
117
+ }
118
+
119
+ .svc-form {
120
+ max-width: 460px;
121
+ }
122
+
123
+ .svc-form el-alert {
124
+ margin: 10px auto;
125
+ }
126
+
127
+ .svc-form el-form-item {
128
+ margin-top: 10px;
129
+ }
130
+
131
+ .form-title {
132
+ margin: 20px auto;
133
+ }
134
+ </style>
frontend/src/views/SVCView.vue ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="svc-container">
3
+ <div class="svc-wrapper">
4
+ <div class="form-panel">
5
+ <h1 class="form-title">基本设置</h1>
6
+ <el-form class="svc-form" :model="initForm" label-width="150px">
7
+ <el-alert type="info" show-icon :closable="false">
8
+ 单个文件处理需要提供音频文件,输出会是单个音频文件; <br>
9
+ 批量处理支持上传多个音频文件,输出会是一个zip压缩包。
10
+ </el-alert>
11
+ <el-form-item label="运行模式" required>
12
+ <el-select v-model="initForm.mode" placeholder="选择运行模式" default-first-option=default-first-option>
13
+ <el-option label="单个处理" value="single"></el-option>
14
+ <el-option label="批量处理" value="batch"></el-option>
15
+ </el-select>
16
+ </el-form-item>
17
+
18
+ <el-alert type="info" show-icon :closable="false">
19
+ 运行设备,默认选择cuda,即GPU计算;如果没有支持的GPU设备可以选择cpu。
20
+ </el-alert>
21
+ <el-form-item label="计算设备" required>
22
+ <el-select v-model="initForm.device" placeholder="选择计算设备" default-first-option="default-first-option">
23
+ <el-option label="GPU(cuda)" value="cuda"></el-option>
24
+ <el-option label="CPU" value="cpu"></el-option>
25
+ </el-select>
26
+ </el-form-item>
27
+
28
+ <el-alert type="info" show-icon :closable="false">
29
+ 选择一个语音模型
30
+ </el-alert>
31
+ <el-form-item label="语音模型" required>
32
+ <el-select v-model="initForm.model" placeholder="选择语音模型">
33
+ <el-option
34
+ v-for="modelName in this.models"
35
+ :key="modelName"
36
+ :label="modelName"
37
+ :value="modelName"
38
+ />
39
+ </el-select>
40
+ </el-form-item>
41
+
42
+ <el-button type="primary" @click="loadModel" v-loading="isLoadingModel">加载模型</el-button>
43
+ </el-form>
44
+ </div>
45
+
46
+ <el-divider v-if="displaySVCForm" />
47
+
48
+ <div class="form-panel single-run" v-if="displaySingleSVCForm">
49
+ <h1 class="form-title">制作一个歌曲。</h1>
50
+ <h3 class="model-tips">你使用的语音模型是 {{initForm.model}}</h3>
51
+ <el-form class="svc-form single-run-form" :model="singleForm" label-width="150px">
52
+ <el-upload
53
+ ref="audioSingleUpload"
54
+ class="audio-upload"
55
+ action=""
56
+ accept=".wav,.mp3"
57
+ :http-request="handleSingleUpload"
58
+ >
59
+ <template #trigger>
60
+ <el-button type="primary" v-loading="isRuningSingle">选择音频文件并开始制作!</el-button>
61
+ </template>
62
+
63
+ <template #tip>
64
+ <div class="el-upload__tip">
65
+ 需要是wav文件哦
66
+ </div>
67
+ </template>
68
+ </el-upload>
69
+ </el-form>
70
+ </div>
71
+
72
+ <div class="form-panel batch-run" v-if="displayBatchSVCForm">
73
+ <h1 class="form-title">制作多个歌曲。</h1>
74
+ <h3 class="model-tips">你使用的语音模型是 {{initForm.model}}</h3>
75
+ <el-form class="svc-form batch-run-form" :model="batchForm" label-width="150px">
76
+ <el-upload
77
+ ref="audioBatchUpload"
78
+ class="audio-upload"
79
+ action=""
80
+ accept=".wav,.mp3"
81
+ :http-request="mergeBatchFiles"
82
+ multiple
83
+ >
84
+ <template #trigger>
85
+ <el-button type="primary">选择音频文件</el-button>
86
+ </template>
87
+
88
+ <el-button type="warning" v-loading="isRunningBatch" @click="handleBatchUpload">开始制作!</el-button>
89
+
90
+ <template #tip>
91
+ <div class="el-upload__tip">
92
+ 需要是wav文件哦
93
+ </div>
94
+ </template>
95
+ </el-upload>
96
+ </el-form>
97
+ </div>
98
+
99
+ </div>
100
+
101
+ <GreetingAudio :title="audioTitle" :src="audioSrc" v-if="audioLoaded"></GreetingAudio>
102
+ </div>
103
+ </template>
104
+
105
+ <script>
106
+ import axios from "axios";
107
+ import JSZip from "jszip";
108
+ import GreetingAudio from "@/components/GreetingAudio.vue";
109
+
110
+ export default {
111
+ name: 'SVCView',
112
+ components: {GreetingAudio},
113
+ data() {
114
+ return {
115
+ models: [],
116
+ initForm: {
117
+ mode: '',
118
+ model: '',
119
+ device: '',
120
+ },
121
+ singleForm: {
122
+ dsid: '',
123
+ srcaudio: null,
124
+ },
125
+ batchForm: {
126
+ dsid: '',
127
+ srcaudio: [],
128
+ },
129
+ displaySVCForm: false,
130
+ displaySingleSVCForm: false,
131
+ displayBatchSVCForm: false,
132
+ isLoadingModel: false,
133
+ isRuningSingle: false,
134
+ isRunningBatch: false,
135
+ audioLoaded: false,
136
+ audioTitle: '',
137
+ audioSrc: '',
138
+ selectedFiles: []
139
+ }
140
+ },
141
+ mounted() {
142
+ this.getModelLists()
143
+ },
144
+ methods: {
145
+ axios,
146
+ async getModelLists() {
147
+ try {
148
+ let resp = (await axios.get('/api/svc/model')).data
149
+ if (resp.code === 200) {
150
+ this.models = resp.data
151
+ console.log(this.models)
152
+ } else {
153
+ this.$message.error('Failed to fetch model information: ' + resp.msg)
154
+ }
155
+ } catch (e) {
156
+ this.$message.error('Failed to fetch model information: ' + e)
157
+ }
158
+ },
159
+ async loadModel() {
160
+ try {
161
+ this.audioLoaded = false
162
+ this.selectedFiles = []
163
+ this.isLoadingModel = true
164
+ let initFormData = new FormData()
165
+ initFormData.append("mode", this.initForm.mode)
166
+ initFormData.append("device", this.initForm.device)
167
+ initFormData.append("model", this.initForm.model)
168
+ if (this.initForm.mode === "" || this.initForm.model === "" || this.initForm.device === "") {
169
+ this.$message.error('都选一下。')
170
+ return
171
+ }
172
+ let resp = (await axios.post('/api/svc/switch', initFormData)).data
173
+ if (resp.code === 200) {
174
+ this.displaySVCForm = true
175
+ console.log(resp.data.mode)
176
+ if (resp.data.mode === "single") {
177
+ this.displaySingleSVCForm = true
178
+ this.displayBatchSVCForm = false
179
+ } else if (resp.data.mode === "batch") {
180
+ this.displaySingleSVCForm = false
181
+ this.displayBatchSVCForm = true
182
+ }
183
+ } else {
184
+ this.$message.error('Failed to load model: ' + resp.msg)
185
+ }
186
+ } catch (e) {
187
+ this.$message.error('Failed to load model: ' + e)
188
+ } finally {
189
+ this.isLoadingModel = false
190
+ }
191
+ },
192
+ async handleSingleUpload(file) {
193
+ try {
194
+ this.isRuningSingle = true
195
+ this.isLoadingModel = true
196
+ console.log("HANDLE UPLOAD")
197
+ console.log(file)
198
+ let srcaudio = file.file
199
+ if (srcaudio) {
200
+ console.log(srcaudio)
201
+ let audioFileName = srcaudio.name
202
+ let runData = new FormData()
203
+ runData.append("srcaudio", srcaudio)
204
+ runData.append("dsid", this.initForm.model)
205
+
206
+ let resp = await axios.post('/api/svc/run', runData, {
207
+ responseType: 'arraybuffer'
208
+ })
209
+ if (resp.status === 200) {
210
+ const audioData = resp.data
211
+ if (audioData) {
212
+ const audioBlob = new Blob([audioData], { type: 'audio/wav' });
213
+ const audioUrl = URL.createObjectURL(audioBlob);
214
+
215
+ this.audioLoaded = true
216
+ this.audioSrc = audioUrl
217
+ this.audioTitle = audioFileName
218
+ }
219
+ } else {
220
+ this.$message.error('Failed to load model: ' + resp.data.msg)
221
+ this.isRuningSingle = false
222
+ this.isLoadingModel = false
223
+ }
224
+ } else {
225
+ this.$message.error('Error unable to get audio file')
226
+ this.isRuningSingle = false
227
+ this.isLoadingModel = false
228
+ }
229
+ } catch (e) {
230
+ this.$message.error('Failed to run: ' + e)
231
+ } finally {
232
+ this.isRuningSingle = false
233
+ this.isLoadingModel = false
234
+ }
235
+ },
236
+ mergeBatchFiles(file) {
237
+ this.selectedFiles.push(file.file)
238
+ },
239
+ async handleBatchUpload() {
240
+ try {
241
+ this.isRunningBatch = true
242
+ this.isLoadingModel = true
243
+
244
+ const audioFiles = this.selectedFiles
245
+ if (audioFiles.length > 0) {
246
+ let runData = new FormData()
247
+ // runData.append('srcaudio', audioFiles)
248
+ for (let i = 0; i < audioFiles.length; i++) {
249
+ runData.append('srcaudio', audioFiles[i])
250
+ }
251
+ runData.append("dsid", this.initForm.model)
252
+
253
+ let resp = await axios.post('/api/svc/batch', runData, {
254
+ responseType: 'arraybuffer'
255
+ })
256
+ if (resp.status === 200) {
257
+ const zipBlob = new Blob([resp.data], { type: 'application/zip' });
258
+
259
+ // Create a temporary link element to trigger the download
260
+ const link = document.createElement('a');
261
+ link.href = URL.createObjectURL(zipBlob);
262
+ link.download = 'output.zip';
263
+
264
+ // Trigger the download
265
+ document.body.appendChild(link);
266
+ link.click();
267
+ document.body.removeChild(link);
268
+
269
+ } else {
270
+ this.$message.error('Failed to load model: ' + resp.data.msg)
271
+ this.isRunningBatch = false
272
+ this.isLoadingModel = false
273
+ }
274
+ } else {
275
+ this.$message.error('Error unable to get audio file')
276
+ this.isRunningBatch = false
277
+ this.isLoadingModel = false
278
+ }
279
+ } catch (e) {
280
+ this.$message.error('Failed to run: ' + e)
281
+ } finally {
282
+ this.isRunningBatch = false
283
+ this.isLoadingModel = false
284
+ }
285
+ },
286
+ }
287
+ }
288
+ </script>
289
+
290
+ <style scoped>
291
+ .svc-container {
292
+ height: 100%;
293
+ width: 100%;
294
+ }
295
+
296
+ .svc-wrapper {
297
+ top: 60px;
298
+ display: flex;
299
+ flex-direction: column;
300
+ justify-content: center;
301
+ align-items: center;
302
+ /*background-color: #ccc;*/
303
+ }
304
+
305
+ .svc-form {
306
+ max-width: 460px;
307
+ }
308
+
309
+ .svc-form el-alert {
310
+ margin: 10px auto;
311
+ }
312
+
313
+ .svc-form el-form-item {
314
+ margin-top: 10px;
315
+ }
316
+
317
+ .form-title {
318
+ margin: 20px auto;
319
+ }
320
+ </style>
frontend/tsconfig.app.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.web.json",
3
+ "include": [
4
+ "env.d.ts", "src/**/*", "src/**/*.vue"],
5
+ "exclude": ["src/**/__tests__/*"],
6
+ "compilerOptions": {
7
+ "composite": true,
8
+ "baseUrl": "./lilyn-svs",
9
+ "allowJs": true,
10
+ "paths": {
11
+ "@/*": ["./src/*"]
12
+ }
13
+ }
14
+ }
frontend/tsconfig.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.node.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.app.json"
9
+ },
10
+ {
11
+ "path": "./tsconfig.vitest.json"
12
+ }
13
+ ]
14
+ }
frontend/tsconfig.node.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.node.json",
3
+ "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
4
+ "compilerOptions": {
5
+ "composite": true,
6
+ "types": ["node"],
7
+ "allowJs": true
8
+ }
9
+ }
frontend/tsconfig.vitest.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "./tsconfig.app.json",
3
+ "exclude": [],
4
+ "compilerOptions": {
5
+ "composite": true,
6
+ "lib": [],
7
+ "types": ["node", "jsdom"],
8
+ "allowJs": true
9
+ }
10
+ }
frontend/vite.config.ts ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fileURLToPath, URL } from 'node:url'
2
+
3
+ import { defineConfig } from 'vite'
4
+ import vue from '@vitejs/plugin-vue'
5
+
6
+ // https://vitejs.dev/config/
7
+ export default defineConfig({
8
+ plugins: [vue()],
9
+ resolve: {
10
+ alias: {
11
+ '@': fileURLToPath(new URL('./src', import.meta.url))
12
+ }
13
+ }
14
+ })
frontend/vitest.config.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fileURLToPath } from 'node:url'
2
+ import { mergeConfig } from 'vite'
3
+ import { configDefaults, defineConfig } from 'vitest/config'
4
+ import viteConfig from './vite.config'
5
+
6
+ export default mergeConfig(
7
+ viteConfig,
8
+ defineConfig({
9
+ test: {
10
+ environment: 'jsdom',
11
+ exclude: [...configDefaults.exclude, 'e2e/*'],
12
+ root: fileURLToPath(new URL('./', import.meta.url))
13
+ }
14
+ })
15
+ )
hubert/__init__.py ADDED
File without changes
hubert/hubert_model.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import random
3
+ from typing import Optional, Tuple
4
+
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as t_func
8
+ from torch.nn.modules.utils import consume_prefix_in_state_dict_if_present
9
+
10
+
11
+ class Hubert(nn.Module):
12
+ def __init__(self, num_label_embeddings: int = 100, mask: bool = True):
13
+ super().__init__()
14
+ self._mask = mask
15
+ self.feature_extractor = FeatureExtractor()
16
+ self.feature_projection = FeatureProjection()
17
+ self.positional_embedding = PositionalConvEmbedding()
18
+ self.norm = nn.LayerNorm(768)
19
+ self.dropout = nn.Dropout(0.1)
20
+ self.encoder = TransformerEncoder(
21
+ nn.TransformerEncoderLayer(
22
+ 768, 12, 3072, activation="gelu", batch_first=True
23
+ ),
24
+ 12,
25
+ )
26
+ self.proj = nn.Linear(768, 256)
27
+
28
+ self.masked_spec_embed = nn.Parameter(torch.FloatTensor(768).uniform_())
29
+ self.label_embedding = nn.Embedding(num_label_embeddings, 256)
30
+
31
+ def mask(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
32
+ mask = None
33
+ if self.training and self._mask:
34
+ mask = _compute_mask((x.size(0), x.size(1)), 0.8, 10, x.device, 2)
35
+ x[mask] = self.masked_spec_embed.to(x.dtype)
36
+ return x, mask
37
+
38
+ def encode(
39
+ self, x: torch.Tensor, layer: Optional[int] = None
40
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
41
+ x = self.feature_extractor(x)
42
+ x = self.feature_projection(x.transpose(1, 2))
43
+ x, mask = self.mask(x)
44
+ x = x + self.positional_embedding(x)
45
+ x = self.dropout(self.norm(x))
46
+ x = self.encoder(x, output_layer=layer)
47
+ return x, mask
48
+
49
+ def logits(self, x: torch.Tensor) -> torch.Tensor:
50
+ logits = torch.cosine_similarity(
51
+ x.unsqueeze(2),
52
+ self.label_embedding.weight.unsqueeze(0).unsqueeze(0),
53
+ dim=-1,
54
+ )
55
+ return logits / 0.1
56
+
57
+ def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
58
+ x, mask = self.encode(x)
59
+ x = self.proj(x)
60
+ logits = self.logits(x)
61
+ return logits, mask
62
+
63
+
64
+ class HubertSoft(Hubert):
65
+ def __init__(self):
66
+ super().__init__()
67
+
68
+ @torch.inference_mode()
69
+ def units(self, wav: torch.Tensor) -> torch.Tensor:
70
+ wav = t_func.pad(wav, ((400 - 320) // 2, (400 - 320) // 2))
71
+ x, _ = self.encode(wav)
72
+ return self.proj(x)
73
+
74
+
75
+ class FeatureExtractor(nn.Module):
76
+ def __init__(self):
77
+ super().__init__()
78
+ self.conv0 = nn.Conv1d(1, 512, 10, 5, bias=False)
79
+ self.norm0 = nn.GroupNorm(512, 512)
80
+ self.conv1 = nn.Conv1d(512, 512, 3, 2, bias=False)
81
+ self.conv2 = nn.Conv1d(512, 512, 3, 2, bias=False)
82
+ self.conv3 = nn.Conv1d(512, 512, 3, 2, bias=False)
83
+ self.conv4 = nn.Conv1d(512, 512, 3, 2, bias=False)
84
+ self.conv5 = nn.Conv1d(512, 512, 2, 2, bias=False)
85
+ self.conv6 = nn.Conv1d(512, 512, 2, 2, bias=False)
86
+
87
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
88
+ x = t_func.gelu(self.norm0(self.conv0(x)))
89
+ x = t_func.gelu(self.conv1(x))
90
+ x = t_func.gelu(self.conv2(x))
91
+ x = t_func.gelu(self.conv3(x))
92
+ x = t_func.gelu(self.conv4(x))
93
+ x = t_func.gelu(self.conv5(x))
94
+ x = t_func.gelu(self.conv6(x))
95
+ return x
96
+
97
+
98
+ class FeatureProjection(nn.Module):
99
+ def __init__(self):
100
+ super().__init__()
101
+ self.norm = nn.LayerNorm(512)
102
+ self.projection = nn.Linear(512, 768)
103
+ self.dropout = nn.Dropout(0.1)
104
+
105
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
106
+ x = self.norm(x)
107
+ x = self.projection(x)
108
+ x = self.dropout(x)
109
+ return x
110
+
111
+
112
+ class PositionalConvEmbedding(nn.Module):
113
+ def __init__(self):
114
+ super().__init__()
115
+ self.conv = nn.Conv1d(
116
+ 768,
117
+ 768,
118
+ kernel_size=128,
119
+ padding=128 // 2,
120
+ groups=16,
121
+ )
122
+ self.conv = nn.utils.weight_norm(self.conv, name="weight", dim=2)
123
+
124
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
125
+ x = self.conv(x.transpose(1, 2))
126
+ x = t_func.gelu(x[:, :, :-1])
127
+ return x.transpose(1, 2)
128
+
129
+
130
+ class TransformerEncoder(nn.Module):
131
+ def __init__(
132
+ self, encoder_layer: nn.TransformerEncoderLayer, num_layers: int
133
+ ) -> None:
134
+ super(TransformerEncoder, self).__init__()
135
+ self.layers = nn.ModuleList(
136
+ [copy.deepcopy(encoder_layer) for _ in range(num_layers)]
137
+ )
138
+ self.num_layers = num_layers
139
+
140
+ def forward(
141
+ self,
142
+ src: torch.Tensor,
143
+ mask: torch.Tensor = None,
144
+ src_key_padding_mask: torch.Tensor = None,
145
+ output_layer: Optional[int] = None,
146
+ ) -> torch.Tensor:
147
+ output = src
148
+ for layer in self.layers[:output_layer]:
149
+ output = layer(
150
+ output, src_mask=mask, src_key_padding_mask=src_key_padding_mask
151
+ )
152
+ return output
153
+
154
+
155
+ def _compute_mask(
156
+ shape: Tuple[int, int],
157
+ mask_prob: float,
158
+ mask_length: int,
159
+ device: torch.device,
160
+ min_masks: int = 0,
161
+ ) -> torch.Tensor:
162
+ batch_size, sequence_length = shape
163
+
164
+ if mask_length < 1:
165
+ raise ValueError("`mask_length` has to be bigger than 0.")
166
+
167
+ if mask_length > sequence_length:
168
+ raise ValueError(
169
+ f"`mask_length` has to be smaller than `sequence_length`, but got `mask_length`: {mask_length} and `sequence_length`: {sequence_length}`"
170
+ )
171
+
172
+ # compute number of masked spans in batch
173
+ num_masked_spans = int(mask_prob * sequence_length / mask_length + random.random())
174
+ num_masked_spans = max(num_masked_spans, min_masks)
175
+
176
+ # make sure num masked indices <= sequence_length
177
+ if num_masked_spans * mask_length > sequence_length:
178
+ num_masked_spans = sequence_length // mask_length
179
+
180
+ # SpecAugment mask to fill
181
+ mask = torch.zeros((batch_size, sequence_length), device=device, dtype=torch.bool)
182
+
183
+ # uniform distribution to sample from, make sure that offset samples are < sequence_length
184
+ uniform_dist = torch.ones(
185
+ (batch_size, sequence_length - (mask_length - 1)), device=device
186
+ )
187
+
188
+ # get random indices to mask
189
+ mask_indices = torch.multinomial(uniform_dist, num_masked_spans)
190
+
191
+ # expand masked indices to masked spans
192
+ mask_indices = (
193
+ mask_indices.unsqueeze(dim=-1)
194
+ .expand((batch_size, num_masked_spans, mask_length))
195
+ .reshape(batch_size, num_masked_spans * mask_length)
196
+ )
197
+ offsets = (
198
+ torch.arange(mask_length, device=device)[None, None, :]
199
+ .expand((batch_size, num_masked_spans, mask_length))
200
+ .reshape(batch_size, num_masked_spans * mask_length)
201
+ )
202
+ mask_idxs = mask_indices + offsets
203
+
204
+ # scatter indices to mask
205
+ mask = mask.scatter(1, mask_idxs, True)
206
+
207
+ return mask
208
+
209
+
210
+ def hubert_soft(
211
+ path: str,
212
+ ) -> HubertSoft:
213
+ r"""HuBERT-Soft from `"A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion"`.
214
+ Args:
215
+ path (str): path of a pretrained model
216
+ """
217
+ hubert = HubertSoft()
218
+ checkpoint = torch.load(path)
219
+ consume_prefix_in_state_dict_if_present(checkpoint, "module.")
220
+ hubert.load_state_dict(checkpoint)
221
+ hubert.eval()
222
+ return hubert
inference/__init__.py ADDED
File without changes