Google ColabでPDFスライドを動画化する手順

Google ColabでPDFスライドを動画化する方法についての記事を書きました。PowerPoint、Keynote、Google Slidesなど様々なソフトで作ったスライドをPDF経由で動画化するシンプルな方法です。フォント埋め込みの問題を解決し、特別なソフトなしで高品質な動画が作れます。ナレーションなしの自動スライドショーが必要な方に役立つかもしれません。🍛

## はじめに
プレゼンテーション資料を動画形式に変換したいケースは多いでしょう。特にナレーションなしで、スライドだけをタイミングを決めて順番に表示する動画を簡単に作成したい場合に、Google Colabを使った方法が便利です。

この記事では、**PDF形式のスライド**をアップロードして、画像に変換し、最終的に動画ファイル(.mp4)として出力する手順を紹介します。PowerPoint、Keynote、Google スライド、LibreOfficeなど、様々なプレゼンテーションソフトからPDFにエクスポートしたファイルを使用できます。この方法なら日本語フォントの問題が発生せず、高品質な動画を作成できます。

## 全体の流れ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/801c9818-8c0e-4e5a-9035-8e26b6423c46.png)

1. プレゼンテーションスライドをPDFにエクスポート
2. Google Colabで必要なツールをインストール
3. 作業用フォルダを作成
4. PDFを画像に変換
5. 画像から動画を作成

## 1. プレゼンテーションスライドをPDFにエクスポート

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/e1b89aef-d82a-4b84-bb6e-c7913e6a5d70.png)

まず最初に、使用しているプレゼンテーションソフトからスライドをPDF形式でエクスポートします。主なソフトでの方法は以下の通りです：

### PowerPoint
1. 「ファイル」→「エクスポート」→「PDF/XPSの作成」を選択
2. PDFファイルとして保存

### Keynote
1. 「ファイル」→「書き出す」→「PDF」を選択
2. PDFファイルとして保存

### Google スライド
1. 「ファイル」→「ダウンロード」→「PDF文書(.pdf)」を選択
2. PDFファイルとしてダウンロード

### LibreOffice Impress
1. 「ファイル」→「エクスポート」→「PDFとしてエクスポート」を選択
2. PDFファイルとして保存

**ポイント**:
- PDFエクスポートの際は、高品質設定を選択すると、より鮮明な動画になります
- フォントが埋め込まれるオプションがある場合は、それを有効にすると良いでしょう
- アニメーションや特殊効果はPDFでは静止画として書き出されます

## 2. Google Colabで必要なツールをインストール

Google Colabを開き、以下のコードを実行して必要なツールをインストールします。

```python
# 必要なツールのインストール
!apt-get update 
!apt-get install -y poppler-utils
```

ここでは`poppler-utils`のみをインストールします。これはPDFを画像に変換するために必要なツールです。

## 3. 作業用フォルダを作成

ファイルを整理するために、作業用の専用フォルダを作成します。

```python
# 作業用フォルダを作成
import os

# 既存のフォルダを削除してから作成（冪等性のため）
!rm -rf slide_work
!mkdir -p slide_work

# 作業ディレクトリを表示
print("作業ディレクトリを作成しました：")
!ls -la | grep slide_work
```

## 4. PDFを画像に変換

次に、Google Colabにスライドをアップロードし、PDFから画像に変換します。画像ファイルは作業用フォルダに保存します。

```python
# PDFファイルをアップロード
from google.colab import files
uploaded = files.upload()  # ここでPDFファイルをアップロードします

# アップロードしたファイル名を取得
pdf_filename = list(uploaded.keys())[0]
print(f"アップロードされたファイル: {pdf_filename}")

# PDFファイルを作業フォルダにコピー
!cp "{pdf_filename}" slide_work/
pdf_path = os.path.join("slide_work", pdf_filename)

# 作業フォルダに移動してPDFを高解像度の画像に変換
!cd slide_work && pdftoppm -png -r 300 "{pdf_filename}" slide

# 生成されたファイルの確認
print("\n変換された画像ファイル：")
!ls -la slide_work/slide*.png
```

このコードを実行すると、まずファイルアップロード用のボタンが表示され、PDFファイルをアップロードできます。アップロードが完了すると、ファイルを作業フォルダにコピーし、`pdftoppm`コマンドがPDFファイルを高解像度のPNG画像に変換します。結果として、「slide-1.png」、「slide-2.png」のように、PDFの各ページがPNG画像として作業フォルダに保存されます。

**ポイント**:
- `-r 300`オプションで300dpiの解像度を指定しています。より高品質が必要な場合は値を大きくしてください
- PDFファイル名にスペースや特殊文字が含まれていても、引用符（""）で囲むことで正しく処理されます
- 作業フォルダ内で処理することで、メインディレクトリを汚さず整理された状態を保てます

## 5. 画像から動画を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/44517dd3-1a2d-4141-91d5-68b6893dd3d6.png)

最後に、OpenCVライブラリを使用して、画像ファイルを1つの動画ファイルにまとめます。動画ファイルも作業フォルダに保存します。

```python
import cv2
import glob
import os

def create_video(image_folder, output_path, fps=1):
    """
    指定フォルダ内の画像ファイルから動画を作成する関数
    
    Parameters:
    image_folder (str): 画像ファイルのあるフォルダパス
    output_path (str): 出力する動画ファイルのパス
    fps (float): フレームレート（1秒あたりの画像数）
    
    Returns:
    bool: 成功した場合はTrue、失敗した場合はFalse
    """
    # 画像ファイルの取得（.jpg, .png対応）
    images = []
    for ext in ('*.jpg', '*.png'):
        images.extend(glob.glob(os.path.join(image_folder, ext)))
    
    # スライド番号でソート（slide-1.png, slide-2.png, ...）
    images = sorted(images, key=lambda x: int(x.split('-')[-1].split('.')[0]) if x.split('-')[-1].split('.')[0].isdigit() else 0)
    
    if not images:
        print("画像が見つかりません")
        return False
    
    print(f"変換する画像: {len(images)}枚")
    print(f"画像リスト（最初の5枚まで）: {images[:min(5, len(images))]}")
    
    # 最初の画像から動画サイズを取得
    frame = cv2.imread(images[0])
    height, width, layers = frame.shape
    
    # 動画ファイルの設定
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
    
    # 各画像を動画に追加
    for image in images:
        frame = cv2.imread(image)
        if frame is not None:
            video.write(frame)
        else:
            print(f"警告: 画像 {image} を読み込めませんでした")
    
    video.release()
    print(f"動画を保存しました: {output_path}")
    return True

# 動画を作成する（1スライドあたり1秒）
output_video = os.path.join("slide_work", "output.mp4")
success = create_video("slide_work", output_video, fps=1)

# 動画が作成された場合のみダウンロード
if success:
    files.download(output_video)
else:
    print("動画の作成に失敗したため、ダウンロードをスキップします")
    
    # デバッグ情報の表示
    print("\nデバッグ情報:")
    !ls -la slide_work
```

この関数では、作業フォルダ内の生成されたPNG画像を順番に読み込み、1つの動画ファイルに結合します。デフォルトでは、1スライドあたり1秒の表示時間に設定されています。

## 完全なGoogle Colabスクリプト例

以下が、すべてを一つにまとめたGoogle Colabでの完全なスクリプトです。このスクリプトをコピーしてColabで実行すると、PDFをアップロードから動画作成までを一連の流れで行えます。

```python
# 1. 必要なツールのインストール
!apt-get update 
!apt-get install -y poppler-utils

# 2. 作業用フォルダを作成
import os

# 既存のフォルダを削除してから作成（冪等性のため）
!rm -rf slide_work
!mkdir -p slide_work

# 作業ディレクトリを表示
print("作業ディレクトリを作成しました：")
!ls -la | grep slide_work

# 3. PDFファイルをアップロード
from google.colab import files
uploaded = files.upload()  # ここでPDFファイルをアップロードします

# アップロードしたファイル名を取得
pdf_filename = list(uploaded.keys())[0]
print(f"アップロードされたファイル: {pdf_filename}")

# PDFファイルを作業フォルダにコピー
!cp "{pdf_filename}" slide_work/
pdf_path = os.path.join("slide_work", pdf_filename)

# 4. 作業フォルダに移動してPDFを高解像度の画像に変換
!cd slide_work && pdftoppm -png -r 300 "{pdf_filename}" slide

# 生成されたファイルの確認
print("\n変換された画像ファイル：")
!ls -la slide_work/slide*.png

# 5. 画像から動画を作成する関数
import cv2
import glob
import os

def create_video(image_folder, output_path, fps=1):
    """
    指定フォルダ内の画像ファイルから動画を作成する関数
    
    Parameters:
    image_folder (str): 画像ファイルのあるフォルダパス
    output_path (str): 出力する動画ファイルのパス
    fps (float): フレームレート（1秒あたりの画像数）
    
    Returns:
    bool: 成功した場合はTrue、失敗した場合はFalse
    """
    # 画像ファイルの取得（.jpg, .png対応）
    images = []
    for ext in ('*.jpg', '*.png'):
        images.extend(glob.glob(os.path.join(image_folder, ext)))
    
    # スライド番号でソート（slide-1.png, slide-2.png, ...）
    images = sorted(images, key=lambda x: int(x.split('-')[-1].split('.')[0]) if x.split('-')[-1].split('.')[0].isdigit() else 0)
    
    if not images:
        print("画像が見つかりません")
        return False
    
    print(f"変換する画像: {len(images)}枚")
    print(f"画像リスト（最初の5枚まで）: {images[:min(5, len(images))]}")
    
    # 最初の画像から動画サイズを取得
    frame = cv2.imread(images[0])
    height, width, layers = frame.shape
    
    # 動画ファイルの設定
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
    
    # 各画像を動画に追加
    for image in images:
        frame = cv2.imread(image)
        if frame is not None:
            video.write(frame)
        else:
            print(f"警告: 画像 {image} を読み込めませんでした")
    
    video.release()
    print(f"動画を保存しました: {output_path}")
    return True

# 6. 動画を作成する（1スライドあたり1秒）
output_video = os.path.join("slide_work", "output.mp4")
success = create_video("slide_work", output_video, fps=1)

# 7. 作成した動画をダウンロード（成功した場合のみ）
if success:
    files.download(output_video)
    
    # 最後に作業フォルダの内容を表示
    print("\n作業フォルダの内容：")
    !ls -la slide_work
else:
    print("動画の作成に失敗したため、ダウンロードをスキップします")
    
    # デバッグ情報の表示
    print("\nデバッグ情報:")
    !ls -la slide_work
```

実行例:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/8d5b5ee8-7fa6-4840-8043-74f0d99b9615.png)


## PDFからの変換のメリット

どのプレゼンテーションソフトを使用するにしても、PDF経由で動画化することには以下のメリットがあります：

1. **フォント埋め込み**: PDFはフォントを埋め込むため、日本語フォントの問題が起こりにくい
2. **互換性**: どのプレゼンテーションソフトからでも同じ手順で変換できる
3. **高品質**: 高解像度のPDFにすることで、高品質な動画に変換可能
4. **軽量**: 画像へ直接変換するより、PDFの方が通常ファイルサイズが小さい

## カスタマイズオプション

### 表示時間の調整

スライドの表示時間を調整したい場合は、`create_video`関数の`fps`パラメータを変更します。
- `fps=0.5`: 1スライドあたり2秒
- `fps=1`: 1スライドあたり1秒（デフォルト）
- `fps=2`: 1スライドあたり0.5秒（半秒）

### 画質の調整

より高品質な画像にしたい場合は、`pdftoppm`コマンドの`-r`オプションの値を増やします。
```python
# より高解像度（600dpi）での変換
!cd slide_work && pdftoppm -png -r 600 "{pdf_filename}" slide
```

高解像度にすると画質は向上しますが、ファイルサイズが大きくなり、処理時間も長くなります。

### 作業フォルダの変更

作業フォルダの名前を変更したい場合は、スクリプト内の`slide_work`を任意の名前に変更してください。
```python
work_dir = "my_slides_project"  # 作業フォルダ名を変更
!rm -rf {work_dir}
!mkdir -p {work_dir}
```

### 各スライドの表示時間を変える

すべてのスライドで同じ表示時間ではなく、スライドごとに異なる表示時間を設定したい場合は、関数を以下のように修正します：

```python
def create_video_with_variable_duration(image_folder, output_path, durations):
    """
    スライドごとに異なる表示時間を設定して動画を作成する関数
    
    Parameters:
    image_folder (str): 画像ファイルのあるフォルダパス
    output_path (str): 出力する動画ファイルのパス
    durations (list): 各スライドの表示時間（秒）のリスト
    
    Returns:
    bool: 成功した場合はTrue、失敗した場合はFalse
    """
    # 画像ファイルの取得とソート
    images = []
    for ext in ('*.jpg', '*.png'):
        images.extend(glob.glob(os.path.join(image_folder, ext)))
    images = sorted(images, key=lambda x: int(x.split('-')[-1].split('.')[0]) if x.split('-')[-1].split('.')[0].isdigit() else 0)
    
    if not images:
        print("画像が見つかりません")
        return False
    
    # 最初の画像から動画サイズを取得
    frame = cv2.imread(images[0])
    height, width, layers = frame.shape
    
    # 動画ファイルの設定（fps=1で固定し、フレーム数で調整）
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video = cv2.VideoWriter(output_path, fourcc, 1, (width, height))
    
    # 各画像を指定された時間分、動画に追加
    for i, image in enumerate(images):
        if i < len(durations):
            duration = durations[i]
        else:
            duration = 1  # デフォルト値
        
        frame = cv2.imread(image)
        if frame is None:
            print(f"警告: 画像 {image} を読み込めませんでした")
            continue
        
        # 指定時間分だけ同じ画像を繰り返し追加
        for _ in range(int(duration)):
            video.write(frame)
    
    video.release()
    print(f"動画を保存しました: {output_path}")
    return True

# 使用例：スライドごとの表示時間を指定（秒単位）
durations = [3, 5, 2, 4, 3]  # 例：1枚目は3秒、2枚目は5秒...
create_video_with_variable_duration("slide_work", "slide_work/output_variable.mp4", durations)
```

## プレゼンテーションソフト別のPDF出力のコツ

### PowerPoint
- 「ファイル」→「エクスポート」→「PDF/XPSの作成」で、オプションとして「発行対象」を「高品質」に設定
- 「ISO 19005-1に準拠(PDF/A)」オプションをオフにすると、ファイルサイズを小さくできる
- 高品質な画像を維持したい場合は、「ドキュメントのプロパティを保持する」を有効にする

### Keynote
- 「ファイル」→「書き出す」→「PDF」で、「画質」を「最高」に設定
- 「各スライドごとにメモを含める」オプションをオフにして、スライドのみを出力

### Google スライド
- 「ファイル」→「ダウンロード」→「PDF文書(.pdf)」を選択
- 印刷設定で「ページあたり1スライド」を選択
- 必要に応じて「背景を含める」オプションを確認

### LibreOffice Impress
- 「ファイル」→「エクスポート」→「PDFとしてエクスポート」を選択
- 「全般」タブで、「品質」を「印刷」に設定
- 「画像」タブで、「lossy compression」の値を小さくし、「画質」を高く設定

## トラブルシューティング

### 「画像が見つかりません」というエラーが表示される場合

1. `!ls -la slide_work/slide*.png` コマンドを実行して、画像ファイルが正しく生成されているか確認してください。
2. ファイルが見つからない場合は、以下を試してみてください：
   ```python
   # 作業フォルダ内のすべてのファイルを確認
   !ls -la slide_work
   
   # PDFから画像への変換を別の方法で試す
   !cd slide_work && pdftocairo -png "{pdf_filename}" slide
   ```

### PDFの変換に失敗する場合

PDFファイルによっては、変換に失敗する場合があります。その場合は、別のツールを試してみてください：

```python
# ImageMagickを使用した変換（代替方法）
!apt-get install -y imagemagick
!cd slide_work && convert -density 300 "{pdf_filename}" -quality 100 slide-%d.png
```

### 「Cannot find file: output.mp4」というエラーが表示される場合

このエラーは通常、動画の作成プロセスが失敗したことを意味します。上記のスクリプトでは、動画作成の成功/失敗を確認し、成功した場合のみダウンロードを試みるようになっています。

### 画像ファイルの名前がソートされない場合

画像ファイルのソートロジックがうまく機能しない場合は、以下のようにソート関数を変更してみてください：

```python
# 別のソート方法
import re
def natural_sort_key(s):
    # 数字部分を数値として扱うための関数
    return [int(text) if text.isdigit() else text.lower() 
            for text in re.split(r'(\d+)', s)]
            
images = sorted(images, key=natural_sort_key)
```

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/9cb81309-bb88-4ba9-8a1e-acb8e732e2a6.png)

プレゼンテーションスライドをPDF経由で動画化する方法を紹介しました。この方法は、PowerPoint、Keynote、Google スライド、LibreOffice Impressなど、様々なプレゼンテーションソフトで作成したスライドに適用できます。

この方法のメリットは以下の通りです：

1. **フォントの問題を回避**: PDF形式は各種フォントを正しく保持
2. **高品質**: 高解像度のPDF書き出しにより、クリアな画質を維持
3. **ソフト非依存**: どのプレゼンテーションソフトを使っても同じ方法で変換可能
4. **簡単**: 特別なソフトウェアをインストールせず、Google Colabのみで完結
5. **整理された環境**: 作業用フォルダを使用することで、ファイルを整理しながら作業可能

もっと複雑な効果やトランジションが必要な場合は、PowerPointの「スライドショーの記録」機能や、専用の動画編集ソフトウェアの使用を検討してみてください。

この記事が皆さんのプレゼンテーション資料の活用の幅を広げる一助となれば幸いです。

## 出力例

https://speakerdeck.com/takataka365/karenozuo-rifang

<iframe width="560" height="315" src="https://www.youtube.com/embed/vXirkBOdhlE?si=uGf5GcG2lFLr2h6w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>