photo-slideshow について
photo-slideshowは、写真フォルダーからMP4動画を作るスキルです。思い出の振り返りや旅行のモンタージュに向けて、写真ごとに異なる決定論的なKen Burns効果、縦横比の混在への対応、音楽に合わせた切り替えを扱います。
主な機能
- 01写真ごとに異なるKen Burns効果
- 02再現性のある決定論的アニメーション
- 03写真間のトランジション
- 04音楽のビートに合わせた切り替え
- 05異なる縦横比をぼかし背景で処理
- 06フォルダーマニフェストから動画生成
活用シーン
- 01結婚式や旅行の記録動画を作成
- 02音楽付きの年間振り返りスライドショー
- 03縦写真と横写真が混在するセットを動画化
- 04同じテンプレートで複数フォルダーを一括レンダリング
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ iart-ai/ ecommerce-video-skills --skill photo-slideshowインストール後、Claude Code で/photo-slideshowを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/aspect-and-layout.mdken-burns.mdtemplating-folder.mdtiming-and-audio.md詳細
スライドショー動画
写真のセットを、ただ並べただけではなく、撮影したように感じられる完成動画に仕上げます。各静止画に穏やかな動き(ケン・バーンズ効果)を加え、音楽に合わせてスライドを切り替え、形の異なる写真を自然に扱い、フォルダー全体を読み込んで、どんな画像セットにも同じテンプレートを適用できるようにするのがポイントです。
使用する場面
- 写真フォルダーから完成した MP4 を 1 本作成する(主な用途)。
- 「思い出」、振り返り、1 年のまとめ、結婚式や旅行の音楽付きモンタージュ。
- 動き、トランジション、テンポの同期が必要な静止画セット全般。
スライドショーをプロらしく見せるための唯一のルール
同じ動きをするスライドを 2 枚続けてはいけません。 素人っぽいスライドショーだとすぐにわかるのは、すべての写真が同じようにゆっくりズームインしていることです。写真ごとに移動方向、ズームイン/アウト、開始時の拡大率を変えます。ただし、再レンダリングした結果が同じになり、フレームベースのレンダラーが安定するよう、インデックスをシードにした決定的な動きにしてください。
// seeded PRNG → same photo always gets the same motion, but each photo differs
const mulberry32 = (a) => () => {
a |= 0; a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
写真ごとのケン・バーンズ効果
各スライドには、少数の動きのパターンから 1 つを選び、繰り返しを避けながら全体にリズムを持たせます。scale と translate を同時にアニメーションさせます。width/height は再レイアウトやちらつきが発生するため、アニメーションさせないでください。
| 調整項目 | 範囲 | 理由 |
|---|---|---|
| ズーム | イン 1.0→1.12 またはアウト 1.12→1.0 | 控えめにします。約 1.15 を超えると急激なズームに感じられます |
| パン | 左/右/上/下/斜めのいずれか | 方向はシードで選び、すべて同じ方向にならないようにします |
| イージング | easeInOutSine | ゆっくりと途切れず、ケン・バーンズ効果に急な加速はありません |
| アンカー | 被写体の方向に transform-origin を設定 | パン中も顔や地平線がフレーム内に収まります |
import { useCurrentFrame, interpolate, Easing } from "remotion";
const frame = useCurrentFrame(); // the ONLY clock
const p = interpolate(frame, [0, durInFrames], [0, 1], {
extrapolateRight: "clamp", easing: Easing.inOut(Easing.sin),
});
const scale = startScale + (endScale - startScale) * p; // e.g. 1.0 → 1.10
const x = panX * p, y = panY * p; // px, sign from seed
// style: transform: `scale(${scale}) translate(${x}px, ${y}px)`
パンによって空の端が見えないよう、開始時の拡大率は常に 1.05 以上にしてください。被写体を考慮したアンカー設定を備えた、シードを使う <KenBurnsImage> コンポーネントの全容は references/ken-burns.md を参照してください。
写真間のトランジション
トランジションは1 種類だけ選び、全体を通して統一してください。ワイプ、回転、キューブを混ぜると、いかにも「テンプレート」という印象になります。0.4~0.6 秒のクロスフェードが、無難で時代を問わない標準設定です。旅行の写真には、ソフトなプッシュも適しています。拍に合わせて切り替える場合を除き、切り替えが唐突にならないよう、前後のスライドを重ねてください。
import { TransitionSeries, linearTiming } from "@remotion/transitions";
import { fade } from "@remotion/transitions/fade";
// <TransitionSeries.Sequence durationInFrames={hold}> ...slide... </Sequence>
// <TransitionSeries.Transition timing={linearTiming({durationInFrames: 15})} presentation={fade()} />
ビート/音楽に同期したスライド切り替え
スライドショーの良し悪しはテンポで決まります。まず曲を決めてビートのタイムスタンプを取得し、それに合わせて各スライドの切り替え位置を調整します。ビート検出は一度だけオフラインで行い、その結果を props に埋め込んでください。フレームごとに音声を解析してはいけません。
# extract beat onset times (seconds) with librosa, write to beats.json
python3 -c "import librosa,json,sys; y,sr=librosa.load(sys.argv[1]); \
_,b=librosa.beat.beat_track(y=y,sr=sr); \
json.dump([float(t) for t in librosa.frames_to_time(b,sr=sr)], open('beats.json','w'))" song.mp3
次に、ビートの秒数をフレーム番号に変換し、スライドの境界を割り当てます。各写真は音楽の単位に合わせて表示します(写真の場合、2 拍ごとや 4 拍ごとが一般的です。毎拍では慌ただしくなりすぎます)。拍数からフレーム数への計算、リズムに基づくスライドの割り当て、音声解析を使わない代替方法(固定 BPM のグリッド)については、references/timing-and-audio.md を参照してください。
アスペクト比が混在する写真
実際のフォルダーには、縦向きのスマートフォン写真と横向きのカメラ写真が混在しています。引き伸ばしは絶対に避けてください。歪みは許されません。優先度の高い順に、次の 3 つの方法があります。
| 方法 | 見た目 | 使用する場面 |
|---|---|---|
| ぼかしたパッド | 写真をフレーム内に収め、その拡大してぼかしたコピーで余白を埋める | 向きが混在する場合。デフォルトの選択肢 |
| 塗りつぶし(カバー) | 写真でフレームを埋め、端を切り取る | 写真の形がフレームとおおむね合っている場合 |
| 全体表示(レターボックス) | 写真全体を単色またはグラデーションの背景上に表示する | すべての画素が重要な場合(文書、アート作品) |
ぼかしたパッドは、被写体を切り取らずにフレームを埋められるため、現在の標準的な方法です。同じソースを 2 回レンダリングします。背面に cover でぼかしたレイヤーを置き、その上に鮮明な写真を contain で表示します。コンポーネントと向きの判定ロジックについては、references/aspect-and-layout.md を参照してください。
キャプション、日付、イントロ/アウトロ
- キャプション/日付:ファイル名、EXIF、またはサイドカー JSON から読み込みます。画面下部の固定された領域に配置し、スライドが落ち着いてから(約 0.3 秒後)フェードインし、スライドが切り替わる前にフェードアウトさせます。
- イントロカード:タイトルと日付の範囲を表示し、最初の写真の前に 1.5~2 秒間保持します。
- アウトロカード:締めのメッセージ、または連絡先/CTA を表示し、写真の途中で黒にフェードして終わるのではなく、意図を持って動画を締めくくれるよう 2~3 秒間保持します。
フォルダーをテンプレート化して動画にする
ここが成果です。フォルダーをコンポジションに指定し、ファイル一覧からタイムラインを導き出して、一度レンダリングします。写真に関する情報はハードコードされていません。枚数、順序、キャプション、表示時間のすべてがデータから決まります。
// data is a prop: [{ src, caption?, date? }]; the timeline is computed, not authored
export const Slideshow = ({ photos, beats, music }) => { /* maps photos → slides */ };
# build the manifest from a folder (sorted, with EXIF dates), then render
node scripts/build-manifest.mjs ./photos > photos.json
npx remotion render Slideshow out/slideshow.mp4 --props=photos.json
色、フォント、トランジション、スライドの表示時間は1つのテーマオブジェクトにまとめて、すべてのレンダリングで一貫性を保ち、写真だけを変更できるようにします。マニフェストビルダー(並べ替え、EXIF 日付、重複除去)、データ駆動型の完全な<Slideshow>、React が使えない場合の FFmpeg のみの方法については、references/templating-folder.mdを参照してください。
出力チェックリスト
- すべての写真の動きはインデックスをシードにしており、方向やズームに変化があり、同じ動きはありません。
- すべての動きは
useCurrentFrame()の純粋関数です。開始時のスケールは1.05以上です。 - 一貫したトランジション(デフォルトはクロスフェード)を使い、ハードカットではなく、スライドを重ねます。
- スライドの切り替えはビートに合わせ、写真は1拍ごとではなく、少なくとも1音楽単位の間表示します。
- 縦横比が異なる写真にはぼかした背景を付け、引き伸ばされないようにします。
- キャプションと日付は固定ゾーンに配置し、イントロカードとアウトロカードで写真セットを挟みます。
- 写真、キャプション、表示時間はフォルダーのマニフェストから取得します。テンプレートは1つで、どの写真セットにも使えます。
納品と検証(レンダリングした静止画 → MP4)
同梱ヘルパー(
scripts/):scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.pngで静止画を一覧にまとめ、scripts/probe-mp4.sh out.mp4 [WxH] [fps]でエンコード結果を検証します。scripts/README.mdを参照してください。
これは重量級のタスクです。成果物はライブページではなくMP4です。スライドショーにはユーザーの写真が入ったフォルダー全体を読み込むため、検証では主に、すべての写真が実際に読み込まれ、歪みなくフレーミングされているかを確認します。
出力要件:
- コンポジションを登録したRemotionプロジェクト(
<Composition>+zodのschema+defaultProps)。Ken Burnsのスケール/パン、トランジション、ビートに合わせたタイミングはすべてuseCurrentFrame()の純粋関数にします(CSSトランジション、タイマー、Date.now()は使わず、動きのシードにはMath.random()ではなくインデックスを使います)。 staticFile()で写真を読み込み、delayRender/continueRenderを使って、各画像(およびキャプションのフォント)がフレームのレンダリング前に読み込まれるようにします。そうしないと、スライドが空白のまま表示されます。表示時間はデータによって変わるため、手作業で設定せず、写真の枚数からcalculateMetadataで計算します。- 成果物=レンダリング済みの
out/*.mp4とプロジェクト(フォルダーが変わったら再レンダリングします)。
検証の流れ — 静止画をレンダリング → 確認 → エンコード。 まず負荷の小さいPNGを作成し、写真の見せ方が適切になってから動画を一度だけ作成します。
# Frame-exact stills WITH THE MANIFEST YOU'LL SHIP (real photo paths), not defaultProps
npx remotion still Slideshow out/f-intro.png --frame=15 --props=photos.json # intro card
npx remotion still Slideshow out/f-mid.png --frame=200 --props=photos.json # a mid slide mid-Ken-Burns
npx remotion still Slideshow out/f-outro.png --frame=500 --props=photos.json # outro card
# end frame = durationInFrames - 1 (npx remotion compositions reads it)
各PNGを確認し、忠実度(各スライドに正しい写真が使われているか、キャプション/日付のテキストが正しいか、ぼかした背景で縦横比の異なる写真を引き伸ばさずに表示できているか)と、アーティファクト(画像が空白または未読み込み、写真の引き伸ばし/歪み、パンによって空の端が見える、開始時のスケールが1.05未満、キャプションが下部3分の1のセーフゾーンからはみ出している、縦横比の誤り/レターボックス)を確認します。
# Only after stills are clean:
npx remotion render Slideshow out/slideshow.mp4 --props=photos.json
npx remotion render Slideshow out/slideshow.gif --props=photos.json --codec=gif # README first-screen proof
バッチ処理(1つのテンプレートで複数のフォルダー): 同じテンプレートを複数の写真セットに適用する場合は、残りをバッチレンダリングする前に、代表となる1つのフォルダーのマニフェストを静止画で検証します。空白画像や縦横比の引き伸ばしのバグをN回ではなく、一度で見つけられます。
完了前に確認すること:
- イントロ/中盤/アウトロの静止画が問題なくレンダリングされ、エラーがなく、写真が実際に読み込まれている(空白ではない)こと。
- キャプション/日付が正しく、固定の下部3分の1のゾーンに配置されていること。縦横比の異なる写真にはぼかした背景が付き、引き伸ばされていないこと。パンによって空の端が見えないこと。
- すべての動きがフレーム駆動で、インデックスをシードにしていること。CSSトランジション/タイマー/
Date.now()/Math.random()を使っていないこと。 - 納品するフォルダーのマニフェストが正しくレンダリングされること。
defaultPropsだけでは不十分です。 - MP4全体がエンコードされ、再生できること。(任意)README用にGIFをレンダリングします。
リファレンスファイル
references/ken-burns.md— 完全にシードされた<KenBurnsImage>Remotion コンポーネント。mulberry32(index)で選択する写真ごとのモーション語彙、被写体を考慮したtransform-origin、安全な開始スケールの計算、FFmpeg のzoompanによる同等の処理を解説しています。references/timing-and-audio.md— librosa によるビート検出、ビート秒からフレームへの変換、音楽単位でのスライド割り当て、音声分析を行わない場合の BPM グリッドによるフォールバック、写真ごとのペーシング予算を解説しています。references/aspect-and-layout.md— 向きの検出、コンポーネントとしてのぼかしパッド/カバー/コンテインの各レイアウト、16:9、9:16、1:1 のセーフゾーンマップを解説しています。これにより、1 つのマスターからすべてのプラットフォーム向けにリフレームできます。references/templating-folder.md— フォルダーからマニフェストを生成するビルダー(自然順ソート、EXIF 撮影日時、ファイル名からのキャプション生成)、イントロ/アウトロカードを含むデータ駆動型の完全な<Slideshow>、純粋な FFmpeg スライドショーのパイプラインを解説しています。