【自動整理】ストック素材のダウンロードを自動整理するChrome拡張機能「Stockpile」を作った

ツール

はじめに

映像制作をしていると、MotionElementsやAudiioから1プロジェクトで数十〜数百のストック素材をダウンロードします。BGM、効果音、動画素材、テンプレートがすべて「ダウンロード」フォルダに混在し、ファイル名は ME_12345678_preview.mp4 のような意味不明な文字列。後から「あの曲なんだっけ」と探すのが本当に大変でした。

この問題を解決するために、ダウンロードをサイト別・カテゴリ別に自動振り分けするChrome拡張機能 Stockpile を作りました。

Stockpile - Download Organizer - Chrome Web Store
Automatically organize downloaded assets from stock sites into folders by site and category

何を作ったか

Stockpileは、ストック素材サイトからのダウンロードを自動的に整理してくれる拡張機能です。

主な機能は以下の通りです。

  • ダウンロードを Stockpile/[サイト名]/[カテゴリ]/ に自動振り分け
  • ページからタイトル、タグ、再生時間などのメタデータを自動取得
  • 検索・フィルタリング可能なダウンロード履歴
  • JSON/CSV形式でのエクスポート

対応サイトは以下の通りです。

なぜ作ったか

課題

映像編集の仕事をしていると、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」の不一致が頻発しました。

最終的に、複数のマッチング戦略をフォールバックで実装しました。

// 1. 完全一致
let metadata = pendingDownloads[downloadUrl];

// 2. 部分一致(URLの一部でマッチ)
if (!metadata) {
  metadata = Object.values(pendingDownloads).find(m =>
    downloadUrl.includes(m.urlPart) || m.originalUrl?.includes(downloadUrl)
  );
}

// 3. ドメインマッチ + タイムスタンプ(最終手段)
if (!metadata) {
  metadata = findByDomainAndTimestamp(downloadUrl);
}

3. 動的コンテンツへの対応

MotionElementsやAudiioはSPAライクな構造で、ページ遷移なしにコンテンツが切り替わります。また、ダウンロードボタンが動的に生成されることも。

MutationObserverを使って、DOMの変更を監視し、新しく追加されたダウンロードリンクを検出するようにしました。

const observer = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    for (const node of mutation.addedNodes) {
      if (node.nodeType === Node.ELEMENT_NODE) {
        const downloadLinks = node.querySelectorAll('a[href*="download"]');
        downloadLinks.forEach(link => attachDownloadListener(link));
      }
    }
  }
});

observer.observe(document.body, { childList: true, subtree: true });

4. XHR/Fetchのインターセプト

一部のダウンロードは、通常のリンククリックではなく、JavaScriptで動的に発火されます。これに対応するため、XHRとFetchをラップして監視しています。

// Fetchのインターセプト
const originalFetch = window.fetch;
window.fetch = async function(...args) {
  const url = args[0]?.url || args[0];
  if (isDownloadUrl(url)) {
    registerPendingDownload(url);
  }
  return originalFetch.apply(this, args);
};

技術選定

フレームワークを使わない選択

PopupやOptionsページのUIには、ReactやVueなどのフレームワークを使わず、Vanilla JavaScriptで実装しました。

拡張機能のサイズを最小限に抑えたかったこと、UIがそこまで複雑ではないこと、ビルドプロセスなしでデバッグしたかったことが理由です。

結果的に、全体で数十KBに収まり、インストール後の動作も軽快です。

ES Modulesの活用

Service Workerでは "type": "module" を指定することで、ES Modulesが使えます。これにより、storageやdatabaseの処理を別ファイルに分離し、コードの見通しが良くなりました。

// service-worker.js
import { getSettings, savePendingDownload } from '../lib/storage.js';
import { addDownloadRecord, searchDownloads } from '../lib/database.js';

学びと気づき

Chrome拡張機能は「3つの世界」を跨ぐ

Chrome拡張機能には3つの実行コンテキストがあります。Webページ上で動くContent Script、バックグラウンドで動くService Worker、独立したページとして動くPopup/Optionsです。

これらの間のデータのやり取りは chrome.runtime.sendMessagechrome.storage を介して行います。最初は戸惑いましたが、一度理解すると責務の分離が明確で設計しやすいと感じました。

ユーザーの行動は予測できない

「ダウンロードボタンをクリックする」という単純な行動にも、様々なパターンがあります。

  • 普通にクリック
  • 右クリック→「名前を付けてリンク先を保存」
  • ミドルクリック
  • ダウンロードマネージャー拡張機能との併用

全てのケースに対応するのは難しいですが、主要なユースケースをカバーしつつ、エッジケースでもクラッシュしないよう防御的なコードを心がけました。

i18nは最初から入れておくべき

日本語と英語の両対応を後から追加したのですが、最初からi18nを意識して実装しておけばよかったと反省。Chromeの chrome.i18n APIは使いやすいので、最初から __MSG_xxx__ 形式でテキストを定義しておくことをおすすめします。

おわりに

自分が欲しいものを自分で作る。プログラマーの特権ですね。

Stockpileを使い始めてから、ダウンロードフォルダのカオスが解消され、素材を探す手間がなくなりました。過去にダウンロードした素材も、拡張機能のポップアップから検索できるので「あの曲なんだっけ」問題も解決。

Chrome拡張機能の開発は、Web技術の知識がそのまま活かせるので、Webエンジニアには取り組みやすい領域です。Manifest V3の制約へ慣れる必要はありますが、一度コツをつかめば、ブラウザ体験を自分好みへカスタマイズできる強力なツールになります。

同じような課題を抱えている方はぜひ使ってみてください。

Stockpile - Download Organizer - Chrome Web Store
Automatically organize downloaded assets from stock sites into folders by site and category
GitHub - atani/stockpile-extension: Chrome extension that automatically organizes downloads from stock sites like MotionElements and Audiio into folders
Chrome extension that automatically organizes downloads from stock sites like MotionElements and Audiio into folders - a...

関連記事

Chrome拡張の開発記事をほかにも書いています。

タイトルとURLをコピーしました