1仕組みと動作原理
唯一の鉄則:同じモーションを使用する写真は2枚と存在してはならず、その差異はインデックスシードによる決定論的疑似乱数でなければならない。
**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.
音楽先行:オフラインで1度ビートを検出して 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 撮影日時、ファイル名からのキャプション抽出をすべてデータから導出。
// 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*.
トランジションは1種類のみ選択し(デフォルトは 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…