@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.
Files changed (37) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +51 -51
  2. package/.github/ISSUE_TEMPLATE/config.yml +2 -2
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +28 -28
  4. package/.github/pull_request_template.md +7 -7
  5. package/.github/workflows/contrib-triage.yml +66 -66
  6. package/.github/workflows/linked-issue.yml +21 -21
  7. package/.github/workflows/publish.yml +63 -0
  8. package/.github/workflows/stale.yml +30 -30
  9. package/.github/workflows/test.yml +33 -33
  10. package/.github/workflows/welcome.yml +22 -22
  11. package/ANALYSIS.md +205 -205
  12. package/PR-REVIEW.md +74 -74
  13. package/README.md +335 -335
  14. package/TODO.md +34 -34
  15. package/docs/HOST-CONTRACT.md +36 -36
  16. package/docs/RVC-GUIDE.md +390 -390
  17. package/docs/USER-GUIDE.md +389 -389
  18. package/docs/acceptance-checklist.md +147 -147
  19. package/docs/adaptive-chunked-playback.md +194 -194
  20. package/docs/macos-test-prompt.md +230 -230
  21. package/docs/windows-gpu-verify-prompt.md +151 -151
  22. package/lib/client.js +7164 -7164
  23. package/lib/index.mjs +3374 -3374
  24. package/package.json +51 -51
  25. package/rvc-server.py +564 -564
  26. package/tests/README.md +45 -45
  27. package/tests/client-load.mjs +406 -406
  28. package/tests/e2e-compact-index.mjs +86 -86
  29. package/tests/e2e-index-ab.mjs +138 -138
  30. package/tests/e2e-real-rvc.mjs +122 -122
  31. package/tests/i18n-keys.mjs +82 -82
  32. package/tests/package-runtime-patch.mjs +88 -88
  33. package/tests/rvc-server-live.mjs +230 -230
  34. package/tests/smoke.mjs +1284 -1284
  35. package/tools/gen-ui-preview.mjs +174 -174
  36. package/tools/mac-verify.sh +158 -158
  37. package/tools/package-runtime.py +214 -214
@@ -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"