0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Codex使ってmacOSアプリ開発してみた!Swift/AppKit製ツールをDMG配布まで持っていく

0
Posted at

はじめに

最近、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に任せたのは、単なるコード生成だけではありません。

実際にはこんな流れで進めました。

  1. 自分が日本語で「こういう機能がほしい」と伝える
  2. Codexが既存コードを読む
  3. 実装方針を整理する
  4. Swift/AppKitのコードを編集する
  5. ビルドスクリプトやREADMEも整える
  6. ローカルでコマンドを実行して検証する
  7. 結果を見て、さらに修正する

感覚としては、「実装できる開発メモ係」が横にいる感じでした。

たとえば、最初にアプリの基本形を作ったあと、実際に触っていて「キャンセルできないと困る」「履歴から失敗分だけ戻したい」となりました。

そこで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-dlpFFmpeg を直接実行する構成にしました。ユーザー側で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連携、メニュー、WKWebViewProcess 実行あたりを素直に扱いたかったからです。

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-dlpFFmpeg を同梱すると、ユーザー側の準備はかなり楽になります。

一方で、DMGサイズは大きくなります。

今回は、まず「ダウンロードしてすぐ使える」ことを優先しました。

Codexを使ってみた感想

かなり相性がよかったです。

特によかったのは、次のあたりです。

  • 日本語で要件を投げられる
  • 既存コードを読んでから直してくれる
  • 実装だけでなくビルドや検証も一緒に進められる
  • READMEやリリース文まで整えられる
  • 途中で「実際に使うとここが足りない」となったときに、そのまま追加実装へ進める

逆に、Codexに丸投げすれば完成、という感じではありません。

アプリの挙動として何が自然か、どこまでを今回のスコープにするか、検証結果をどう判断するかは、人間側が見た方がいいです。

ただ、そこさえ自分で見れば、実装の手数をかなり任せられます。

個人開発で「作りたいものはあるけど、細かい実装と検証が重い」という場面では、かなり助かる使い方だと思いました。

今後やりたいこと

次にやるなら、このあたりです。

  • notarization対応
  • ダウンロード完了通知
  • CSVやテキストからの一括URL投入
  • Cookie/ログインが必要なサイトへの対応
  • 自動アップデート
  • 履歴のエクスポート
  • 保存先プリセット

特にnotarizationは、配布アプリとしては早めに対応したいところです。

まとめ

Codexを使って、macOSネイティブの動画リファレンス収集アプリ ReferenceCollection を作ってみました。

結果として、Swift/AppKit実装、yt-dlp / FFmpeg 同梱、キュー、履歴、再DL、簡易ブラウザ、Universal DMG配布まで進められました。

使ってみて感じたのは、Codexは「コードを生成するツール」というより、実装、検証、記録、リリース準備まで一緒に走れる開発パートナーとして使うと強い、ということです。

小さな個人ツールでも、配布物として動かす、履歴を残す、失敗時の戻り道を作る、というところまで入れると、日常でちゃんと使えるアプリに近づきます。

リポジトリはこちらです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?