Spaces:
Running
Running
E9: 以太坊简史 测试修复 + 使用说明(最终版)
Browse files- README.md +18 -1
- ethereum/js/chapters.js +4 -4
- ethereum/js/world.js +1 -1
README.md
CHANGED
|
@@ -154,7 +154,7 @@ python shouhouzi/tools/subset_font.py <ZCOOLKuaiLe-Regular.ttf> # 重新做字
|
|
| 154 |
| E6 | 音频:Worker 内纯 JS DSP(PolyBLEP 锯齿波、SVF 滤波、乒乓延迟)合成 13 段情绪配乐 + 60 余个画面同步音效;主线程卷积混响 + 人声链 + 压缩/限幅母带,整体约 -14 dBFS RMS | ✅ 完成 |
|
| 155 |
| E7 | 播放器 + MP4 导出(Mediabunny:H.264/AAC 优先,自动回退 VP9/AV1 + Opus;可流式写盘)+ 24/30/60fps + 横竖屏,已实测导出并回放 | ✅ 完成 |
|
| 156 |
| E8 | 横/竖封面 PNG + 抖音 / B站发布文案(含自动章节时间戳与数据说明) | ✅ 完成 |
|
| 157 |
-
| E9 | 自动化测试
|
| 158 |
|
| 159 |
## E1. 风格定义:「深空 · 光之晶体」
|
| 160 |
|
|
@@ -191,3 +191,20 @@ python shouhouzi/tools/subset_font.py <ZCOOLKuaiLe-Regular.ttf> # 重新做字
|
|
| 191 |
| 音频 | Web Worker 里用纯 JS DSP 逐采样合成配乐(合成器 pad / 琶音 / 低音 / 鼓)与音效(冲击、上升音、故障音、心跳……),主线程再用卷积混响 + 压缩 + 限幅做母带,与旁白混音 |
|
| 192 |
| 导出 | Mediabunny(MP4 封装 + WebCodecs 编码):自动选择 H.264/AAC,不支持时回退;支持直接流式写入磁盘 |
|
| 193 |
| 字幕 | 逐词点亮的卡拉 OK 式字幕 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
| E6 | 音频:Worker 内纯 JS DSP(PolyBLEP 锯齿波、SVF 滤波、乒乓延迟)合成 13 段情绪配乐 + 60 余个画面同步音效;主线程卷积混响 + 人声链 + 压缩/限幅母带,整体约 -14 dBFS RMS | ✅ 完成 |
|
| 155 |
| E7 | 播放器 + MP4 导出(Mediabunny:H.264/AAC 优先,自动回退 VP9/AV1 + Opus;可流式写盘)+ 24/30/60fps + 横竖屏,已实测导出并回放 | ✅ 完成 |
|
| 156 |
| E8 | 横/竖封面 PNG + 抖音 / B站发布文案(含自动章节时间戳与数据说明) | ✅ 完成 |
|
| 157 |
+
| E9 | 自动化测试(横竖屏 63 句逐句截图 ×2、音轨电平、两种画幅/帧率导出并解码回放)、修复构图与字体回退、最终推送 | ✅ 完成 |
|
| 158 |
|
| 159 |
## E1. 风格定义:「深空 · 光之晶体」
|
| 160 |
|
|
|
|
| 191 |
| 音频 | Web Worker 里用纯 JS DSP 逐采样合成配乐(合成器 pad / 琶音 / 低音 / 鼓)与音效(冲击、上升音、故障音、心跳……),主线程再用卷积混响 + 压缩 + 限幅做母带,与旁白混音 |
|
| 192 |
| 导出 | Mediabunny(MP4 封装 + WebCodecs 编码):自动选择 H.264/AAC,不支持时回退;支持直接流式写入磁盘 |
|
| 193 |
| 字幕 | 逐词点亮的卡拉 OK 式字幕 |
|
| 194 |
+
|
| 195 |
+
## E4. 使用说明(以太坊简史)
|
| 196 |
+
|
| 197 |
+
- **在线观看**:https://ethscriptions-pols.static.hf.space/ethereum/index.html (首页导航:https://ethscriptions-pols.static.hf.space/ )
|
| 198 |
+
- **时长**:6 分 54 秒,共 13 章、63 句旁白,画面同步点均由逐词时间戳自动换算。
|
| 199 |
+
- **播放**:空格播放/暂停,←/→ 快退/快进 5 秒,M 静音,F 全屏;章节卡片可以直接跳转;在进度条上悬停会显示时间和章节。
|
| 200 |
+
- **画幅**:横屏 16:9(B站)/ 竖屏 9:16(抖音)。竖屏单独设计了构图,不是简单裁切;打开「手机安全区参考线」可以查看抖音的遮挡区域。
|
| 201 |
+
- **导出**:选画幅 / 帧率(24·30·60)/ 码率 / 范围 → 开始导出。桌面版 Chrome / Edge 输出 H.264 + AAC;浏览器不支持时自动回退。支持「边渲染边写入磁盘」。
|
| 202 |
+
- **封面**:`ethereum/covers/cover-landscape.png`、`cover-portrait.png`,网页里也可以实时重绘下载。
|
| 203 |
+
- **发布文案**:抖音 / B站标题、简介(含章节时间戳与数据说明)、标签、分区建议,点一下就能复制。
|
| 204 |
+
|
| 205 |
+
### 重新构建
|
| 206 |
+
```bash
|
| 207 |
+
pip install edge-tts mutagen fonttools brotli
|
| 208 |
+
python ethereum/tools/build_voice.py # 改文案(含 {标记})后重新配音与对齐
|
| 209 |
+
python ethereum/tools/fonts.py <字体源目录> # 重新子集化三款字体
|
| 210 |
+
```
|
ethereum/js/chapters.js
CHANGED
|
@@ -547,9 +547,9 @@
|
|
| 547 |
if (offK < 0.9) K.glow(ctx, bx, by, 90, C.orange, 0.3 * a * (1 - offK));
|
| 548 |
}
|
| 549 |
};
|
| 550 |
-
if (pw > 0) { const [x, y] = H.bx(0.22, 0.55, 0.27, 0.
|
| 551 |
if (ps > 0) {
|
| 552 |
-
const [x, y] = H.bx(0.78, 0.55, 0.75, 0.
|
| 553 |
ctx.save(); if (P) { ctx.translate(x, y); ctx.scale(0.62, 0.62); ctx.translate(-x, -y); } ctx.globalAlpha *= ps;
|
| 554 |
for (let i = 0; i < 5; i++) { const a = i / 5 * K.TAU + t * 0.4; const sx = x + Math.cos(a) * 110, sy = y + Math.sin(a) * 60; K.glow(ctx, sx, sy, 50, C.gold, 0.6); const sh = [[0, -26], [22, -16], [18, 12], [0, 28], [-18, 12], [-22, -16], [0, -26]].map(([px, py]) => [sx + px, sy + py]); K.neon(ctx, sh, C.gold, 2, 1); }
|
| 555 |
ctx.restore();
|
|
@@ -588,7 +588,7 @@
|
|
| 588 |
// 矿机熄灭 + 能耗
|
| 589 |
const mk = H.lf('mine', null, 0.6);
|
| 590 |
if (mk > 0) {
|
| 591 |
-
const [x, y] = H.bx(0.18, 0.6, 0.27, 0.
|
| 592 |
ctx.save(); if (P) { ctx.translate(x, y); ctx.scale(0.62, 0.62); ctx.translate(-x, -y); } ctx.globalAlpha *= 1 - K.clamp(H.m('energy') / 1); rigs(x, y, mk, off); ctx.restore();
|
| 593 |
const ek2 = H.lf('energy', null, 0.6);
|
| 594 |
if (ek2 > 0) {
|
|
@@ -779,7 +779,7 @@
|
|
| 779 |
icons.forEach(([m, a, b], i) => {
|
| 780 |
const k = K.life(t, H.at(m), H.at('bye'), 0.6, 0.6);
|
| 781 |
if (k <= 0) return;
|
| 782 |
-
const x = P ? L.box.x + L.box.w * (0.14 + i * 0.24) : c.x + (i - 1.5) * 300, y = c.y + c.s * (P ? 2.
|
| 783 |
pop(ctx, x, y, k, () => { K.glass(ctx, -120, -60, 240, 120, { tint: [C.red, C.etc, C.mag, C.gold][i] }); K.text(ctx, a, 0, -14, { size: 32, color: C.white }); K.text(ctx, b, 0, 28, { size: 24, fam: 'num', color: [C.red, '#7CE07C', C.mag, C.gold][i] }); });
|
| 784 |
});
|
| 785 |
const ok = H.lf('open', 'bye', 0.9, 0.6);
|
|
|
|
| 547 |
if (offK < 0.9) K.glow(ctx, bx, by, 90, C.orange, 0.3 * a * (1 - offK));
|
| 548 |
}
|
| 549 |
};
|
| 550 |
+
if (pw > 0) { const [x, y] = H.bx(0.22, 0.55, 0.27, 0.8); ctx.save(); if (P) { ctx.translate(x, y); ctx.scale(0.62, 0.62); ctx.translate(-x, -y); } rigs(x, y, pw, 0); K.text(ctx, '工作量证明 · PoW', x, y + 130, { size: 34, color: C.orange, alpha: pw }); K.text(ctx, '矿机算力竞争记账', x, y + 176, { size: 26, color: K.rgba(C.white, 0.7), alpha: pw }); ctx.restore(); }
|
| 551 |
if (ps > 0) {
|
| 552 |
+
const [x, y] = H.bx(0.78, 0.55, 0.75, 0.8);
|
| 553 |
ctx.save(); if (P) { ctx.translate(x, y); ctx.scale(0.62, 0.62); ctx.translate(-x, -y); } ctx.globalAlpha *= ps;
|
| 554 |
for (let i = 0; i < 5; i++) { const a = i / 5 * K.TAU + t * 0.4; const sx = x + Math.cos(a) * 110, sy = y + Math.sin(a) * 60; K.glow(ctx, sx, sy, 50, C.gold, 0.6); const sh = [[0, -26], [22, -16], [18, 12], [0, 28], [-18, 12], [-22, -16], [0, -26]].map(([px, py]) => [sx + px, sy + py]); K.neon(ctx, sh, C.gold, 2, 1); }
|
| 555 |
ctx.restore();
|
|
|
|
| 588 |
// 矿机熄灭 + 能耗
|
| 589 |
const mk = H.lf('mine', null, 0.6);
|
| 590 |
if (mk > 0) {
|
| 591 |
+
const [x, y] = H.bx(0.18, 0.6, 0.27, 0.8);
|
| 592 |
ctx.save(); if (P) { ctx.translate(x, y); ctx.scale(0.62, 0.62); ctx.translate(-x, -y); } ctx.globalAlpha *= 1 - K.clamp(H.m('energy') / 1); rigs(x, y, mk, off); ctx.restore();
|
| 593 |
const ek2 = H.lf('energy', null, 0.6);
|
| 594 |
if (ek2 > 0) {
|
|
|
|
| 779 |
icons.forEach(([m, a, b], i) => {
|
| 780 |
const k = K.life(t, H.at(m), H.at('bye'), 0.6, 0.6);
|
| 781 |
if (k <= 0) return;
|
| 782 |
+
const x = P ? L.box.x + L.box.w * (0.14 + i * 0.24) : c.x + (i - 1.5) * 300, y = c.y + c.s * (P ? 2.2 : 2.1);
|
| 783 |
pop(ctx, x, y, k, () => { K.glass(ctx, -120, -60, 240, 120, { tint: [C.red, C.etc, C.mag, C.gold][i] }); K.text(ctx, a, 0, -14, { size: 32, color: C.white }); K.text(ctx, b, 0, 28, { size: 24, fam: 'num', color: [C.red, '#7CE07C', C.mag, C.gold][i] }); });
|
| 784 |
});
|
| 785 |
const ok = H.lf('open', 'bye', 0.9, 0.6);
|
ethereum/js/world.js
CHANGED
|
@@ -147,7 +147,7 @@
|
|
| 147 |
|
| 148 |
// 晶体在各章节的"站位"(相对内容框,0..1)。章节交界处平滑过渡。
|
| 149 |
const POSE = {
|
| 150 |
-
open: { l: [0.5, 0.
|
| 151 |
origin: { l: [0.86, 0.22, 0.09], p: [0.83, 0.07, 0.07] },
|
| 152 |
ico: { l: [0.14, 0.2, 0.08], p: [0.15, 0.06, 0.07] },
|
| 153 |
genesis: { l: [0.5, 0.13, 0.07], p: [0.5, 0.05, 0.07] },
|
|
|
|
| 147 |
|
| 148 |
// 晶体在各章节的"站位"(相对内容框,0..1)。章节交界处平滑过渡。
|
| 149 |
const POSE = {
|
| 150 |
+
open: { l: [0.5, 0.39, 0.25], p: [0.5, 0.36, 0.18] },
|
| 151 |
origin: { l: [0.86, 0.22, 0.09], p: [0.83, 0.07, 0.07] },
|
| 152 |
ico: { l: [0.14, 0.2, 0.08], p: [0.15, 0.06, 0.07] },
|
| 153 |
genesis: { l: [0.5, 0.13, 0.07], p: [0.5, 0.05, 0.07] },
|