dsh-live-speak 1.0.1 → 1.0.2

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/RELEASE-NOTES.md CHANGED
@@ -204,6 +204,26 @@ dsh-live-speak
204
204
 
205
205
  ---
206
206
 
207
+ ## 1.0.2 更新说明
208
+
209
+ **修掉:标题和正文连成一句**
210
+
211
+ 一篇带小标题的文章,原来会念成「帘卷西风济南的趵突泉边,如今游人如织」——标题和正文粘在一起,一口气读下来。
212
+
213
+ 根因不在标点,而在清洗阶段:为了去掉替换文件名后留下的空格,有一处正则用了「任意空白」,而这个「空白」把段落之间的**换行也算在内**。于是单独一行的标题被并进下一段,连成一句话,后面「短行补标点」的处理也就永远轮不到它。
214
+
215
+ 现在:
216
+
217
+ - 只抹掉同一行里的空格,换行原样保留,标题重新独立成行
218
+ - 标题行末尾补的是**句号**,不是省略号——实测下来省略号几乎不产生停顿,句号才有一次真正的断句
219
+ - 顺带修好同类问题:列表项(`- 写周报` 这种)和短的独立行,现在各自成句,不再粘成一片
220
+
221
+ **验证**:真实播报一篇 246 字、带一个短行标题的文章,切成 2 段,标题被读成「帘卷西风。」后接正文;两段合成零失败、零重试,播放间隙 2 毫秒,全程无跳段。
222
+
223
+ 当前版本:**1.0.2**
224
+
225
+ ---
226
+
207
227
  ## 1.0.1 更新说明
208
228
 
209
229
  **新增:本地语音档,可选可换**
@@ -224,5 +244,3 @@ dsh-live-speak
224
244
  - 本地合成若返回空音频(只有文件头、时长 0),原来会被当成成功、播放 0 秒等于丢段——现在会检测出来并换声线重试
225
245
 
226
246
  **一条不变的原则**:合成失败不跳过。任何一段合成不出来,就一直重试到成功为止,宁可晚几秒开口,也不让内容缺一段。
227
-
228
- 当前版本:**1.0.1**
package/index.js CHANGED
@@ -452,7 +452,9 @@ function cleanForSpeech(raw) {
452
452
  text = text.replace(/`([^`]*)`/gu, '$1')
453
453
  // 标题行不补句号,改补省略号:句号只给 200 毫秒,听起来还是"帘卷西风济南的趵突泉边"一句;
454
454
  // 省略号的停顿明显长一截,标题和正文才听得出是两件事。
455
- text = text.replace(/^\s{0,3}#{1,6}\s+(.+)$/gmu, (match, title) => (/[。!?!?::,,…]$/u.test(title) ? title : `${title}……`))
455
+ // (2026-11 实测推翻上面这条:24kHz MP3 里省略号的停顿比无标点还短,句号才有真正的断句。
456
+ // 所以这里补句号,标题和正文之间才有约 0.39 秒的空隙。)
457
+ text = text.replace(/^\s{0,3}#{1,6}\s+(.+)$/gmu, (match, title) => (/[。!?!?::,,…]$/u.test(title) ? title : `${title}。`))
456
458
  text = text.replace(/^\s{0,3}>\s?/gmu, '')
457
459
  text = text.replace(/^\s{0,3}[-*+]\s+/gmu, '')
458
460
  text = text.replace(/^\s{0,3}\d+[.)]\s+/gmu, '')
@@ -469,8 +471,10 @@ function cleanForSpeech(raw) {
469
471
  // 收尾:压缩空白,逐行补停顿标点(换行是分段的天然边界)
470
472
  text = text.replace(/[ \t\u00A0]+/gu, ' ')
471
473
  text = text.replace(/\s+([。!?!?…;;,,])/gu, '$1')
472
- // 替换文件名后会在中文之间留一个空格("已保存为 网页"),TTS 会读成停顿,顺手抹掉
473
- text = text.replace(/([\u4E00-\u9FFF])\s+(?=[\u4E00-\u9FFF])/gu, '$1')
474
+ // 替换文件名后会在中文之间留一个空格("已保存为 网页"),TTS 会读成停顿,顺手抹掉。
475
+ // 只能抹行内空白:换成 \s 会把换行一起吃掉,把单独一行的标题粘进下一段正文
476
+ // ("老书店的下午" + 换行 + "街角那家……" 变成一整句),短行补省略号的分支就永远走不到。
477
+ text = text.replace(/([\u4E00-\u9FFF])[ \t\u00A0]+(?=[\u4E00-\u9FFF])/gu, '$1')
474
478
  const lines = text
475
479
  .split('\n')
476
480
  .map((line) => line.trim())
@@ -479,8 +483,9 @@ function cleanForSpeech(raw) {
479
483
  text = lines
480
484
  .map((line) => {
481
485
  if (/[。!?!?…;;::,,]$/u.test(line)) return line
482
- // 短、没逗号的一行,基本就是没写成 Markdown 的小标题:补省略号,让停顿明显
483
- if (line.length <= TITLE_LINE_MAX && !/[,,::;;]/u.test(line)) return `${line}……`
486
+ // 短、没逗号的一行,基本就是没写成 Markdown 的小标题;长句是正文。
487
+ // 两者都补句号——省略号在 Edge 的声线里几乎没有停顿(实测比无标点还短),
488
+ // 只有句号才有断句,标题和正文才听得出是两件事。
484
489
  return `${line}。`
485
490
  })
486
491
  .join('\n')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-live-speak",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "用高保真神经语音实时播报模型输出(边生成边念),自带播放器,可随时暂停/恢复/停止。",
5
5
  "type": "module",
6
6
  "main": "./index.js",