Spaces:
Running
Running
gemini: 双声旁白、波形对齐字幕、时间轴(step 2)
Browse files- .gitattributes +10 -0
- gemini/README.md +1 -1
- gemini/audio/n01.mp3 +3 -0
- gemini/audio/n02.mp3 +3 -0
- gemini/audio/n03.mp3 +3 -0
- gemini/audio/n04.mp3 +3 -0
- gemini/audio/n05.mp3 +3 -0
- gemini/audio/n06.mp3 +3 -0
- gemini/audio/n07.mp3 +3 -0
- gemini/audio/n08.mp3 +3 -0
- gemini/audio/n09.mp3 +3 -0
- gemini/audio/n10.mp3 +3 -0
- gemini/engine/timeline.js +684 -0
- gemini/tools/align.mjs +23 -0
- gemini/tools/narrate.py +109 -0
.gitattributes
CHANGED
|
@@ -62,3 +62,13 @@ taurus/covers/cover-channels-3x4.jpg filter=lfs diff=lfs merge=lfs -text
|
|
| 62 |
taurus/covers/cover-bili-4x3.jpg filter=lfs diff=lfs merge=lfs -text
|
| 63 |
taurus/covers/cover-douyin-4x3.jpg filter=lfs diff=lfs merge=lfs -text
|
| 64 |
taurus/covers/cover-douyin-3x4.jpg filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
taurus/covers/cover-bili-4x3.jpg filter=lfs diff=lfs merge=lfs -text
|
| 63 |
taurus/covers/cover-douyin-4x3.jpg filter=lfs diff=lfs merge=lfs -text
|
| 64 |
taurus/covers/cover-douyin-3x4.jpg filter=lfs diff=lfs merge=lfs -text
|
| 65 |
+
gemini/audio/n04.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 66 |
+
gemini/audio/n08.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 67 |
+
gemini/audio/n06.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 68 |
+
gemini/audio/n02.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 69 |
+
gemini/audio/n05.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 70 |
+
gemini/audio/n09.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 71 |
+
gemini/audio/n01.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 72 |
+
gemini/audio/n10.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 73 |
+
gemini/audio/n07.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 74 |
+
gemini/audio/n03.mp3 filter=lfs diff=lfs merge=lfs -text
|
gemini/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
| 9 |
| # | 阶段 | 状态 |
|
| 10 |
|---|---|---|
|
| 11 |
| 1 | 计划 / 大纲 / 步骤(本文件) | ✅ 完成 |
|
| 12 |
-
| 2 | 旁白定稿(10 幕)+ **男女双声交替 TTS**(晓晓 × 云希,象征双生)+ 波形对齐字幕 + 时间轴 |
|
| 13 |
| 3 | 画面引擎:极光虹彩天幕 + 镜像万花筒粒子 + 星图投影;10 个场景(横/竖屏) | ⏳ |
|
| 14 |
| 4 | 双星荣誉徽章:两半镜像飞入合璧、3D 旋转、镭射刻名、双色绶带、单独导出 PNG | ⏳ |
|
| 15 |
| 5 | 灵动配乐(A 大调 · 竖琴 / 钢片琴 / 拨弦 / 长笛对答卡农)+ 音效,预渲染并混音 | ⏳ |
|
|
|
|
| 9 |
| # | 阶段 | 状态 |
|
| 10 |
|---|---|---|
|
| 11 |
| 1 | 计划 / 大纲 / 步骤(本文件) | ✅ 完成 |
|
| 12 |
+
| 2 | 旁白定稿(10 幕)+ **男女双声交替 TTS**(晓晓 × 云希,象征双生)+ 波形对齐字幕 + 时间轴 | ✅ 完成(89 句,总长 4:41;字幕起点统一提前 60 ms,有声帧 100% 覆盖) |
|
| 13 |
| 3 | 画面引擎:极光虹彩天幕 + 镜像万花筒粒子 + 星图投影;10 个场景(横/竖屏) | ⏳ |
|
| 14 |
| 4 | 双星荣誉徽章:两半镜像飞入合璧、3D 旋转、镭射刻名、双色绶带、单独导出 PNG | ⏳ |
|
| 15 |
| 5 | 灵动配乐(A 大调 · 竖琴 / 钢片琴 / 拨弦 / 长笛对答卡农)+ 音效,预渲染并混音 | ⏳ |
|
gemini/audio/n01.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a0e8ac33ec3853cfc82e49d1b3b140f761c1af2f138f6801dcd49c0d0082d48e
|
| 3 |
+
size 265868
|
gemini/audio/n02.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a63004f10efcf1a15b85485a72463552bf7d0583a15ece47e77cd924e69c0dd6
|
| 3 |
+
size 331244
|
gemini/audio/n03.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7b71a51e34b066736f29106b22764d7a59724b73ef5d6affba2383ddda573e8a
|
| 3 |
+
size 355724
|
gemini/audio/n04.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8667b3ad88104e67b2f85ab5e09ba172b37019f93467ba12e88d828a943758f1
|
| 3 |
+
size 466316
|
gemini/audio/n05.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:09cbc0f42e4ba6e0bddd65b353b49a875aea45100445169e68b1082cdda89bd1
|
| 3 |
+
size 279116
|
gemini/audio/n06.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f5ac1073b5786b5975307b0bc137bc74e0a9223bd2af6ef775c260c3dce14c25
|
| 3 |
+
size 351404
|
gemini/audio/n07.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:de3fcd97543f18cbec5ef9a4633a8e1a74fa713f649adf603f92a58647b29846
|
| 3 |
+
size 250028
|
gemini/audio/n08.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fc3a3e1e5aaebff38d3dba5319783d0b051dd8317a98ca2b6d83f2d95b939eea
|
| 3 |
+
size 343340
|
gemini/audio/n09.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:155b2ec66b31faf6fd8c8894a4c11aaa3b638ba30f711a006f1f4005caa33869
|
| 3 |
+
size 248588
|
gemini/audio/n10.mp3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:30f7457402b53a6ebc35cdadde85ffe6f531015ffd7d9d6d2e17f9f9f7cea61e
|
| 3 |
+
size 162764
|
gemini/engine/timeline.js
ADDED
|
@@ -0,0 +1,684 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 由 tools/narrate.py 生成、tools/align.mjs 校准:双声旁白时间轴与字幕
|
| 2 |
+
export const TIMELINE = {
|
| 3 |
+
"duration": 280.852,
|
| 4 |
+
"voices": {
|
| 5 |
+
"f": "zh-CN-XiaoxiaoNeural",
|
| 6 |
+
"m": "zh-CN-YunxiNeural"
|
| 7 |
+
},
|
| 8 |
+
"acts": [
|
| 9 |
+
{
|
| 10 |
+
"id": "twin",
|
| 11 |
+
"title": "序章 · 镜中的双星",
|
| 12 |
+
"tone": "dawn",
|
| 13 |
+
"start": 0,
|
| 14 |
+
"end": 25.453,
|
| 15 |
+
"voice": {
|
| 16 |
+
"file": "audio/n01.mp3",
|
| 17 |
+
"at": 2.4,
|
| 18 |
+
"dur": 21.753
|
| 19 |
+
},
|
| 20 |
+
"subs": [
|
| 21 |
+
{
|
| 22 |
+
"t0": 2.38,
|
| 23 |
+
"t1": 5.52,
|
| 24 |
+
"text": "五月的尾巴,夏天刚刚推开门",
|
| 25 |
+
"who": "f"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"t0": 5.54,
|
| 29 |
+
"t1": 9.04,
|
| 30 |
+
"text": "夜空里,有两颗星总是肩并着肩",
|
| 31 |
+
"who": "m"
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"t0": 9.06,
|
| 35 |
+
"t1": 10.68,
|
| 36 |
+
"text": "一颗明亮温暖",
|
| 37 |
+
"who": "f"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"t0": 10.7,
|
| 41 |
+
"t1": 12.28,
|
| 42 |
+
"text": "一颗清澈闪烁",
|
| 43 |
+
"who": "m"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"t0": 12.3,
|
| 47 |
+
"t1": 14.44,
|
| 48 |
+
"text": "它们像一面镜子的两边",
|
| 49 |
+
"who": "f"
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"t0": 14.46,
|
| 53 |
+
"t1": 16.71,
|
| 54 |
+
"text": "又像同一个灵魂的两种声音",
|
| 55 |
+
"who": "m"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"t0": 16.73,
|
| 59 |
+
"t1": 19.62,
|
| 60 |
+
"text": "这就是黄道第三宫,双子座",
|
| 61 |
+
"who": "f"
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"t0": 19.64,
|
| 65 |
+
"t1": 22.35,
|
| 66 |
+
"text": "5 月 21 日到 6 月 21 日出生的你",
|
| 67 |
+
"who": "m"
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"t0": 22.37,
|
| 71 |
+
"t1": 24.23,
|
| 72 |
+
"text": "欢迎回到自己的星空",
|
| 73 |
+
"who": "f"
|
| 74 |
+
}
|
| 75 |
+
]
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"id": "locate",
|
| 79 |
+
"title": "找到他们",
|
| 80 |
+
"tone": "night",
|
| 81 |
+
"start": 25.453,
|
| 82 |
+
"end": 54.949,
|
| 83 |
+
"voice": {
|
| 84 |
+
"file": "audio/n02.mp3",
|
| 85 |
+
"at": 26.453,
|
| 86 |
+
"dur": 27.196
|
| 87 |
+
},
|
| 88 |
+
"subs": [
|
| 89 |
+
{
|
| 90 |
+
"t0": 26.433,
|
| 91 |
+
"t1": 29.773,
|
| 92 |
+
"text": "冬天和早春的夜晚,先找到猎户座",
|
| 93 |
+
"who": "m"
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"t0": 29.793,
|
| 97 |
+
"t1": 32.473,
|
| 98 |
+
"text": "从猎户脚下的参宿七出发",
|
| 99 |
+
"who": "f"
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"t0": 32.493,
|
| 103 |
+
"t1": 34.843,
|
| 104 |
+
"text": "穿过肩头那颗橙红的参宿四",
|
| 105 |
+
"who": "m"
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"t0": 34.863,
|
| 109 |
+
"t1": 36.923,
|
| 110 |
+
"text": "一直向东北方延伸",
|
| 111 |
+
"who": "f"
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"t0": 36.943,
|
| 115 |
+
"t1": 39.193,
|
| 116 |
+
"text": "你会看到两颗相距不远的亮星",
|
| 117 |
+
"who": "m"
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"t0": 39.523,
|
| 121 |
+
"t1": 42.553,
|
| 122 |
+
"text": "西边那颗是北河二,卡斯托尔",
|
| 123 |
+
"who": "f"
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"t0": 42.573,
|
| 127 |
+
"t1": 46.483,
|
| 128 |
+
"text": "东边更亮的那颗,是北河三,波吕克斯",
|
| 129 |
+
"who": "m"
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
"t0": 46.503,
|
| 133 |
+
"t1": 50.273,
|
| 134 |
+
"text": "从两颗星向下,两串星星并排伸展",
|
| 135 |
+
"who": "f"
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"t0": 50.293,
|
| 139 |
+
"t1": 53.623,
|
| 140 |
+
"text": "就像一对兄弟,手牵着手,站在银河边上",
|
| 141 |
+
"who": "m"
|
| 142 |
+
}
|
| 143 |
+
]
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"id": "pair",
|
| 147 |
+
"title": "两颗星的秘密",
|
| 148 |
+
"tone": "violet",
|
| 149 |
+
"start": 54.949,
|
| 150 |
+
"end": 86.486,
|
| 151 |
+
"voice": {
|
| 152 |
+
"file": "audio/n03.mp3",
|
| 153 |
+
"at": 55.949,
|
| 154 |
+
"dur": 29.237
|
| 155 |
+
},
|
| 156 |
+
"subs": [
|
| 157 |
+
{
|
| 158 |
+
"t0": 55.949,
|
| 159 |
+
"t1": 58.679,
|
| 160 |
+
"text": "有意思的是,排名第一的北河二",
|
| 161 |
+
"who": "f"
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"t0": 58.999,
|
| 165 |
+
"t1": 61.029,
|
| 166 |
+
"text": "其实没有北河三亮",
|
| 167 |
+
"who": "m"
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"t0": 61.049,
|
| 171 |
+
"t1": 63.759,
|
| 172 |
+
"text": "北河三是一颗橙色的巨星",
|
| 173 |
+
"who": "f"
|
| 174 |
+
},
|
| 175 |
+
{
|
| 176 |
+
"t0": 63.779,
|
| 177 |
+
"t1": 65.299,
|
| 178 |
+
"text": "离我们大约34 光年",
|
| 179 |
+
"who": "m"
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"t0": 65.629,
|
| 183 |
+
"t1": 71.019,
|
| 184 |
+
"text": "2006 年,天文学家确认,它身边有一颗行星在转动",
|
| 185 |
+
"who": "f"
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"t0": 71.039,
|
| 189 |
+
"t1": 72.319,
|
| 190 |
+
"text": "而北河二更神奇",
|
| 191 |
+
"who": "m"
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"t0": 72.749,
|
| 195 |
+
"t1": 74.349,
|
| 196 |
+
"text": "它看起来是一颗星",
|
| 197 |
+
"who": "f"
|
| 198 |
+
},
|
| 199 |
+
{
|
| 200 |
+
"t0": 74.659,
|
| 201 |
+
"t1": 76.859,
|
| 202 |
+
"text": "其实是六颗恒星组成的大家庭",
|
| 203 |
+
"who": "m"
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"t0": 77.269,
|
| 207 |
+
"t1": 79.339,
|
| 208 |
+
"text": "三对双星彼此环绕",
|
| 209 |
+
"who": "f"
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
"t0": 79.359,
|
| 213 |
+
"t1": 81.469,
|
| 214 |
+
"text": "一起跳着一支很慢很慢的舞",
|
| 215 |
+
"who": "m"
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"t0": 81.489,
|
| 219 |
+
"t1": 85.259,
|
| 220 |
+
"text": "你看,双子座连星星本身,都喜欢热闹",
|
| 221 |
+
"who": "f"
|
| 222 |
+
}
|
| 223 |
+
]
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"id": "myth",
|
| 227 |
+
"title": "卡斯托尔与波吕克斯",
|
| 228 |
+
"tone": "amber",
|
| 229 |
+
"start": 86.486,
|
| 230 |
+
"end": 127.24,
|
| 231 |
+
"voice": {
|
| 232 |
+
"file": "audio/n04.mp3",
|
| 233 |
+
"at": 87.486,
|
| 234 |
+
"dur": 38.453
|
| 235 |
+
},
|
| 236 |
+
"subs": [
|
| 237 |
+
{
|
| 238 |
+
"t0": 87.486,
|
| 239 |
+
"t1": 91.906,
|
| 240 |
+
"text": "古希腊传说里,卡斯托尔和波吕克斯是一对孪生兄弟",
|
| 241 |
+
"who": "m"
|
| 242 |
+
},
|
| 243 |
+
{
|
| 244 |
+
"t0": 91.926,
|
| 245 |
+
"t1": 94.996,
|
| 246 |
+
"text": "一个擅长驯马,一个擅长拳击",
|
| 247 |
+
"who": "f"
|
| 248 |
+
},
|
| 249 |
+
{
|
| 250 |
+
"t0": 95.016,
|
| 251 |
+
"t1": 98.636,
|
| 252 |
+
"text": "他们一起航海,一起冒险,从不分开",
|
| 253 |
+
"who": "m"
|
| 254 |
+
},
|
| 255 |
+
{
|
| 256 |
+
"t0": 98.656,
|
| 257 |
+
"t1": 100.826,
|
| 258 |
+
"text": "可是卡斯托尔是凡人",
|
| 259 |
+
"who": "f"
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"t0": 100.846,
|
| 263 |
+
"t1": 103.316,
|
| 264 |
+
"text": "波吕克斯却拥有不死之身",
|
| 265 |
+
"who": "m"
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"t0": 103.336,
|
| 269 |
+
"t1": 106.126,
|
| 270 |
+
"text": "当卡斯托尔在战斗中倒下",
|
| 271 |
+
"who": "f"
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"t0": 106.146,
|
| 275 |
+
"t1": 108.156,
|
| 276 |
+
"text": "波吕克斯请求宙斯",
|
| 277 |
+
"who": "m"
|
| 278 |
+
},
|
| 279 |
+
{
|
| 280 |
+
"t0": 108.176,
|
| 281 |
+
"t1": 111.086,
|
| 282 |
+
"text": "把自己的永生,分一半给兄弟",
|
| 283 |
+
"who": "f"
|
| 284 |
+
},
|
| 285 |
+
{
|
| 286 |
+
"t0": 111.106,
|
| 287 |
+
"t1": 115.736,
|
| 288 |
+
"text": "从此,他们一天在天上,一天在冥界,永远在一起",
|
| 289 |
+
"who": "m"
|
| 290 |
+
},
|
| 291 |
+
{
|
| 292 |
+
"t0": 116.266,
|
| 293 |
+
"t1": 120.046,
|
| 294 |
+
"text": "宙斯被这份情谊打动,把他们升上星空",
|
| 295 |
+
"who": "f"
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"t0": 120.066,
|
| 299 |
+
"t1": 123.606,
|
| 300 |
+
"text": "后来的水手们相信,看见桅杆上的蓝色火光",
|
| 301 |
+
"who": "m"
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
"t0": 123.916,
|
| 305 |
+
"t1": 126.006,
|
| 306 |
+
"text": "就是双子在为他们护航",
|
| 307 |
+
"who": "f"
|
| 308 |
+
}
|
| 309 |
+
]
|
| 310 |
+
},
|
| 311 |
+
{
|
| 312 |
+
"id": "china",
|
| 313 |
+
"title": "北河与井宿",
|
| 314 |
+
"tone": "ink",
|
| 315 |
+
"start": 127.24,
|
| 316 |
+
"end": 152.385,
|
| 317 |
+
"voice": {
|
| 318 |
+
"file": "audio/n05.mp3",
|
| 319 |
+
"at": 128.24,
|
| 320 |
+
"dur": 22.846
|
| 321 |
+
},
|
| 322 |
+
"subs": [
|
| 323 |
+
{
|
| 324 |
+
"t0": 128.26,
|
| 325 |
+
"t1": 132.3,
|
| 326 |
+
"text": "在中国古代的星空里,他们有一个好听的名字",
|
| 327 |
+
"who": "f"
|
| 328 |
+
},
|
| 329 |
+
{
|
| 330 |
+
"t0": 132.32,
|
| 331 |
+
"t1": 133.16,
|
| 332 |
+
"text": "北河",
|
| 333 |
+
"who": "m"
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"t0": 133.18,
|
| 337 |
+
"t1": 136.82,
|
| 338 |
+
"text": "北河二,北河三,守在银河的北岸",
|
| 339 |
+
"who": "f"
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"t0": 136.84,
|
| 343 |
+
"t1": 138.66,
|
| 344 |
+
"text": "它们属于二十八宿中的井宿",
|
| 345 |
+
"who": "m"
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"t0": 138.96,
|
| 349 |
+
"t1": 142.24,
|
| 350 |
+
"text": "井宿,是南方朱雀七宿的第一宿",
|
| 351 |
+
"who": "f"
|
| 352 |
+
},
|
| 353 |
+
{
|
| 354 |
+
"t0": 142.26,
|
| 355 |
+
"t1": 145.51,
|
| 356 |
+
"text": "古人看着它,观测天象,校准历法",
|
| 357 |
+
"who": "m"
|
| 358 |
+
},
|
| 359 |
+
{
|
| 360 |
+
"t0": 145.89,
|
| 361 |
+
"t1": 149.67,
|
| 362 |
+
"text": "东方和西方,看见的是同一片星空",
|
| 363 |
+
"who": "f"
|
| 364 |
+
},
|
| 365 |
+
{
|
| 366 |
+
"t0": 149.69,
|
| 367 |
+
"t1": 151.1,
|
| 368 |
+
"text": "却讲出了不同的故事",
|
| 369 |
+
"who": "m"
|
| 370 |
+
}
|
| 371 |
+
]
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"id": "discover",
|
| 375 |
+
"title": "两颗行星的发现",
|
| 376 |
+
"tone": "deep",
|
| 377 |
+
"start": 152.385,
|
| 378 |
+
"end": 183.553,
|
| 379 |
+
"voice": {
|
| 380 |
+
"file": "audio/n06.mp3",
|
| 381 |
+
"at": 153.385,
|
| 382 |
+
"dur": 28.868
|
| 383 |
+
},
|
| 384 |
+
"subs": [
|
| 385 |
+
{
|
| 386 |
+
"t0": 153.465,
|
| 387 |
+
"t1": 155.635,
|
| 388 |
+
"text": "双子座还有一个了不起的身份",
|
| 389 |
+
"who": "m"
|
| 390 |
+
},
|
| 391 |
+
{
|
| 392 |
+
"t0": 155.655,
|
| 393 |
+
"t1": 156.865,
|
| 394 |
+
"text": "发现之地",
|
| 395 |
+
"who": "f"
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"t0": 156.885,
|
| 399 |
+
"t1": 161.115,
|
| 400 |
+
"text": "1781 年,英国天文学家威廉·赫歇尔",
|
| 401 |
+
"who": "m"
|
| 402 |
+
},
|
| 403 |
+
{
|
| 404 |
+
"t0": 161.525,
|
| 405 |
+
"t1": 165.075,
|
| 406 |
+
"text": "在双子座里,看见一个缓缓移动的光点",
|
| 407 |
+
"who": "f"
|
| 408 |
+
},
|
| 409 |
+
{
|
| 410 |
+
"t0": 165.095,
|
| 411 |
+
"t1": 168.935,
|
| 412 |
+
"text": "那是天王星,人类用望远镜发现的第一颗行星",
|
| 413 |
+
"who": "m"
|
| 414 |
+
},
|
| 415 |
+
{
|
| 416 |
+
"t0": 168.955,
|
| 417 |
+
"t1": 172.955,
|
| 418 |
+
"text": "1930 年,美国青年克莱德·汤博",
|
| 419 |
+
"who": "f"
|
| 420 |
+
},
|
| 421 |
+
{
|
| 422 |
+
"t0": 172.975,
|
| 423 |
+
"t1": 175.155,
|
| 424 |
+
"text": "一张一张地比对星空底片",
|
| 425 |
+
"who": "m"
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"t0": 175.175,
|
| 429 |
+
"t1": 178.925,
|
| 430 |
+
"text": "他在双子座的星星之间,找到了冥王星",
|
| 431 |
+
"who": "f"
|
| 432 |
+
},
|
| 433 |
+
{
|
| 434 |
+
"t0": 178.945,
|
| 435 |
+
"t1": 182.315,
|
| 436 |
+
"text": "好奇心,在这里两次改写了太阳系的地图",
|
| 437 |
+
"who": "m"
|
| 438 |
+
}
|
| 439 |
+
]
|
| 440 |
+
},
|
| 441 |
+
{
|
| 442 |
+
"id": "meteor",
|
| 443 |
+
"title": "双子座流星雨",
|
| 444 |
+
"tone": "snow",
|
| 445 |
+
"start": 183.553,
|
| 446 |
+
"end": 206.286,
|
| 447 |
+
"voice": {
|
| 448 |
+
"file": "audio/n07.mp3",
|
| 449 |
+
"at": 184.553,
|
| 450 |
+
"dur": 20.432
|
| 451 |
+
},
|
| 452 |
+
"subs": [
|
| 453 |
+
{
|
| 454 |
+
"t0": 184.533,
|
| 455 |
+
"t1": 188.893,
|
| 456 |
+
"text": "每年十二月中旬,双子座还会送出一份礼物",
|
| 457 |
+
"who": "f"
|
| 458 |
+
},
|
| 459 |
+
{
|
| 460 |
+
"t0": 188.913,
|
| 461 |
+
"t1": 190.433,
|
| 462 |
+
"text": "双子座流星雨",
|
| 463 |
+
"who": "m"
|
| 464 |
+
},
|
| 465 |
+
{
|
| 466 |
+
"t0": 190.453,
|
| 467 |
+
"t1": 193.593,
|
| 468 |
+
"text": "它是一年中最壮观的流星雨之一",
|
| 469 |
+
"who": "f"
|
| 470 |
+
},
|
| 471 |
+
{
|
| 472 |
+
"t0": 193.613,
|
| 473 |
+
"t1": 196.963,
|
| 474 |
+
"text": "高峰时,每小时可以看到100 多颗",
|
| 475 |
+
"who": "m"
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"t0": 196.983,
|
| 479 |
+
"t1": 198.873,
|
| 480 |
+
"text": "它的源头不是彗星",
|
| 481 |
+
"who": "f"
|
| 482 |
+
},
|
| 483 |
+
{
|
| 484 |
+
"t0": 198.893,
|
| 485 |
+
"t1": 201.053,
|
| 486 |
+
"text": "而是一颗叫法厄同的小行星",
|
| 487 |
+
"who": "m"
|
| 488 |
+
},
|
| 489 |
+
{
|
| 490 |
+
"t0": 201.073,
|
| 491 |
+
"t1": 202.883,
|
| 492 |
+
"text": "冬夜里抬头许愿吧",
|
| 493 |
+
"who": "f"
|
| 494 |
+
},
|
| 495 |
+
{
|
| 496 |
+
"t0": 203.193,
|
| 497 |
+
"t1": 205.003,
|
| 498 |
+
"text": "星星会从双子的方向飞来",
|
| 499 |
+
"who": "m"
|
| 500 |
+
}
|
| 501 |
+
]
|
| 502 |
+
},
|
| 503 |
+
{
|
| 504 |
+
"id": "sign",
|
| 505 |
+
"title": "风象 · 水星 · ♊",
|
| 506 |
+
"tone": "aurora",
|
| 507 |
+
"start": 206.286,
|
| 508 |
+
"end": 236.789,
|
| 509 |
+
"voice": {
|
| 510 |
+
"file": "audio/n08.mp3",
|
| 511 |
+
"at": 207.286,
|
| 512 |
+
"dur": 28.204
|
| 513 |
+
},
|
| 514 |
+
"subs": [
|
| 515 |
+
{
|
| 516 |
+
"t0": 207.276,
|
| 517 |
+
"t1": 210.396,
|
| 518 |
+
"text": "双子座的符号,像罗马数字二",
|
| 519 |
+
"who": "f"
|
| 520 |
+
},
|
| 521 |
+
{
|
| 522 |
+
"t0": 210.416,
|
| 523 |
+
"t1": 213.446,
|
| 524 |
+
"text": "两根柱子,被上下两道横梁连在一起",
|
| 525 |
+
"who": "m"
|
| 526 |
+
},
|
| 527 |
+
{
|
| 528 |
+
"t0": 213.896,
|
| 529 |
+
"t1": 215.736,
|
| 530 |
+
"text": "它属于风象星座",
|
| 531 |
+
"who": "f"
|
| 532 |
+
},
|
| 533 |
+
{
|
| 534 |
+
"t0": 215.756,
|
| 535 |
+
"t1": 217.346,
|
| 536 |
+
"text": "守护星是水星",
|
| 537 |
+
"who": "m"
|
| 538 |
+
},
|
| 539 |
+
{
|
| 540 |
+
"t0": 217.366,
|
| 541 |
+
"t1": 221.786,
|
| 542 |
+
"text": "水星是众神的信使,跑得最快,消息最灵",
|
| 543 |
+
"who": "f"
|
| 544 |
+
},
|
| 545 |
+
{
|
| 546 |
+
"t0": 221.806,
|
| 547 |
+
"t1": 226.006,
|
| 548 |
+
"text": "所以人们说,双子座好奇,聪明,反应快",
|
| 549 |
+
"who": "m"
|
| 550 |
+
},
|
| 551 |
+
{
|
| 552 |
+
"t0": 226.026,
|
| 553 |
+
"t1": 228.056,
|
| 554 |
+
"text": "会表达,爱学习",
|
| 555 |
+
"who": "f"
|
| 556 |
+
},
|
| 557 |
+
{
|
| 558 |
+
"t0": 228.076,
|
| 559 |
+
"t1": 229.866,
|
| 560 |
+
"text": "总能把陌生人变成朋友",
|
| 561 |
+
"who": "m"
|
| 562 |
+
},
|
| 563 |
+
{
|
| 564 |
+
"t0": 229.886,
|
| 565 |
+
"t1": 232.166,
|
| 566 |
+
"text": "有人说双子座善变",
|
| 567 |
+
"who": "f"
|
| 568 |
+
},
|
| 569 |
+
{
|
| 570 |
+
"t0": 232.186,
|
| 571 |
+
"t1": 235.436,
|
| 572 |
+
"text": "其实,那是一颗心里,装着两倍的世界",
|
| 573 |
+
"who": "m"
|
| 574 |
+
}
|
| 575 |
+
]
|
| 576 |
+
},
|
| 577 |
+
{
|
| 578 |
+
"id": "medal",
|
| 579 |
+
"title": "双星荣誉徽章",
|
| 580 |
+
"tone": "royal",
|
| 581 |
+
"start": 236.789,
|
| 582 |
+
"end": 259.402,
|
| 583 |
+
"voice": {
|
| 584 |
+
"file": "audio/n09.mp3",
|
| 585 |
+
"at": 237.789,
|
| 586 |
+
"dur": 20.313
|
| 587 |
+
},
|
| 588 |
+
"subs": [
|
| 589 |
+
{
|
| 590 |
+
"t0": 237.819,
|
| 591 |
+
"t1": 240.119,
|
| 592 |
+
"text": "所以,如果你是双子座",
|
| 593 |
+
"who": "m"
|
| 594 |
+
},
|
| 595 |
+
{
|
| 596 |
+
"t0": 240.139,
|
| 597 |
+
"t1": 243.039,
|
| 598 |
+
"text": "这枚双星荣誉徽章,属于你",
|
| 599 |
+
"who": "f"
|
| 600 |
+
},
|
| 601 |
+
{
|
| 602 |
+
"t0": 243.059,
|
| 603 |
+
"t1": 244.339,
|
| 604 |
+
"text": "一半是北河二的银",
|
| 605 |
+
"who": "m"
|
| 606 |
+
},
|
| 607 |
+
{
|
| 608 |
+
"t0": 244.679,
|
| 609 |
+
"t1": 246.709,
|
| 610 |
+
"text": "一半是北河三的金",
|
| 611 |
+
"who": "f"
|
| 612 |
+
},
|
| 613 |
+
{
|
| 614 |
+
"t0": 246.729,
|
| 615 |
+
"t1": 248.749,
|
| 616 |
+
"text": "上面刻着你的名字",
|
| 617 |
+
"who": "m"
|
| 618 |
+
},
|
| 619 |
+
{
|
| 620 |
+
"t0": 248.769,
|
| 621 |
+
"t1": 252.729,
|
| 622 |
+
"text": "和你最闪亮的天赋,好奇,灵动,真诚",
|
| 623 |
+
"who": "f"
|
| 624 |
+
},
|
| 625 |
+
{
|
| 626 |
+
"t0": 253.049,
|
| 627 |
+
"t1": 254.979,
|
| 628 |
+
"text": "双子座,星空的信使",
|
| 629 |
+
"who": "m"
|
| 630 |
+
},
|
| 631 |
+
{
|
| 632 |
+
"t0": 255.289,
|
| 633 |
+
"t1": 258.179,
|
| 634 |
+
"text": "世界因为你的好奇,变得更有趣",
|
| 635 |
+
"who": "f"
|
| 636 |
+
}
|
| 637 |
+
]
|
| 638 |
+
},
|
| 639 |
+
{
|
| 640 |
+
"id": "coda",
|
| 641 |
+
"title": "尾声 · 两束光",
|
| 642 |
+
"tone": "dusk",
|
| 643 |
+
"start": 259.402,
|
| 644 |
+
"end": 280.852,
|
| 645 |
+
"voice": {
|
| 646 |
+
"file": "audio/n10.mp3",
|
| 647 |
+
"at": 260.402,
|
| 648 |
+
"dur": 13.15
|
| 649 |
+
},
|
| 650 |
+
"subs": [
|
| 651 |
+
{
|
| 652 |
+
"t0": 260.482,
|
| 653 |
+
"t1": 262.522,
|
| 654 |
+
"text": "下一次,夏天的风吹过来",
|
| 655 |
+
"who": "m"
|
| 656 |
+
},
|
| 657 |
+
{
|
| 658 |
+
"t0": 262.872,
|
| 659 |
+
"t1": 265.862,
|
| 660 |
+
"text": "抬头看看那两颗并肩的星星",
|
| 661 |
+
"who": "f"
|
| 662 |
+
},
|
| 663 |
+
{
|
| 664 |
+
"t0": 265.882,
|
| 665 |
+
"t1": 267.272,
|
| 666 |
+
"text": "你从来不是一个人",
|
| 667 |
+
"who": "m"
|
| 668 |
+
},
|
| 669 |
+
{
|
| 670 |
+
"t0": 267.292,
|
| 671 |
+
"t1": 270.272,
|
| 672 |
+
"text": "你是两束光,也是一整片星空",
|
| 673 |
+
"who": "f"
|
| 674 |
+
},
|
| 675 |
+
{
|
| 676 |
+
"t0": 270.292,
|
| 677 |
+
"t1": 273.602,
|
| 678 |
+
"text": "双子座,保持好奇,闪闪发光",
|
| 679 |
+
"who": "fm"
|
| 680 |
+
}
|
| 681 |
+
]
|
| 682 |
+
}
|
| 683 |
+
]
|
| 684 |
+
};
|
gemini/tools/align.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ♊ 字幕 ↔ 旁白波形对齐(全新编写)。node gemini/tools/align.mjs (环境变量 FF = ffmpeg)
|
| 2 |
+
// 起点:吸附到真实发声点前 LEAD 秒;终点:到下一句开口前、本句最后一个有声帧之后 TAIL 秒
|
| 3 |
+
import fs from 'fs'; import path from 'path'; import { execFileSync } from 'child_process'; import { fileURLToPath } from 'url';
|
| 4 |
+
const DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'), FILE = path.join(DIR, 'engine/timeline.js');
|
| 5 |
+
const src = fs.readFileSync(FILE, 'utf8'), TL = JSON.parse(src.slice(src.indexOf('{'), src.lastIndexOf('}') + 1));
|
| 6 |
+
const SR = 8000, FR = 100, LEAD = .06, TAIL = .15, stats = [];
|
| 7 |
+
for (const A of TL.acts) {
|
| 8 |
+
const b = execFileSync(process.env.FF || 'ffmpeg', ['-v', 'error', '-i', path.join(DIR, A.voice.file), '-ac', '1', '-ar', '' + SR, '-f', 'f32le', '-'], { maxBuffer: 1 << 26 });
|
| 9 |
+
const x = new Float32Array(b.buffer, b.byteOffset, b.length / 4), hop = SR / FR, e = [];
|
| 10 |
+
for (let i = 0; i < x.length; i += hop) { let s = 0; for (let j = i; j < i + hop && j < x.length; j++) s += x[j] * x[j]; e.push(Math.sqrt(s / hop)); }
|
| 11 |
+
const th = Math.max(.01, Math.max(...e) * .055), loud = f => e[f] > th;
|
| 12 |
+
const onset = f0 => { let best = null; for (let d = -40; d <= 40; d++) { const f = f0 + d; if (f >= 0 && loud(f) && [1, 2, 3, 4, 5].every(k => f - k < 0 || !loud(f - k)) && (best === null || Math.abs(d) < Math.abs(best))) best = d; } return best === null ? f0 : f0 + best; };
|
| 13 |
+
const on = A.subs.map((s, i) => i === 0 ? e.findIndex(v => v > th) : onset(Math.round((s.t0 - A.voice.at) * FR))); // 每幕首句 = 音频第一个有声帧
|
| 14 |
+
A.subs.forEach((s, i) => { const t0 = A.voice.at + on[i] / FR - LEAD; stats.push(Math.round((t0 + LEAD - s.t0) * 1000)); s.t0 = +t0.toFixed(3);
|
| 15 |
+
const lim = i < on.length - 1 ? on[i + 1] : e.length; let last = on[i]; for (let f = on[i]; f < lim; f++) if (loud(f)) last = f;
|
| 16 |
+
s.t1 = +(A.voice.at + last / FR + TAIL).toFixed(3); });
|
| 17 |
+
A.subs.forEach((s, i) => { const n = A.subs[i + 1]; if (n && (s.t1 > n.t0 - .02 || n.t0 - s.t1 < .3)) s.t1 = +(n.t0 - .02).toFixed(3); if (s.t1 < s.t0 + .5) s.t1 = +(s.t0 + .5).toFixed(3); });
|
| 18 |
+
// 校验:每个有声帧都应被某条字幕覆盖
|
| 19 |
+
let miss = 0; e.forEach((v, f) => { if (v > th) { const t = A.voice.at + f / FR; if (!A.subs.some(s => t >= s.t0 && t <= s.t1)) miss++; } }); A._miss = miss;
|
| 20 |
+
}
|
| 21 |
+
const miss = TL.acts.reduce((a, A) => a + A._miss, 0); TL.acts.forEach(A => delete A._miss);
|
| 22 |
+
fs.writeFileSync(FILE, src.slice(0, src.indexOf('{')) + JSON.stringify(TL, null, 1) + src.slice(src.lastIndexOf('}') + 1));
|
| 23 |
+
console.log(`对齐 ${stats.length} 条;原始偏差 ${Math.min(...stats)}~${Math.max(...stats)} ms;有声却无字幕的帧 ${miss}`);
|
gemini/tools/narrate.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ♊ 双子座 —— 双声旁白生成(全新编写)
|
| 2 |
+
# 用法(在仓库根目录):PYTHONPATH=<含 edge_tts 的目录> python3 gemini/tools/narrate.py
|
| 3 |
+
# 输出:gemini/audio/n01..n10.mp3 与 gemini/engine/timeline.js
|
| 4 |
+
import asyncio, json, os, subprocess, sys, array
|
| 5 |
+
import edge_tts, imageio_ffmpeg
|
| 6 |
+
|
| 7 |
+
FF = imageio_ffmpeg.get_ffmpeg_exe()
|
| 8 |
+
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
|
| 9 |
+
VOICE = {'f': 'zh-CN-XiaoxiaoNeural', 'm': 'zh-CN-YunxiNeural'}
|
| 10 |
+
RATE = {'f': '-4%', 'm': '-2%'}
|
| 11 |
+
SR = 24000
|
| 12 |
+
LINE_GAP = 0.32 # 句间停顿
|
| 13 |
+
LEAD = {0: 2.4}; DEFAULT_LEAD = 1.0
|
| 14 |
+
GAP = 1.3; OUTRO = 6.0
|
| 15 |
+
|
| 16 |
+
F, M, B = 'f', 'm', 'fm'
|
| 17 |
+
# 字幕显示用写法(朗读仍用汉字读音)
|
| 18 |
+
DISPLAY = {'二零零六年': '2006 年', '一七八一年': '1781 年', '一九三零年': '1930 年', '三十四光年': '34 光年', '五月二十一日到六月二十一日': '5 月 21 日到 6 月 21 日', '一百多颗': '100 多颗'}
|
| 19 |
+
def shown(t):
|
| 20 |
+
for a, b in DISPLAY.items(): t = t.replace(a, b)
|
| 21 |
+
return t.rstrip('。,、')
|
| 22 |
+
ACTS = [
|
| 23 |
+
('twin', '序章 · 镜中的双星', 'dawn', [
|
| 24 |
+
(F, '五月的尾巴,夏天刚刚推开门。'), (M, '夜空里,有两颗星总是肩并着肩。'), (F, '一颗明亮温暖,'), (M, '一颗清澈闪烁。'),
|
| 25 |
+
(F, '它们像一面镜子的两边,'), (M, '又像同一个灵魂的两种声音。'), (F, '这就是黄道第三宫,双子座。'),
|
| 26 |
+
(M, '五月二十一日到六月二十一日出生的你,'), (F, '欢迎回到自己的星空。')]),
|
| 27 |
+
('locate', '找到他们', 'night', [
|
| 28 |
+
(M, '冬天和早春的夜晚,先找到猎户座。'), (F, '从猎户脚下的参宿七出发,'), (M, '穿过肩头那颗橙红的参宿四,'), (F, '一直向东北方延伸。'),
|
| 29 |
+
(M, '你会看到两颗相距不远的亮星。'), (F, '西边那颗是北河二,卡斯托尔。'), (M, '东边更亮的那颗,是北河三,波吕克斯。'),
|
| 30 |
+
(F, '从两颗星向下,两串星星并排伸展,'), (M, '就像一对兄弟,手牵着手,站在银河边上。')]),
|
| 31 |
+
('pair', '两颗星的秘密', 'violet', [
|
| 32 |
+
(F, '有意思的是,排名第一的北河二,'), (M, '其实没有北河三亮。'), (F, '北河三是一颗橙色的巨星,'), (M, '离我们大约三十四光年。'),
|
| 33 |
+
(F, '二零零六年,天文学家确认,它身边有一颗行星在转动。'), (M, '而北河二更神奇。'), (F, '它看起来是一颗星,'),
|
| 34 |
+
(M, '其实是六颗恒星组成的大家庭。'), (F, '三对双星彼此环绕,'), (M, '一起跳着一支很慢很慢的舞。'), (F, '你看,双子座连星星本身,都喜欢热闹。')]),
|
| 35 |
+
('myth', '卡斯托尔与波吕克斯', 'amber', [
|
| 36 |
+
(M, '古希腊传说里,卡斯托尔和波吕克斯是一对孪生兄弟。'), (F, '一个擅长驯马,一个擅长拳击。'), (M, '他们一起航海,一起冒险,从不分开。'),
|
| 37 |
+
(F, '可是卡斯托尔是凡人,'), (M, '波吕克斯却拥有不死之身。'), (F, '当卡斯托尔在战斗中倒下,'), (M, '波吕克斯请求宙斯,'),
|
| 38 |
+
(F, '把自己的永生,分一半给兄弟。'), (M, '从此,他们一天在天上,一天在冥界,永远在一起。'), (F, '宙斯被这份情谊打动,把他们升上星空。'),
|
| 39 |
+
(M, '后来的水手们相信,看见桅杆上的蓝色火光,'), (F, '就是双子在为他们护航。')]),
|
| 40 |
+
('china', '北河与井宿', 'ink', [
|
| 41 |
+
(F, '在中国古代的星空里,他们有一个好听的名字,'), (M, '北河。'), (F, '北河二,北河三,守在银河的北岸。'),
|
| 42 |
+
(M, '它们属于二十八宿中的井宿。'), (F, '井宿,是南方朱雀七宿的第一宿。'), (M, '古人看着它,观测天象,校准历法。'),
|
| 43 |
+
(F, '东方和西方,看见的是同一片星空,'), (M, '却讲出了不同的故事。')]),
|
| 44 |
+
('discover', '两颗行星的发现', 'deep', [
|
| 45 |
+
(M, '双子座还有一个了不起的身份,'), (F, '发现之地。'), (M, '一七八一年,英国天文学家威廉·赫歇尔,'), (F, '在双子座里,看见一个缓缓移动的光点。'),
|
| 46 |
+
(M, '那是天王星,人类用望远镜发现的第一颗行星。'), (F, '一九三零年,美国青年克莱德·汤博,'), (M, '一张一张地比对星空底片。'),
|
| 47 |
+
(F, '他在双子座的星星之间,找到了冥王星。'), (M, '好奇心,在这里两次改写了太阳系的地图。')]),
|
| 48 |
+
('meteor', '双子座流星雨', 'snow', [
|
| 49 |
+
(F, '每年十二月中旬,双子座还会送出一份礼物,'), (M, '双子座流星雨。'), (F, '它是一年中最壮观的流星雨之一,'),
|
| 50 |
+
(M, '高峰时,每小时可以看到一百多颗。'), (F, '它的源头不是彗星,'), (M, '而是一颗叫法厄同的小行星。'),
|
| 51 |
+
(F, '冬夜里抬头许愿吧,'), (M, '星星会从双子的方向飞来。')]),
|
| 52 |
+
('sign', '风象 · 水星 · ♊', 'aurora', [
|
| 53 |
+
(F, '双子座的符号,像罗马数字二,'), (M, '两根柱子,被上下两道横梁连在一起。'), (F, '它属于风象星座,'), (M, '守护星是��星。'),
|
| 54 |
+
(F, '水星是众神的信使,跑得最快,消息最灵。'), (M, '所以人们说,双子座好奇,聪明,反应快,'), (F, '会表达,爱学习,'),
|
| 55 |
+
(M, '总能把陌生人变成朋友。'), (F, '有人说双子座善变,'), (M, '其实,那是一颗心里,装着两倍的世界。')]),
|
| 56 |
+
('medal', '双星荣誉徽章', 'royal', [
|
| 57 |
+
(M, '所以,如果你是双子座,'), (F, '这枚双星荣誉徽章,属于你。'), (M, '一半是北河二的银,'), (F, '一半是北河三的金。'),
|
| 58 |
+
(M, '上面刻着你的名字,'), (F, '和你最闪亮的天赋,好奇,灵动,真诚。'), (M, '双子座,星空的信使。'), (F, '世界因为你的好奇,变得更有趣。')]),
|
| 59 |
+
('coda', '尾声 · 两束光', 'dusk', [
|
| 60 |
+
(M, '下一次,夏天的风吹过来,'), (F, '抬头看看那两颗并肩的星星。'), (M, '你从来不是一个人,'), (F, '你是两束光,也是一整片星空。'),
|
| 61 |
+
(B, '双子座,保持好奇,闪闪发光。')]),
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
async def tts(text, who):
|
| 65 |
+
com = edge_tts.Communicate(text, VOICE[who], rate=RATE[who], boundary='WordBoundary')
|
| 66 |
+
audio = bytearray(); words = []
|
| 67 |
+
async for ch in com.stream():
|
| 68 |
+
if ch['type'] == 'audio': audio += ch['data']
|
| 69 |
+
elif ch['type'] == 'WordBoundary': words.append((ch['offset'] / 1e7, (ch['offset'] + ch['duration']) / 1e7, ch['text']))
|
| 70 |
+
pcm = subprocess.run([FF, '-v', 'error', '-i', 'pipe:0', '-f', 's16le', '-ac', '1', '-ar', str(SR), 'pipe:1'], input=bytes(audio), capture_output=True, check=True).stdout
|
| 71 |
+
a = array.array('h'); a.frombytes(pcm)
|
| 72 |
+
# 去掉首尾静音(保留 40ms)
|
| 73 |
+
th = 500; i0 = next((i for i, v in enumerate(a) if abs(v) > th), 0); i1 = len(a) - next((i for i, v in enumerate(reversed(a)) if abs(v) > th), 0)
|
| 74 |
+
i0 = max(0, i0 - int(.04 * SR)); i1 = min(len(a), i1 + int(.06 * SR))
|
| 75 |
+
return a[i0:i1], [(s - i0 / SR, e - i0 / SR, w) for s, e, w in words]
|
| 76 |
+
|
| 77 |
+
async def line(who, text):
|
| 78 |
+
if who != B: return await tts(text, who)
|
| 79 |
+
(a, w), (b, _) = await tts(text, F), await tts(text, M)
|
| 80 |
+
n = max(len(a), len(b)); out = array.array('h', [0] * n)
|
| 81 |
+
for i in range(n):
|
| 82 |
+
v = (a[i] if i < len(a) else 0) * .62 + (b[i] if i < len(b) else 0) * .62
|
| 83 |
+
out[i] = int(max(-32767, min(32767, v)))
|
| 84 |
+
return out, w
|
| 85 |
+
|
| 86 |
+
async def main():
|
| 87 |
+
os.makedirs(os.path.join(ROOT, 'audio'), exist_ok=True)
|
| 88 |
+
t = 0.0; acts = []
|
| 89 |
+
for k, (aid, title, tone, lines) in enumerate(ACTS):
|
| 90 |
+
lead = LEAD.get(k, DEFAULT_LEAD); at = t + lead
|
| 91 |
+
pcm = array.array('h'); subs = []
|
| 92 |
+
for who, text in lines:
|
| 93 |
+
a, _ = await line(who, text)
|
| 94 |
+
st = len(pcm) / SR
|
| 95 |
+
subs.append({'t0': round(at + st, 3), 't1': round(at + st + len(a) / SR, 3), 'text': shown(text), 'who': who})
|
| 96 |
+
pcm += a; pcm += array.array('h', [0] * int(LINE_GAP * SR))
|
| 97 |
+
print(aid, who, text, flush=True)
|
| 98 |
+
dur = len(pcm) / SR - LINE_GAP
|
| 99 |
+
f = f'audio/n{k + 1:02d}.mp3'
|
| 100 |
+
subprocess.run([FF, '-v', 'error', '-y', '-f', 's16le', '-ar', str(SR), '-ac', '1', '-i', 'pipe:0', '-c:a', 'libmp3lame', '-b:a', '96k', os.path.join(ROOT, f)], input=pcm.tobytes(), check=True)
|
| 101 |
+
end = at + dur + GAP + (OUTRO if k == len(ACTS) - 1 else 0)
|
| 102 |
+
acts.append({'id': aid, 'title': title, 'tone': tone, 'start': round(t, 3), 'end': round(end, 3), 'voice': {'file': f, 'at': round(at, 3), 'dur': round(dur, 3)}, 'subs': subs})
|
| 103 |
+
t = end
|
| 104 |
+
tl = {'duration': round(t, 3), 'voices': VOICE, 'acts': acts}
|
| 105 |
+
with open(os.path.join(ROOT, 'engine/timeline.js'), 'w') as fp:
|
| 106 |
+
fp.write('// 由 tools/narrate.py 生成、tools/align.mjs 校准:双声旁白时间轴与字幕\nexport const TIMELINE = ' + json.dumps(tl, ensure_ascii=False, indent=1) + ';\n')
|
| 107 |
+
print('total', round(t, 2), 'subs', sum(len(a['subs']) for a in acts))
|
| 108 |
+
|
| 109 |
+
asyncio.run(main())
|