🎺

Next.js13とSupabase、ChatGPT、Whisperで自動要約アプリ構築

2023/03/16に公開

Next.js 13 と Supabase、ChatGPT、Whisper で自動要約アプリを構築しました。

音声、動画ファイルを読み込むと、自動的に要約が作成されます。

文字起こしには OpenAI の Whisper、要約には ChatGPT を使用しています。

ChatGPT の prompt を少し変更すると、会議の議事録にも対応できます。

ぜひ参考にして下さい。

システム構成

完成イメージ

動画

こちらの動画は、音声認識 AI チャットアプリをベースに構築しています。

https://youtu.be/EjJnsC6At_8

まだ音声認識 AI チャットアプリを構築していない方は、下記の動画を参考にしてください。

Next.js13 と Supabase で音声認識 AI チャットアプリ(ChatGPT、Whisper)構築

https://zenn.dev/hathle/books/next-supabase-voice-book

https://youtu.be/MwXqq0H2MpA

ブログ構築

Next.js13 App Directory の認証機能と Supabase のベースはこちらを参考にして下さい。

https://zenn.dev/hathle/books/next-supabase-blog-book

https://youtu.be/nQ7lKzI6RlE

機能

  • 動画、音声ファイルのアップロード
  • FFmpeg でファイルを MP3 に変換
  • Whisper で音声を文字起こし
  • ChatGPT でテキストを要約

ソースコード

ソースコードは LINE 登録するとダウンロードできますので、ぜひ登録をお願いします。

最新の動画情報が届きます。

LINE お友達登録
https://lin.ee/NKoTJnV

解説

今回の要約アプリのメインとなるpost-new.tsxファイルを解説していきます。

音声、動画ファイルをアップロードして、文字起こしと要約を生成し、Supabase に保存しています。

MP3 ファイルへの変換はffmpeg.wasmライブラリを使用しています。

ffmpeg.wasm とは?

https://www.npmjs.com/package/@ffmpeg/ffmpeg?activeTab=readme

ffmpeg.wasm は、ブラウザ上で動作する JavaScript ライブラリであり、FFmpeg を WebAssembly に変換して実行するものです。

JavaScript よりも高速なバイナリコードをブラウザで実行できるようにする技術であり、大規模な動画編集や変換のような重い処理をブラウザ上で実行することができます。

FFmpeg の WebAssembly バージョンを使用しているため、FFmpeg でサポートされているさまざまな動画および音声フォーマットをサポートしています。

WebAssembly による高速化により、ファイルのエンコード、デコード、トランスコード、クロップ、トリミング、サイズ変更など、さまざまな処理を高速かつ効率的に実行できます。

Web Worker を使用して並列処理を行い、ブラウザの UI スレッドをブロックすることなく、動画および音声の変換や処理を実行できます。

Promise をサポートしているため、非同期処理を実行し、コールバック関数を使用することができます。

post-new.tsx

app/components/post/post-new.tsx

FFmpeg のインスタンスを作成し、設定を行います。

ファイルサイズの最大値と、サポートされるファイルタイプを定義します。

const ffmpeg = createFFmpeg({
  corePath: 'https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js',
  log: true,
})
const MAX_FILE_SIZE = 25000000
const fileTypes = ['mp4', 'mp3']

ffmpeg.wasm をロードします。

ffmpeg.wasm は 20MB くらいあるので、wasm ファイルは遅延ロードする設計となっています。

遅延ロードはffmpeg.loadをしたタイミングで行われます。

ffmpeg.isLoadedでロードしているかチェックをすることができます。

// FFmpeg Load
useEffect(() => {
  const load = async () => {
    await ffmpeg.load()
  }
  // Loadチェック
  if (!ffmpeg.isLoaded()) {
    load()
  }
}, [])

ffmpeg.FS を使用して ffmpeg オブジェクトのファイルシステムにファイルを書き込み、ffmpeg.run を使用してファイルを mp3 形式に変換します。

  • オーディオサンプリング周波数:16000
  • チャンネル:1
  • ビットレート:96kbps

ffmpeg.runのコマンドライン引数はたくさんあるので、公式ドキュメントを参考にして下さい

  • -i input_file:入力ファイルを指定します。
  • -f format:入力または出力ファイルの形式を指定します。
  • -c:v codec:ビデオコーデックを指定します。
  • -c:a codec:オーディオコーデックを指定します。
  • -b:v bitrate:ビデオビットレートを指定します。
  • -b:a bitrate:オーディオビットレートを指定します。
  • -s resolution:ビデオ解像度を指定します。
  • -t duration:入力ビデオの切り出し時間を指定します。

https://github.com/ffmpegwasm/ffmpeg.wasm/blob/master/docs/api.md

https://ffmpeg.org/ffmpeg.html

// ffmpeg.wasmがアクセス可能なメモリストレージに保存
ffmpeg.FS('writeFile', file.name, await fetchFile(file))

await ffmpeg.run(
  '-i', // 入力ファイルを指定
  file.name,
  '-vn', // ビデオストリームを無視し、音声ストリームのみを出力
  '-ar', // オーディオサンプリング周波数
  '16000',
  '-ac', // チャンネル
  '1',
  '-b:a', // ビットレート
  '96k',
  '-f', // 出力ファイルのフォーマット
  'mp3',
  'output.mp3'
)

ffmpeg.FS を使用して、変換された音声ファイルを取得し、Blob に変換します。

Blob のサイズをチェックし、最大ファイルサイズを超えた場合はアラートを表示して処理を中止します。

// ffmpeg.wasmがアクセス可能なメモリストレージから取得
const readData = ffmpeg.FS('readFile', 'output.mp3')
// Blog生成
const audioBlob = new Blob([readData.buffer], { type: 'audio/mp3' })

// サイズチェック Whisperは最大25MB
if (audioBlob.size > MAX_FILE_SIZE) {
  alert('サイズが大きすぎます')
  setLoading(false)
  return
}

変換された音声ファイルを使用して、Whisper API を呼び出し、音声ファイルを文字起こしします。

文字起こしできなかった場合は、アラートを表示して処理を中止します。

// FormData
const formData = new FormData()
formData.append('file', audio_file)

// Whisper APIコール
const response = await fetch(`/api/whisper`, {
  method: 'POST',
  body: formData,
})
const response_data = await response.json()
const transcript = response_data.transcript.trim()

if (!transcript) {
  setLoading(false)
  alert('文字起こしできませんでした')
  return
}

文字起こししたテキストを ChatGPT に送信して要約してもらいます。

// ChatGPT APIに送信
const body = JSON.stringify({ prompt: transcript })
const response2 = await fetch('/api/chatgpt', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body,
})

const response_data2 = await response2.json()
const content = response_data2.text.trim()

if (!content) {
  setLoading(false)
  alert('要約できませんでした')
  return
}

文字起こししたテキストと要約を Supabase に保存します。

保存したら、キャッシュをクリアして、詳細画面に遷移します。

// postsテーブル追加
const { data: insertData, error: insertError } = await supabase
  .from('posts')
  .insert({
    prompt: transcript,
    content: content,
  })
  .select()

if (insertError) {
  alert(insertError.message)
  setLoading(false)
  return
}

// キャッシュクリア
router.refresh()
// 詳細画面に遷移
router.push(`/post/${insertData[0].id}`)

next.config.js

Next.js で FFmpeg を使用するには、SharedArrayBufferを有効にする必要があります。

/** @type {import('next').NextConfig} */
module.exports = {
  reactStrictMode: true,
  experimental: {
    appDir: true,
  },
  async headers() {
    return [
      {
        // SharedArrayBufferを有効化するためのヘッダーを追加
        source: '/(.*)',
        headers: [
          {
            key: 'Cross-Origin-Opener-Policy',
            value: 'same-origin',
          },
          {
            key: 'Cross-Origin-Embedder-Policy',
            value: 'require-corp',
          },
        ],
      },
    ]
  },
}

Next.js 13 App Directry

  • コードをサーバーコンポーネントとクライアントコンポーネントで組織的に管理することができる
  • コードの可読性の向上
  • 開発効率の向上
  • セキュリティ向上

App Directory はサーバーコンポーネントがデフォルトとなっています。

クライアントコンポーネントと指定しない限りサーバーサイドでレンダリングされるので、セキュリティが向上し表示スピードも上がります。

https://beta.nextjs.org/docs

Supabase

  • API を提供するため、既存のアプリケーションから移行する際のコストが低い
  • Firebase のようなバックエンドサービスで、PostgreSQL を利用できる
  • PostgreSQL のデータベースを使用するため、複雑なクエリを実行することができる
  • 完全にオープンソースであり、自由にカスタマイズすることができる
  • セキュリティやスケーラビリティに優れており、大規模なアプリケーションにも対応している

Supabase はバックエンド機能を提供するクラウドサービスで、データベースに PostgreSQL を使用しています。

フロントエンドとバックエンドの境界がなくなるので、より効率的な開発が可能になります。

https://supabase.com/docs

フルスタックチャンネル Q&A サイト

講座の不明点や個人開発の疑問点など、何でもご質問ください。

https://www.fullstackchannel.com/

今後ともよろしくお願いします。

Discussion