はじめに
手元には、ブログ取材で撮りためた大量の写真があります。
これをブログだけに眠らせず、YouTubeの長尺動画やSNSのショート動画として残せないか。そこから始まったのが、今回の Photo Story Video(写真アーカイブ動画制作OS) です。
私はプログラマーではありません。最初からコードを書けたわけでも、動画生成の仕組みに詳しかったわけでもありません。それでも、AIと会話しながら仕様を決め、Cursorに実装してもらい、Codexにレビューと実写真テストを任せることで、次のところまで到達できました。
- 写真をFinderからドラッグ&ドロップ
- タイトル、導入文、写真ごとの文章を編集
- 並び順をドラッグで変更
- プロジェクトを保存・再読込
- 同じ編集データからYouTube用の横型MP4を生成
- 同じ編集データからSNS用の縦型MP4を生成
- iPhoneのJPEG・HEICにも対応
- 元写真を変更しない
この記事では、完成した機能だけでなく、どうやって非エンジニアが開発を前へ進めたか、そして実写真テストで見つかった失敗も含めてまとめます。
最初の目的は「数万枚の写真を検索資産にする」こと
当初は、iCloudにある写真をスライドショーにしてYouTubeへ保存したい、という素朴なアイデアでした。
ただ写真を流すだけではなく、1話題につき1本の動画にして、タイトルや短い説明を添える。そうすれば過去の写真が、ブログ記事とは別の入口を持つ検索資産になります。
考えた運用は次のようなものです。
ここで重要だったのは、横型動画を作った後に切り抜いて縦型にするのではなく、同じ編集データから、それぞれの画面サイズに合わせて再描画することでした。
CapCut運用ではなく専用アプリを選んだ理由
最初はCapCutを使う案も考えました。数本を手作業で作るなら、とても便利です。
しかし、写真アーカイブを何十本、何百本と作る場合、毎回同じ作業が発生します。
| 作業 | 汎用動画編集ソフト | 今回の専用アプリ |
|---|---|---|
| 写真の読み込み | 毎回操作 | 固定手順 |
| 横型と縦型 | 別々に編集しやすい | 同じデータから生成 |
| テキスト配置 | 手動調整が中心 | 固定テンプレート |
| 再生成 | プロジェクト依存 |
project.jsonから再生成 |
| 大量運用 | 操作が積み重なる | ルーティン化しやすい |
そこで、「何でもできる動画編集ソフト」ではなく、写真スライド動画だけを速く作る小さな専用アプリに絞りました。
いきなり実装せず、まず仕様を文章にした
開発の最初に行ったのはコーディングではありません。以下をMarkdownで決めていきました。
- 写真をどう管理するか
- カードの種類をどう分けるか
- 横型と縦型の関係をどうするか
- 元写真をどう守るか
- どこまでをMVPにするか
動画は次の4種類のカードで構成します。
| カード | 役割 |
|---|---|
| リード | タイトルと導入文 |
| 写真 | 写真、見出し、本文 |
| 基本情報 | 住所や営業時間など(MVPではGUI未実装) |
| エンディング | 終了メッセージと注意書き |
カードの並び順は、保存データ内の cards 配列を唯一の正本にしました。
{
"schema_version": 1,
"photos": [],
"cards": [
{ "card_type": "lead" },
{ "card_type": "photo" },
{ "card_type": "photo" },
{ "card_type": "ending" }
]
}
実際の保存データにはID、表示時間、文章、写真への相対パスなどが入ります。ここでは構造だけを簡略化しています。
AIとの役割分担
今回うまく機能したのは、AIに全部を一度に頼まず、役割を分けたことでした。
人間が担当したのは、「何を作りたいか」「どこまで必要か」「実際に使えるか」の判断です。
CursorにはIssue単位で実装範囲を限定して依頼し、作業後には変更ファイル、テスト結果、コミットハッシュ、未解決事項を報告してもらいました。Codexには、その報告をうのみにせず、コード差分と実写真で再確認してもらいました。
この往復により、非エンジニアでも「何が終わり、何がまだなのか」を見失いにくくなりました。
MVPの技術構成
技術はできるだけ小さくしました。
| 役割 | 採用技術 |
|---|---|
| GUI | PySide6 / Qt Widgets |
| 画像読み込み・描画 | Pillow |
| HEIC / HEIF | pillow-heif |
| 動画生成 | FFmpeg |
| 保存 | JSON |
| テスト | pytest |
アプリ内では、GUI、保存、写真取り込み、描画、FFmpeg実行を分離しています。
app/
├── main.py # GUI起動
├── cli.py # コマンドライン再生成
├── gui/ # 画面操作
└── core/ # 保存・取り込み・描画・FFmpeg
出力形式は、現時点で次のとおりです。
| 用途 | 解像度 | 形式 |
|---|---|---|
| YouTube長尺 | 1920×1080 | H.264 High / yuv420p / 30fps |
| SNSショート | 1080×1920 | H.264 High / yuv420p / 30fps |
元写真を絶対に壊さない設計
写真アーカイブで最も怖いのは、元写真を上書きしたり、移動したりすることです。
そこで、読み込んだ写真はプロジェクト内へ作業用コピーを作り、元ファイルは変更しない設計にしました。保存データには元写真の絶対パスを残さず、プロジェクト内の相対パスを保存します。
動画生成の前後でSHA-256を比較し、元写真と作業用コピーが一致することも確認しました。
実写真で初めて見つかった「iPhone JPEGなのに読めない」問題
自動テストが47件通った段階で、実際のiPhone 16の写真を読み込ませました。すると、拡張子は .jpeg なのに「未対応形式」として拒否されました。
原因は、最近のiPhone写真が深度情報用の補助画像を含むことでした。Pillowで開くと、通常の JPEG ではなく MPO と判定される写真があります。
ファイル名: IMG_7813.jpeg
Pillowの判定: MPO
フレーム数: 2
撮影機種: iPhone 16
生成画像だけを使ったテストでは、この問題を発見できませんでした。
修正では、次の条件を満たす場合だけMPOをJPEGとして受け入れました。
- 拡張子が
.jpgまたは.jpeg - ファイル先頭がJPEGのSOIシグネチャ
FF D8 - 主画像(第1フレーム)だけを表示・動画生成に使う
- 補助画像を別カードにしない
-
.mpoファイルを無条件には許可しない
実写真3枚で再検証した結果は次のとおりです。
| 確認項目 | 結果 |
|---|---|
| 写真取り込み | 3枚成功 |
| 保存・再読込 | 成功 |
| 横型MP4 | 1920×1080で生成成功 |
| 縦型MP4 | 1080×1920で生成成功 |
| EXIF回転 | 正立表示 |
| 元写真のSHA-256 | 前後一致 |
| 自動テスト | 50 passed |
この経験から、テストが全部通ることと、実際に使えることは別だと実感しました。自分が普段使う写真で試す工程は欠かせません。
現在の操作方法
現時点では、リポジトリを開いて次のコマンドで起動します。
cd /Users/NakazonoShingo/Cursor/photo-story-video
python3 -m app.main
起動後の流れはシンプルです。
- 新規プロジェクト用の空フォルダを選ぶ
- Finderから写真をドロップする
- タイトル、導入文、写真ごとの文章を入力する
- 写真をドラッグして並べ替える
- 保存する
- 横型動画、縦型動画を書き出す
次はmacOSの .app ランチャーを用意し、ターミナルを開かなくても起動できるようにする予定です。
まだ完成していないこと
MVPなので、以下は未完成です。
- BGMとナレーション
- トランジション
- 基本情報カードのGUI編集
- 複数のデザインテンプレート
- 15〜60秒への自動尺調整
- YouTube・SNSへの自動投稿
- AIによる文章生成
ただし、最も大事な「写真を選び、文章を書き、同じデータから横型と縦型を生成する」ところまでは動いています。
非エンジニアとして学んだこと
1. AIには完成品ではなく、次の小さなゴールを渡す
「動画アプリを全部作って」ではなく、「写真データモデルだけ」「横型出力だけ」「実写真の不具合だけ」のように範囲を区切ると、結果を確認しやすくなりました。
2. 作業報告にコミットハッシュと未実施事項を含める
AIが何をしたかだけでなく、何をしていないかも毎回明記すると、勝手に範囲が広がるのを防げます。
3. 実素材テストを早めに行う
今回のMPO問題は、生成JPEGでは見つかりませんでした。iPhone写真を使うアプリなら、iPhone写真で確認する必要があります。
4. 人間は「使えるかどうか」の責任を持つ
コードを書けなくても、「この操作は面倒」「この写真が読めないなら使えない」という判断はできます。むしろ、それこそがプロダクトを前に進める重要な役割でした。
まとめ
今回作ったのは、派手な万能動画編集ソフトではありません。写真アーカイブを継続的に動画へ変えるための、小さな専用道具です。
非エンジニアでも、目的を言葉にし、仕様を小さく分け、AIに実装とレビューを分担させ、実素材で検証すれば、動くデスクトップアプリまで到達できました。
AIがコードを書く時代でも、最後に必要なのは「何を作るか」と「本当に使えるか」を判断する人間です。
これからは、起動方法の簡略化、BGM、テンプレート追加、実運用10本を目標に改善していきます。
開発時点の環境
- macOS
- Python 3.11
- PySide6
- Pillow / pillow-heif
- FFmpeg
- Cursor
- Codex
- pytest: 50 tests passed
はじめに
手元には、ブログ取材で撮りためた大量の写真があります。
これをブログだけに眠らせず、YouTubeの長尺動画やSNSのショート動画として残せないか。そこから始まったのが、今回の Photo Story Video(写真アーカイブ動画制作OS) です。
私はプログラマーではありません。最初からコードを書けたわけでも、動画生成の仕組みに詳しかったわけでもありません。それでも、AIと会話しながら仕様を決め、Cursorに実装してもらい、Codexにレビューと実写真テストを任せることで、次のところまで到達できました。
- 写真をFinderからドラッグ&ドロップ
- タイトル、導入文、写真ごとの文章を編集
- 並び順をドラッグで変更
- プロジェクトを保存・再読込
- 同じ編集データからYouTube用の横型MP4を生成
- 同じ編集データからSNS用の縦型MP4を生成
- iPhoneのJPEG・HEICにも対応
- 元写真を変更しない
この記事では、完成した機能だけでなく、どうやって非エンジニアが開発を前へ進めたか、そして実写真テストで見つかった失敗も含めてまとめます。
最初の目的は「数万枚の写真を検索資産にする」こと
当初は、iCloudにある写真をスライドショーにしてYouTubeへ保存したい、という素朴なアイデアでした。
ただ写真を流すだけではなく、1話題につき1本の動画にして、タイトルや短い説明を添える。そうすれば過去の写真が、ブログ記事とは別の入口を持つ検索資産になります。
考えた運用は次のようなものです。
ここで重要だったのは、横型動画を作った後に切り抜いて縦型にするのではなく、同じ編集データから、それぞれの画面サイズに合わせて再描画することでした。
CapCut運用ではなく専用アプリを選んだ理由
最初はCapCutを使う案も考えました。数本を手作業で作るなら、とても便利です。
しかし、写真アーカイブを何十本、何百本と作る場合、毎回同じ作業が発生します。
| 作業 | 汎用動画編集ソフト | 今回の専用アプリ |
|---|---|---|
| 写真の読み込み | 毎回操作 | 固定手順 |
| 横型と縦型 | 別々に編集しやすい | 同じデータから生成 |
| テキスト配置 | 手動調整が中心 | 固定テンプレート |
| 再生成 | プロジェクト依存 |
project.jsonから再生成 |
| 大量運用 | 操作が積み重なる | ルーティン化しやすい |
そこで、「何でもできる動画編集ソフト」ではなく、写真スライド動画だけを速く作る小さな専用アプリに絞りました。
いきなり実装せず、まず仕様を文章にした
開発の最初に行ったのはコーディングではありません。以下をMarkdownで決めていきました。
- 写真をどう管理するか
- カードの種類をどう分けるか
- 横型と縦型の関係をどうするか
- 元写真をどう守るか
- どこまでをMVPにするか
動画は次の4種類のカードで構成します。
| カード | 役割 |
|---|---|
| リード | タイトルと導入文 |
| 写真 | 写真、見出し、本文 |
| 基本情報 | 住所や営業時間など(MVPではGUI未実装) |
| エンディング | 終了メッセージと注意書き |
カードの並び順は、保存データ内の cards 配列を唯一の正本にしました。
{
"schema_version": 1,
"photos": [],
"cards": [
{ "card_type": "lead" },
{ "card_type": "photo" },
{ "card_type": "photo" },
{ "card_type": "ending" }
]
}
実際の保存データにはID、表示時間、文章、写真への相対パスなどが入ります。ここでは構造だけを簡略化しています。
AIとの役割分担
今回うまく機能したのは、AIに全部を一度に頼まず、役割を分けたことでした。
人間が担当したのは、「何を作りたいか」「どこまで必要か」「実際に使えるか」の判断です。
CursorにはIssue単位で実装範囲を限定して依頼し、作業後には変更ファイル、テスト結果、コミットハッシュ、未解決事項を報告してもらいました。Codexには、その報告をうのみにせず、コード差分と実写真で再確認してもらいました。
この往復により、非エンジニアでも「何が終わり、何がまだなのか」を見失いにくくなりました。
MVPの技術構成
技術はできるだけ小さくしました。
| 役割 | 採用技術 |
|---|---|
| GUI | PySide6 / Qt Widgets |
| 画像読み込み・描画 | Pillow |
| HEIC / HEIF | pillow-heif |
| 動画生成 | FFmpeg |
| 保存 | JSON |
| テスト | pytest |
アプリ内では、GUI、保存、写真取り込み、描画、FFmpeg実行を分離しています。
app/
├── main.py # GUI起動
├── cli.py # コマンドライン再生成
├── gui/ # 画面操作
└── core/ # 保存・取り込み・描画・FFmpeg
出力形式は、現時点で次のとおりです。
| 用途 | 解像度 | 形式 |
|---|---|---|
| YouTube長尺 | 1920×1080 | H.264 High / yuv420p / 30fps |
| SNSショート | 1080×1920 | H.264 High / yuv420p / 30fps |
元写真を絶対に壊さない設計
写真アーカイブで最も怖いのは、元写真を上書きしたり、移動したりすることです。
そこで、読み込んだ写真はプロジェクト内へ作業用コピーを作り、元ファイルは変更しない設計にしました。保存データには元写真の絶対パスを残さず、プロジェクト内の相対パスを保存します。
動画生成の前後でSHA-256を比較し、元写真と作業用コピーが一致することも確認しました。
実写真で初めて見つかった「iPhone JPEGなのに読めない」問題
自動テストが47件通った段階で、実際のiPhone 16の写真を読み込ませました。すると、拡張子は .jpeg なのに「未対応形式」として拒否されました。
原因は、最近のiPhone写真が深度情報用の補助画像を含むことでした。Pillowで開くと、通常の JPEG ではなく MPO と判定される写真があります。
ファイル名: IMG_7813.jpeg
Pillowの判定: MPO
フレーム数: 2
撮影機種: iPhone 16
生成画像だけを使ったテストでは、この問題を発見できませんでした。
修正では、次の条件を満たす場合だけMPOをJPEGとして受け入れました。
- 拡張子が
.jpgまたは.jpeg - ファイル先頭がJPEGのSOIシグネチャ
FF D8 - 主画像(第1フレーム)だけを表示・動画生成に使う
- 補助画像を別カードにしない
-
.mpoファイルを無条件には許可しない
実写真3枚で再検証した結果は次のとおりです。
| 確認項目 | 結果 |
|---|---|
| 写真取り込み | 3枚成功 |
| 保存・再読込 | 成功 |
| 横型MP4 | 1920×1080で生成成功 |
| 縦型MP4 | 1080×1920で生成成功 |
| EXIF回転 | 正立表示 |
| 元写真のSHA-256 | 前後一致 |
| 自動テスト | 50 passed |
この経験から、テストが全部通ることと、実際に使えることは別だと実感しました。自分が普段使う写真で試す工程は欠かせません。
現在の操作方法
現時点では、リポジトリを開いて次のコマンドで起動します。
cd ~/Cursor/photo-story-video
python3 -m app.main
起動後の流れはシンプルです。
- 新規プロジェクト用の空フォルダを選ぶ
- Finderから写真をドロップする
- タイトル、導入文、写真ごとの文章を入力する
- 写真をドラッグして並べ替える
- 保存する
- 横型動画、縦型動画を書き出す
次はmacOSの .app ランチャーを用意し、ターミナルを開かなくても起動できるようにする予定です。
まだ完成していないこと
MVPなので、以下は未完成です。
- BGMとナレーション
- トランジション
- 基本情報カードのGUI編集
- 複数のデザインテンプレート
- 15〜60秒への自動尺調整
- YouTube・SNSへの自動投稿
- AIによる文章生成
ただし、最も大事な「写真を選び、文章を書き、同じデータから横型と縦型を生成する」ところまでは動いています。
非エンジニアとして学んだこと
1. AIには完成品ではなく、次の小さなゴールを渡す
「動画アプリを全部作って」ではなく、「写真データモデルだけ」「横型出力だけ」「実写真の不具合だけ」のように範囲を区切ると、結果を確認しやすくなりました。
2. 作業報告にコミットハッシュと未実施事項を含める
AIが何をしたかだけでなく、何をしていないかも毎回明記すると、勝手に範囲が広がるのを防げます。
3. 実素材テストを早めに行う
今回のMPO問題は、生成JPEGでは見つかりませんでした。iPhone写真を使うアプリなら、iPhone写真で確認する必要があります。
4. 人間は「使えるかどうか」の責任を持つ
コードを書けなくても、「この操作は面倒」「この写真が読めないなら使えない」という判断はできます。むしろ、それこそがプロダクトを前に進める重要な役割でした。
まとめ
今回作ったのは、派手な万能動画編集ソフトではありません。写真アーカイブを継続的に動画へ変えるための、小さな専用道具です。
非エンジニアでも、目的を言葉にし、仕様を小さく分け、AIに実装とレビューを分担させ、実素材で検証すれば、動くデスクトップアプリまで到達できました。
AIがコードを書く時代でも、最後に必要なのは「何を作るか」と「本当に使えるか」を判断する人間です。
これからは、起動方法の簡略化、BGM、テンプレート追加、実運用10本を目標に改善していきます。
開発時点の環境
- macOS
- Python 3.11
- PySide6
- Pillow / pillow-heif
- FFmpeg
- Cursor
- Codex
- pytest: 50 tests passed
