卡通3D动画故事短片 (three.js + Remotion)
产出内容:1080×1920、30fps、45-65秒的短片。一个以参考风格讲述的真实原创故事:一段过去时的荒诞钩子,嘲笑者,然后“你看”,再以一句台词收尾。扁平卡通卡片立于带有墨水轮廓的手绘3D世界中,暖色光源与冷色阴影形成对比,配合镜头模糊和无硬切运镜。快语速旁白(约3.7词/秒)、逐字字幕、原创配乐和合成音效,母带处理至-14 LUFS。
已实现示例:虚假购物者(1937年,Sylvan Goldman付钱给演员推他的新购物车),位于project/src/videos/fake-shoppers/和project/stories/fake-shoppers/目录。
基本原则(始终遵循)
- 故事、角色、声音和音乐均为我们原创。 仅参考视觉风格和公式,绝不使用其脚本、角色或镜头。绝不在视频、标题或描述中提及频道名称。参考视频是私人学习材料:不得上传或发送。
- 事实经过研究并注明来源,并置于故事
SCRIPT.md文件的顶部。笑话可以是夸张或真实事实,但绝不可将虚构历史当作事实呈现。 - 仅使用免费本地工具。 使用edge-tts或用户自己的声音;不使用付费生成器。任何连接器使用信用点前需先征得同意。
- 声音中无停顿(句内停顿剪至0.07秒,句末为0.2秒)。字幕应开启(参考风格)。视频在最后一个单词结束后0.5秒内结束,无片尾或行动号召。
- 选择主题前先检查
references/TOPICS_DONE.md。 - 两道审批关口,然后“直接执行”(继承自原始技能):
- 脚本: 展示脚本(含来源),等待用户批准后再进行配音和镜头计划。此后无需询问,直接工作至关口2。
- 评审看板: 在完整渲染前,用户观看半尺寸草稿(评审看板),在特定时刻或场景上添加备注,然后点击“Looks good”(步骤8)。在此之前重复迭代;未经此步骤不得进行完整渲染。
- 用户的品味和已学检查项(来自过去的评审看板)。在编写脚本(步骤2)、镜头计划(步骤4)、场景(步骤5)和角色(步骤6)之前,阅读两个偏好文件。在步骤8时,连同可读性陷阱一起检查两个检查文件:
- 所有视频技能共享:
~/video-editor/review-board/PREFERENCES.md、~/video-editor/review-board/CHECKS.md; - 仅此风格:
references/PREFERENCES.md、references/CHECKS.md。
- 所有视频技能共享:
- 接手现有故事: 首先运行
node ~/video-editor/review-board/cli.mjs show stories/<name>/review。在没有会话运行时用户发送的备注应优先处理(步骤8.5)。 - 仅进行一次完整渲染。 在后台启动,告知用户预计时间(1080p下约4fps,60秒2K视频约10分钟),不要轮询:任务结束后会报告。
--scale 0.5的预览可重复运行。
本台Mac(配置与测试日期:2026-11-07)
- 工作项目:
~/video-editor/toon3d-shorts/,由setup.sh创建。包含src/engine(通用)、src/videos/<name>/(每个视频一个文件夹)、stories/<name>/(脚本、配音、线索、配乐)、audio/(合成工具包+混音器)、tools/measure.py、scripts/和public/。此技能的project/中包含一份全新副本。可通过bash ~/.claude/skills/toon3d-story-shorts/setup.sh <dir>在任何位置重建,该命令会运行npm install(固定版本)和pip install --user scipy。 - Node 24 + Remotion 4.0.539: 通过ANGLE/Metal使用GPU。初始捆绑约需20秒,之后每帧静态图渲染约1.3秒;
--scale 0.5下全60秒预览约需45秒。 - Python 3.9: numpy、PIL、scipy。ffmpeg-full。whisper-cli,模型位于
~/video-editor/models/ggml-large-v3-turbo-q5_0.bin。edge-tts(python3 -m edge_tts)。 - 参考研究(需自行构建,不包含):
~/video-editor/reference-study/,基于您学习过的参考短片构建(自行下载至~/Downloads/reference-shorts/;仅供学习,不得分享)。作者研究了19个短片;references/中的数字即来源于此:STYLE_BIBLE.md和SCRIPTS_ANNOTATED.mdmeasure/(逐帧统计、转录文本、2秒/60帧联系表、1fps帧、summary.json),作为tools/measure.py ours ... --refs的基线frames/和sheets/(用于并排对比的1fps/2秒视图)
- 将
TMPDIR/REMOTION_TMP设置为~/video-editor/remotion-tmp(stills.mjs会自动执行)。Remotion的捆绑文件会堆积于此。仅在用户同意后删除旧的remotion-webpack-bundle-*文件夹。
工作流程
-
创意 → 主题 → 事实。 当用户没有主题或要求创意时,进行一轮创意生成(
references/VIRAL_IDEAS.md包含数据、来源、评分标准和事实规则):-
当前热门内容(需进行几次YouTube研究;若连接器会消耗信用点需先询问):
- 参考频道最受欢迎的短片:使用
youtube_channel_shorts,channel_id为<REFERENCE_CHANNEL_ID>(您正在学习其短片的频道),sort_by设为popular。将新标题添加到TOPICS_DONE.md。 - 细分领域突破:使用
faceless_outliers_videos,semanticquery为每个热门类型(例如“揭露奇怪历史发明、把戏或法律背后隐藏原因的动画短片,带有反转结局”),videoType设为shorts,languages设为["english"],minOutlierScore为3。忽略非该细分领域的爆款(游戏、小工具)。记录标题模式,并将已被讲述的故事添加到TOPICS_DONE.md。
- 参考频道最受欢迎的短片:使用
-
候选故事: 从来源列表中收集约30个真实故事,使用WebSearch/WebFetch。将每个故事映射到一个热门类型:古老物品或习俗背后的隐藏原因、外国制度或漏洞、心理学、金钱把戏或骗局、寓言或悖论。
-
评分: 使用10项评分标准(满分20分)。对前10名,使用
youtube_search(type设为shorts)检查新鲜度:剔除过去12个月内观看量已超过100万的已有故事,或寻找新的角度。 -
展示5-8个创意,包括:一个符合热门模式的暂定标题、一行钩子、主体、机制+类比、评分明细、为何可能爆火(该类型的参考数据),以及2-3个来源。全部保存至
references/IDEAS_BANK.md。用户选择一个。 -
研究选中的主题:
- 每个数字、日期、名称和声明至少有2个独立可靠来源;
- 传说需标注为传说;戏剧化的嘲讽需标注;
- 在
stories/<name>/SCRIPT.md顶部放置一个事实块(声明→链接→信心等级)。
对于大型主题,可使用深度研究技能完成此步骤。
一个好的主题是一个令人惊讶的真实故事,具有一个中心对象和一个观众能想象的隐藏机制,以及一个能解释它的日常类比(例如汤碗、手机从桌上滑落、电梯按钮)。
-
-
脚本(160-240词,45-65秒)。该频道的测量公式(
references/STYLE_BIBLE.md第2节、SCRIPTS_ANNOTATED.md):- 钩子(在约5秒内完成,9-20词): 一个被陈述为事实的荒谬行为,使用过去时(“他付钱给陌生人,让他们在自己的杂货店里假装购物。”)。备选:一个悖论式声明(“X是违法的,但...”)或一个条件式承诺(“如果你曾...”)。绝不能是问句。
- 背景: 1-2行,使用现在时,包含具体数字和年份。
- 怀疑者: 有人嘲笑或嘲讽,使用带引号的、符合角色设定的嘲讽。每个视频使用1-2句嘲讽。
- 观众提问: “等等,...?”/“但是如何做到的?”(每个视频约一个)。
- 升级: 时间跳跃和重复(“一周又一周”,“到了七月...”),加上每10-15秒一个夸张的笑话(“如此X以至于Y”,一种贬低性的比较)。
- 转折: “然后,有一天晚上/早上...”。
- 揭示: “你看,”在运行时长的约65-87%处出现,用1-3句简单的句子解释真正的机制,最好通过日常类比。(“你看,”在钩子后立即出现也可作为背景转换。)
- 收尾: 一句简短的陈述式台词,重新诠释一切(怀疑者被证明是错的)。在此处切出。额外加分:将最后一句写成能自然衔接回第一句,实现无缝循环。
- 风格: 钩子使用过去时,然后转为现在时;口语化表达,使用缩写;每句约14个词;连接词为“所以 / 但是 / 然后”,绝不使用“接着”。第二人称(“想象你...”)是有效模式。
- 绝不叙述观众已能看到的内容(继承自原始技能)。声音补充画面无法呈现的内容:原因、数字、笑话、利害关系。画面展示动作。
- 标题: 围绕主体制造好奇缺口,4-8个词,使用热门模式(“那个[主体][荒谬结果]”、“为什么[群体][做了奇怪的事]”、“如何一个[小东西][做了大事]”、“[主体]真正意味着什么”、“那个[人物]成就了[悖论]”)。与脚本一起提供3个选项。
- 钩子测试: 大声朗读第一句。它必须能在约3秒内抓住注意力,并让人感觉不对劲或不可能。如果需要上下文铺垫,则重写。
- 为关口1提交: 脚本、3个标题以及包含来源的事实块。
-
配音 放入
stories/<name>/voice/:- 生成:
python3 -m edge_tts --voice en-US-AndrewNeural --rate=+14% --text "$(cat ../script.txt)" --write-media raw.mp3,或使用用户的录音。 - 收紧:
python3 scripts/tighten_voice.py raw.mp3 voice.wav --sentence-gap 0.12将停顿剪至0.07秒,句末为0.12秒(参考视频中平均每段超过0.25秒的停顿约1处),输出48kHz单声道。 - 单词时间码:对16kHz副本使用 whisper-cli
-l en -ml 1 -sow -oj,然后转换为words.json([{w, a, b}],单位秒)。使用脚本中的拼写,而非whisper的。同时复制一份为words_authored.json:这是镜头计划所依据的时间线(story.ts 会从两者构建RETIME;如果文件相同,则表示无变化)。 - 用户自己的录音: 将其单词与脚本进行比较,并测量其语速(词/秒,超过0.25秒的停顿)。如果朗读已经很紧凑,收紧帮助不大;如果语速低于以下目标,则制作一个变调不变速的
--tempo 1.1版本,让用户听过后再选择。 - 在镜头计划之后替换配音(相同的单词,新的录音;2026-11-08为“虚假购物者”做过):不要手动重新调整任何时间。
- 将旧的时间码保留为
words_authored.json,并将新的时间码放入words.json(脚本拼写,相同的单词数)。不同的单词意味着需要新的镜头计划。 RETIME = makeRetime(authored, spoken, FPS)(engine/retime.ts)将每个输出帧映射到 authored 时间线,逐词跟随新配音,并使用±2个单词的平滑(中位起始时间误差约1帧)。index.tsx 使用total: RETIME.total, retime: RETIME;cast.ts 口型同步保持导入words_authored.json。- 在静态图上检查几个关键词帧,然后将新草稿发布到评审看板,每个场景都在
--changed下列出。 - 声音随之移动,无需手动重新放置任何内容:
node scripts/retime_cues.mjs <name>保留cues_authored.json一次,并输出时间内的cues.json(事件、dur参数、背景乐、嗖嗖声)以及timemap.json。python3 score.py music.wav <RETIME.total> timemap.json:重音跟随画面,节拍网格按段落跟随。没有映射表,配乐保持不变(逐样本检查)。python3 audio/mix.py stories/<name>/cues.json public/<name>/mix.wav,然后对混合音频运行whisper以确认每个单词都能被听到。
- 将旧的时间码保留为
- 目标:3.6-3.9词/秒,钩子在≤6秒内结束。TOTAL = ceil(最后一个单词结束时间 × 30) + 6帧。
- 在镜头计划前收紧,绝不事后。 每个镜头帧都基于收紧后的
words.json,因此事后重新收紧意味着重新调整整个视频的时间。(示例视频跳过了此步骤,保留了10个超过0.25秒的停顿,而参考视频只有约1个。)
- 生成:
-
镜头计划 位于
src/videos/<name>/story.ts(参考示例):SHOTS:{id, a, b, light, set}(单位帧)。大约每 2.2秒(约每8个词) 出现一个新的视觉元素。动作落在关键词上,在其开始前0-3帧。TRANSITIONS:镜头如何进入:dissolve(4-8帧)用于地点或时间变化iris(12-16帧,cx/cy)透过舷窗、窥视孔、镜头或时钟dust(20-26帧)当一股气或云充满画面时flash(4帧)仅在钩子之后 同一场景内无过渡的镜头会自动通过链式相机滑行连接,中心位于边界上(makeCamAt;chain= 18帧,或按每个进入镜头记录的大小来设定滑行距离),因此布景切换在运动过程中完成。绝不硬切。 如果链式边界移动超过约1100像素或转向超过90°,则改用dissolve。
CAMS:每个镜头的关键帧{f, dur?, dissolve?, s: f => dolly(pos, look, pushK(f, f0, rate, punch?), {fov, roll})}。相机规则(references/studies/camera.md,基于12部30fps短片):- 帧预算:15-20%静止,~45%蠕动,~33%滑行(3-30像素/帧,宽度540时),≤3%快速。
- 大约每1.4秒一个新的取景构图。大部分是小幅重构图:将关键帧包装为
withReframes(CAMS, ENDS),每个长定格镜头每42帧进行一次14帧的、±5%主体距离的侧向平移,同时保持该关键帧自身的推进。标记有意的静止镜头为hold: true。每分钟约4次快速移动:峰值35-55像素/帧(上限70),持续20-34帧。 - 每10秒约7次移动,大部分是小而慢的移动(峰值约10像素/帧,移动80-150像素)。使用
withNudges(CAMS, [[shot, baseKeyF, at, dur, ±0.05-0.07], ...])在长时间定格镜头内进行小幅侧向重构图(它们保持该关键帧的推进),然后withReframes填充剩余部分。示例经过此重新关键帧处理后的相机,在每类指标上都在参考范围内(静止19.5/蠕动49.9/滑行27.1/快速3.5%,每10秒7.4次移动;见studies/camera.md)。 - 在静态图上检查每次轻微移动的方向。 向...
…
[此为截断内容,继续翻译]