tips

FlowPickがブラウザ内で数百の動画セグメントをMP4に結合する仕組み

サーバー不要、FFmpegインストール不要、トランスコード不要——FlowPickがHLS/DASHセグメント結合をブラウザタブ内で完結させる技術パスを完全解説。
FlowPick チーム
13 分で読了
# 技術 # ffmpeg # wasm # hls # dash # アーキテクチャ

FlowPickのダウンロードボタンをクリックし、数分待つと、完全なMP4がダウンロードフォルダに現れる。

これは少し直感に反する。ブラウザはダウンロードツールではないし、ましてや動画処理ツールでもない。従来のストリーミングダウンロードソリューションは例外なく、ローカルにインストールしたプログラムに依存するか、データをサーバーに送って処理して返してもらうかのどちらかだ。

FlowPickは全工程をブラウザタブ内で完結させる。サーバーなし、アプリなし、コマンドラインなし。この記事では、なぜこれが技術的に可能なのか、そして実際にどう実現しているのかを説明する。

まず問題を明確にする

HLS動画は転送時に一つの完全なファイルではない。それは再生リストファイル(M3U8)と数百の2〜10秒のセグメント.ts ファイル)だ。プレイヤーはリストを読み、順番にセグメントを取得し、リアルタイムでつなげて再生する。

30分の1080P動画を10秒セグメントにすると180ファイルになる。DASHフォーマットも同様だが、セグメントは .m4s で、音声と動画が二つの独立したストリームで転送される。

この動画を「ダウンロードする」とは何を意味するのか?

  1. M3U8/MPDを解析し、すべてのセグメントURLを取得
  2. 数百のセグメントを一つずつダウンロード
  3. AES-128暗号化があれば各セグメントを復号
  4. 数百の断片を一つの連続したファイルにつなげる
  5. HLS TSセグメントなら、MP4(プレイヤーフレンドリーなフォーマット)に再多重化
  6. DASHなら、動画トラックと音声トラックを結合

これは単純なファイルダウンロードではなく、処理パイプラインだ。従来のツールはこれらをFFmpegコマンドラインやサーバーに任せるが、FlowPickはパイプライン全体をブラウザ内に持ち込んだ。

検出が第一歩:webRequest API

セグメントを処理する前に、FlowPickはページ上にどんな動画があるかを知る必要がある。

ブラウザ拡張機能は webRequest APIを通じてネットワーク層ですべてのリクエストを監視できる——DOM層ではなく、実際のHTTPリクエスト層だ。現在のタブから発行されるすべてのネットワークリクエストについて、FlowPickはリクエストURLとレスポンスヘッダーを見ることができる。

FlowPickは Content-Type とURLパターンをスキャンし、HLS/DASH/直リンク動画/音声/画像の特徴にヒットしたリクエストを記録する。FlowPickポップアップを開いたときに見えるリストは、その時点までに捕捉されたすべてのメディアリソースだ。

これは、動画の再生が始まっていないとFlowPickがストリームを認識できない理由も説明している——M3U8リクエストはプレイヤー初期化時にのみ発行され、FlowPickは既に発生したリクエストしか見ることができず、能動的に探査することはできない。

核心的難題:TSセグメントをMP4に変換

動画セグメントのダウンロードが完了すると、フォーマットの問題が立ちはだかる。

HLSのセグメントはMPEG-TSフォーマット(.ts)だ。180個の .ts ファイルを直接バイナリで首尾結合すると、大きな .ts ファイルが得られる——このファイルは一部のプレイヤーで再生できるが、ほとんどのデバイス、動画編集ソフト、動画プラットフォームはMP4を好む。

TSとMP4の核心的な違いはコンテナフォーマットだ:

MPEG-TSMP4
設計目的放送伝送、パケットロス耐性ファイル保存、ランダムアクセス
各パケット188バイト固定サイズ可変長Box構造
インデックス位置独立したインデックスなし、シーケンシャル読み取りのみmoov atom、ファイル先頭に配置可能
ランダムシーク面倒moovインデックスで直接ジャンプ

TSをMP4に変換する本質は、中の動画と音声の生データを取り出し、MP4のBox構造に詰め直すことだ。この操作を remux(再多重化) と呼び、コーデックには触れない。動画と音声のすべてのビットはそのままで、「殻」だけを交換する。

再多重化はGPUを必要とせず、CPU負荷も非常に小さい。主な時間はファイルI/Oに費やされる。これがFlowPickがブラウザ内で実現できる技術的前提だ——もし再エンコード(transcoding)が必要なら、ブラウザのCPU性能ではまったく太刀打ちできない。

二つのエンジンの選択ロジック

FlowPick内部には二つの再多重化エンジンがあり、シナリオに応じて自動切り替えする:

エンジン1:FFmpeg WASM

FFmpegは業界標準の動画処理コマンドラインツールで、ほぼすべてのフォーマットに対応する。**WebAssembly(WASM)**はブラウザ内でネイティブに近い速度で実行できるバイナリフォーマットだ。FFmpegをWASMにコンパイルすれば、ブラウザタブ内でFFmpegの全機能を呼び出せる。

FlowPickがFFmpeg WASMを呼び出す際、実際に実行されるコマンドはおおよそ以下のようになる:

ffmpeg -f concat -safe 0 -i filelist.txt \
  -c copy \
  -movflags +faststart \
  -bsf:a aac_adtstoasc \
  output.mp4

パラメータの分解:

パラメータ役割
-f concat連結デマルチプレクサで複数のTSセグメントを処理
-c copyストリームコピー、ゼロ再エンコード
-movflags +faststartインデックスをファイル先頭に移動、プログレッシブダウンロード対応
-bsf:a aac_adtstoascAACパッケージングフォーマットを変換(TSはADTS、MP4はASC)

-c copy が鍵だ——これによりFlowPickは純粋な再多重化のみを行い、品質損失はゼロで、高いCPU性能も不要。

WASMのFFmpegはブラウザのJavaScriptエンジン上で動作するためパフォーマンスペナルティがあるが、再多重化はI/OバウンドなタスクでCPUバウンドではないため、実際のパフォーマンスは悪くない。実測データ(Chrome + Intel i7-13700 + 16GB RAM):

動画セグメント数ファイルサイズFFmpeg WASM再多重化時間
10分 1080P~60~200 MB~3秒
30分 1080P~180~600 MB~8秒
60分 4K~360~2.5 GB~30秒
120分 4K~720~5 GB~60秒

この速度なら日常使用に十分だ。

FFmpeg WASMの制限: WASMは32ビットアドレス空間で動作し、理論上のメモリ上限は4GB。実際のシナリオでは、FFmpeg WASMはすべてのセグメントを仮想ファイルシステム(WASMメモリ)に書き込む必要があり、大容量ファイルのシナリオでは大量のメモリを消費し、制限を超えるとクラッシュする。

エンジン2:TSToMP4Muxer

これはFlowPickが独自開発した純粋JavaScriptのストリーミング再多重化器で、TS → MP4の単一シナリオに特化しているが、アプローチがまったく異なる。

FFmpeg WASMのモードは:まず全セグメントを集め、それから一括処理。TSToMP4Muxerのモードは:ダウンロードしながら処理しながらディスクに書き込む

具体的には、TSToMP4Muxerはセグメントがダウンロードされてくるのと同時に、TSデータパケット(各パケット固定188バイト)をリアルタイムで解析し、PES(Packetized Elementary Stream)データを抽出し、リアルタイムでMP4 Boxフォーマットに変換し、ブラウザのStreams APIを通じて継続的にディスクに書き込む。

利点は明らかだ:

  • メモリ使用量がファイルサイズに依存しない — 5GBの動画処理と200MBの動画処理でメモリ使用量はほぼ同じ
  • FFmpeg WASMのロード不要(30MB以上のWASMファイル)、起動が速い
  • 大容量ファイルにネイティブ対応 — メモリ内に同時に保持するのはごく一部のデータのみ

代償としてカバー範囲が狭い:単一TS → MP4のみで、DASHの音声・動画結合のような二入力が必要なタスクは処理できない。

FlowPickの切り替えロジック:通常のHLSは優先的にTSToMP4Muxerを使用。DASHや二ストリーム結合が必要な場合はFFmpeg WASMを使用。ファイルが2GBを超えるHLSもTSToMP4Muxerを使用するか、TSを直接出力して再多重化をスキップし、WASMのメモリオーバーフローを回避する。

ファイルの書き込み先:File System Access API

処理済みの動画データをどこに置くか?従来のダウンロードツールでは問題にならないが、ブラウザのJavaScriptはデフォルトでファイルシステムへの直接書き込み権限を持たない。

FlowPickはChromeとEdgeが提供する File System Access API(FSA) を使用する。ユーザーが保存パスを選択すると、FSAは書き込み可能なファイルハンドルを提供し、JavaScriptがストリーミング方式で継続的に書き込むことを可能にする。全データをメモリに集めてから一括出力するのではない。

つまり:5GBの4K動画でも、メモリ内に同時に存在するのは現在処理中の小さなデータブロックのみ。FlowPickには理論上ファイルサイズの上限がない(制限はディスク容量とWASMメモリ上限で、後者はTSToMP4Muxerで回避)。

Firefoxも対応しているが、Blob方式を取るため、メモリ挙動がChromeのFSAと若干異なり、超大容量ファイル時の安定性はやや劣る。

AES-128復号:Web Crypto API

一部のHLSストリームは各セグメントを暗号化し、M3U8に以下のような行がある:

#EXT-X-KEY:METHOD=AES-128,URI="https://api.example.com/key?token=..."

FlowPickはこのタグを検出すると、まずKey URIにリクエストを送ってキーを取得する(現在のCookieとセッションを付与。ブラウザ拡張機能からのリクエストなので、ブラウザの実際のセッションそのもの)。キー取得後、ダウンロード完了した各セグメントに対し、ブラウザ内蔵の Web Crypto API でAES-128-CBC復号を行い、復号済みデータを再多重化パイプラインに送る。

キー取得と復号の全工程は完全にブラウザローカルで発生し、キーがFlowPickのサーバーを経由することは一切ない。

FlowPickが処理できないもの: Widevine、PlayReady、FairPlayなどのDRM体系。この種のコンテンツ保護は復号キーを専用ハードウェアやOSモジュールにバインドしており、JavaScriptからはアクセスできない。これは技術的制限であり、機能の取捨選択ではない。

DASH:音声と動画を別々にダウンロードして結合

DASHはHLSより一段階複雑だ——音声と動画が二つの独立したストリームで、それぞれセグメント化され、それぞれ転送される。ダウンロードするのは完全な動画セグメントの束ではなく、純粋な動画セグメントの束(音声なし)と純粋な音声セグメントの束(映像なし)だ。

FlowPickのDASH処理:

  1. MPD XMLを解析し、動画AdaptationSetと音声AdaptationSetを識別
  2. 両方のセグメントを同時ダウンロード(並列)
  3. それぞれinit segmentをダウンロード(コーデックパラメータを含み、結合時に必須)
  4. 両方のデータをFFmpeg WASMに送り、音声・動画結合を実行

結合コマンドはおおよそ:

ffmpeg \
  -f concat -safe 0 -i video_list.txt \
  -f concat -safe 0 -i audio_list.txt \
  -c:v copy -c:a copy \
  -movflags +faststart \
  output.mp4

同様に -c copy、ゼロ再エンコード。主な時間は両方のセグメントの並列ダウンロードと最終的な結合I/Oに費やされる。

並列ダウンロード:セグメントは一つずつ待たなくていい

数百のセグメントをシリアルダウンロードすると、速度は完全に単一HTTPリクエストのレイテンシに支配される。FlowPickはマルチスレッド並列ダウンロードを使用し、デフォルト2並列(保守的な値、サーバー側のレート制限を回避)、設定で4〜6に調整可能。

複数のセグメントを並列ダウンロードし、先に完了したものから処理するが、ファイル書き込み時はシーケンス番号順に並べ替え、結合順序が正しいことを保証する。FlowPick内部ではダウンロードキューと順序付き書き込みバッファを維持してこの詳細を処理する。

ダウンロード失敗したセグメントは自動リトライ(デフォルト3回)。セグメント粒度が小さいため、単一失敗のリトライコストは非常に小さく、最初からやり直す必要はない。

出力フォーマットの実際の違い

FlowPickは2つの出力をサポートする:

MP4:再多重化後の標準MP4、moov atomをファイル先頭に配置(faststart)、互換性が最も高く、長期保存、アップロード、動画編集に適する。代償として再多重化ステップが必要で、大容量動画では数十秒余分に待つことになる。

TS:全セグメントを直接バイナリ結合、CPUオーバーヘッドゼロ、ほぼ瞬時に完了。ただしファイルはMP4より5-15%大きく(TSコンテナ自体のオーバーヘッド)、一部のデバイスやプラットフォームではTSのサポートがMP4ほど良くない。

ダウンロード後にBilibiliにアップロードしたりCapCutに読み込むならMP4を選ぶ。一時保存や後でFFmpegコマンドラインで処理する予定ならTSを選び、時間を節約する。

ブラウザ内でこれを行うことの代償

できることを説明したので、できないことも明確にする:

再エンコードはない。 FlowPickはコンテナのみを変更し、コーデックには触れない。H.265の動画は出てきてもH.265のままで、古いプレイヤーがH.265に対応していなければやはり再生できない。FlowPickはこれを助けられない。

FFmpeg WASMのメモリ上限がある。 4GBの32ビットWASMアドレス空間に、ブラウザ自身のオーバーヘッドが加わるため、実際に使えるのは4GBより少ない。2GB以上のファイルで自動的に戦略を切り替える(TSToMP4MuxerまたはTS直接出力)のは、この制限を回避するためだ。

WASMはネイティブFFmpegより遅い。 WASMはネイティブコードではなく、パフォーマンスペナルティがある。60分4K動画でFFmpeg WASMは約30秒かかるが、ネイティブFFmpegなら5秒以内かもしれない。日常使用では30秒は許容範囲。毎日大量のコンテンツを処理するなら、ネイティブFFmpegコマンドラインの方が適している。

ハードウェアアクセラレーションはない。 FFmpegネイティブ版はNVENC/QSV/VideoToolboxを呼び出してGPUアクセラレーションエンコード/デコードが可能だが、WASM版にはその能力がない。ただし -c copy はエンコード/デコードを伴わないため、この制限はFlowPickのユースケースでは影響が小さい。

DRMは回避できない。 これはハードリミットであり、技術的取捨選択ではない。Widevineの設計目標は、ブラウザ拡張機能がキーにアクセスできないようにすることだ。


全体として、FlowPickがブラウザ内でHLS/DASHダウンロードを完全処理できるのは、いくつかのキーテクノロジーの組み合わせによる:WebAssemblyがFFmpegをブラウザ内で動作可能にし、-c copy が処理コストをI/Oのみに抑え、TSToMP4Muxerが大容量ファイルのメモリ問題を解決し、File System Access APIが大容量ファイルのディスク書き込み問題を解決し、Web Crypto APIがAES-128復号を処理する。どれも「ブラックマジック」ではなく、すべてモダンブラウザの標準機能の直接応用だ。


おすすめ記事