1实现原理 · 为什么它能做到
唯一铁律:没有两张照片可以用同一种运动,且这种差异必须是由索引播种的确定性伪随机。
**No two slides may move the same way.** The instant-giveaway of an amateur slideshow is every photo doing the identical slow zoom-in. Vary direction, zoom in vs. out, and start scale per photo — but make it **deterministic** (seeded by index), so re-renders are identical and a frame-based renderer stays stable.
播种机制是 mulberry32 + 索引播种:同一张照片永远同一运动,相邻照片不同。
`mulberry32` is a tiny, fast, well-distributed 32-bit PRNG. Seed it with the photo index so photo 0 always gets the same move, but adjacent photos differ.
Ken Burns 只动 transform,且起始 scale ≥1.05,保证平移不露白边。
Always start scale ≥ `1.05` so a pan never exposes an empty edge.
混合比例照片用 blurred-pad(模糊底 + contain 前景原图),绝不拉伸。
Blurred-pad wins for mixed folders: it fills the frame, never crops the subject, and the soft blur reads as intentional depth rather than empty bars.
配乐先行:离线测一次 beat 烘进 props,绝不在渲染时逐帧做音频分析。
The rule: **lock the music first, derive slide boundaries from it.** Detect beats once, offline, and bake them into props — audio analysis per frame is non-deterministic and slow.
文件夹 → manifest:自然排序、EXIF 拍摄时间、文件名提取 caption,全部派生自数据。
// natural sort so photo2 < photo10 (default lexical sort breaks this)
图片必须加载门控(staticFile + delayRender/continueRender),否则某帧会空白弹出。
- Photos loaded via `staticFile()`, gated with `delayRender`/`continueRender` so each image (and any caption font) is present before its frame renders — otherwise a slide pops in blank.
验证环重点是「每张照片是否真的加载并正确重构图」,而不是画面美感。
A slideshow ingests a whole folder of user photos — the verify pass is mostly *did every photo actually load and reframe without distortion*.
转场只选一种(默认 0.4–0.6s 交叉溶解),且要重叠而不是硬切,除非切在拍点。
Pick **one** transition and keep it consistent — mixing wipes, spins and cubes screams "template."
2核心能力
3外部依赖
| Kind | Dependency / Endpoint |
|---|---|
| package | remotion(frame/interpolate/Easing/Img/Audio/Sequence) |
| package | @remotion/transitions(TransitionSeries + fade) |
| package | sharp + exifr(读图像尺寸与 EXIF DateTimeOriginal) |
| package | librosa + soundfile(pip 安装;离线测 beat 与 onset) |
| cli | ffmpeg(blurred-pad、zoompan、xfade 与仓库共享 contact-sheet.sh) |
| network | iart.ai(reference 尾部推广链接,具 utm 参数;仅读者点击才请求) |
4管控建议与判定依据
- scripts/: 执行本地脚本或命令: skill 目录内无自带脚本;SKILL.md/reference 指示 agent 执行 `node scripts/build-manifest.
- 网络端点: 声明外部网络访问: 执行期无业务 API 调用;照片与音乐来自用户本地文件。
- 本地工作目录: 文件读写与工件输出: 在用户工程内写 `photos.json`/`props.json`、`beats.json`、`out/<name>.mp4`、`out/*.gif
5第二遍独立确认
- [discrepancy] 图片加载门控口径是否实现一致 — SKILL.md 要求 staticFile() + delayRender/continueRender 门控,但 templating-folder.md 的 Slideshow 直接把 manifest 里的文件系统路径(join(dir, f))交给 Img/KenBurnsImage,既未用 staticFile 也无 delayRender——照抄 reference 会拿不到 Remotion public/ 之外的资源,且仍可能出现空白帧。
- [discrepancy] 被引用的「打包脚本」是否真的存在 — reference 以 `scripts/build-manifest.mjs` 文件名给出源码、SKILL.md 也写 `node scripts/build-manifest.mjs ./photos`,但仓库根 scripts/ 只有 seek-shot.sh / contact-sheet.sh / probe-mp4.sh,没有 build-manifest.mjs;`python beats.py` 同理(脚本内联在文档里)。使用前必须自行落盘。
- [ok] 起始 scale 与运动确定性口径 — SKILL.md 的 start scale ≥ 1.05、seeded by index 与 ken-burns.md 的 `const base = 1.05;`、`mulberry32(index * 2654435761)` 完全一致。
- [ok] 适配策略与判据是否自洽 — aspect-and-layout.md 的三策略表与 fitStrategy 阈值(<0.15 → cover)一致;SKILL.md 的 never stretch 与 blurred-pad 默认选择一致。
- [ok] 换片节奏的量化声明 — timing-and-audio.md 的每张 3–6s、低于约 2.5s 会显仓促、每 2/4 拍换片,与 SKILL.md 的 photos hold ≥1 musical unit, not every beat 一致。
- [unlocatable] 隐私面:照片元数据是否被提示处理 — manifest 读取 EXIF DateTimeOriginal 并把日期上屏,但全文未讨论照片 GPS/相机信息的处理与输出视频的元数据策略;源码内定位不到任何隐私处理声明,记 unlocatable(能力缺口,不作为档位依据)。
6结论
58ffd9961bdf764e…