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?

FC2動画のダウンロード技術を深掘り:HLSセグメント解析から実装の勘所まで

0
Posted at

FC2動画のダウンロード技術を深掘り:HLSセグメント解析から実装の勘所まで

※本記事は技術的な学習・研究を目的としており、著作権で保護されたコンテンツの無断利用を推奨するものではありません。利用の際は必ず各プラットフォームの利用規約および著作権法を遵守してください。

はじめに:「あの動画、あとで見たい」から始まった技術的挑戦

こんにちは、普段はメディア配信システムの開発に関わっているエンジニアです。

先日、FC2で公開されていた技術カンファレンスの登壇動画を見つけたんですが、「後で資料と照らし合わせながら見返したい」「通勤中のオフライン環境でも再生したい」と思った瞬間、あの「ダウンロードボタンが見当たらない」という現実に直面しました。

「埋め込みプレイヤーのソースを見れば取れるでしょ?」と思った方もいるかもしれません。実際、数年前までは比較的簡単に動画ファイルの直接リンクが見つかるケースもありました。しかし、2024年現在、FC2の配信アーキテクチャはHLS(HTTP Live Streaming)を基盤とした複雑な構造へと進化しており、単純なURL直叩きでは対応できなくなっています。

かといって、怪しいサードパーティ製ツールにアカウント情報やCookieを渡すのも抵抗がある…。

そんな「技術的には可能なのに、手軽に安全に使える手段がない」というギャップを埋めたくて、今回ご紹介する fc2_downloader_ja の技術アーキテクチャを、開発者の視点から分解してみようと思います。

本記事では、単なる「使い方の紹介」ではなく、「FC2のHLSストリームをどう解析し、どう再構築しているか」 という技術的な中身に焦点を当てます。コード例も交えつつ、実装の勘所を共有できれば幸いです。


FC2動画配信の技術的構造:HLSと.tsセグメントの正体

1. HLSストリームとは何か

FC2では、再生の安定性と画質の適応制御を実現するため、HLS(HTTP Live Streaming)方式を採用しています。

ブラウザで動画を再生する際、実際には以下のようなフローで処理が行われています:

1. 動画ページ読み込み
2. プレイヤーが.m3u8プレイリストを取得
3. プレイリストに記載された.tsセグメントファイルを順次ダウンロード
4. ブラウザのMedia Source Extensions (MSE) でバッファリング・再生

つまり、私たちが「1つの動画ファイル」として認識しているものも、技術的には数百〜数千個の小さな断片として配信されているのです。
4low.png

2. Networkタブで覗いてみる

開発者ツール(F12)→ Networkタブ → 「media」または「xhr」でフィルターをかけ、FC2の動画を再生してみてください。以下のようなリクエストが確認できるはずです:

https://video12345.fc2.com/up/video/xxx/playlist.m3u8?token=abcdef123456

この.m3u8ファイルが、実際の動画セグメント一覧を記述したプレイリストです。中身を見てみると、以下のような構造になっています:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXTINF:9.960000,
segment_00001.ts
#EXTINF:9.960000,
segment_00002.ts
#EXTINF:5.120000,
segment_00003.ts
#EXT-X-ENDLIST

💡 補足:#EXTINF は各セグメントの再生時間(秒)、#EXT-X-TARGETDURATION は最大セグメント長を示します。これらの値を元に、プレイヤーがバッファリング戦略を決定しています。


簡易実装:Pythonで.m3u8解析からダウンロードまで

以下は、学習目的のサンプルコードです。実際の運用では、Cookie管理、トークン更新、レート制限など、より多くの配慮が必要になります。

import requests
import re
from urllib.parse import urljoin, urlparse

def fetch_m3u8(playlist_url: str, referer: str) -> list[str]:
    """HLSプレイリストを取得し、セグメントURL一覧を返す"""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
        "Referer": referer
    }
    resp = requests.get(playlist_url, headers=headers)
    resp.raise_for_status()
    
    segments = []
    base_url = playlist_url.rsplit('/', 1)[0] + '/'
    
    for line in resp.text.splitlines():
        line = line.strip()
        if line and not line.startswith('#') and line.endswith('.ts'):
            segments.append(urljoin(base_url, line))
    return segments

def download_segments(segments: list[str], output_dir: str, referer: str):
    """各.tsセグメントを順次ダウンロード"""
    headers = {
        "User-Agent": "Mozilla/5.0",
        "Referer": referer
    }
    import os
    os.makedirs(output_dir, exist_ok=True)
    
    for i, seg_url in enumerate(segments):
        resp = requests.get(seg_url, headers=headers, timeout=30)
        resp.raise_for_status()
        with open(f"{output_dir}/seg_{i:04d}.ts", "wb") as f:
            f.write(resp.content)
        print(f"Downloaded {i+1}/{len(segments)}")

# 使用例(実際にはページ解析等で.m3u8 URLを取得需要)
playlist = "https://video12345.fc2.com/up/video/xxx/playlist.m3u8?token=abc"
referer = "https://fc2.com/video/xxx"

segs = fetch_m3u8(playlist, referer)
download_segments(segs, "./output", referer)

⚠️ 注意点:FC2は頻繁にトークン発行ロジックやURL署名アルゴリズムを変更します。このコードは「その時点で動作する」保証はありません。本番環境では、セッション維持、403エラー時のリトライ、セグメント整合性チェックなど、より高度な対応が必要になります。


当サービスのアーキテクチャ:なぜ「サーバーに保存しない」設計なのか

fc2_downloader_ja の最大の特徴は、「ユーザーのダウンロードファイルを一切サーバーにキャッシュしない」 という設計思想にあります。これには明確な技術的・法的な理由があります。

1. プライバシーとセキュリティの観点

  • ユーザーがダウンロードした動画のURL、IPアドレス、利用履歴などを記録しない
  • サーバー側にファイルを一時保存しないため、情報漏洩リスクが原理的に排除される
  • すべてクライアントサイドで完結するフロー(またはプロキシ経由のストリーミング転送)を採用

2. 著作権対応の設計

当サービスは「ダウンロード支援ツール」であり、「コンテンツの再配布プラットフォーム」ではありません。技術的には、以下のようなフローで実現しています:

[ユーザーブラウザ] 
    ↓ (1) 動画URLを送信
[当サーバー] 
    ↓ (2) FC2ページから.m3u8プレイリストを取得(キャッシュなし)
    ↓ (3) セグメント一覧を解析し、直接ダウンロード用リンクを生成
[ユーザーブラウザ] 
    ↓ (4) FC2 CDN からセグメントを直接取得 & ffmpeg.wasm で結合

この「中継のみで保存しない」アーキテクチャにより、当サーバーには著作権対象ファイルが一切存在しない 状態を維持できています。これは法的リスクの低減だけでなく、ストレージコストの削減、スケーラビリティの向上にも寄与しています。

3. .ts結合処理の実装方針

HLSストリームの課題は、「数百個のセグメントを1つのMP4に再構築する」という点です。これをユーザーに透過的に処理するため、当サービスでは以下の2つのアプローチを用意しています:

  1. クライアントサイド結合(ffmpeg.wasm)
    WebAssembly版ffmpegをブラウザで実行し、ダウンロード後に自動結合。サーバー負荷ゼロですが、端末性能に依存します。

  2. サーバーサイド・パイプライン結合(限定利用)
    長時間動画向けに、ストリーミング転送中にffmpegでマージ。ただし、一時ファイルはメモリ上で処理し、完了後即時破棄します。

// ffmpeg.wasm による.ts→MP4結合のイメージ
const { createFFmpeg, fetchFile } = require('@ffmpeg/ffmpeg');
const ffmpeg = createFFmpeg({ log: true });

async function mergeTsSegments(segmentBlobs, outputName) {
  await ffmpeg.load();
  
  // 各セグメントを仮想ファイルシステムに書き込み
  for (let i = 0; i < segmentBlobs.length; i++) {
    ffmpeg.FS(`writeFile`, `seg_${i}.ts`, await fetchFile(segmentBlobs[i]));
  }
  
  // concat demuxer で連結(コピーコーデックで再エンコードなし)
  const concatList = segmentBlobs.map((_, i) => `file 'seg_${i}.ts'`).join('\n');
  ffmpeg.FS('writeFile', 'concat.txt', concatList);
  
  await ffmpeg.run('-f', 'concat', '-safe', '0', '-i', 'concat.txt', '-c', 'copy', outputName);
  
  const data = ffmpeg.FS('readFile', outputName);
  return new Blob([data.buffer], { type: 'video/mp4' });
}

実際の運用では、メモリ制限、処理タイムアウト、進捗通知など、プロダクションレベルの配慮が必要です。


実際の使い方:3ステップで完了するシンプル設計

技術的な中身は複雑でも、ユーザー体験はシンプルであるべき。当サービスのUIは以下の3ステップのみで構成されています。

  1. FC2動画のURLをコピー
    例:https://fc2.com/video/xxx

  2. 入力欄に貼り付けて「解析を開始」をクリック
    約3〜10秒でプレイリスト解析完了。利用可能な画質・形式オプションが表示されます。

  3. 希望の形式(MP4 / MP3)を選択して保存
    ブラウザの標準ダウンロード機能を使用するため、追加ソフト不要。

スマホでも同じ操作性

レスポンシブデザインを採用しているため、iPhone / Android のブラウザからも全く同じ手順で利用可能です。iOSの場合、Largeファイルのダウンロード時に「ファイル」アプリ経由での保存が必要になることがありますが、これはOSのセキュリティポリシーによるものです。


技術的なメリット:なぜこのツールが「軽量で高速」なのか

✅ 1. ステートレスなAPI設計

各リクエストが独立して処理されるため、セッション管理やデータベース接続が不要。これにより、サーバーリソースの効率的な利用と、突発的なトラフィック増加への耐性を実現しています。

✅ 2. CDNフレンドリーな配信

動画セグメントはFC2のCDN(*.fc2.com)から直接取得するため、当サーバーの帯域幅を圧迫しません。結合処理が必要な場合も、中間キャッシュを最小限に抑える設計としています。

✅ 3. クライアントサイドでの進捗表示

解析・結合中のステータスは、WebSocketまたはServer-Sent Events(SSE)でリアルタイムにクライアントへ通知。ユーザーが「待たされている感」を感じないよう、技術的に配慮しています。

// SSEによる進捗通知の簡易例
const eventSource = new EventSource('/api/progress?job_id=xxx');
eventSource.onmessage = (e) => {
  const data = JSON.parse(e.data);
  //  { step: "merging", percent: 78, message: "セグメント結合中..." }
  updateProgressBar(data.percent);
  appendLog(data.message);
};

開発者としての想い:オープンな技術を、より多くの人に

私自身、長年ストリーミング配信やメディア処理の開発に携わってきましたが、「技術的にできること」と「ユーザーが実際に使えるもの」の間には、常に大きなギャップがあると感じています。

このツールを作った理由はシンプルです:

「面白いコンテンツを、好きな時に、好きなデバイスで楽しみたい」

という当たり前の欲求を、技術でサポートしたかったからです。

もちろん、FC2のプラットフォームポリシーや著作権法とのバランスは常に意識しています。本サービスは:

  • 公開動画のメタデータのみを処理
  • 会員限定・年齢制限コンテンツには対応しない(認証フロー未実装)
  • 商用利用・再配布を目的とした利用を規約で禁止

といった制限を設けています。技術は中立ですが、その使い方は責任を持って設計すべきだと考えています。


Qiita読者への技術的プレゼント:自分で作ってみるためのヒント

もし「自分でも似たツールを作ってみたい」と思われた方に向けて、学習リソースをいくつかご紹介します。

🔍 参考になる技術スタック

用途 推奨技術
HTTPクライアント requests (Python), axios (Node.js)
HTML/プレイリスト解析 BeautifulSoup, lxml, 正規表現
動画結合処理 ffmpeg, ffmpeg.wasm, fluent-ffmpeg
トークン管理 セッション維持、Cookieジャー、リトライロジック
フロントエンド Next.js + Tailwind, Streamlit(プロトタイプ用)
非同期ジョブ asyncio + aiohttp, BullMQ + Redis

📚 学習に役立つリソース

💡 実装時のアドバイス

  1. User-AgentとRefererの適切な設定:FC2はボット対策が厳しめ。ブラウザと見なされるヘッダーが必須。
  2. トークン有効期限の扱い:.m3u8 URLに含まれるトークンには有効期限がある場合が多い。タイムアウト時の再取得ロジックを実装しましょう。
  3. レート制限の尊重:連続リクエストは避け、time.sleep() などで間隔を空ける。429エラーが出たら要注意。
  4. エラーハンドリングの充実:ネットワークエラー、403、セグメント欠損など、想定される失敗ケースを網羅し、ユーザーに分かりやすいメッセージを表示。

おわりに:技術は、誰かの「ちょっと嬉しい」を創れる

最後になりましたが、fc2_downloader_ja は、個人開発者が「自分の不便を自分で解決した」ことから始まったプロジェクトです。

Qiitaのコミュニティでは、技術的な深掘りと実用性のバランスが取れた記事が好まれる傾向があります。本記事が、単なる「ツール紹介」ではなく、「技術的な仕組みへの理解」を深めるきっかけになれば幸いです。

もし実際に使ってみて、「ここが分かりにくい」「こういう機能が欲しい」といったフィードバックがありましたら、ぜひサイトの問い合わせフォームからご連絡ください。オープンな技術開発は、ユーザーとの対話から進化していくものと信じています。


📌 免責事項
本サービスは、FC2, Inc. とは一切関係ありません。
ダウンロードしたコンテンツの利用は、必ず著作権法および各プラットフォームの利用規約に従ってください。
個人での視聴・学習目的以外の利用(再配布、商用利用、改変など)はご遠慮ください。


タグ: #FC2 #動画ダウンロード #HLS #Webスクレイピング #Python #JavaScript #ffmpeg #API解析 #Qiita #技術記事

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?