dsh-voice-say 0.1.0 → 0.4.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/README.md CHANGED
@@ -1,74 +1,196 @@
1
- # dsh-voice-say —— 语音合成(音色复刻)
1
+ # dsh-voice-say
2
2
 
3
- 把任意文字**用一段参考音频的音色**说出来。两条路:
3
+ 把「用某个音色、按某种情绪说一句话」做成 DSH 里可安装的一个插件。
4
4
 
5
- | 引擎 | 怎么工作 | 需要什么 | 速度 |
6
- |---|---|---|---|
7
- | **cloud** | 把参考音频上传给服务商做**音色复刻**,拿到 voice_id 后合成 | 一个 API key(MiniMax 海螺,或任何 OpenAI 风格 `/audio/speech` 接口) | 秒级 |
8
- | **local** | 本机 [F5-TTS](https://github.com/SWivid/F5-TTS) 零样本克隆,完全离线 | 跑一次「装环境」:venv + 约 1.8GB 模型 | CPU 上一句几十秒到几分钟 |
5
+ 两条通道,同一组主参数:
9
6
 
10
- > 本插件**只负责合成**,不负责发到哪儿。要在 QQ 里发语音,需要 qq-bridge 那侧的发语音工具(或者自己把 wav 发出去)。
7
+ | 通道 | 引擎 | 需要什么 |
8
+ | --- | --- | --- |
9
+ | `local` | Breeze TTS 2(一个 exe + 一个 gguf) | 一份本地模型,免费离线,Vulkan / CPU 都行 |
10
+ | `cloud` | 任意 OpenAI 风格 TTS(BreezeBlue 等) | `apiKey` + `voiceId`,一次 HTTP |
11
11
 
12
- ## 装
12
+ ## 核心:调声音不靠调快慢
13
13
 
14
- ```bash
15
- dsh plugin --profile <你的profile> add dsh-voice-say
14
+ ```
15
+ instructions 自然语言写情绪、语气、停顿 —— 怎么说话
16
+ guidanceScale 模型听不听这段指令(1.0 = 不额外施加,>2 容易发毛)
16
17
  ```
17
18
 
18
- 或在 DSH 里 `plugin_manager` → `install_bundle <本目录或 .tgz>`。装完刷新页面,**设置 → 语音合成**。
19
+ 语速、音高这类硬参数**不是**主要手段。想让它慢一点、冷一点、带点哽咽,
20
+ 正确做法是把话写进 `instructions`,然后决定要不要把 `guidanceScale` 拉高一点。
19
21
 
20
- ## 用
22
+ 两条通道怎么映射:
23
+
24
+ | 插件参数 | 本地(breeze-cli) | 云端(HTTP) |
25
+ | --- | --- | --- |
26
+ | `instructions` | `--instruction` | `instructions` |
27
+ | `guidanceScale` | `--cfg-scale` | `guidance_scale`(可改成 `cfg_scale`) |
21
28
 
22
- ### 云端(推荐起步)
29
+ ## 风格:不用自己想词
23
30
 
24
- 设置页里选 `cloud` → 选服务商 → 填 API Key(OpenAI 兼容的还要 Base URL / 模型 / 音色)→ 保存。
25
- MiniMax 那条会把参考音频上传复刻一次,之后复用缓存的 voice_id。
31
+ 自己写 instructions 最难的是「想不出词」。所以内置 18 个风格,点一下就填好:
26
32
 
27
- ### 本地(免费离线)
33
+ | id | 风格 | 建议强度 | 一句话 |
34
+ | --- | --- | --- | --- |
35
+ | `tsundere` | 😤 傲娇 | 1.45 | 嘴上不饶人,明明在意却装作无所谓 |
36
+ | `aloof` | 🧊 高冷 | 1.40 | 情绪和音量都压下去,懒得解释 |
37
+ | `coquettish` | 🥺 撒娇 | 1.40 | 尾音上扬拖长,带鼻音,在讨反应 |
38
+ | `gentle` | 🌙 温柔 | 1.20 | 轻声细语,句尾软下来 |
39
+ | `genki` | ⚡ 元气 | 1.30 | 语速偏快,音色明亮 |
40
+ | `lazy` | 🛋️ 慵懒 | 1.35 | 懒散拖音,像刚睡醒 |
41
+ | `tired` | 😮‍💨 疲惫 | 1.35 | 沙哑、气息不足,偶尔叹气 |
42
+ | `choked` | 💧 哽咽 | 1.50 | 忍着哭,气息不稳,句子中间断一下 |
43
+ | `perfunctory` | 😐 敷衍 | 1.40 | 只给最短回应,明显没兴趣 |
44
+ | `angry` | 🔥 生气 | 1.50 | 压着火,字咬得重,但不喊 |
45
+ | `shy` | 😳 害羞 | 1.45 | 越来越小声,像不敢说完 |
46
+ | `whisper` | 🤫 耳语 | 1.50 | 气声,贴着耳朵说 |
47
+ | `broadcast` | 🎙️ 正经播报 | 1.15 | 播音腔,吐字清晰,无个人情绪 |
48
+ | `aggrieved` | 🥲 委屈 | 1.45 | 拖尾音带鼻音,像刚被欺负 |
49
+ | `mysterious` | 🌫️ 神秘低语 | 1.50 | 压低声线,很慢,句尾下坠 |
50
+ | `excited` | ✨ 兴奋 | 1.35 | 音调高、快,亮得忍不住笑 |
51
+ | `coaxing` | 🫧 哄人 | 1.25 | 放软放慢,一句一句哄,带笑意 |
52
+ | `deadpan` | 🫥 面瘫 | 1.35 | 完全平,像在念清单 |
28
53
 
29
- 点「一键装环境」,它会:
54
+ **可以叠加**:选「傲娇 + 高冷」= 两段描述按顺序拼成一条 `--instruction`,
55
+ 建议强度取其中最大值,之后还能自己拉 `guidanceScale` 微调。
30
56
 
31
- 1. 建 venv(优先 `uv`,没有就 `python -m venv`)
32
- 2. `pip install f5-tts faster-whisper soundfile`
33
- 3. 下模型 —— **都走国内可达的镜像**:F5-TTS 与 whisper 走 ModelScope,声码器走 `aifasthub.com`
34
- (官方 HuggingFace 的 xet 存储在国内会 302 到连不上的主机,这条路不通)
35
- 4. 把参考音频转成 24k 单声道并裁到 12 秒,然后用本地 whisper 转写参考文字
57
+ 设置页里点风格 → 下面实时显示**实际会发给引擎的那段 instructions**;
58
+ 不爽就再往 `instructions` 框里补一句自己的话(傲娇 + 高冷 + 「最后一句压下去」)。
36
59
 
37
- 装完把参考音频放进 `<数据根>/ref/`(或脚本参数 `node scripts/setup.mjs <参考音频>`),写好后设置页里「参考音频 / 参考文字」会变 ✓。
60
+ 工具侧一样:`voice_say { text, styles: ["tsundere","aloof"] }`,
61
+ `voice_styles` 列出全部风格(可带 `query` 过滤)。
38
62
 
39
- ### 工具与接口
63
+ ## 读音导演:先断句,再念
64
+
65
+ 引擎念的是**标点**。同一句「我不要」,写成 `我不要。` 和 `我不要……` 是两个意思;
66
+ 而 TTS 自己不知道她此刻是什么心情、刚才在跟谁聊什么。所以在合成之前,多加了一道:
40
67
 
41
68
  ```
42
- 工具:voice_say({ text, provider?, speed? }) → { out, seconds, engine, ... }
43
- GET /api/voice-say/status 状态(引擎、key、本地环境、参考音频)
44
- POST /api/voice-say/config 改配置(存到 <数据根>/config.json)
45
- POST /api/voice-say/synth { text, provider?, speed? }
46
- GET /api/voice-say/audio?name=xxx.wav 取回合成结果(页内试听)
47
- POST /api/voice-say/setup 启动「装环境」(后台跑,日志同下)
48
- GET /api/voice-say/setup 读装环境日志尾巴
49
- POST /api/voice-say/reference { src } 换参考音频:转码(文字仍需转写或手写)
69
+ 台词 → 读音导演(和 QQ 同一个大脑)→ 打好标点的台词 → Breeze / 云端 → 音频
50
70
  ```
51
71
 
52
- ## 数据放哪
72
+ 导演看三样东西,全部从本机真实数据里取,不看摘要、不猜:
73
+
74
+ | 它看什么 | 从哪儿取 |
75
+ | --- | --- |
76
+ | 人设 | dsh-life 的 `identity.json`(名字、九项人格参数、原则) |
77
+ | 此刻情绪 | 情感引擎的 `params.json`(某个会话的七情当前值 / 上限、语言指纹) |
78
+ | 当时情节 | QQ 桥接的 `social-v2.json`(这个会话最近几条消息、正在聊的话题、上次被什么叫醒) |
79
+
80
+ 然后它只做一件事:**重排标点与停顿**——哪儿断句、哪儿留一口气、结尾是句号还是省略号。
81
+
82
+ **硬约束**:字词一个都不许改。
83
+
84
+ - 模型输出会先跟原文逐字核对(只比汉字/字母/数字,标点不计)。
85
+ - 不一致就先重试一次(明确要求「只许动标点」)。
86
+ - 还是不一致,就走**盖章法**:把它想出来的标点剥下来,原封不动盖回原文的词上——标点是它的,字还是原字。
87
+ - 再不行就作废,**退回原文照念**。合成永远不会因为导演而失败或卡死(有超时)。
53
88
 
54
- 默认 `<用户主目录>/.dsh-voice`,可用插件配置的 `dataRoot` 或环境变量 `DSH_VOICE_HOME` 改:
89
+ 设置页里「试算标点」只算不合成,「看上下文」能看到导演手里到底有什么料(不发模型请求)。
55
90
 
56
91
  ```
57
- config.json 设置页写的配置(覆盖插件 patch 里的 config)
58
- venv/ 本地引擎的 Python 环境
59
- models/ f5tts / vocos-mel-24khz / whisper-small
60
- ref/ ref-24k.wav(24k 单声道)+ ref-text.txt(参考文字)
61
- out/ 合成结果
62
- setup.log 装环境日志
92
+ voice_direct { text: "你不要再这样了,我真的会难过的。", styles: ["choked"] }
93
+ → 原句 你不要再这样了,我真的会难过的。
94
+ → 处理 你不要……再这样了。我真的……会难过的。
95
+ → 说明 用两处省略号把话断开,让气卡在「再这样」和「会难过」前面……
96
+ → 情绪 哽咽压抑
63
97
  ```
64
98
 
99
+ ## 装
100
+
101
+ ```bash
102
+ dsh plugin --profile <profile> add dsh-voice-say
103
+ ```
104
+
105
+ 或者手工:把包目录加进 profile 的 `dsh.profile.bundles`,重启 DSH。
106
+ 装好以后在 **设置 → 语音合成** 里就能用。
107
+
108
+ 本地通道要先有一份 Breeze TTS 2:`breeze-cli.exe` + `*.gguf`。
109
+ 插件会自己按常见位置探测(`D:/dprz/breeze-tts2`、`$BREEZE_ROOT`、`$BREEZE_CLI`、`$BREEZE_MODEL`),
110
+ 探不到就在设置页里手填。
111
+
112
+ ## 用
113
+
114
+ 七个工具:
115
+
116
+ | 工具 | 干什么 |
117
+ | --- | --- |
118
+ | `voice_say` | 合成一句话(`text` / `provider` / `voice` / `styles` / `instructions` / `guidanceScale` / `seed` / `direct` / `scope`) |
119
+ | `voice_preview` | 合成一小段试听,听当前风格与参数的效果 |
120
+ | `voice_direct` | 只算标点不合成:看看导演会把这句话断成什么样 |
121
+ | `voice_styles` | 列出 18 个风格(`query` 可过滤),叠加时把 id 传给 `styles` |
122
+ | `voice_list` | 列已保存音色,顺便报引擎是否就位 |
123
+ | `voice_add` | 把一段音频存成音色(`name` + `text` 逐字转写 + `audio`) |
124
+ | `voice_delete` | 删一个音色 |
125
+
126
+ 设置页里还能**上传本地音频**(浏览器选文件 → base64 上传 → 落盘 → 编码成 `voices/<name>.breeze`),
127
+ 以及直接播放试听片段。
128
+
129
+ ## 上传音色时只有一条硬要求
130
+
131
+ `ref_text` 必须是那段音频里**一字不差的转写**。引擎靠它对齐音色,写错整条音色就歪了。
132
+ 另外:5~15 秒、安静环境、单人说话最稳;带混响或背景音乐会把噪点一起克隆进去。
133
+
134
+ ## 配置
135
+
136
+ 配置合并顺序:插件 patch 的 `config` ← 被 `<dataRoot>/config.json` 覆盖
137
+ (设置页写的是后者,改完立刻生效,不用动 profile)。
138
+
139
+ | 键 | 默认 | 说明 |
140
+ | --- | --- | --- |
141
+ | `provider` | `local` | `local` / `cloud` |
142
+ | `instructions` | 一句傲娇味的默认值 | 怎么说话;用风格时它就是「补充」 |
143
+ | `styles` | `[]` | 选中的风格 id 数组(可多个,按顺序拼) |
144
+ | `guidanceScale` | `1` | 1.0~3.0,1.0 = 不额外施加 |
145
+ | `seed` | `42` | 换种子 = 换一种演绎 |
146
+ | `maxChars` | `300` | 单次合成字数上限 |
147
+ | `defaultVoice` | 空 | 不指定就用 `instructions` 现场设计嗓音 |
148
+ | `cli` / `model` | 自动探测 | breeze-cli.exe 与 .gguf |
149
+ | `voicesDir` / `inboxDir` | 自动探测 | 音色目录 / 上传暂存目录 |
150
+ | `device` / `gpuIndex` | `vulkan` / `1` | 多显卡机器要钉住独显 |
151
+ | `ffmpeg` | 自动探测 | 非 wav 的参考音频要它转码 |
152
+ | `cloudBaseUrl` / `cloudApiKey` | 空 | 云端通道 |
153
+ | `cloudVoiceId` / `cloudModel` | 空 / `breeze-tts-2` | 云端音色与模型名 |
154
+ | `cloudStyle` | `json` | `json` = OpenAI 风格;`form` = breeze-server 原生表单 |
155
+ | `cloudGuidanceField` | `guidance_scale` | 有的服务叫 `cfg_scale` |
156
+ | `timeoutMs` | `600000` | 单次合成超时 |
157
+ | `director.enabled` | `true` | 合成前要不要先过读音导演 |
158
+ | `director.baseUrl` / `director.model` | `https://ai.xyclub.top/v1` / `deepseek-v4.1-flash` | 导演用的模型(和 QQ 同一个) |
159
+ | `director.apiKey` / `apiKeyEnv` | 空 / `DPSK_API_KEY` | key 优先用它,其次环境变量,其次 `~/.dsh/.credentials.yaml` |
160
+ | `director.scope` | 自动 | 钉住某个会话的剧情与情绪(`group:xxx` / `private:xxx`) |
161
+ | `director.timeoutMs` | `20000` | 超时就退回原文照念 |
162
+ | `lifeHome` / `emotionHome` / `bridgeState` | 自动探测 | 人设 / 情绪 / 剧情三份数据的目录 |
163
+
164
+ ## HTTP
165
+
166
+ 全部只认本机同源(`127.0.0.1` / `localhost` / `[::1]`)。
167
+
168
+ | 方法 | 路径 | 说明 |
169
+ | --- | --- | --- |
170
+ | GET | `/api/voice-say/status` | 状态:引擎、音色、云端、导演、默认参数 |
171
+ | GET | `/api/voice-say/styles` | 18 个风格 + 当前选中的 |
172
+ | GET | `/api/voice-say/direct/context` | 导演手里有什么料(人设 / 情绪 / 剧情),不发模型请求 |
173
+ | POST | `/api/voice-say/direct` | 只算标点:`{text,styles,instructions,scope}` |
174
+ | POST | `/api/voice-say/direct/cache` | 清掉导演的标点缓存 |
175
+ | POST | `/api/voice-say/config` | 改配置(写进 `config.json`) |
176
+ | GET | `/api/voice-say/voices` | 列音色 |
177
+ | POST | `/api/voice-say/voices` | 上传音色:`{name,text,description,filename,data}`(`data` 为 base64)或 `{name,text,src}` |
178
+ | POST | `/api/voice-say/voices/delete` | `{name}` |
179
+ | POST | `/api/voice-say/synth` | 合成:`{text,provider,voice,styles,instructions,guidanceScale,seed,direct,scope}` |
180
+ | POST | `/api/voice-say/preview` | 试听(同上,`text` 可省) |
181
+ | POST | `/api/voice-say/cloud/test` | 云端连通性(真发一次极短合成) |
182
+ | GET | `/api/voice-say/audio?name=` | 取回合成出来的音频 |
183
+
65
184
  ## 已知限制
66
185
 
67
- - CPU 上本地合成**慢**(一句几十秒到几分钟)。要快就上 GPU(装 CUDA 版 torch)或改用云端。
68
- - 本地克隆质量取决于参考音频:**5~15 秒干净人声**最好;带伴奏/唱歌的素材会跑偏。
69
- - 「参考文字」如果与实际音频不符,克隆会明显失真 —— 转写不可靠时可以手写 `ref-text.txt`。
70
- - 云端复刻需要服务商支持(MiniMax 支持上传音频;OpenAI 官方 `/audio/speech` 只能用预置音色)。
186
+ - 本地通道每次合成都起一个进程、加载一次模型,第一声比云端慢;连续多句时云端更划算。
187
+ - 引擎本身没有语速数值参数,`speed` 之类的硬参数在这里**故意不做**。
188
+ - `guidanceScale` 超过 2 之后音质会发毛,只在嗓音明显不听指令时才往上顶。
189
+ - 音色目录与 QQ 桥接共用时,这里存好的音色在群里可以直接用。
190
+ - 读音导演多花 2~3 秒(一次 `chat/completions`),并且**不生成新内容**:它只分配标点。
191
+ 真要改字,是主对话那边的事,不是这里。
192
+ - 导演看的是「本机最近一段对话」,如果你要念的台词跟那段剧情无关,把 `director.scope` 钉到对的会话,或者干脆 `direct:false`。
71
193
 
72
- ## 许可
194
+ ## License
73
195
 
74
- MIT。
196
+ MIT