👓

Google Apps ScriptとChatGPTでGoogleスライドを自動生成する

2024/06/11に公開

はじめに

日々の業務でプレゼンテーション作成に追われていませんか?
会議やプロジェクト報告のたびに、
スライド作成に多くの時間を費やしている方も多いのではないでしょうか。
そんな解決策として、近ごろはスライド自動作成サービスが多く展開されているように思います。

今回はその技術的な一端に触れてみたく、
Google Apps Script(GAS)でGoogleスライドの自動生成をやってみます。

SaaSで展開されている有料サービスのように機能リッチではないですが、
プレゼンテーション作成の時間を短縮でき、生産性を少し向上できればと思います。

構成

プレゼン内容の生成にはChatGPT、スライド生成にはGASを利用します。
今回はGUIで出力した結果をmarkdown形式の文字列としてGASに読み込ませます。

GUIとAPIにおけるレスポンス形式

ChatGPTは、GUIとAPI両方でmarkdown形式のレスポンスを取得することが可能です。
google geminiでは、APIのみでmarkdown形式のレスポンスを取得することが可能です。
今回はGUIでの結果を使うのでChatGPTを利用します。

ドキュメントからスライドを生成するスクリプト

function createSlidesFromMarkdownText() {
  // Markdownテキスト
  const markdownText = `
# クラウドストレージの営業資料

## 目次
1. [概要](#概要)
2. [メリット](#メリット)
3. [主な機能](#主な機能)
4. [料金プラン](#料金プラン)
5. [導入事例](#導入事例)
6. [お問い合わせ](#お問い合わせ)

## 概要
クラウドストレージは、データをインターネット上に保存し、どこからでもアクセス可能にするサービスです。安全かつ柔軟なデータ管理を提供し、業務効率を大幅に向上させます。

## メリット
### 1. コスト削減
物理的なサーバーの維持費や初期導入コストを削減できます。

### 2. データの安全性
高いセキュリティ対策とバックアップ機能により、データ紛失のリスクを最小限に抑えます。

### 3. スケーラビリティ
ビジネスの成長に応じて、ストレージ容量を柔軟に拡張できます。

### 4. アクセスの容易さ
インターネット接続があれば、どこからでもデータにアクセス可能です。

## 主な機能
- **データ同期** 複数デバイス間でのデータ同期が可能。
- **共有機能** ファイルやフォルダを簡単に共有でき、コラボレーションを促進。
- **バージョン管理** ファイルの過去のバージョンを保存し、必要に応じて復元。
- **アクセス制限** ユーザーごとにアクセス権限を設定可能。

## 料金プラン
### 基本プラン
- **月額**: ¥1,000
- **容量**: 100GB

### ビジネスプラン
- **月額**: ¥5,000
- **容量**: 1TB
- **追加機能**: 拡張セキュリティ、詳細なアクセス権限管理

### エンタープライズプラン
- **月額**: ¥20,000
- **容量**: 無制限
- **追加機能**: 専任サポート、カスタム機能の提供

## 導入事例
### 事例1: ABC株式会社
ABC株式会社では、クラウドストレージを導入することで、全社的なデータ共有とコラボレーションが大幅に向上しました。

### 事例2: XYZ有限会社
XYZ有限会社では、クラウドストレージのバージョン管理機能を活用し、ドキュメント管理の効率化を実現しました。

## お問い合わせ
ご質問や詳細な情報については、以下の連絡先までお問い合わせください。

- **電話**: 0120-123-456
- **メール**: info@cloudstorage.com
- **ウェブサイト**: [クラウドストレージ公式サイト](https://www.cloudstorage.com)

`;

  // Google スライドの新規プレゼンテーションを作成
  const presentation = SlidesApp.create('CreateSlidesFromText')
  const slides = presentation.getSlides();

  // デフォルトのレイアウトが強制設定されるため、最初のスライドを削除
  slides[0].remove();

  // 見出しでテキストを分割し、スライドごとに処理
  const slideTexts = markdownText.trim().split('\n').reduce((acc, line) => {
    if (line.startsWith('# ') || line.startsWith('## ')) {
      acc.push(line.replace(/\*\*/g, ''));
    } else {
      acc[acc.length - 1] += '\n' + line.replace(/\*\*/g, '');
    }
    return acc;
  }, []);

  slideTexts.forEach((slideText, index) => {
    // 新しいスライドを追加
    const slide = presentation.appendSlide();
    // スライドのテキストを解析して要素に分割
    const elements = slideText.split('\n');
    let yOffset = index === 0 ? 200 : 40;
    let textBox;
    elements.forEach((element, elementIndex) => {
      if (!element.trim()) {
        return;
      }
      // 要素の種類(見出し、箇条書きなど)を判別
      if (element.startsWith('# ')) {
        const title = element.substr(2);
        textBox = slide.insertTextBox(title).setTop(yOffset).setWidth(500).setLeft(130);
        textBox.getText().getTextStyle().setFontSize(24).setBold(true);
      } else if (element.startsWith('## ')) {
        const subtitle = element.substr(3);
        textBox = slide.insertTextBox(subtitle).setTop(yOffset).setWidth(500).setLeft(130);
        textBox.getText().getTextStyle().setFontSize(18).setBold(true);
        yOffset += 40; 
      } else if (element.startsWith('### ')) {
        const subtitle = element.substr(4);
        textBox = slide.insertTextBox(subtitle).setTop(yOffset).setWidth(500).setLeft(130);
        textBox.getText().getTextStyle().setFontSize(14).setBold(true);
        yOffset += 40;
      } else if (element.startsWith('- ')) {
        let lengvar = textBox.getText().asString();
        const listItem = element.substr(2);
        if (textBox) {
          textBox.getText().appendText('\n・ ' + listItem);
        } else {
          textBox = slide.insertTextBox('・ ' + listItem).setTop(yOffset).setWidth(500).setLeft(130);
        }
        textBox.getText().getRange(lengvar.length, listItem.length + 2 + lengvar.length).getTextStyle().setFontSize(12).setBold(false);
        yOffset += 20;
      } else {
        textBox = slide.insertTextBox(element).setTop(yOffset).setWidth(500).setLeft(130);
        textBox.getText().getTextStyle().setFontSize(14);
        yOffset += 40;
      }
    });
  });

  // プレゼンテーションの URL をログに出力
  Logger.log('Generated Slides URL: ' + presentation.getUrl());
}

ChatGPT

まずはChatGPTでプレゼン内容を生成します。

コピーマークから内容をコピーします。

GAS

生成結果貼り付け

  const markdownText = `
# クラウドストレージの営業資料

## 目次
1. [概要](#概要)
〜
〜
〜
〜
- **ウェブサイト**: [クラウドストレージ公式サイト](https://www.cloudstorage.com)
`;

ChatGPTで生成した内容を貼り付けます。
不要な文言は削りましょう。

見出しでページ分割

  // Google スライドの新規プレゼンテーションを作成
  const presentation = SlidesApp.create('CreateSlidesFromText')
  const slides = presentation.getSlides();

  // デフォルトのレイアウトが強制設定されるため、最初のスライドを削除
  slides[0].remove();

  // 見出しでテキストを分割し、スライドごとに処理
  const slideTexts = markdownText.trim().split('\n').reduce((acc, line) => {
    if (line.startsWith('# ') || line.startsWith('## ')) {
      acc.push(line.replace(/\*\*/g, ''));
    } else {
      acc[acc.length - 1] += '\n' + line.replace(/\*\*/g, '');
    }
    return acc;
  }, []);

新規のプレゼンテーションを作成します。スライドは個人のドライブ直下に作成されます。
この際に1枚目のスライドは強制でデフォルトのレイアウト設定が入ってしまうので、
removeでスライドを一端削除します。
その後、スライドのページを分割する単位の見出しでテキストを分割します。
今回は見出しのH1(#はじまり),H2(##はじまり)をページ分割の対象としています。

ページ単位でmarkdown要素を解析してスライドに反映していく

  slideTexts.forEach((slideText, index) => {
    // 新しいスライドを追加
    const slide = presentation.appendSlide();
    // スライドのテキストを解析して要素に分割
    const elements = slideText.split('\n');
    let yOffset = index === 0 ? 200 : 40;
    let textBox;
    elements.forEach((element, elementIndex) => {
      if (!element.trim()) {
        return;
      }
      // 要素の種類(見出し、箇条書きなど)を判別
      if (element.startsWith('# ')) {
        const title = element.substr(2);
        textBox = slide.insertTextBox(title).setTop(yOffset).setWidth(500).setLeft(130);
        textBox.getText().getTextStyle().setFontSize(24).setBold(true);
      } else if (element.startsWith('## ')) {
        const subtitle = element.substr(3);
        textBox = slide.insertTextBox(subtitle).setTop(yOffset).setWidth(500).setLeft(130);
        textBox.getText().getTextStyle().setFontSize(18).setBold(true);
        yOffset += 40; 
      } else if (element.startsWith('### ')) {
        const subtitle = element.substr(4);
        textBox = slide.insertTextBox(subtitle).setTop(yOffset).setWidth(500).setLeft(130);
        textBox.getText().getTextStyle().setFontSize(14).setBold(true);
        yOffset += 40;
      } else if (element.startsWith('- ')) {
        let lengvar = textBox.getText().asString();
        const listItem = element.substr(2);
        if (textBox) {
          textBox.getText().appendText('\n・ ' + listItem);
        } else {
          textBox = slide.insertTextBox('・ ' + listItem).setTop(yOffset).setWidth(500).setLeft(130);
        }
        textBox.getText().getRange(lengvar.length, listItem.length + 2 + lengvar.length).getTextStyle().setFontSize(12).setBold(false);
        yOffset += 20;
      } else {
        textBox = slide.insertTextBox(element).setTop(yOffset).setWidth(500).setLeft(130);
        textBox.getText().getTextStyle().setFontSize(14);
        yOffset += 40;
      }
    });
  });

ページ分割したテキスト単位でスライドを作成して、
1行ずつそのテキストの要素をmarkdownのルール前提でパースしていきます。
パースした内容を挿入したいテキストボックスの形式
(フォントサイズや太字設定、挿入位置など)を指定して挿入します。

insertTextBox(title).setTop(yOffset).setWidth(500).setLeft(130)

新規テキストボックスを挿入します。
setTopはスライドのTOPからの位置を指定します。
setWidthはテキストボックスの幅を指定します。
setLeftスライドの左端からの位置を指定します。

textBox.getText().getRange(lengvar.length, listItem.length + 2 + lengvar.length).getTextStyle().setFontSize(12).setBold(false)

既存テキストボックス全体の長さを計算して、追加(append)した文字にのみ指定のフォントや太字設定を反映します。

yOffset

テキストボックスの縦の表示位置を指定します。

textBox.getText().appendText('\n・ ' + listItem)

既存テキストボックスに追記します。

ファイルから読み込みたい場合

googleドキュメントにmarkdown形式のテキストを保存して、
そのファイルから文字列を読み込んでスライドを生成することも可能です。
markdownTextの設定部分を以下のように変更します。

  // Markdownテキスト
  const docId = 'ドキュメントID';
  // ファイルの内容を取得
  // Google ドキュメントを開く
  const doc = DocumentApp.openById(docId);
  
  // ドキュメントの内容を取得
  const body = doc.getBody();
  const markdownText = body.getText();

ドキュメントID

Google ドキュメントからテキストを読み込むには、Google Apps ScriptのDocumentAppサービスを使用します。以下は、Google ドキュメントからテキストを読み込み、そのテキストを使用してGoogle スライドを作成するコードです。

まず、Google ドキュメントのIDを取得する必要があります。ドキュメントのIDは、Google ドキュメントのURLから取得できます。
例えば、URLが次のような場合は、ファイルIDは「1G6Z7y7Z_h0gXXXXX」になります。

https://docs.google.com/document/d/1G6Z7y7Z_h0gXXXXX/edit

実行

デプロイ&実行

GASでデプロイして実行します。

結果

無事にスライドが生成されました。

さいごに


WEB画面等のプロンプト入力用のGUIを用意して、
ChatGptやgeminiのAPIから取得した結果を
ファイルに出力したものからスライドを生成するようにすれば、より自動化できそうですね。

合同会社カメレオンミーム Tech Blog

Discussion