YouTubeの再生リストを再生時間順に並び替える拡張機能を作りました

山田 蓮

大分を拠点に、全国でWebサイト制作・業務ツール開発・Webマーケティングを手がけるフリーランスです。

YouTubeの再生リストを、動画の長さ順に並び替えたいと思ったことはないでしょうか。

移動時間が15分あるとき、その枠に収まる動画を再生リストから選びたい。ところがYouTubeの画面では、各動画の長さがサムネイルの隅にばらばらに表示されるだけで、長さで並び替える機能も、長さで絞り込む機能もありません。短いものを探すには、上から順に目で追っていくしかない。

これが不便だったので、Chrome拡張を作りました。Manifest V3、JavaScript 3,159行です。

はじめにお断りしておくと、この拡張は一般公開していません。ストアにも出しておらず、自分の環境だけで動かしています。作りが荒いからではなく、公開すると仕組みとして成立しないためです。理由は後述します。

ただ、使ってみたい方はご連絡ください。

何ができるのか

再生リストを開くと、各動画の再生時間が一覧で並びます。そこからできるのは3つです。

  • 並び替え — 再生時間の短い順・長い順に並び替えます
  • 絞り込み — 「10分以内」のように長さで絞り込みます
  • 一括操作 — 絞り込んだものをまとめて削除したり、別の再生リストへ移したりします

やりたかったことに対しては、これで足ります。15分の枠があるなら15分以内で絞り込んで、短い順に並べておけば、あとは上から再生するだけです。

ここから先は、作るうえで詰まった部分の話になります。単純な機能に見えて、YouTube Data API の制限が設計をほとんど決めてしまいました。

作りの前提

  • 形式 — Chrome拡張(Manifest V3)。素のJavaScript、ビルド工程なし
  • API — YouTube Data API v3。認証は chrome.identity による OAuth2
  • 権限 — identity と storage のみ。ホスト権限は Google API のドメインだけ

YouTubeのページは読み取らない

作り始める前に決めたことがひとつあります。データの取得と更新は、すべてAPI経由で行うということです。YouTubeのページからHTMLを読み取って情報を組み立てることはしません。

理由のひとつは利用規約です。YouTubeの利用規約は、禁止している行為のひとつとして次のものを挙げています。

自動化された手段(ロボット、ボットネット、スクレーパなど)を使用して本サービスにアクセスすること。ただし、(a)公開されている検索エンジンを YouTube の robots.txt ファイルに従って使用する場合、または(b)YouTube が事前に書面で許可している場合を除きます。

出典:YouTube 利用規約(2023年6月1日発効)

もうひとつは壊れにくさです。ページの構造に依存した拡張機能は、YouTubeが画面を変更するたびに動かなくなります。APIの応答形式は公開された仕様なので、画面が変わっても影響を受けません。

ただし1か所だけ例外があります。YouTubeのホームや検索結果から動画を複数選び、まとめて再生リストに入れる機能です。おすすめの一覧を返すAPIが存在しないため、ここだけはページの上に選択用のチェックボックスを重ねています。

その代わり、ページから読むのはリンクのURLに含まれる動画IDだけに絞りました。タイトルや再生時間、チャンネル名はページから読まず、すべてAPIで取り直します。画面の要素名や入れ子の構造も前提にしていないので、レイアウトが変わっても壊れません。

読み取りが1、書き込みが50

YouTube Data API には1日あたりの割り当てがあります。公式の説明によれば、標準で10,000。この数字だけ見ると十分に思えますが、操作ごとの単価が問題でした。

  • 一覧の取得(playlistItems.list)— 1
  • 動画情報の取得(videos.list)— 1
  • 並び順の変更(playlistItems.update)— 50
  • 削除(playlistItems.delete)— 50

読み取りと書き込みで50倍の差があります。そして並び替えは、動画1本を動かすごとに50を消費します。

つまり100本の再生リストを素直に並び替えると、100回の移動で5,000。1日の割り当ての半分が1回の並び替えで消えます。数回やれば打ち止めです。

読み取りは気軽に、書き込みは1回ずつ数える。この方針がそのまま設計になりました。

移動回数を最小にする

並び替えのAPIは「この動画を何番目に置く」という形でしか呼べません。1回の呼び出しで1本しか動かせない以上、何本動かさずに済ませられるかが割り当ての消費量を直接決めます。

ここで使えるのが最長増加部分列(LIS)です。

現在の並びの各要素を「目標順での位置」に置き換えると、並び替えは数列の問題になります。たとえば現在の並びが目標順で [2, 0, 1, 3] の位置に対応しているなら、すでに昇順になっている部分——この場合 [0, 1, 3]——は動かさなくても正しい順序関係を保っています。

そして「動かさなくてよい要素」を最大にすることは、そのまま「動かす要素を最小にする」ことです。最長の増加部分列を求めれば、移動回数の最小解が得られます。

現在の並び 目標順での位置 動かす本数 CA BD 2 0 1 3 動かす 0, 1, 3 は増加しているので据え置き 1本(50) — 素直に全部動かすと 4本(200)

動かさなくてよい要素を最大にすると、そのまま移動回数が最小になります

実際の再生リストは既存の並びと目標の並びがある程度似ていることが多いので、この効きはかなり大きくなります。全体を作り直すのではなく、ずれている数本だけを動かせば済みます。

算出した手順を、呼ぶ前に検証する

ここが一番気を使った部分です。

並び替えは取り消せません。誤った手順を実行すれば、利用者の再生リストが壊れた状態で確定します。しかも割り当てを消費しているので、やり直しの余地も小さい。

そこで、APIを1回も呼ばないうちに手順を配列上で実行してみて、結果が目標の並びと一致するかを確かめるようにしました。一致しなければLISの結果は使わず、単純だが確実な方法へ切り替えます。

ここには来ない想定。来たとしても黙って壊れた並びを書き込まないよう、正しさが保証できる方法へ落とす。

実装に残したコメントです。分岐自体は通らない前提ですが、通ったときに黙って壊すのだけは避けたかった。「起きないはずの経路で何が起きるか」は、意識して決めておかないと空白のまま残ります。

あわせて、並び替え・長さの解釈・手順の生成にはテストを書きました。UIのない純粋な計算部分なので、Node から直接呼んで確かめられます。

割り当てのリセットは太平洋時間

もうひとつ、地味に面倒だったのが割り当てのリセット時刻です。

YouTube Data API の割り当ては太平洋時間の0時にリセットされます。日本時間ではありません。しかも夏時間があるので、日本との時差は16時間と17時間のあいだで年2回入れ替わります。

「あと何時間で回復するか」を表示するには、この境界を正しく求める必要があります。自前で時差を計算すると夏時間の切り替え日に必ず間違えるので、Intl にタイムゾーンを渡して太平洋時間の日付を出させ、そこから境界を探す方法にしました。

日付の文字列を得るのに en-CA ロケールを使っています。このロケールは YYYY-MM-DD 形式で出力するので、加工せずそのまま比較に使えます。

公開していない理由

この拡張は自分だけで使っていて、ストアには出していません。作りが荒いからではなく、公開すると成立しない仕組みだからです。

ここまで書いてきた割り当ての10,000は、利用者ごとではありません。APIを登録したプロジェクト単位で、全利用者が同じ枠を共有します。自分ひとりで使っている分には、並び替えを数回やっても収まります。ところが公開して10人が使えば、1人あたり1,000。数人が並び替えを1回ずつやっただけで、その日は全員が使えなくなります。

枠を増やすには申請が必要で、その前に利用規約に準拠していることを示す監査を通す必要があります。

加えて、Googleアカウントでログインさせる仕組みにも上限があります。この拡張のように開発中の状態(テスト)のままだと、利用できるのは登録した100人まで、ログインの許可も7日で切れます。一般に公開するには、Googleによる確認の手続きが必要です。再生リストを書き換える権限を求める以上、ここは避けて通れません。

割り当ての枠、規約の監査、ログインの確認。個人の道具としては、そこまでして公開する理由がありませんでした。

逆に言えば、APIの割り当てが「作れるけれど配れない」という線引きを作っているということです。技術的には動くのに、配布の形だけが選べない。外部APIに依存する道具を作るときは、この線がどこにあるかを先に見ておいたほうがよさそうです。

同じことで困っている方がいれば、お問い合わせからご連絡ください。仕組みの話であればお答えできます。

権限は最小限にする

拡張機能は、要求する権限がそのままインストール時の警告文になります。ここはケチったほうがいいところです。

この拡張が要求しているのは identity(Googleログイン)と storage(割り当ての記録)だけです。通信先も Google の API ドメインに限定しています。YouTubeのページに差し込むスクリプトは、選択操作のためだけのもので、ページの内容を読み取ってどこかに送ることはしていません。

権限を最小にすると実装は面倒になります。ただ、拡張機能は一度インストールされると利用者から中身が見えないので、要求しないという形でしか安心を示せません。

まとめ

この拡張を作って分かったことは3つでした。

  • 外部APIの制限は、機能ではなく設計を決めます。読み取り1・書き込み50という単価差が、アルゴリズムの選択まで規定しました
  • 取り消せない操作は、実行前に手順を検証する。配列の上でなら何度でも試せます
  • 時刻とタイムゾーンは、自前で計算せず標準の仕組みに任せる
  • 割り当ての枠は利用者ごとではなくプロジェクト単位。作れるかどうかと、配れるかどうかは別でした

どれも、作り始める前に仕様書を読んでいれば分かったことでした。外部APIを使う道具は、機能を考えるより先に制限を読んだほうが早いと思います。

参考資料

SHARE