はじめに
映像制作やコンテンツ制作をしていると、MotionElementsやAudiioといったストック素材サイトから大量のファイルをダウンロードすることがあります。気づけばダウンロードフォルダは BGM、効果音、動画素材、テンプレートファイルでカオス状態に…。
「またあの素材どこにダウンロードしたっけ…」
この問題を解決するために、StockpileというChrome拡張機能を作りました。
何を作ったか
Stockpileは、ストック素材サイトからのダウンロードを自動的に整理してくれる拡張機能です。
主な機能:
- 自動フォルダ振り分け: ダウンロードを
Stockpile/[サイト名]/[カテゴリ]/に自動保存 - メタデータ抽出: ページからタイトル、タグ、再生時間などを自動取得
- ダウンロード履歴: 検索・フィルタリング可能な履歴管理
- エクスポート機能: JSON/CSV形式でバックアップ
対応サイト:
- MotionElements(動画、BGM、効果音、テンプレート等)
- Audiio(BGM、効果音)
なぜ作ったか
課題
映像編集の仕事をしていると、1つのプロジェクトで数十〜数百のストック素材をダウンロードすることがあります。
- 動画素材
- BGM
- 効果音
- モーショングラフィックステンプレート
- LUTファイル
これらが全て「ダウンロード」フォルダに一緒くたに放り込まれる。ファイル名も ME_12345678_preview.mp4 のような意味不明な名前。後から「あの曲なんだっけ」と探すのが本当に大変でした。
既存のソリューション
フォルダを手動で作って整理する方法もありますが、ダウンロードのたびに手作業でフォルダに移動するのは面倒すぎる。ダウンロードマネージャー系の拡張機能も試しましたが、ストック素材サイト特有の「カテゴリ情報」や「メタデータ」を活用した整理には対応していませんでした。
「じゃあ作るか」と。
開発で苦労した点
1. Manifest V3への対応
Chrome拡張機能のManifest V3は、V2と比べて制約が多くなっています。特にバックグラウンドページが廃止され、Service Workerに移行したことで、いくつかの設計変更が必要でした。
// manifest.json
{
"manifest_version": 3,
"background": {
"service_worker": "background/service-worker.js",
"type": "module"
}
}
Service Workerは必要なときだけ起動し、アイドル状態になると停止します。そのため、ダウンロード待ちのメタデータを一時的に保持する方法として、変数ではなく chrome.storage.local を使う必要がありました。
2. ダウンロードURLのマッチング問題
これが一番苦労した部分です。
ストック素材サイトでは、ダウンロードボタンをクリックしてから実際のファイルがダウンロードされるまでに、複数のリダイレクトが発生することがあります。
クリック → API呼び出し → 認証 → CDNリダイレクト → 実際のダウンロード
Content Scriptで取得した「クリック時のURL」と、chrome.downloads.onDeterminingFilenameで受け取る「実際のダウンロードURL」が一致しないケースが頻発しました。
最終的に、複数のマッチング戦略をフォールバックで実装することで解決しました。
3. 動的コンテンツへの対応
MotionElementsやAudiioはSPAライクな構造で、ページ遷移なしにコンテンツが切り替わります。また、ダウンロードボタンが動的に生成されることも。
MutationObserverを使って、DOMの変更を監視し、新しく追加されたダウンロードリンクを検出するようにしました。
4. XHR/Fetchのインターセプト
一部のダウンロードは、通常のリンククリックではなく、JavaScriptで動的に発火されます。これに対応するため、XHRとFetchをラップして監視する必要がありました。
技術選定
フレームワークを使わない選択
PopupやOptionsページのUIには、ReactやVueなどのフレームワークを使わず、Vanilla JavaScriptで実装しました。
理由:
- 拡張機能のサイズを最小限に抑えたかった
- UIがそこまで複雑ではない
- ビルドプロセスなしでデバッグしたかった
結果的に、全体で数十KBに収まり、インストール後の動作も軽快です。
学びと気づき
Chrome拡張機能は「3つの世界」を跨ぐ
- Content Script: Webページのコンテキストで動作
- Service Worker: バックグラウンドで動作
- Popup/Options: 独立したページとして動作
これらの間のデータのやり取りは chrome.runtime.sendMessage や chrome.storage を介して行う必要があり、最初は戸惑いました。しかし一度理解すると、責務の分離が明確で設計しやすいと感じました。
ユーザーの行動は予測できない
「ダウンロードボタンをクリックする」という単純な行動にも、様々なパターンがあります。全てのケースに対応するのは難しいですが、主要なユースケースをカバーしつつ、エッジケースでもクラッシュしないよう防御的なコードを心がけました。
i18nは最初から入れておくべき
日本語と英語の両対応を後から追加したのですが、最初からi18nを意識して実装しておけばよかったと反省。Chromeの chrome.i18n APIは使いやすいので、最初から対応しておくことをおすすめします。
おわりに
自分が欲しいものを自分で作る。プログラマーの特権ですね。
Stockpileを使い始めてから、ダウンロードフォルダのカオスが解消され、素材を探す時間が大幅に減りました。
もし同じような課題を抱えている方がいれば、ぜひ使ってみてください。
Chrome Web Store: https://chromewebstore.google.com/detail/stockpile-download-organi/dghocnhifhkndkmolgkhcibnkapikjil
GitHub: https://github.com/atani/stockpile-extension

