纪录 3D 视频(TikTok,德语)
所有内容均在代码中构建:HyperFrames 加 Three.js。请先阅读 hyperframes 技能,其中包含契约、check、snapshot 和 render。
语言:始终使用德语。 这也适用于脚本、配音、标题、字幕以及对用户的回复。
格式(根据三个参考视频测量)
- 技术规格: 1080×1920,30 fps。
- 时长:标准 60–90 秒(用户明确要求;约 150–200 个单词,10–14 个镜头)。仅在用户明确要求时缩短。
- 视角: 观众就是你。“你走向海滩。”“你坐在莫斯科附近的一个掩体里。”“你的闹钟响了。”
- 倒计时结构: 时间是支柱。“3 小时后” → “还剩 2.5 小时” → “还剩 30 分钟” → “0 小时” → “+40 分钟”。或者“第 0/25/100/160 分钟”或“第 1/3/7/10 天”。转折点是“0 小时”或“0 分钟”。
- 结构:
- 平静的开场(田园或日常),副标题中注明日期和地点。
- 迹象和升级,每个镜头都是一个时间跳跃。
- 灾难或转折点。
- 用硬数字结账,最好显示在风格化的地图上(“≈ 230,000”,警报时用红色数字)。
- 解释部分“...平均而言” / “为什么?”,带有深色面板和平面示意图,字幕为“示意图,不按比例”。
- 结尾:一个教训或一个“对你”的问题。
- 剪辑: 镜头之间的硬切(每 2-4 秒)。在每个镜头内,摄像机缓慢移动,如滑轨或推进,在整个镜头期间使用
smooth()。
设计
3D 世界(Three.js r149,assets/vendor/three.min.js,在场景的 <template> 中加载):
- 风格化、柔和的低多边形世界,具有光滑材质(roughness ~0.8)、温暖的阳光和柔和阴影(PCFSoft,1024 分辨率贴图)。
- 天空作为渐变纹理,带有雾效。使用 ACES 色调映射,曝光度约 0.62,否则一切看起来都会苍白。
- 角色 = 关节人偶: 胶囊状的腿、躯干和手臂,球形头部,发帽,单色服装,没有面部。辅助函数
person(seed, {top, pants, scale}),动画使用idle()/walk(),相位永远不会同步。 - 使用盒子、圆柱体和球体作为道具,细节通过
canvasTex()(表盘、标志、窗户)绘制。 - 夜晚场景使用自发光窗户;根据距离分层设置
emissiveIntensity表示关灯。 - 每个镜头是
W.<id> = {scene, camera, update(tLocal, T)},SHOTS定义时间。通过 GSAP 代理(onUpdate → renderAt)以及hf-seek进行渲染。 - 模板:
templates/beispiel-keiner-arbeitet/welt.html,包含以下场景:卧室 + 闹钟、开放式办公室、公交车站、超市、医院走廊、垃圾街 + 老鼠、城市夜景(关灯)、厨房 + 蜡烛、窗户/阳台。复制助手、场景和渲染器,重用或替换场景。 - 人像画面裁剪很窄(水平约 27°):将相机放得比想象中更远,并将角色置于画面中央。
- 面板下方的保持镜头:
{ id, t0, t1, hold: true }以最终状态渲染场景。
左上方标题块(scripts/titel.py,数据在 titel.json 中),Open Sans,白色,柔和阴影:
- 副标题,32 像素,字重 600,大写字母带间距,例如“26. DEZEMBER 2004, ≈ 10:00 UHR”。
- 主标题,132 像素,字重 700,例如“Noch 30 Min.”,“Minute 0”,“Tag 7”。警报时可以是红色(
"farbe":"#ff4b3e")。 - 副标题行,32 像素,字重 600,大写字母,例如“DAS MEER ZIEHT SICH ZURÜCK”。
- 位置:左侧 72 像素,上方 228 像素。每次切换时更换(0.2 秒模糊进入)。
字幕(scripts/untertitel.py):
- Open Sans 字重 700 斜体,54 像素,y ≈ 1210 处居中。
- 显示一个短语(直到标点符号或最多 5 个单词)。尚未说出的单词变暗(0.5),说话时点亮。
- 关键词用浅蓝色
#6cc0ff(--blau "Meer,Welle")。
结账地图(模板 bilanz.html,路径来自 europa-paths.json 或 Natural Earth):
- 平面地图:海洋
#123f73,陆地米色#e9dfc6,目标国家更亮且带有白色边框,黄色城市点。 - 白色圆圈从中心脉动,标题块中的大数字递增并变为红色。
解释部分(模板 erklaerung.html = 链条,vorrat.html = 同一条带中的两个阶段):
- 场景严重变暗(上方
rgba(20,26,38,.97),下方稍有透光)。 - 标题块“原因 / 为什么? / 示意图,不按比例”。
- 明亮的图像条带(y 640–1040),带有平面插图(剖面图、链条、腔室……)和大写标签。
- 条带下方的青色
#5fd0e8状态句子。 - 红色叉号或绿色勾号图标(OpenMoji)。
音频: 声音是时间轴。音效需谨慎使用:
- 切换时使用 whoosh-short
- 转折点时使用 impact-bass
- 叉号或警报时使用 error/pop
音乐仅在用户提供文件时使用。
流程(如果指定了主题,则无需提问)
bash scripts/setup.sh(每个容器一次),然后bash scripts/projekt.sh <slug>。- 编写
skript.txt:每个镜头一行,使用“你”的形式,短句。- 时长:60 秒 ≈ 150 个单词,70 秒 ≈ 165,90 秒 ≈ 210(Piper 约 2.5 词/秒,含停顿)。
- 数字为配音拼写出来,显示带有
zwölf{12}。 - 仅在有可靠来源时使用事实;估计值使用“大约”/“约”,思想实验使用虚拟语气或作为场景。
python3 scripts/voice.py skript.txt assets/audio --speed 1.08 --gap 0.45- 结果:
voice.wav和words.json,根据lines[].start剪切镜头。 - 用户的自己录制有优先权。
- 结果:
titel.json→python3 scripts/titel.py titel.json compositions/titel.htmlpython3 scripts/untertitel.py assets/audio/words.json compositions/untertitel.html --blau "..."- 构建
compositions/welt.html(3D)和compositions/erklaerung.html(图表);从模板中获取index.html(图层:世界 1,解释 2,标题 3,字幕 4,音频)。 npx hyperframes check --json(错误必须消除;明亮天空上的对比度警告是风格原因)。- 然后
npx hyperframes snapshot --at a,b,c,d,e,f --no-end -o snap,每次调用最多约 6 个时间点。 - 查看并修正图片。
- 然后
- 后台渲染:
npx hyperframes render -o renders/<slug>.mp4 -q delivery -w 2 --quiet。- WebGL 在 CPU 上运行,这需要几分钟。
- 然后用 ffprobe 检查,查看联系表并将文件发送给用户。
声音(用户要求:友好的女声)
- 首选:用户的 ElevenLabs 文件(以前是“Jessica – Playful, Bright, Warm”,eleven_v4)。流程:
- 将脚本作为文本块提供给用户录制(每个镜头一行)。
- 使用词时间生成参考:
python3 scripts/voice.py skript.txt /tmp/ref --voice ~/.cache/geo-voice/vits-piper-de_DE-kerstin-low/de_DE-kerstin-low.onnx --gap 0.4 - 对齐:
python3 scripts/align.py /tmp/ref assets/audio/<eleven>.mp3 assets/audio(基于 MFCC 的 DTW,行起始对齐到实际停顿)。 结果:voice.wav+words.json;使用silencedetect双重检查行边界。 - 所有时间(剪辑、标题、效果、面板)都从新的词时间导出。
- 无文件时的备用方案:女性 Piper 声音(Kerstin/Ramona/Eva,来自 GitHub
k2-fsa/sherpa-onnx发布版tts-models)。听起来很合成;仅作为备选方案,并告知用户。 - 无法进行语音克隆(模型被阻止),不得克隆陌生或名人声音。
音乐和音效
用户始终想要轻柔的背景音乐和效果音效。Openverse、Freesound、Pixabay、archive.org 和 HeyGen 被屏蔽,因此两者都使用 scripts/klang.py 合成生成。
- 音乐:
python3 scripts/klang.py musik musik-plan.json assets/music/musik.wavmusik-plan.json为每个部分定义:和弦、能量、亮度以及可选的 BPM 脉冲。再加上一些钢琴音符。- 各部分遵循戏剧结构:阴郁 → 紧张(脉冲) → 低谷几乎无声 → 希望时温暖的调式和弦。
- 在视频中使用
data-volume~0.22 放在声音下方(音乐文件约 −18 LUFS)。
- 音效:
python3 scripts/klang.py sfx assets/sfx生成风声、隆隆声、收音机、钟表、脚步声、队列、警笛、钟声、心跳、嗡嗡声、轰鸣声。- 再加上附带的 whoosh/impact/riser 来自
media-use。 - 音量 0.15–0.45,持续声音使用
data-fade-in/out。
- 再加上附带的 whoosh/impact/riser 来自
- 控制: 通过
showspectrumpic查看频谱(咔哒声 = 垂直线)。我无法自己听,因此告知用户混音是测量得到的而非听出来的。 - 对于敏感主题(大屠杀、受害者),减少音乐;仅使用风声或嗡嗡声。
敏感历史主题
- 禁止使用万字符、党卫军符文或其他纳粹符号,以及带脸的肇事者肖像(TikTok 会屏蔽,即使在历史背景下)。旗帜和制服保持抽象。
- 集中营和暴力只做暗示(栅栏、瞭望塔、雾),不展示受害者。语气客观且批判。
- 在假设情景结尾处,对现实进行归类(“现实” / “为什么几乎不可能”)并展示解放。
始终提升(用户要求)
每个项目都应在氛围、效果、动画和音频方面超越前一个。当前状态保存在 templates/beispiel-tschernobyl/ 中:
- 粒子
particles(scene, n, seed, {x,y,z,v,drift,grav,life,color,size,opacity,add,fog}):确定性,并且始终使用柔和的圆形纹理SOFT(没有它会出现方形)。用于火、余烬、烟雾、雨、灰尘、水柱和树叶。 - 光线作为叙事: 警报灯闪烁,火点光源闪烁,光束是加色的,地平线上的辉光是渐变平面(不是球体)。
- 摄像机: 撞击时晃动(
exp(-u*2.2)),每个镜头慢速推拉。 - 外观层
look.html(z-index 在世界之上,在标题/字幕之下):胶片颗粒(15 fps,确定性)、渐晕、爆炸时的白色闪光、沉默时刻淡出黑色、结尾淡出。 - 音频:
klang.py sfx2用于盖革计数器(3 种密度)、警报、爆炸、火、警笛 2、公共汽车、雨和吱吱声。在musik-plan.json中,有luft(泛音)、sub(低音)和stille(硬静音窗口,例如在巨响之后)。还有一个 riser,正好在转折点上升(开始 = 命中点 − 10.03 秒)。 - 支撑性结构: 开始和结束相互镜像(你睡觉 ↔ 你醒着站在窗边),中间是地图和图表。
陷阱
-
cam()只有 400 米可视距离。对于所有更远的东西(地平线、发电厂、拱门),使用camFar(3000+)并将雾设置得足够远,否则它会不可见。 -
人像窗口:精确规划通过开口的背景物体(角度 = atan(高度/距离))。
-
质量优先于速度(用户要求):始终使用 1080×1920、
-q delivery、完全阴影和抗锯齿。一个 70 秒的渲染在云端(CPU WebGL)大约需要 40-60 分钟。提前告知用户,不要降低画质。 -
pkill -f <模式>会杀死自己的 shell,如果模式出现在命令中。请改用 PID 终止进程。 -
Piper 声音听感很合成(16 kHz)。建议用户自行录制或提供 ElevenLabs 文件。
-
对于包含未知音素的行,无法进行对齐。
voice.py会根据能量和音节长度分布单词;在快照中检查时间。 -
场景 ID 必须是唯一的(前缀
er-、ti-、ut-)。 -
容器是临时的:立即发送视频。只有将技能和项目推送到 Git 仓库才能保留。