はじめに
最近、OpenAI Codexを使ってアプリ開発を進めています。
今回はその実例として、macOSネイティブの動画リファレンス収集アプリ ReferenceCollection を作りました。
GitHub:
https://github.com/k0suk/ReferenceCollection
最新リリース:
https://github.com/k0suk/ReferenceCollection/releases/tag/v1.3.0
この記事は、
- Codexに日本語で相談しながらアプリを作る
- Swift/AppKitの実装を進める
-
yt-dlp/FFmpegを同梱する - Universal DMGを作る
- 実際にDMGから起動して確認する
というところまでやってみた記録です。
注意: 動画の保存は、著作権・各サービスの利用規約・自分が利用できる権利の範囲内で行う前提です。この記事は、macOSアプリ開発と配布検証の記録です。
作ったもの
作ったのは ReferenceCollection というmacOSアプリです。
ざっくり言うと、動画URLを入れて、保存先と画質を選んで、参考動画を整理して保存するためのツールです。
主な機能はこんな感じです。
- 動画URLの入力
- 保存先フォルダの指定
- 最高画質MP4、1080pまで、720pまで、音声のみのプリセット
- 動画タイトルとサムネイルのプレビュー
- クリップボードのURL候補表示
- 複数URLのダウンロードキュー
- 実行中ダウンロードのキャンセル
- ダウンロード履歴の保存
- 履歴の検索と状態フィルタ
- 失敗した項目だけの再ダウンロード
- Finder表示、URLを開く、最新項目の再DL
- YouTube / Vimeo検索用の簡易ブラウザ
- Intel Mac / Apple Silicon Mac両対応のUniversal DMG配布
最初は「URLを入れて保存できればいい」くらいの気持ちでした。
でも実際に使いながら作っていくと、
- 複数本まとめてキューに入れたい
- ダウンロード中にキャンセルしたい
- 失敗したものだけ再実行したい
- 前に保存したものを履歴から探したい
- ブラウザで探した動画URLをそのままアプリに渡したい
みたいな欲がどんどん出てきます。
ここをCodexと会話しながら順番に実装していきました。
Codexにやってもらったこと
今回の開発でCodexに任せたのは、単なるコード生成だけではありません。
実際にはこんな流れで進めました。
- 自分が日本語で「こういう機能がほしい」と伝える
- Codexが既存コードを読む
- 実装方針を整理する
- Swift/AppKitのコードを編集する
- ビルドスクリプトやREADMEも整える
- ローカルでコマンドを実行して検証する
- 結果を見て、さらに修正する
感覚としては、「実装できる開発メモ係」が横にいる感じでした。
たとえば、最初にアプリの基本形を作ったあと、実際に触っていて「キャンセルできないと困る」「履歴から失敗分だけ戻したい」となりました。
そこでCodexに、
実行中のダウンロードをキャンセルできるようにして
待機中のキューは消さないで
失敗した履歴だけ再DLできるようにして
同じURLが重複してキューに入らないようにして
という感じで伝えました。
Codexは ReferenceCollection.swift を読み、Process を保持してキャンセルできるようにしたり、履歴から再DL対象を選ぶ処理を追加したり、READMEとバージョン情報まで更新してくれました。
もちろん、最終的な仕様判断は自分でします。
ただ、実装の手数がかなり減るので、「じゃあ次はここを試そう」と進める速度が上がりました。
できあがった構成
リポジトリ構成はかなりシンプルです。
ReferenceCollection
├── README.md
├── assets/AppIcon.png
├── build_universal_dmg.sh
├── packaging/Info.plist
├── resources/bin/.gitkeep
├── scripts/fetch_engines.sh
└── swift/ReferenceCollection.swift
アプリ本体はSwift/AppKitです。
動画取得は、アプリ内に同梱した yt-dlp と FFmpeg を直接実行する構成にしました。ユーザー側でPython仮想環境を作ったり、別途CLIツールを入れたりしなくていいようにしたかったからです。
.app の中では、次のような場所に実行ファイルを入れています。
Contents/Resources/bin/yt-dlp
Contents/Resources/bin/ffmpeg-arm64
Contents/Resources/bin/ffmpeg-x86_64
実装の中身を少しだけ
AppKitでUIを作る
今回はSwiftUIではなくAppKitで作りました。
理由は、macOSアプリとしてウィンドウ、Finder連携、メニュー、WKWebView、Process 実行あたりを素直に扱いたかったからです。
UI部品はこんな感じで置いています。
private let urlField = NSTextField()
private let saveField = NSTextField()
private let qualityPopup = NSPopUpButton()
private let startButton = NSButton(title: "ダウンロード開始", target: nil, action: nil)
private let addQueueButton = NSButton(title: "キューに追加", target: nil, action: nil)
private let cancelButton = NSButton(title: "キャンセル", target: nil, action: nil)
昔ながらのAppKitですが、こういう単機能macOSツールを作るにはけっこう相性がいいです。
最高画質はMP4に寄せる
画質プリセットは、まずMP4で扱いやすくする方針にしました。
QualityPreset(
title: "最高画質(MP4)",
format: "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/bv*+ba/b",
mergeFormat: "mp4",
needsFFmpeg: true
)
最高画質を狙うと、映像と音声が別ストリームになることがあります。
そこで FFmpeg を使ってMP4にまとめるようにしています。
キャンセルできるようにする
実行中のダウンロードを止めるには、起動した Process をあとから触れる必要があります。
private var activeProcess: Process?
private var isCancellingCurrentDownload = false
ここで大事だったのは、「キャンセルしたら全部消える」ではなく「実行中の1件だけ止めて、待機中のキューは残す」ことでした。
こういう細かい挙動は、実際に使ってみるとかなり効きます。
履歴を再利用できるようにする
履歴はただのログではなく、再ダウンロードの入口として使えるようにしました。
struct DownloadHistoryItem: Codable {
let id: UUID
let title: String
let url: String
let thumbnailURL: String?
let filePath: String?
let saveDirectory: String
let status: String
let date: Date
}
検索や状態フィルタも入れています。
すべて完了失敗取消
さらに「失敗分再DL」では、過去の失敗を全部戻すのではなく、URLごとの最新結果を見て、最新が失敗しているものだけを対象にしています。
過去に失敗していても、そのあと成功しているURLは再DLしない。
こういう地味なところを詰めると、ツールとして使いやすくなります。
アプリ内ブラウザも入れた
ReferenceCollectionには、WKWebView を使った簡易ブラウザも入れています。
private var browserWindow: NSWindow?
private var browserWebView: WKWebView?
動画ページを探して、このURLを使う を押すと、現在のページURLをメイン画面のURL欄に反映できます。
ブラウザで探す、URLをコピーする、アプリに戻る、貼り付ける、という往復を減らしたかったので入れました。
DMG配布までやってみた
今回よかったのは、実装だけで終わらせず、配布物まで作ったことです。
build_universal_dmg.sh では、Apple Silicon向けとIntel Mac向けをそれぞれビルドして、lipo でUniversal binaryにしています。
swiftc -target arm64-apple-macos11.0 -O \
swift/ReferenceCollection.swift \
-framework AppKit \
-framework WebKit \
-o build/ReferenceCollection-arm64
swiftc -target x86_64-apple-macos11.0 -O \
swift/ReferenceCollection.swift \
-framework AppKit \
-framework WebKit \
-o build/ReferenceCollection-x86_64
lipo -create \
build/ReferenceCollection-arm64 \
build/ReferenceCollection-x86_64 \
-output ReferenceCollection.app/Contents/MacOS/ReferenceCollection
そのあと、ad-hoc署名してDMGを作ります。
codesign --force --deep --sign - ReferenceCollection.app
hdiutil create -volname ReferenceCollection -srcfolder build/dmg-stage -ov -format UDZO ReferenceCollection-universal.dmg
未notarizeなので、初回起動時にmacOSに止められる場合があります。
今のところは右クリックから「開く」で起動する運用です。
リリース履歴
GitHub Releasesには、次の3つを置いています。
| バージョン | 内容 |
|---|---|
| v1.0.0 | 初期版。Swift/AppKitアプリ、MP4保存、タイトル/サムネイルプレビュー、Universal launcher |
| v1.1.0 |
yt-dlp/FFmpeg同梱、キュー、履歴、更新チェック、クリップボードURL候補、簡易ブラウザなどを追加 |
| v1.3.0 | 履歴検索、履歴フィルタ、表示中履歴への操作、キャンセル、失敗項目の再DLを追加 |
v1.3.0では、ReferenceCollection-universal.dmg を配布物として添付しています。
DMGサイズは約88MBです。
実際に確認したこと
Codexに作ってもらって終わり、ではなく、動作確認もしました。
確認した内容は次の通りです。
- Universal arm64/x86_64ビルド
- ad-hocコード署名
- DMGチェックサム検証
- DMGからのアプリ起動
- 履歴検索/フィルタUIの確認
- 実行中ダウンロードのキャンセル
- キュー保持
- 失敗した最新履歴だけの再DL
- 重複URLのキュー投入防止
-
yt-dlp --simulateによる最高画質指定の抽出確認
やっぱり、ビルドが通るだけと、DMGから起動して実際に触るところまでやるのは違います。
特に、保存先をNAS配下にしたときの使い勝手や、内蔵ブラウザからURLを拾う流れ、失敗履歴の扱いは、実際に触らないと見えづらい部分でした。
詰まったところ
サイト側条件で失敗することがある
実際のテスト中に HTTP Error 403: Forbidden が出るケースがありました。
動画サイト側の仕様変更、認証条件、Cookieが必要なページなどは、アプリ側だけでは完全に吸収できません。
ここはアプリの不具合なのか、サイト側の制限なのかを切り分ける必要があります。
今回は、失敗したものを履歴に残し、あとで再DLできるようにしました。
失敗分再DLの対象をどう決めるか
「失敗分を再DL」と聞くと簡単そうですが、履歴が増えると少しややこしくなります。
たとえば、同じURLが過去に失敗していても、その後成功しているなら、もう再DLしなくていいはずです。
なので、URLごとの最新結果だけを見て、最新が失敗しているものだけを対象にしました。
外部エンジン同梱でサイズが大きくなる
yt-dlp と FFmpeg を同梱すると、ユーザー側の準備はかなり楽になります。
一方で、DMGサイズは大きくなります。
今回は、まず「ダウンロードしてすぐ使える」ことを優先しました。
Codexを使ってみた感想
かなり相性がよかったです。
特によかったのは、次のあたりです。
- 日本語で要件を投げられる
- 既存コードを読んでから直してくれる
- 実装だけでなくビルドや検証も一緒に進められる
- READMEやリリース文まで整えられる
- 途中で「実際に使うとここが足りない」となったときに、そのまま追加実装へ進める
逆に、Codexに丸投げすれば完成、という感じではありません。
アプリの挙動として何が自然か、どこまでを今回のスコープにするか、検証結果をどう判断するかは、人間側が見た方がいいです。
ただ、そこさえ自分で見れば、実装の手数をかなり任せられます。
個人開発で「作りたいものはあるけど、細かい実装と検証が重い」という場面では、かなり助かる使い方だと思いました。
今後やりたいこと
次にやるなら、このあたりです。
- notarization対応
- ダウンロード完了通知
- CSVやテキストからの一括URL投入
- Cookie/ログインが必要なサイトへの対応
- 自動アップデート
- 履歴のエクスポート
- 保存先プリセット
特にnotarizationは、配布アプリとしては早めに対応したいところです。
まとめ
Codexを使って、macOSネイティブの動画リファレンス収集アプリ ReferenceCollection を作ってみました。
結果として、Swift/AppKit実装、yt-dlp / FFmpeg 同梱、キュー、履歴、再DL、簡易ブラウザ、Universal DMG配布まで進められました。
使ってみて感じたのは、Codexは「コードを生成するツール」というより、実装、検証、記録、リリース準備まで一緒に走れる開発パートナーとして使うと強い、ということです。
小さな個人ツールでも、配布物として動かす、履歴を残す、失敗時の戻り道を作る、というところまで入れると、日常でちゃんと使えるアプリに近づきます。
リポジトリはこちらです。