@memef1f1y/dsh-plugin-tts 0.6.0
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.
- package/.github/workflows/test.yml +33 -0
- package/ANALYSIS.md +205 -0
- package/LICENSE +21 -0
- package/PR-REVIEW.md +75 -0
- package/README.md +297 -0
- package/README.zh.md +263 -0
- package/Snipaste_2026-08-16_13-28-10.png +0 -0
- package/Snipaste_2026-08-16_13-28-23.png +0 -0
- package/Snipaste_2026-08-16_13-28-37.png +0 -0
- package/Snipaste_2026-08-16_13-29-01.png +0 -0
- package/Snipaste_2026-08-16_13-29-11.png +0 -0
- package/TODO.md +23 -0
- package/cordis.patch.yml +4 -0
- package/docs/CLOUD-GUIDE.md +216 -0
- package/docs/COSYVOICE-GUIDE.md +216 -0
- package/docs/EDGE-GUIDE.md +166 -0
- package/docs/HOST-CONTRACT.md +36 -0
- package/docs/INDEX-TTS2-GUIDE.md +208 -0
- package/docs/RVC-GUIDE.md +390 -0
- package/docs/USER-GUIDE.md +334 -0
- package/docs/acceptance-checklist.md +148 -0
- package/docs/adaptive-chunked-playback.md +194 -0
- package/docs/macos-test-prompt.md +230 -0
- package/docs/windows-gpu-verify-prompt.md +151 -0
- package/lib/client.js +7036 -0
- package/lib/index.mjs +3288 -0
- package/logo.png +0 -0
- package/package.json +52 -0
- package/pnpm-workspace.yaml +7 -0
- package/rvc-server.py +564 -0
- package/tests/README.md +45 -0
- package/tests/client-load.mjs +393 -0
- package/tests/e2e-compact-index.mjs +86 -0
- package/tests/e2e-index-ab.mjs +138 -0
- package/tests/e2e-real-rvc.mjs +122 -0
- package/tests/i18n-keys.mjs +82 -0
- package/tests/mock-registry.mjs +50 -0
- package/tests/package-runtime-patch.mjs +88 -0
- package/tests/rvc-server-live.mjs +230 -0
- package/tests/smoke.mjs +1107 -0
- package/tools/gen-ui-preview.mjs +174 -0
- package/tools/mac-verify.sh +158 -0
- package/tools/make-pack.mjs +155 -0
- package/tools/package-runtime.py +214 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""package-runtime.py — build a portable RVC inference runtime for dsh-plugin-tts.
|
|
3
|
+
|
|
4
|
+
Strategy: copy the PROVEN package set from an existing RVC WebUI install
|
|
5
|
+
(runtime/ python + infer/ + hubert/rmvpe assets + ffmpeg), then optionally
|
|
6
|
+
upgrade torch/torchaudio to a CUDA build that supports the local GPU. This keeps
|
|
7
|
+
the dependency set that is known to work together and fixes the one weak point:
|
|
8
|
+
old torch builds (e.g. cu118 / torch 2.0) cannot run Blackwell (RTX 50-series,
|
|
9
|
+
sm_120) at full speed — cu128 builds include sm_120 kernels.
|
|
10
|
+
|
|
11
|
+
Works on Windows / Linux / macOS (--platform auto detects; the Linux/macOS
|
|
12
|
+
paths are written blind — no CI machine, test before shipping).
|
|
13
|
+
|
|
14
|
+
Result (portable folder):
|
|
15
|
+
<out>/
|
|
16
|
+
├── runtime/ # copied python env (torch optionally upgraded)
|
|
17
|
+
├── infer/ # RVC inference core
|
|
18
|
+
├── assets/hubert/ # hubert_base.pt
|
|
19
|
+
├── assets/rmvpe/ # rmvpe.pt
|
|
20
|
+
├── ffmpeg(.exe) # copied when available
|
|
21
|
+
├── rvc-server.py
|
|
22
|
+
└── 启动服务.bat / start-rvc-server.sh
|
|
23
|
+
|
|
24
|
+
Run rvc-server.py from the portable root — it auto-detects the env
|
|
25
|
+
(runtime/python) and the RVC core (infer/ next to it), no WebUI needed.
|
|
26
|
+
|
|
27
|
+
Usage (Windows):
|
|
28
|
+
python tools/package-runtime.py ^
|
|
29
|
+
--rvc-dir "E:\\AI\\RVC20240604Nvidia\\RVC20240604Nvidia" ^
|
|
30
|
+
--out "D:\\rvc-portable" [--torch cu128] [--torch-version 2.7.0]
|
|
31
|
+
Usage (Linux/macOS):
|
|
32
|
+
python3 tools/package-runtime.py --rvc-dir /opt/RVC --out ~/rvc-portable --skip-torch
|
|
33
|
+
"""
|
|
34
|
+
import argparse
|
|
35
|
+
import os
|
|
36
|
+
import shutil
|
|
37
|
+
import subprocess
|
|
38
|
+
import sys
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def detect_platform(value):
|
|
42
|
+
if value and value != "auto":
|
|
43
|
+
return value
|
|
44
|
+
if sys.platform.startswith("win"):
|
|
45
|
+
return "windows"
|
|
46
|
+
if sys.platform.startswith("darwin"):
|
|
47
|
+
return "darwin"
|
|
48
|
+
return "linux"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def dir_gb(p):
|
|
52
|
+
if not os.path.isdir(p):
|
|
53
|
+
return 0.0
|
|
54
|
+
total = 0
|
|
55
|
+
for root, _, files in os.walk(p):
|
|
56
|
+
for f in files:
|
|
57
|
+
try:
|
|
58
|
+
total += os.path.getsize(os.path.join(root, f))
|
|
59
|
+
except OSError:
|
|
60
|
+
pass
|
|
61
|
+
return total / (1024 ** 3)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def copytree(src, dst):
|
|
65
|
+
if os.path.exists(dst):
|
|
66
|
+
shutil.rmtree(dst, ignore_errors=True)
|
|
67
|
+
shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def patch_infer_for_pyav(infer_dir):
|
|
71
|
+
"""macOS-only: make RVC's infer/lib/audio.py work with modern PyAV.
|
|
72
|
+
|
|
73
|
+
The RVC infer code calls ``av.open(fp, "rb")`` / ``av.open(fp, "wb", ...)``.
|
|
74
|
+
Old PyAV (bundled by the Windows RVC WebUI runtime) accepted those modes,
|
|
75
|
+
but every PyAV >= 10 rejects them for file-like objects (only 'r'/'w'/None
|
|
76
|
+
are allowed), and macOS arm64 wheels only exist for these newer versions.
|
|
77
|
+
Without this fix /convert fails with "ValueError: mode must be 'r', 'w',
|
|
78
|
+
or None, got: rb". This rewrites the copied (packaged) audio.py in place;
|
|
79
|
+
it never touches the user's RVC install.
|
|
80
|
+
"""
|
|
81
|
+
audio = os.path.join(infer_dir, "lib", "audio.py")
|
|
82
|
+
if not os.path.exists(audio):
|
|
83
|
+
print("[package] (no infer/lib/audio.py — skip PyAV patch)", flush=True)
|
|
84
|
+
return
|
|
85
|
+
with open(audio, "r", encoding="utf-8") as f:
|
|
86
|
+
src = f.read()
|
|
87
|
+
new = src.replace('av.open(i, "rb")', 'av.open(i, "r")').replace(
|
|
88
|
+
'av.open(o, "wb", format=format)', 'av.open(o, "w", format=format)'
|
|
89
|
+
)
|
|
90
|
+
if new == src:
|
|
91
|
+
print("[package] (infer/lib/audio.py already PyAV-compatible)", flush=True)
|
|
92
|
+
return
|
|
93
|
+
with open(audio, "w", encoding="utf-8") as f:
|
|
94
|
+
f.write(new)
|
|
95
|
+
print("[package] patched infer/lib/audio.py: av.open 'rb'->'r', 'wb'->'w'", flush=True)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def step(msg, fn):
|
|
99
|
+
print("[package] %s ..." % msg, flush=True)
|
|
100
|
+
fn()
|
|
101
|
+
print("[package] done", flush=True)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main():
|
|
105
|
+
ap = argparse.ArgumentParser(description="Build a portable RVC runtime for dsh-plugin-tts")
|
|
106
|
+
ap.add_argument("--rvc-dir", required=True, help="existing RVC WebUI install root")
|
|
107
|
+
ap.add_argument("--out", default="rvc-portable", help="output portable folder")
|
|
108
|
+
ap.add_argument("--platform", default="auto", choices=["auto", "windows", "linux", "darwin"],
|
|
109
|
+
help="target platform (auto = this machine)")
|
|
110
|
+
ap.add_argument("--skip-torch", action="store_true",
|
|
111
|
+
help="skip the torch upgrade (portable works with the copied torch; "
|
|
112
|
+
"cu128 is only needed for full speed on RTX 50-series/Blackwell)")
|
|
113
|
+
ap.add_argument("--torch", default="cu128", choices=["cu118", "cu121", "cu124", "cu126", "cu128"])
|
|
114
|
+
ap.add_argument("--torch-version", default="2.7.0")
|
|
115
|
+
ap.add_argument("--index-url", default="https://download.pytorch.org/whl",
|
|
116
|
+
help="pytorch wheel index (use a mirror if the official CDN is slow, "
|
|
117
|
+
"e.g. https://mirrors.aliyun.com/pytorch-wheels)")
|
|
118
|
+
a = ap.parse_args()
|
|
119
|
+
|
|
120
|
+
platform = detect_platform(a.platform)
|
|
121
|
+
is_win = platform == "windows"
|
|
122
|
+
print("[package] target platform: %s" % platform, flush=True)
|
|
123
|
+
|
|
124
|
+
rvc = os.path.abspath(a.rvc_dir)
|
|
125
|
+
out = os.path.abspath(a.out)
|
|
126
|
+
runtime = os.path.join(rvc, "runtime")
|
|
127
|
+
if not os.path.isdir(os.path.join(rvc, "infer")):
|
|
128
|
+
sys.exit("ERROR: %s is not an RVC install (no infer/)" % rvc)
|
|
129
|
+
if not os.path.isdir(runtime):
|
|
130
|
+
sys.exit("ERROR: %s has no runtime/ python" % rvc)
|
|
131
|
+
|
|
132
|
+
py_rel = os.path.join("runtime", "python.exe") if is_win else os.path.join("runtime", "bin", "python")
|
|
133
|
+
|
|
134
|
+
os.makedirs(out, exist_ok=True)
|
|
135
|
+
torch_spec = "%s+%s" % (a.torch_version, a.torch)
|
|
136
|
+
|
|
137
|
+
step("copy runtime (%.1f GB)" % dir_gb(runtime), lambda: copytree(runtime, os.path.join(out, "runtime")))
|
|
138
|
+
step("copy infer/", lambda: copytree(os.path.join(rvc, "infer"), os.path.join(out, "infer")))
|
|
139
|
+
# macOS-only: the packaged infer must tolerate modern PyAV (see the helper).
|
|
140
|
+
if platform == "darwin":
|
|
141
|
+
step("patch infer/lib/audio.py for modern PyAV (macOS)", lambda: patch_infer_for_pyav(os.path.join(out, "infer")))
|
|
142
|
+
if os.path.isdir(os.path.join(rvc, "assets", "hubert")):
|
|
143
|
+
step("copy assets/hubert", lambda: copytree(os.path.join(rvc, "assets", "hubert"), os.path.join(out, "assets", "hubert")))
|
|
144
|
+
if os.path.isdir(os.path.join(rvc, "assets", "rmvpe")):
|
|
145
|
+
step("copy assets/rmvpe", lambda: copytree(os.path.join(rvc, "assets", "rmvpe"), os.path.join(out, "assets", "rmvpe")))
|
|
146
|
+
|
|
147
|
+
# ffmpeg: Windows uses the RVC-bundled ffmpeg.exe; elsewhere try the system
|
|
148
|
+
# binary so the folder stays self-contained, otherwise fall back to PATH.
|
|
149
|
+
ffmpeg_src = os.path.join(rvc, "ffmpeg.exe") if is_win else shutil.which("ffmpeg")
|
|
150
|
+
if ffmpeg_src and os.path.exists(ffmpeg_src):
|
|
151
|
+
fname = os.path.basename(ffmpeg_src)
|
|
152
|
+
step("copy %s" % fname, lambda: shutil.copy2(ffmpeg_src, os.path.join(out, fname)))
|
|
153
|
+
else:
|
|
154
|
+
print("[package] WARN: no bundled ffmpeg found — rvc-server will fall back to system ffmpeg", flush=True)
|
|
155
|
+
|
|
156
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
157
|
+
server_src = os.path.join(os.path.dirname(here), "rvc-server.py")
|
|
158
|
+
if os.path.exists(server_src):
|
|
159
|
+
step("copy rvc-server.py", lambda: shutil.copy2(server_src, os.path.join(out, "rvc-server.py")))
|
|
160
|
+
else:
|
|
161
|
+
print("[package] WARN: rvc-server.py not found next to tools/ — copy it manually", flush=True)
|
|
162
|
+
|
|
163
|
+
py = os.path.join(out, py_rel)
|
|
164
|
+
if not os.path.exists(py):
|
|
165
|
+
print("[package] WARN: %s not found — check the runtime layout for platform %s" % (py_rel, platform), flush=True)
|
|
166
|
+
|
|
167
|
+
# torch upgrade: pointless on macOS (no NVIDIA CUDA); auto-skip with a note.
|
|
168
|
+
if platform == "darwin" and not a.skip_torch:
|
|
169
|
+
print("[package] note: skipping torch upgrade on macOS (no CUDA); the copied torch is kept", flush=True)
|
|
170
|
+
elif a.skip_torch:
|
|
171
|
+
print("[package] skipping torch upgrade (--skip-torch); the portable runtime "
|
|
172
|
+
"works with the copied torch (cu128 only matters for RTX 50-series speed)", flush=True)
|
|
173
|
+
else:
|
|
174
|
+
def upgrade_torch():
|
|
175
|
+
# PyPI stays the primary index (for torch's deps like typing-extensions);
|
|
176
|
+
# the pytorch index is added for the pinned +cu128 build. The local
|
|
177
|
+
# version tag (==2.7.0+cu128) only matches cu128 wheels, so pip cannot
|
|
178
|
+
# pick the CPU build from PyPI.
|
|
179
|
+
cmd = [py, "-m", "pip", "install", "--upgrade", "--no-input",
|
|
180
|
+
"--extra-index-url", "%s/%s" % (a.index_url.rstrip("/"), a.torch),
|
|
181
|
+
"torch==%s" % torch_spec, "torchaudio==%s" % torch_spec]
|
|
182
|
+
print("[package] running: %s" % " ".join(cmd), flush=True)
|
|
183
|
+
subprocess.check_call(cmd)
|
|
184
|
+
step("upgrade torch -> %s" % torch_spec, upgrade_torch)
|
|
185
|
+
|
|
186
|
+
if is_win:
|
|
187
|
+
launcher = os.path.join(out, "启动服务.bat")
|
|
188
|
+
with open(launcher, "w", encoding="gbk", errors="replace") as f:
|
|
189
|
+
f.write("@echo off\r\n"
|
|
190
|
+
"cd /d \"%~dp0\"\r\n"
|
|
191
|
+
"echo Starting dsh-plugin-tts RVC server on http://127.0.0.1:4892 ...\r\n"
|
|
192
|
+
"start \"dsh-tts-rvc\" \"%~dp0runtime\\python.exe\" \"%~dp0rvc-server.py\" --port 4892\r\n")
|
|
193
|
+
print("[package] wrote %s" % launcher, flush=True)
|
|
194
|
+
else:
|
|
195
|
+
launcher = os.path.join(out, "start-rvc-server.sh")
|
|
196
|
+
with open(launcher, "w", encoding="utf-8") as f:
|
|
197
|
+
f.write("#!/bin/sh\n"
|
|
198
|
+
"cd \"$(dirname \"$0\")\"\n"
|
|
199
|
+
"echo 'Starting dsh-plugin-tts RVC server on http://127.0.0.1:4892 ...'\n"
|
|
200
|
+
"exec \"$PWD/runtime/bin/python\" \"$PWD/rvc-server.py\" --port 4892\n")
|
|
201
|
+
os.chmod(launcher, 0o755)
|
|
202
|
+
print("[package] wrote %s (chmod +x)" % launcher, flush=True)
|
|
203
|
+
|
|
204
|
+
if os.path.exists(py):
|
|
205
|
+
print("[package] verifying torch...", flush=True)
|
|
206
|
+
subprocess.check_call([py, "-c",
|
|
207
|
+
"import torch; print('torch', torch.__version__, 'cuda', torch.cuda.is_available(), "
|
|
208
|
+
"'cap', torch.cuda.get_device_capability(0) if torch.cuda.is_available() else None)"])
|
|
209
|
+
total = dir_gb(out)
|
|
210
|
+
print("[package] OK. Portable runtime ready: %s (%.1f GB)" % (out, total), flush=True)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == "__main__":
|
|
214
|
+
main()
|