@memef1f1y/dsh-plugin-tts 0.6.5 → 0.6.7
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/ISSUE_TEMPLATE/bug_report.yml +51 -51
- package/.github/ISSUE_TEMPLATE/config.yml +2 -2
- package/.github/ISSUE_TEMPLATE/feature_request.yml +28 -28
- package/.github/pull_request_template.md +7 -7
- package/.github/workflows/contrib-triage.yml +66 -66
- package/.github/workflows/linked-issue.yml +21 -21
- package/.github/workflows/publish.yml +63 -0
- package/.github/workflows/stale.yml +30 -30
- package/.github/workflows/test.yml +33 -33
- package/.github/workflows/welcome.yml +22 -22
- package/ANALYSIS.md +205 -205
- package/PR-REVIEW.md +74 -74
- package/README.md +335 -335
- package/TODO.md +34 -34
- package/docs/HOST-CONTRACT.md +36 -36
- package/docs/RVC-GUIDE.md +390 -390
- package/docs/USER-GUIDE.md +389 -389
- package/docs/acceptance-checklist.md +147 -147
- package/docs/adaptive-chunked-playback.md +194 -194
- package/docs/macos-test-prompt.md +230 -230
- package/docs/windows-gpu-verify-prompt.md +151 -151
- package/lib/client.js +7164 -7164
- package/lib/index.mjs +3374 -3374
- package/package.json +51 -51
- package/rvc-server.py +564 -564
- package/tests/README.md +45 -45
- package/tests/client-load.mjs +406 -406
- package/tests/e2e-compact-index.mjs +86 -86
- package/tests/e2e-index-ab.mjs +138 -138
- package/tests/e2e-real-rvc.mjs +122 -122
- package/tests/i18n-keys.mjs +82 -82
- package/tests/package-runtime-patch.mjs +88 -88
- package/tests/rvc-server-live.mjs +230 -230
- package/tests/smoke.mjs +1284 -1284
- package/tools/gen-ui-preview.mjs +174 -174
- package/tools/mac-verify.sh +158 -158
- package/tools/package-runtime.py +214 -214
package/tools/mac-verify.sh
CHANGED
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# mac-verify.sh — end-to-end verification of the dsh-plugin-tts RVC server on macOS.
|
|
3
|
-
#
|
|
4
|
-
# Checks, in order:
|
|
5
|
-
# 1. GET /health server up + device/CPU info
|
|
6
|
-
# 2. GET /files?kind=pth model discovery
|
|
7
|
-
# 3. POST /load load the voice model (+ optional index)
|
|
8
|
-
# 4. POST /convert real conversion of synthesized speech
|
|
9
|
-
# 5. output file sanity (RIFF/duration)
|
|
10
|
-
# 6. POST /compact-index (only when an index is given)
|
|
11
|
-
#
|
|
12
|
-
# Usage: bash mac-verify.sh <model.pth> [index.index] [host:port]
|
|
13
|
-
# PY env var overrides the python used for JSON/base64 (default python3 —
|
|
14
|
-
# use the rvc-work venv python: ~/rvc-work/venv/bin/python)
|
|
15
|
-
#
|
|
16
|
-
# Exit code 0 = all checks passed; otherwise the failing step's exit code.
|
|
17
|
-
set -u
|
|
18
|
-
|
|
19
|
-
MODEL="${1:-}"
|
|
20
|
-
INDEX="${2:-}"
|
|
21
|
-
HOST="${3:-127.0.0.1:4892}"
|
|
22
|
-
BASE="http://$HOST"
|
|
23
|
-
PY="${PY:-python3}"
|
|
24
|
-
FAIL=0
|
|
25
|
-
|
|
26
|
-
if [ -z "$MODEL" ]; then
|
|
27
|
-
echo "usage: bash mac-verify.sh <model.pth> [index.index] [host:port]"
|
|
28
|
-
exit 2
|
|
29
|
-
fi
|
|
30
|
-
if ! command -v "$PY" >/dev/null 2>&1; then
|
|
31
|
-
echo "ERROR: python not found: $PY (set PY=.../venv/bin/python)"
|
|
32
|
-
exit 2
|
|
33
|
-
fi
|
|
34
|
-
echo "== target: $BASE model: $MODEL index: ${INDEX:-<none>} py: $PY =="
|
|
35
|
-
|
|
36
|
-
step() { echo; echo "== [$1/6] $2 =="; }
|
|
37
|
-
|
|
38
|
-
step 1 "/health"
|
|
39
|
-
curl -sS --max-time 15 "$BASE/health" | "$PY" -c '
|
|
40
|
-
import json, sys
|
|
41
|
-
d = json.load(sys.stdin)
|
|
42
|
-
print(json.dumps(d, ensure_ascii=False, indent=1))
|
|
43
|
-
assert d.get("ok") is True, "health ok != true"
|
|
44
|
-
assert d.get("device") == "cpu", "expected cpu device on macOS, got %r" % d.get("device")
|
|
45
|
-
assert d.get("cuda_available") is False, "cuda must not be available on macOS"
|
|
46
|
-
' || FAIL=1
|
|
47
|
-
|
|
48
|
-
step 2 "/files?kind=pth"
|
|
49
|
-
curl -sS --max-time 15 "$BASE/files?kind=pth" | "$PY" -c '
|
|
50
|
-
import json, sys
|
|
51
|
-
d = json.load(sys.stdin)
|
|
52
|
-
files = d.get("files", [])
|
|
53
|
-
print("discovered %d .pth files" % len(files))
|
|
54
|
-
for f in files[:5]:
|
|
55
|
-
print(" ", f["name"], f["size"])
|
|
56
|
-
' || FAIL=1
|
|
57
|
-
|
|
58
|
-
step 3 "/load"
|
|
59
|
-
"$PY" - "$BASE" "$MODEL" "$INDEX" <<'PYEOF' || FAIL=1
|
|
60
|
-
import json, sys, urllib.request
|
|
61
|
-
base, model, index = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
62
|
-
payload = {"model": model}
|
|
63
|
-
if index:
|
|
64
|
-
payload["index"] = index
|
|
65
|
-
req = urllib.request.Request(base + "/load",
|
|
66
|
-
data=json.dumps(payload).encode(),
|
|
67
|
-
headers={"Content-Type": "application/json"})
|
|
68
|
-
try:
|
|
69
|
-
with urllib.request.urlopen(req, timeout=300) as r:
|
|
70
|
-
print(json.dumps(json.load(r), ensure_ascii=False))
|
|
71
|
-
except urllib.error.HTTPError as e:
|
|
72
|
-
print("load HTTP %s: %s" % (e.code, e.read().decode("utf-8", "ignore")[:600]))
|
|
73
|
-
sys.exit(1)
|
|
74
|
-
PYEOF
|
|
75
|
-
|
|
76
|
-
step 4 "/convert (real speech -> converted wav)"
|
|
77
|
-
TMPD="$(mktemp -d)"
|
|
78
|
-
trap 'rm -rf "$TMPD"' EXIT
|
|
79
|
-
if command -v say >/dev/null 2>&1; then
|
|
80
|
-
say -o "$TMPD/speech.aiff" "你好,这是一段跨平台语音转换测试。" 2>/dev/null \
|
|
81
|
-
&& ffmpeg -y -loglevel error -i "$TMPD/speech.aiff" -ac 1 -ar 44100 "$TMPD/speech.wav"
|
|
82
|
-
fi
|
|
83
|
-
if [ ! -s "$TMPD/speech.wav" ]; then
|
|
84
|
-
echo "note: say/ffmpeg failed, synthesizing a 3s tone instead"
|
|
85
|
-
ffmpeg -y -loglevel error -f lavfi -i "sine=frequency=440:duration=3" -ac 1 -ar 44100 "$TMPD/speech.wav"
|
|
86
|
-
fi
|
|
87
|
-
ls -l "$TMPD/speech.wav"
|
|
88
|
-
"$PY" - "$BASE" "$TMPD/speech.wav" "$TMPD/out.wav" <<'PYEOF' || FAIL=1
|
|
89
|
-
import base64, json, sys, time, urllib.request
|
|
90
|
-
base, wav, out = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
91
|
-
data = open(wav, "rb").read()
|
|
92
|
-
payload = {
|
|
93
|
-
"audio_base64": base64.b64encode(data).decode("ascii"),
|
|
94
|
-
"params": {"f0_method": "rmvpe", "index_rate": 0.75},
|
|
95
|
-
}
|
|
96
|
-
t0 = time.time()
|
|
97
|
-
req = urllib.request.Request(base + "/convert",
|
|
98
|
-
data=json.dumps(payload).encode(),
|
|
99
|
-
headers={"Content-Type": "application/json"})
|
|
100
|
-
try:
|
|
101
|
-
with urllib.request.urlopen(req, timeout=900) as r:
|
|
102
|
-
resp = json.load(r)
|
|
103
|
-
except urllib.error.HTTPError as e:
|
|
104
|
-
print("convert HTTP %s: %s" % (e.code, e.read().decode("utf-8", "ignore")[:800]))
|
|
105
|
-
sys.exit(1)
|
|
106
|
-
wall = time.time() - t0
|
|
107
|
-
out_b64 = resp.get("audio_base64")
|
|
108
|
-
if not out_b64:
|
|
109
|
-
print("CONVERT FAILED (no audio in response):", json.dumps(resp, ensure_ascii=False)[:600])
|
|
110
|
-
sys.exit(1)
|
|
111
|
-
open(out, "wb").write(base64.b64decode(out_b64))
|
|
112
|
-
sr = int(resp.get("sample_rate", 0))
|
|
113
|
-
in_sec = len(data) / 2.0 / 44100.0
|
|
114
|
-
print("convert ok: wall=%.1fs sample_rate=%s out=%s (%.2fx realtime)" % (wall, sr, out, wall / max(in_sec, 0.01)))
|
|
115
|
-
PYEOF
|
|
116
|
-
|
|
117
|
-
step 5 "output sanity (RIFF + duration)"
|
|
118
|
-
# RIFF magic check. NB: macOS `od -An -tx1` pads bytes with *two* spaces
|
|
119
|
-
# ("52 49 46 46") while GNU `od` uses one ("52 49 46 46"), so a literal
|
|
120
|
-
# "52 49 46 46" grep wrongly fails on macOS even for a valid RIFF file.
|
|
121
|
-
# Strip whitespace and compare the raw hex instead (portable across both).
|
|
122
|
-
if command -v xxd >/dev/null 2>&1; then
|
|
123
|
-
MAGIC="$(head -c 4 "$TMPD/out.wav" | xxd -p | tr -d '[:space:]')"
|
|
124
|
-
else
|
|
125
|
-
MAGIC="$(head -c 4 "$TMPD/out.wav" | od -An -tx1 | tr -d '[:space:]')"
|
|
126
|
-
fi
|
|
127
|
-
if [ "$MAGIC" != "52494646" ]; then echo "FAIL: not a RIFF file"; FAIL=1; fi
|
|
128
|
-
if command -v ffprobe >/dev/null 2>&1; then
|
|
129
|
-
DUR="$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TMPD/out.wav" 2>/dev/null)"
|
|
130
|
-
echo "out.wav duration: ${DUR:-unknown}s (input speech was ~3s)"
|
|
131
|
-
fi
|
|
132
|
-
|
|
133
|
-
if [ -n "$INDEX" ]; then
|
|
134
|
-
step 6 "/compact-index"
|
|
135
|
-
"$PY" - "$BASE" "$INDEX" <<'PYEOF' || FAIL=1
|
|
136
|
-
import json, sys, urllib.request
|
|
137
|
-
base, idx = sys.argv[1], sys.argv[2]
|
|
138
|
-
req = urllib.request.Request(base + "/compact-index",
|
|
139
|
-
data=json.dumps({"index": idx, "target_vectors": 2000}).encode(),
|
|
140
|
-
headers={"Content-Type": "application/json"})
|
|
141
|
-
try:
|
|
142
|
-
with urllib.request.urlopen(req, timeout=900) as r:
|
|
143
|
-
print(json.dumps(json.load(r), ensure_ascii=False, indent=1))
|
|
144
|
-
except urllib.error.HTTPError as e:
|
|
145
|
-
print("compact-index HTTP %s: %s" % (e.code, e.read().decode("utf-8", "ignore")[:600]))
|
|
146
|
-
sys.exit(1)
|
|
147
|
-
PYEOF
|
|
148
|
-
else
|
|
149
|
-
step 6 "skipped (/compact-index needs an index path)"
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
|
-
echo
|
|
153
|
-
if [ "$FAIL" -eq 0 ]; then
|
|
154
|
-
echo "ALL CHECKS PASSED (macOS)"
|
|
155
|
-
else
|
|
156
|
-
echo "SOME CHECKS FAILED — see output above"
|
|
157
|
-
fi
|
|
158
|
-
exit "$FAIL"
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# mac-verify.sh — end-to-end verification of the dsh-plugin-tts RVC server on macOS.
|
|
3
|
+
#
|
|
4
|
+
# Checks, in order:
|
|
5
|
+
# 1. GET /health server up + device/CPU info
|
|
6
|
+
# 2. GET /files?kind=pth model discovery
|
|
7
|
+
# 3. POST /load load the voice model (+ optional index)
|
|
8
|
+
# 4. POST /convert real conversion of synthesized speech
|
|
9
|
+
# 5. output file sanity (RIFF/duration)
|
|
10
|
+
# 6. POST /compact-index (only when an index is given)
|
|
11
|
+
#
|
|
12
|
+
# Usage: bash mac-verify.sh <model.pth> [index.index] [host:port]
|
|
13
|
+
# PY env var overrides the python used for JSON/base64 (default python3 —
|
|
14
|
+
# use the rvc-work venv python: ~/rvc-work/venv/bin/python)
|
|
15
|
+
#
|
|
16
|
+
# Exit code 0 = all checks passed; otherwise the failing step's exit code.
|
|
17
|
+
set -u
|
|
18
|
+
|
|
19
|
+
MODEL="${1:-}"
|
|
20
|
+
INDEX="${2:-}"
|
|
21
|
+
HOST="${3:-127.0.0.1:4892}"
|
|
22
|
+
BASE="http://$HOST"
|
|
23
|
+
PY="${PY:-python3}"
|
|
24
|
+
FAIL=0
|
|
25
|
+
|
|
26
|
+
if [ -z "$MODEL" ]; then
|
|
27
|
+
echo "usage: bash mac-verify.sh <model.pth> [index.index] [host:port]"
|
|
28
|
+
exit 2
|
|
29
|
+
fi
|
|
30
|
+
if ! command -v "$PY" >/dev/null 2>&1; then
|
|
31
|
+
echo "ERROR: python not found: $PY (set PY=.../venv/bin/python)"
|
|
32
|
+
exit 2
|
|
33
|
+
fi
|
|
34
|
+
echo "== target: $BASE model: $MODEL index: ${INDEX:-<none>} py: $PY =="
|
|
35
|
+
|
|
36
|
+
step() { echo; echo "== [$1/6] $2 =="; }
|
|
37
|
+
|
|
38
|
+
step 1 "/health"
|
|
39
|
+
curl -sS --max-time 15 "$BASE/health" | "$PY" -c '
|
|
40
|
+
import json, sys
|
|
41
|
+
d = json.load(sys.stdin)
|
|
42
|
+
print(json.dumps(d, ensure_ascii=False, indent=1))
|
|
43
|
+
assert d.get("ok") is True, "health ok != true"
|
|
44
|
+
assert d.get("device") == "cpu", "expected cpu device on macOS, got %r" % d.get("device")
|
|
45
|
+
assert d.get("cuda_available") is False, "cuda must not be available on macOS"
|
|
46
|
+
' || FAIL=1
|
|
47
|
+
|
|
48
|
+
step 2 "/files?kind=pth"
|
|
49
|
+
curl -sS --max-time 15 "$BASE/files?kind=pth" | "$PY" -c '
|
|
50
|
+
import json, sys
|
|
51
|
+
d = json.load(sys.stdin)
|
|
52
|
+
files = d.get("files", [])
|
|
53
|
+
print("discovered %d .pth files" % len(files))
|
|
54
|
+
for f in files[:5]:
|
|
55
|
+
print(" ", f["name"], f["size"])
|
|
56
|
+
' || FAIL=1
|
|
57
|
+
|
|
58
|
+
step 3 "/load"
|
|
59
|
+
"$PY" - "$BASE" "$MODEL" "$INDEX" <<'PYEOF' || FAIL=1
|
|
60
|
+
import json, sys, urllib.request
|
|
61
|
+
base, model, index = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
62
|
+
payload = {"model": model}
|
|
63
|
+
if index:
|
|
64
|
+
payload["index"] = index
|
|
65
|
+
req = urllib.request.Request(base + "/load",
|
|
66
|
+
data=json.dumps(payload).encode(),
|
|
67
|
+
headers={"Content-Type": "application/json"})
|
|
68
|
+
try:
|
|
69
|
+
with urllib.request.urlopen(req, timeout=300) as r:
|
|
70
|
+
print(json.dumps(json.load(r), ensure_ascii=False))
|
|
71
|
+
except urllib.error.HTTPError as e:
|
|
72
|
+
print("load HTTP %s: %s" % (e.code, e.read().decode("utf-8", "ignore")[:600]))
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
PYEOF
|
|
75
|
+
|
|
76
|
+
step 4 "/convert (real speech -> converted wav)"
|
|
77
|
+
TMPD="$(mktemp -d)"
|
|
78
|
+
trap 'rm -rf "$TMPD"' EXIT
|
|
79
|
+
if command -v say >/dev/null 2>&1; then
|
|
80
|
+
say -o "$TMPD/speech.aiff" "你好,这是一段跨平台语音转换测试。" 2>/dev/null \
|
|
81
|
+
&& ffmpeg -y -loglevel error -i "$TMPD/speech.aiff" -ac 1 -ar 44100 "$TMPD/speech.wav"
|
|
82
|
+
fi
|
|
83
|
+
if [ ! -s "$TMPD/speech.wav" ]; then
|
|
84
|
+
echo "note: say/ffmpeg failed, synthesizing a 3s tone instead"
|
|
85
|
+
ffmpeg -y -loglevel error -f lavfi -i "sine=frequency=440:duration=3" -ac 1 -ar 44100 "$TMPD/speech.wav"
|
|
86
|
+
fi
|
|
87
|
+
ls -l "$TMPD/speech.wav"
|
|
88
|
+
"$PY" - "$BASE" "$TMPD/speech.wav" "$TMPD/out.wav" <<'PYEOF' || FAIL=1
|
|
89
|
+
import base64, json, sys, time, urllib.request
|
|
90
|
+
base, wav, out = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
91
|
+
data = open(wav, "rb").read()
|
|
92
|
+
payload = {
|
|
93
|
+
"audio_base64": base64.b64encode(data).decode("ascii"),
|
|
94
|
+
"params": {"f0_method": "rmvpe", "index_rate": 0.75},
|
|
95
|
+
}
|
|
96
|
+
t0 = time.time()
|
|
97
|
+
req = urllib.request.Request(base + "/convert",
|
|
98
|
+
data=json.dumps(payload).encode(),
|
|
99
|
+
headers={"Content-Type": "application/json"})
|
|
100
|
+
try:
|
|
101
|
+
with urllib.request.urlopen(req, timeout=900) as r:
|
|
102
|
+
resp = json.load(r)
|
|
103
|
+
except urllib.error.HTTPError as e:
|
|
104
|
+
print("convert HTTP %s: %s" % (e.code, e.read().decode("utf-8", "ignore")[:800]))
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
wall = time.time() - t0
|
|
107
|
+
out_b64 = resp.get("audio_base64")
|
|
108
|
+
if not out_b64:
|
|
109
|
+
print("CONVERT FAILED (no audio in response):", json.dumps(resp, ensure_ascii=False)[:600])
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
open(out, "wb").write(base64.b64decode(out_b64))
|
|
112
|
+
sr = int(resp.get("sample_rate", 0))
|
|
113
|
+
in_sec = len(data) / 2.0 / 44100.0
|
|
114
|
+
print("convert ok: wall=%.1fs sample_rate=%s out=%s (%.2fx realtime)" % (wall, sr, out, wall / max(in_sec, 0.01)))
|
|
115
|
+
PYEOF
|
|
116
|
+
|
|
117
|
+
step 5 "output sanity (RIFF + duration)"
|
|
118
|
+
# RIFF magic check. NB: macOS `od -An -tx1` pads bytes with *two* spaces
|
|
119
|
+
# ("52 49 46 46") while GNU `od` uses one ("52 49 46 46"), so a literal
|
|
120
|
+
# "52 49 46 46" grep wrongly fails on macOS even for a valid RIFF file.
|
|
121
|
+
# Strip whitespace and compare the raw hex instead (portable across both).
|
|
122
|
+
if command -v xxd >/dev/null 2>&1; then
|
|
123
|
+
MAGIC="$(head -c 4 "$TMPD/out.wav" | xxd -p | tr -d '[:space:]')"
|
|
124
|
+
else
|
|
125
|
+
MAGIC="$(head -c 4 "$TMPD/out.wav" | od -An -tx1 | tr -d '[:space:]')"
|
|
126
|
+
fi
|
|
127
|
+
if [ "$MAGIC" != "52494646" ]; then echo "FAIL: not a RIFF file"; FAIL=1; fi
|
|
128
|
+
if command -v ffprobe >/dev/null 2>&1; then
|
|
129
|
+
DUR="$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TMPD/out.wav" 2>/dev/null)"
|
|
130
|
+
echo "out.wav duration: ${DUR:-unknown}s (input speech was ~3s)"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
if [ -n "$INDEX" ]; then
|
|
134
|
+
step 6 "/compact-index"
|
|
135
|
+
"$PY" - "$BASE" "$INDEX" <<'PYEOF' || FAIL=1
|
|
136
|
+
import json, sys, urllib.request
|
|
137
|
+
base, idx = sys.argv[1], sys.argv[2]
|
|
138
|
+
req = urllib.request.Request(base + "/compact-index",
|
|
139
|
+
data=json.dumps({"index": idx, "target_vectors": 2000}).encode(),
|
|
140
|
+
headers={"Content-Type": "application/json"})
|
|
141
|
+
try:
|
|
142
|
+
with urllib.request.urlopen(req, timeout=900) as r:
|
|
143
|
+
print(json.dumps(json.load(r), ensure_ascii=False, indent=1))
|
|
144
|
+
except urllib.error.HTTPError as e:
|
|
145
|
+
print("compact-index HTTP %s: %s" % (e.code, e.read().decode("utf-8", "ignore")[:600]))
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
PYEOF
|
|
148
|
+
else
|
|
149
|
+
step 6 "skipped (/compact-index needs an index path)"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
echo
|
|
153
|
+
if [ "$FAIL" -eq 0 ]; then
|
|
154
|
+
echo "ALL CHECKS PASSED (macOS)"
|
|
155
|
+
else
|
|
156
|
+
echo "SOME CHECKS FAILED — see output above"
|
|
157
|
+
fi
|
|
158
|
+
exit "$FAIL"
|