@oh-story/dsh 0.1.13 → 0.1.14

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.
@@ -5,6 +5,9 @@ description: >
5
5
  把带时间戳的 narration.json 合成为中文解说音频。使用 MiMo TTS(mimo-v2.5-tts)或
6
6
  Fish Audio(s2.1-pro-free)或显式配置的通用 IndexTTS HTTP 服务逐段生成语音,
7
7
  按时间窗动态适配语速并处理响度;输入输出时间线上的旁白,产出 tts_segments 与 tts_meta.json。
8
+ 外发说明:每段旁白文字会发给所选 TTS 服务(MiMo / Fish Audio / 用户自托管的 IndexTTS),--voice-ref 的参考音频会发给 MiMo。
9
+ 另含实验性的英译中 dub 路径:只在显式选择 dub 模式并传 --confirm-voice-rights 时运行,会把源视频音频发到 MiMo ASR,
10
+ 并以原说话人的声音为参考经 MiMo voiceclone 克隆配音;只可用于用户有权使用、且说话人同意被克隆声音的内容。
8
11
  触发词:配音、语音合成、TTS、解说配音、
9
12
  voiceover、text to speech、旁白配音。
10
13
  ---
@@ -14,7 +17,21 @@ description: >
14
17
  本技能读取带时间戳的旁白稿,为每一段生成独立音频,并把语音适配到对应时间窗,随后记录下游合成所需的放置元数据。
15
18
  默认引擎是 MiMo TTS(`mimo-v2.5-tts`);也可显式选择 Fish Audio(默认模型 `s2.1-pro-free`)。
16
19
 
17
- ## 2. 环境要求
20
+ ## 2. 远程服务与数据外发
21
+
22
+ 本技能只在下表中被选中的路径上联网,每条路径发出的内容如下。实读文本指 `narration.json` 段落去掉格式与舞台提示后的文字。
23
+
24
+ | 路径 | 服务与凭据 | 发送内容 |
25
+ |------|------------|----------|
26
+ | 默认解说 TTS(`--tts-provider auto\|mimo-tts`) | MiMo chat 接口 `<MIMO_TTS_API_URL 或 MIMO_API_URL>/chat/completions`,`MIMO_TTS_API_KEY` 或 `MIMO_API_KEY`,模型 `mimo-v2.5-tts` | 每段实读文本、一句自然语言语气/语速指令(由段的 `emotion` 与时间窗算出)、内置音色名(默认 `冰糖`);`auto` 下 MiMo key 缺失且设置了 `FISH_API_KEY` 时改走 Fish Audio 行 |
27
+ | 解说声音克隆(`--voice-ref <audio>` / `VOICE_REF`) | 同上,模型 `mimo-v2.5-tts-voiceclone` | 上述内容,外加参考音频(转成 24 kHz 单声道 WAV,最长 30 秒)的 base64,每段请求都带 |
28
+ | Fish Audio(`--tts-provider fish-audio`) | `FISH_TTS_API_URL`(默认 `https://api.fish.audio/v1/tts`),`FISH_API_KEY` | 每段实读文本、数值语速、音色 ID `FISH_TTS_REFERENCE_ID`;不发送本地音频 |
29
+ | 自托管 IndexTTS(`--tts-provider index-tts`) | 用户自己部署、由 `INDEX_TTS_ENDPOINT` 指定的 HTTP(S) 服务 | `{"voice": INDEX_TTS_VOICE, "text": 实读文本}`;不发送本地音频 |
30
+ | 实验性 dub(见 §8) | MiMo ASR(`MIMO_API_URL`,`MIMO_API_KEY`,`mimo-v2.5-asr`)与 MiMo voiceclone(TTS 接口与凭据,`mimo-v2.5-tts-voiceclone`) | 源视频整条音轨按 6 秒分窗送 ASR;每句中文译文连同从源音频截取的约 10 秒原说话人声音(克隆参考)送 voiceclone |
31
+
32
+ `--voice-ref` 与 dub 都会把一个真实人物的声音发给 MiMo 用于克隆:只在用户有权使用该音频、且声音主人同意被克隆时使用。
33
+
34
+ ## 3. 环境要求
18
35
 
19
36
  ```bash
20
37
  export MIMO_API_KEY=*** # 也可使用仅供 TTS 的 MIMO_TTS_API_KEY
@@ -30,14 +47,14 @@ export TTS_PROVIDER=index-tts
30
47
 
31
48
  下面的 `scripts/...` 均相对于本技能目录。若执行器从仓库根目录启动,请给脚本路径加上本技能的绝对目录。
32
49
 
33
- ## 3. 输入契约
50
+ ## 4. 输入契约
34
51
 
35
52
  默认输入为 `work_dir/narration.json`。每段必须包含 `start`、`end` 与 `narration`,可选字段包括
36
53
  `pause_after_ms` 和 `overlaps_speech`。时间统一表示音频最终放置的**输出时间线秒数**。
37
54
 
38
55
  cut 流程先剪后配:`narration.json` 本身就是按剪后成片的输出时间写的,不存在另一份映射稿。
39
56
 
40
- ## 4. 运行命令
57
+ ## 5. 运行命令
41
58
 
42
59
  ```bash
43
60
  python3 scripts/voiceover.py --work-dir <work_dir> --narration <narration.json> \
@@ -48,7 +65,7 @@ python3 scripts/voiceover.py --work-dir <work_dir> --narration <narration.json>
48
65
 
49
66
  单独运行且省略 `--narration` 时,默认读取 `work_dir/narration.json`;`--narration` 只用于指定其他路径的同格式稿件。
50
67
 
51
- ## 5. 输出契约
68
+ ## 6. 输出契约
52
69
 
53
70
  - `tts_segments/*.wav`:每段旁白对应一个音频文件。
54
71
  - `tts_meta.json`:包含 `segments`、`engine`、`voice`(实际使用的 provider、模型、音色或参考音频)与 `narration`。每段记录 `audio_path`、时间、
@@ -63,7 +80,7 @@ python3 scripts/voiceover.py --work-dir <work_dir> --narration <narration.json>
63
80
  `--allow-partial-tts` 降级为可交付的部分成功;异常记录标为 `required: true` 并带策略 ID。
64
81
  仅含 `[停顿]` 等清理标记、清理后无实读文本的作者段也属于必需段错误。
65
82
 
66
- ## 6. 运行规则
83
+ ## 7. 运行规则
67
84
 
68
85
  - 分段音频按内容缓存在 `tts_segments/cache/`:键是实读文本、实际发给供应商的语气请求(MiMo 是那句自然语言指令,语速只在 ≥+6% 或 ≤-3% 时改变措辞;Fish Audio 是数值 speed;index-tts 没有段级控制)与 TTS 设置,不含段序号和时间窗;因此段位变化让名义语速从 +5% 变成 -2% 时,MiMo 不重新合成;
69
86
  缓存 WAV 的 `size`/`mtime_ns` 变了即失效。`narr_NNN.wav` 是指向缓存的硬链接(不支持时为副本),`tts_meta.json`
@@ -87,10 +104,19 @@ python3 scripts/voiceover.py --work-dir <work_dir> --narration <narration.json>
87
104
  dub 的 voiceclone 台词走同一道检查与重试(被拒的留在 `dub_tts/line_NNN_raw.rejected.wav`)。
88
105
  旧版本缓存下的这类 WAV 在重跑时不再复用,会重新合成。设为 `0` 关闭这道检查。
89
106
  - `TTS_WORKERS`、`TTS_TIMEOUT`、`TTS_RETRIES`、`ALLOW_PARTIAL_TTS` 用于调整并发、超时、重试与部分成功策略。
90
- - dub 模式有独立的确定性门禁:`dub.py --stage render` 在语音克隆前写 `dub_lint.json`,
91
- 空行、重叠或越界译文即中止。`dub.py` 只由编排入口的 `--edit-mode dub` 调用,没有单独的手动阶段。
92
107
 
93
- ## 7. 能力边界
108
+ ## 8. 实验性 dub 配音(英译中、克隆原声)
109
+
110
+ `dub.py` 是实验功能:把英文原声翻译成中文,并用原说话人的克隆音色整轨替换人声。它与上面的解说配音是两条独立路径,`voiceover.py` 从不调用它。
111
+
112
+ - **触发**:只在用户明确要求英译中配音时,由编排入口以 `--edit-mode dub --confirm-voice-rights` 运行;编排入口把确认参数原样转给 `dub.py` 的准备和渲染两个阶段。没有单独的手动阶段。
113
+ - **确认门禁**:`dub.py` 的两个阶段都必须带 `--confirm-voice-rights`,缺少时在抽取音频和发出任何请求之前退出,并说明会外发什么。编排入口在 dub 模式下同样拒绝缺少该参数的运行,在其他模式下拒绝该参数。
114
+ - **外发内容**:准备阶段把源视频音轨按 6 秒分窗发给 MiMo ASR(`mimo-v2.5-asr`)转写英文;渲染阶段把每句中文译文和从源音频第 2 秒起截取的约 10 秒原说话人声音(`dub_reference.wav`)一起发给 MiMo voiceclone(`mimo-v2.5-tts-voiceclone`)。
115
+ - **权利与同意**:只能用于用户有权使用的视频与音频,且被克隆声音的说话人已同意。Agent 必须先向用户确认这两点,用户确认后才能加 `--confirm-voice-rights`;无法确认时不要运行 dub,也不要用它冒充他人发言。
116
+ - **确定性门禁**:渲染阶段在语音克隆前写 `dub_lint.json`,空行、重叠或越界译文即中止,不发 voiceclone 请求。
117
+ - **本地产物**:`dub_source.wav`、`dub_transcript.json`、`dub_brief.md`、`dub_reference.wav`、Agent 写的 `dub_script.json`、`dub_lint.json`、`dub_tts/`、`dub_manifest.json` 与 `dub_<name>.mp4`,都只写在 `work_dir`。
118
+
119
+ ## 9. 能力边界
94
120
 
95
121
  - 超窗时保留原稿并记录日志;assemble 有界提速放不下则在渲染前以 `no_safe_fit` 阻断。批准稿加 `--preserve-approved-text`,超窗即在 TTS 阶段失败。
96
122
  - 不混流、不压低原声、不渲染字幕。
@@ -5,6 +5,12 @@ faithful Chinese translation spoken in the ORIGINAL speaker's cloned voice
5
5
  (`mimo-v2.5-tts-voiceclone`, same MIMO_API_KEY, pure stdlib + ffmpeg, no GPU). This differs
6
6
  from recap/解说, which overlays Chinese commentary on ducked original audio.
7
7
 
8
+ Experimental, and only on explicit request: both stages refuse to run without
9
+ `--confirm-voice-rights`, the user's confirmation that they may use this video's audio and clone
10
+ its speaker's voice. Remote calls: `prepare` sends 6-second windows of the source audio to MiMo
11
+ ASR; `render` sends each Chinese line plus the ~10-second source-voice reference clip to MiMo
12
+ voiceclone.
13
+
8
14
  Division of labour (the same as recap): CODE does only the mechanical parts; the AGENT does all
9
15
  the judgment. So there are NO text heuristics here (no sentence-splitting, hook-dedup, or junk
10
16
  filters) — those are exactly the things an LLM does better, and trying to do them in code is
@@ -50,6 +56,14 @@ DUB_MIN_ASR_WINDOW_SECONDS = 0.5
50
56
  DUB_TTS_STYLE_PROMPT = "自然、清晰,保持原说话人的音色与节奏,语气平稳。"
51
57
 
52
58
  DUB_SCHEMA_VERSION = 1
59
+ # dub sends the source speech to MiMo ASR and uses the source speaker's own voice as the
60
+ # voiceclone reference, so neither stage runs until the caller passes the user's explicit
61
+ # confirmation. Checked before any audio extraction or network request (fail closed).
62
+ VOICE_RIGHTS_REQUIRED = (
63
+ "[dub] 需要显式确认 --confirm-voice-rights:dub 会把源视频的音频分窗发送到 MiMo ASR 转写,"
64
+ "并截取原说话人约 10 秒声音作为 MiMo voiceclone 的参考音频来克隆其音色。"
65
+ "只有当用户有权使用这段视频与音频、且说话人同意被克隆声音时才可确认;否则不要运行 dub。"
66
+ )
53
67
  # Tolerate ~1-frame rounding in agent-estimated timings so the gate blocks only GENUINE
54
68
  # errors, not rounding noise (the old render clamped such cases via min(slot_end, nxt)).
55
69
  DUB_TIMING_EPS = 0.05
@@ -536,7 +550,14 @@ def main():
536
550
  help="prepare writes the ASR brief; render lints dub_script.json before voiceclone")
537
551
  ap.add_argument("--video", required=True, help="source video")
538
552
  ap.add_argument("--work-dir", required=True, help="work directory containing dub artifacts")
553
+ ap.add_argument(
554
+ "--confirm-voice-rights",
555
+ action="store_true",
556
+ help="required: the user confirms they may use this video's audio and clone its speaker's voice",
557
+ )
539
558
  args = ap.parse_args()
559
+ if not args.confirm_voice_rights:
560
+ raise SystemExit(VOICE_RIGHTS_REQUIRED)
540
561
  video, work = Path(args.video), Path(args.work_dir)
541
562
  if args.stage == "prepare":
542
563
  stage_prepare(video, work)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-story/dsh",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "A DSH plugin for fiction, short-drama, interactive-game, and video-recap production",
5
5
  "keywords": [
6
6
  "deepseek-harness",