Google Colabを使用してYouTube動画からスライドを抽出しPDFにまとめる方法 (python)

# Google Colabを使用してYouTube動画からスライドを抽出しPDFにまとめる方法 (python)

## はじめに

オンライン学習やウェビナーの普及に伴い、YouTubeには教育的な動画が数多くアップロードされています。これらの動画の中には、スライドを使用したプレゼンテーションも多く、後で復習したい場合にスライドだけを簡単に参照できれば便利です。

本記事では、Google Colaboratory（通称：Google Colab）を使用して、YouTube動画からスライドを自動的に抽出し、PDFにまとめるPythonスクリプトの開発過程と使用方法について詳しく解説します。このツールを使用することで、長時間の動画から効率的にキーポイントを抽出し、復習や参照に適した形式で保存することができます。

## Google Colabとは

Google Colabは、Googleが提供する無料のクラウドベースのJupyter Notebook環境です。主な特徴として：

1.  ブラウザ上で直接Pythonコードを実行できる
2.  GPUやTPUなどの高性能ハードウェアを無料で利用できる
3.  多くのデータサイエンス関連ライブラリがプリインストールされている
4.  Google Driveとの連携が容易

これらの特徴により、本プロジェクトのような画像処理や機械学習タスクに適しています。また、環境構築の手間がなく、高性能な計算リソースを利用できるため、効率的に作業を進められます。

## プロジェクトの目的

このプロジェクトの主な目的は以下の通りです：

1.  YouTube動画から定期的にフレームを抽出する
2.  抽出したフレームから重複や類似したものを除去し、ユニークなスライドのみを取得する
3.  取得したスライドをPDF形式でまとめ、簡単に参照できるようにする

これにより、長時間の講義やプレゼンテーション動画から、核となる情報を素早く抽出し、効率的な学習や復習を可能にします。

## 開発の流れ

スクリプトの開発は以下の段階を経て行われました：

1.  動画のダウンロード
2.  フレームの抽出
3.  類似フレームの除去
4.  PDFの作成

それぞれの段階で直面した課題と、その解決方法について詳しく説明します。

### 1. 動画のダウンロード

YouTubeの動画をダウンロードするために、`yt-dlp`ライブラリを使用しました。このライブラリは`youtube-dl`の改良版で、より高速で安定しています。

Google Colab環境では、以下のコマンドでライブラリをインストールできます：

```python
!pip install yt-dlp
```

動画をダウンロードする関数は以下の通りです：

```python
import yt_dlp

def download_video(url, output_path='video.mp4'):
    ydl_opts = {
        'outtmpl': output_path,
        'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
    }
    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
        try:
            ydl.download([url])
        except Exception as e:
            print(f"動画のダウンロード中にエラーが発生しました: {str(e)}")
            return False
    return True
```

この関数は、指定されたURLの動画を最高品質でダウンロードします。`ydl_opts`でダウンロードオプションを指定しており、ここでは最高品質の動画と音声を別々にダウンロードし、自動的にマージするように設定しています。

### 2. フレームの抽出

動画からフレームを抽出するために、OpenCVライブラリ(`cv2`)を使用しました。Google Colabには既にインストールされていますが、最新版が必要な場合は以下のコマンドでインストールできます：

```python
!pip install opencv-python-headless
```

フレーム抽出の関数は以下の通りです：

```python
import cv2
import numpy as np

def frame_diff(frame1, frame2):
    gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
    gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
    
    diff = cv2.absdiff(gray1, gray2)
    _, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)
    
    return np.sum(thresh) / thresh.size

def extract_unique_frames(video_path, interval=10):
    video = cv2.VideoCapture(video_path)
    fps = video.get(cv2.CAP_PROP_FPS)
    frame_interval = int(fps * interval)
    
    _, prev_frame = video.read()
    frame_count = 0
    total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
    
    unique_frames = []
    
    for i in range(0, total_frames, frame_interval):
        video.set(cv2.CAP_PROP_POS_FRAMES, i)
        ret, curr_frame = video.read()
        if not ret:
            break
        
        diff = frame_diff(prev_frame, curr_frame)
        
        if diff > 0.03 or frame_count == 0:  # 3%以上の変化、または最初のフレーム
            unique_frames.append(curr_frame)
            print(f"{frame_count + 1}枚目のフレームを保存しました（{i/fps:.1f}秒）")
            frame_count += 1
            prev_frame = curr_frame
        else:
            print(f"類似フレームをスキップしました（{i/fps:.1f}秒）")
    
    return unique_frames
```

この関数は10秒ごとにフレームを抽出し、前のフレームと比較して十分な変化がある場合のみ保存します。`frame_diff`関数は2つのフレーム間の差分を計算し、その差分が閾値（現在は3%）を超える場合に新しいフレームとして保存します。

### 3. 類似フレームの除去

フレーム抽出の段階で既に類似フレームの除去を行っていますが、さらに精度を上げたい場合は、より高度な画像比較アルゴリズムを導入することができます。例えば、特徴点マッチングや機械学習を用いた画像分類などが考えられます。

### 4. PDFの作成

抽出したフレームをPDFにまとめるために、`reportlab`と`PyPDF2`ライブラリを使用しました。Google Colab環境では、以下のコマンドでこれらのライブラリをインストールできます：

```python
!pip install reportlab PyPDF2
```

PDF作成の関数は以下の通りです：

```python
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, landscape
from reportlab.lib.utils import ImageReader
from PyPDF2 import PdfMerger
import io
from PIL import Image

def resize_image(image, max_size=1000):
    h, w = image.shape[:2]
    if max(h, w) > max_size:
        if h > w:
            new_h, new_w = max_size, int(max_size * w / h)
        else:
            new_h, new_w = int(max_size * h / w), max_size
        image = cv2.resize(image, (new_w, new_h))
    return image

def create_pdf_page(args):
    image, page_size, page_number = args
    img_buffer = io.BytesIO()
    Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)).save(img_buffer, format='JPEG')
    img_buffer.seek(0)
    
    packet = io.BytesIO()
    can = canvas.Canvas(packet, pagesize=page_size)
    img = ImageReader(img_buffer)
    can.drawImage(img, 0, 0, width=page_size[0], height=page_size[1], preserveAspectRatio=True, anchor='c')
    can.setFont("Helvetica", 12)
    can.drawString(30, 30, f"Page {page_number}")
    can.showPage()
    can.save()
    
    packet.seek(0)
    return packet.getvalue()

def create_pdf(frames, pdf_name='slides.pdf'):
    page_size = landscape(letter)
    
    with concurrent.futures.ThreadPoolExecutor() as executor:
        pdf_pages = list(executor.map(create_pdf_page, [(resize_image(frame), page_size, i+1) for i, frame in enumerate(frames)]))
    
    merger = PdfMerger()
    for page in pdf_pages:
        merger.append(io.BytesIO(page))
    
    with open(pdf_name, 'wb') as f:
        merger.write(f)
    
    print(f"PDFファイル '{pdf_name}' を作成しました。")
```

この過程で、各ページに番号を追加し、画像のアスペクト比を維持しながらページに合わせてリサイズする処理も行っています。また、`concurrent.futures.ThreadPoolExecutor`を使用して並列処理を行い、PDF作成の速度を向上させています。

## 完全なコード

以下に、Google Colab環境ですぐに実行できる完全なコードを示します：

```python
!pip install yt-dlp opencv-python-headless numpy Pillow reportlab PyPDF2

import yt_dlp
import cv2
import numpy as np
from PIL import Image
import io
from google.colab import files
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, landscape
from reportlab.lib.utils import ImageReader
import concurrent.futures
from PyPDF2 import PdfMerger

def download_video(url, output_path='video.mp4'):
    ydl_opts = {
        'outtmpl': output_path,
        'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
    }
    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
        try:
            ydl.download([url])
        except Exception as e:
            print(f"動画のダウンロード中にエラーが発生しました: {str(e)}")
            return False
    return True

def frame_diff(frame1, frame2):
    gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
    gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
    
    diff = cv2.absdiff(gray1, gray2)
    _, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)
    
    return np.sum(thresh) / thresh.size

def extract_unique_frames(video_path, interval=10):
    video = cv2.VideoCapture(video_path)
    fps = video.get(cv2.CAP_PROP_FPS)
    frame_interval = int(fps * interval)
    
    _, prev_frame = video.read()
    frame_count = 0
    total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
    
    unique_frames = []
    
    for i in range(0, total_frames, frame_interval):
        video.set(cv2.CAP_PROP_POS_FRAMES, i)
        ret, curr_frame = video.read()
        if not ret:
            break
        
        diff = frame_diff(prev_frame, curr_frame)
        
        if diff > 0.03 or frame_count == 0:  # 3%以上の変化、または最初のフレーム
            unique_frames.append(curr_frame)
            print(f"{frame_count + 1}枚目のフレームを保存しました（{i/fps:.1f}秒）")
            frame_count += 1
            prev_frame = curr_frame
        else:
            print(f"類似フレームをスキップしました（{i/fps:.1f}秒）")
    
    return unique_frames

def resize_image(image, max_size=1000):
    h, w = image.shape[:2]
    if max(h, w) > max_size:
        if h > w:
            new_h, new_w = max_size, int(max_size * w / h)
        else:
            new_h, new_w = int(max_size * h / w), max_size
        image = cv2.resize(image, (new_w, new_h))
    return image

def create_pdf_page(args):
    image, page_size, page_number = args
    img_buffer = io.BytesIO()
    Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)).save(img_buffer, format='JPEG')
    img_buffer.seek(0)
    
    packet = io.BytesIO()
    can = canvas.Canvas(packet, pagesize=page_size)
    img = ImageReader(img_buffer)
    can.drawImage(img, 0, 0, width=page_size[0], height=page_size[1], preserveAspectRatio=True, anchor='c')
    can.setFont("Helvetica", 12)
    can.drawString(30, 30, f"Page {page_number}")
    can.showPage()
    can.save()
    
    packet.seek(0)
    return packet.getvalue()

def create_pdf(frames, pdf_name='slides.pdf'):
    page_size = landscape(letter)
    
    with concurrent.futures.ThreadPoolExecutor() as executor:
        pdf_pages = list(executor.map(create_pdf_page, [(resize_image(frame), page_size, i+1) for i, frame in enumerate(frames)]))
    
    merger = PdfMerger()
    for page in pdf_pages:
        merger.append(io.BytesIO(page))
    
    with open(pdf_name, 'wb') as f:
        merger.write(f)
    
    print(f"PDFファイル '{pdf_name}' を作成しました。")

# メイン処理
video_url = 'https://www.youtube.com/watch?v=qFLnag9MXw4'  # ここにYouTube動画のURLを入力してください

print("動画のダウンロードを開始します...")
if download_video(video_url):
    print("動画のダウンロードが完了しました。ユニークなフレームの抽出を開始します...")
    unique_frames = extract_unique_frames('video.mp4', interval=10)  # 10秒ごとにフレームをチェック
    print(f"合計{len(unique_frames)}枚のユニークなフレーム（スライド）を抽出しました")

    print("PDFファイルを作成しています...")
    create_pdf(unique_frames)

    files.download('slides.pdf')
    print("処理が完了しました。'slides.pdf'ファイルがダウンロードされます。このファイルをローカルのダウンロードフォルダで見つけることができます。")
else:
    print("動画のダウンロードに失敗したため、処理を中止しました。")
```

## コードの使用方法

1.  Google Colabで新しいノートブックを開きます。
    *   ブラウザで https://colab.research.google.com/ にアクセスします。
    *   「新しいノートブック」を選択します。

2.  上記のコード全体をコピーし、ノートブックの1つのセルに貼り付けます。

3.  `video_url`変数に処理したいYouTube動画のURLを設定します。
    *   例: `video_url = 'https://www.youtube.com/watch?v=abcdefghijk'`

4.  セルを実行します。
    *   Shift + Enterキーを押すか、セルの左側にある再生ボタンをクリックします。

5.  処理が完了するまで待機します。
    *   動画のダウンロード、フレームの抽出、PDFの作成の各段階で進捗が表示されます。

6.  処理が完了すると、ブラウザが自動的にPDFファイルのダウンロードを開始します。
    *   ダウンロードしたPDFファイルは通常、コンピュータの「ダウンロード」フォルダに保存されます。

## 注意点

1.  **著作権の遵守**: このツールは個人的な学習目的での使用を想定しています。著作権法を遵守し、許可なく配布や商用利用をしないようにしてください。

2.  **処理時間**: 動画の長さや品質によっては、処理に時間がかかる場合があります。特に長時間の動画を処理する際はご注意ください。

3.  **Google Colabのセッション制限**: Google Colabのセッションには時間制限があります（通常12時間）。非常に長い動画や多数の動画を連続して処理する場合は、セッションが切断される可能性があります。

4.  **ストレージ容量**: 一時的にダウンロードした動画やフレーム画像がGoogle Colabの環境に保存されます。大量の動画を処理する場合は、ストレージ容量に注意してください。

5.  **インターネット接続**: 動画のダウンロードとPDFのアップロードには安定したインターネット接続が必要です。

6.  **YouTube の利用規約**: YouTubeの利用規約を遵守してください。動画のダウンロードが許可されていない場合もあります。

7.  **画質と精度**: 抽出されるスライドの品質は元動画の品質に依存します。また、スライドの変更を完璧に検出できない場合もあります。

## 発展的な使用方法と改善案

1.  **OCRの導入**: 抽出したスライドに対してOCR（光学文字認識）を適用することで、テキスト検索可能なPDFを作成できます。これにより、キーワード検索や内容の分析が容易になります。

2.  **機械学習による精度向上**: 画像分類や物体検出などの機械学習技術を用いて、スライドの変更をより正確に検出することができます。例えば、スライドのレイアウトや内容の変化を学習したモデルを使用することで、微妙な変更も捉えられるようになります。

3.  **自動目次生成**: スライドのタイトルや見出しを自動的に認識し、PDFの目次を生成する機能を追加できます。これにより、長いプレゼンテーションの内容を素早く把握することが可能になります。

4.  **複数動画の一括処理**: 複数のYouTube動画URLを入力として受け取り、一括で処理する機能を追加できます。これにより、関連する一連の講義やプレゼンテーションを効率的に処理することができます。

5.  **カスタマイズオプションの追加**: フレーム抽出の間隔、類似度の閾値、PDFのレイアウトなど、ユーザーが自由にカスタマイズできるオプションを追加することで、より柔軟な使用が可能になります。

6.  **ウェブアプリケーション化**: このツールをウェブアプリケーションとして実装することで、プログラミングの知識がなくても誰でも簡単に使用できるようになります。

7.  **音声トランスクリプションの追加**: 動画の音声をテキストに変換し、各スライドに対応する説明文として追加することで、より包括的な学習資料を作成できます。

## まとめ

このツールを使用することで、YouTube動画からスライドを効率的に抽出し、PDFとしてまとめることができます。Google Colabの環境を利用することで、特別な環境構築なしに高度な処理を実行できる利点があります。

学習や研究のための資料作成に活用できるこのツールを、ぜひ試してみてください。また、提案した発展的な使用方法や改善案を参考に、より高度で効率的なツールへと発展させることもできるでしょう。

プログラミングやデータサイエンスに興味のある方は、このコードをベースに機能の拡張や最適化にチャレンジしてみることをお勧めします。新しいアイデアを実装することで、より使いやすく、より多くの人々に役立つツールを作り出すことができるかもしれません。

最後に、このようなツールを使用する際は、常に著作権法を遵守し、倫理的な使用を心がけることが重要です。教育や個人的な学習目的での使用に留め、コンテンツ制作者の権利を尊重しましょう。