1. AI SDK V5による音声文字起こし機能の概要と実装方針 [0]

  1. AIによる音声文字起こしの高い精度:
    1. AIは驚くべき精度で音声を文字起こしすることができ、異なるアクセント、バックグラウンドノイズ、複数の言語にも自動で対応する [0]。
    2. AI SDKを使用すると、この機能を他の機能と同様に簡単にアプリケーションに追加できる [2]。
  2. 本レッスンの目的: 任意の音声ファイルをテキストに変換する音声文字起こし機能を構築する [3]。
  3. 開発アプローチ:
    1. AI SDKを使用して音声ファイルを処理するルートハンドラを作成する [4]。
    2. 音声アップロードおよび文字起こし結果表示用のUIを構築する [6]。

2. APIルートの実装: バックエンドでの音声ファイル処理 [7]

2.1. ルートハンドラの初期設定とインポート [7]

  1. APIルートの作成開始: まずAPIルートから着手する [7]。
  2. ファイル構造の作成:
    1. VS Codeのappフォルダ内のAPIフォルダにtranscribe-audioという新しいフォルダを作成する [8]。
    2. その中にroute.tsファイルを作成する [9]。
  3. POSTハンドラの定義: リクエストパラメータを受け入れるPOSTハンドラから始める [10]。
    1. exportを用いてfunction host request of type requestとしてエクスポートする [11]。
  4. 必要なインポート:
    1. AI SDK/OpenAIからOpenAIをインポートする必要がある [12]。
    2. transcribe関数をインポートする [13]。
      1. この関数は現在「実験的」とマークされているが、本用途には問題なく機能する [14]。
      2. 視聴時には安定版になっている可能性もあるため、ドキュメントを確認することを推奨する [15]。
      3. ただし、これをtranscribeとしてインポートする [17]。

2.2. ファイルアップロードの処理 [18]

  1. 音声文字起こしエンドポイントの特性: 以前のエンドポイントとは異なり、テキストを含むJSONではなく、ファイルアップロードを受け取る [18]。
  2. ファイルアップロードのハンドリング:
    1. リクエストをフォームデータとしてパースする。これはブラウザがファイルをアップロードする際の形式である [20]。
    2. const dataをawait request.formData()で取得する [22]。
  3. 音声ファイルの抽出: フォームデータから音声ファイルを抽出する [23]。
    1. const audio fileはformData.get('audio')をFileとして取得する [24]。
    2. フロントエンドのフォームで使用するフィールド名は「audio」である [25]。
  4. ファイル受信のバリデーション: ファイルが実際に受信されたことを確認するためのバリデーションを追加する [26]。
    1. もしaudioFileが存在しない場合、new Response('No audio file provided', { status: 400 })を返して早期に終了する [27]。

2.3. 音声データの変換と文字起こし [29]

  1. 重要な変換ステップ: transcribe関数は音声をUint8Arrayとして必要とし、ファイルオブジェクトではない [29]。
    1. そのため、ファイルを変換する必要がある [31]。
  2. ファイルからArrayBufferへの変換: まずファイルをArrayBufferに変換する [32]。
    1. const arrayBufferはawait audioFile.arrayBuffer()で取得する [33]。
  3. ArrayBufferからUint8Arrayへの変換: そのArrayBufferからUint8Arrayを作成する [34]。
    1. const unsignedIntArrayはnew Uint8Array(arrayBuffer)として生成する [35]。
    2. これにより、AIが必要とする形式の生の音声データが得られる [36]。
  4. 音声の文字起こし: これで音声を文字起こしできる [37]。
    1. await transcribeを呼び出す [38]。
    2. オブジェクトを引数として渡す [39]。
    3. モデルをOpenAI.transcription('whisper-1')に設定する [40]。
      1. WhisperはOpenAIの音声認識モデルであり、文字起こしに優れている [41]。
    4. 準備した音声データをaudioプロパティにunsignedIntArrayとして渡す [42]。
  5. 結果の取得と返却: 結果をtranscript定数にキャプチャし、JSONとして返す [44]。
    1. return Response.json(transcript)とする [44]。

2.4. エラーハンドリング [45]

  1. 適切なエラーハンドリングの追加: すべての処理を適切なエラーハンドリングでラップする [45]。
    1. tryブロックを使用し、catchブロックでエラーを捕捉する [46]。
    2. エラーが発生した場合、console.error('Error transcribing audio', error)でログを出力する [46]。
    3. その後、new Response('Failed to transcribe audio', { status: 500 })を返す [47]。
  2. ルートハンドラの準備完了: これでルートハンドラは完成である [48]。
    1. 音声ファイルを受け取り、適切な形式に変換し、Whisperを使用して文字起こしを行い、結果を返す [49]。

3. UIの実装: フロントエンドでのファイルアップロードと結果表示 [50]

3.1. UIファイルの作成と基本構造 [50]

  1. UIファイルの作成: UIフォルダ内にtranscribe-audioという新しいフォルダを作成し、その中にpage.tsxを作成する [50]。
  2. 基本的なコンポーネント構造: 他のページと同じレイアウトを使用するため、事前にコードを貼り付けておく [51]。
  3. クライアントコンポーネントの指定: これはクライアントコンポーネントであるため、"use client"ディレクティブを使用する [52]。
  4. コンポーネント名: コンポーネント名はTranscribeAudioPageである [53]。
  5. コンテナ構造: divコンテナがあり、上部に文字起こし結果が表示され、フォームは下部に固定される [54]。

3.2. 必要な状態変数と参照の定義 [58]

  1. ファイルアップロードの処理: 音声文字起こしでは、テキスト入力とは異なる方法でファイルアップロードを処理する必要がある [58]。
  2. 必要な状態と参照の設定:
    1. reactからuseStateとuseRefをインポートする [59]。
    2. コンポーネント内で以下の状態変数を定義する [61]。
      1. isLoadingとsetIsLoading: ロード状態用で、初期値はfalseである [62]。
      2. errorとsetError: エラーメッセージ用で、型はstring | null、初期値はnullである [64]。
      3. transcriptとsetTranscript: 文字起こし結果用で、初期値はnullである [65]。
        1. 型はTranscriptResult | nullであり、この型は後ほど定義する [67]。
      4. selectedFileとsetSelectedFile: 選択された実際のファイルオブジェクトを保存する用で、型はブラウザのFile型またはnull、初期値はnullである [69]。
      5. fileInputRef: ファイル入力要素にアクセスするためのrefで、型はHTMLInputElement | null、初期値はnullである [72]。
  3. TypeScriptインターフェースの定義: TranscriptResultに対するTypeScriptエラーがあるため、そのインターフェースを定義する [74]。
    1. interface TranscriptResultにはfullTextが含まれ、型はstringである [75]。
    2. モデルによってはオプションのsegmentsも含まれる可能性があり、これはstart: number、end: number、text: stringの配列である [76]。
    3. 検出された言語(string)と秒単位の持続時間(number)も含まれる場合がある [78]。

3.3. ファイル入力とボタンのUI要素 [79]

  1. ファイル入力の作成: 必要な状態と参照がすべて揃ったので、フォームのフレックスコンテナ内にファイル入力を作成する [79]。
    1. inputタグにref={fileInputRef}、type="file"、accept="audio/*"(音声ファイルのみに選択を制限)、className="hidden"、id="audio-upload"を設定する [80]。
    2. この実際の入力は非表示にし、より見栄えの良いラベルを作成する [83]。
  2. スタイル付きラベルの追加: アップロードボタンとして機能するスタイル付きラベルを追加する [85]。
    1. 入力のすぐ下にlabelタグを配置する [86]。
    2. ファイルが選択されているかどうかに基づいて、「Change file」または「Select audio file」をテキストとして表示する [86]。
    3. このラベルはid="audio-upload"の入力に対応する [87]。
    4. 見た目を良くするためにTailwind CSSクラスを多数追加する。スタイルは任意だが、このコードはGitHubリポジトリで利用可能である [88]。
  3. 文字起こしボタンの追加: ラベルの隣に文字起こしボタンを追加する [90]。
    1. buttonタグのテキストは「Transcribe」、type="submit"である [91]。
    2. このボタンは、isLoadingがtrueの場合、またはファイルが選択されていない場合(!selectedFile)に無効化される [92]。
    3. このボタンにも見た目を良くするためのTailwind CSSクラスを追加する [93]。
  4. 入力、ラベル、ボタンのラッピング: 入力、ラベル、ボタンを別のdivタグでラップする [94]。
    1. このdivにflex gap-2を追加し、入力とボタンが横に並ぶようにする [95]。

3.4. フォーム送信ハンドラのロジック [98]

  1. フォーム送信の実装: handleSubmitというasync関数を実装する [98]。
    1. eventオブジェクトはReact.FormEvent<HTMLFormElement>型としてアクセスできる [100]。
    2. 関数本体内で、デフォルトの送信動作をevent.preventDefault()で阻止する [101]。
  2. ファイルの選択確認: ファイルが選択されているか確認する [102]。
    1. もし!selectedFileの場合、setError('Please select an audio file')を呼び出し、早期に終了する [102]。
  3. ロード状態の設定とエラーのクリア: ファイルが選択されている場合、setIsLoading(true)を設定し、以前設定されていたエラーをsetError(null)でクリアする [103]。
  4. API呼び出し: tryブロック内でAPI呼び出しを行う [104]。
    1. const formDataをnew FormData()で作成する [104]。
    2. formData.append('audio', selectedFile)でaudioという文字列とselectedFileをフォームデータに追加する [105]。
    3. このフォームデータを使用してfetchリクエストを行う [107]。
      1. const responseはawait fetchで取得する [108]。
      2. APIエンドポイントは、先ほど作成した/api/transcribe-audioである [109]。
      3. オプションとして、methodを'POST'に、bodyをformDataに設定する [110]。
      4. フォームデータの場合、ブラウザがContent-Typeヘッダを自動的に処理するため、設定は不要である [112]。
    4. レスポンスのチェック: response.okでない場合、new Error('Failed to transcribe audio')をスローする [113]。
    5. 成功時の処理: リクエストが成功した場合、const dataをawait response.json()で取得する [114]。
      1. setTranscript(data)で状態を更新し、setSelectedFile(null)で選択されたファイルをクリアする [114]。
      2. fileInputRef.currentが存在する場合、current.value = ''で入力をリセットする [115]。
  5. catchブロックでのエラー処理:
    1. エラーをコンソールにログ出力する [116]。
    2. エラーがErrorのインスタンスである場合、setError(error.message)でエラーメッセージを設定する [117]。
    3. それ以外の場合は、setError('Something went wrong. Please try again.')とする [118]。
  6. ロード状態のリセット: 最後に、setIsLoading(false)でisLoadingをfalseにリセットする [120]。
  7. ハンドラの接続: このハンドラをフォームに接続する [121]。
    1. formタグのonSubmitプロパティにhandleSubmitを設定する [122]。

3.5. 結果の表示とロード/エラー状態のUI [123]

  1. 文字起こし結果の表示: リクエストが完了したら、フォーム要素の上にあるJSXで文字起こし結果を表示する [123]。
    1. transcript状態変数が存在し、かつisLoadingがfalse(フェッチリクエストが正常に完了したことを意味する)の場合にレンダリングする [124]。
    2. divタグにH3タグで「Transcript」と表示し、pタグでtranscript.textをレンダリングする [125]。
    3. 見た目を良くするためにTailwind CSSクラスも追加する [126]。
    4. オプションで、言語(transcript.language)や持続時間(transcript.durationInSeconds)が存在する場合、それらを表示するJSXを追加できる [127]。
  2. ロードとエラー状態の表示: 文字起こし表示の上にロードとエラーの状態を追加する [129]。
    1. errorが存在する場合、divタグにエラーメッセージを表示する [130]。
    2. isLoadingがtrueに設定されている場合、divタグに「Transcribing audio」というテキストを表示する [131]。

3.6. 選択されたファイル名の表示とフォームのリセット [133]

  1. 選択されたファイル名の表示とクリア: フォーム内のファイル入力コントロールの上に、選択されたファイル名を表示し、クリアできるようにする [133]。
    1. selectedFileが存在する場合、divタグをレンダリングする [133]。
    2. spanタグで「Selected: 」の後にselectedFile.nameを表示する [133]。
    3. 「Remove」というテキストのボタンを表示する [133]。
      1. ボタンのtypeは"button"である [134]。
      2. onClickはresetFormという関数に設定する。この関数は後ほど定義する [135]。
      3. divタグとボタンにもスタイルを追加する [136]。
  2. リセット関数の実装: resetForm関数を実装する [137]。
    1. setSelectedFile(null)で選択されたファイルをnullに設定する [137]。
    2. setTranscript(null)で文字起こし結果をnullに設定する [138]。
    3. setError(null)でエラーもnullに設定する [139]。
    4. fileInputRefが存在する場合、current.value = ''でファイル入力をクリアする [140]。
    5. これにより、すべての状態がクリアされ、ファイル入力がリセットされる [142]。
  3. onChangeイベントハンドラの追加: 以前の実装で忘れていたinputのonChangeイベントハンドラを追加する [143]。
    1. onChangeをhandleFileChangeに設定する [144]。
    2. handleFileChange関数を定義する [145]。
      1. これはアロー関数であり、React.ChangeEvent<HTMLInputElement>型のeventにアクセスできる [146]。
      2. 関数本体内で、const fileをevent.target.files?.[0]で取得する [146]。
      3. ファイルが存在する場合、setSelectedFile(file)で選択されたファイルを設定する [147]。
      4. setTranscript(null)でUIをリセットし、setError(null)でエラーもnullに設定する [147]。
    3. これでUIは完成である [148]。

4. 機能テストと応用、および考慮事項 [149]

4.1. 機能のテスト [149]

  1. ブラウザでのアクセス: ブラウザでlocalhost:3000/transcribe-audioに移動する [150]。
    1. フォームが下部に表示され、「Select audio file」ラベルと現在無効になっている「Transcribe」ボタンが見えるはずである [151]。
  2. 音声ファイルの選択: 「Select audio file」をクリックし、コンピュータから音声ファイルを選択する [153]。
    1. 任意の音声ファイルを使用できる [154]。
    2. 例えば、sample-audio.mp3というMP3ファイルを選択すると、それがフォームのすぐ上に表示される [155]。
    3. 選択されたファイルを削除するための「Remove」ボタンもある [156]。
  3. 文字起こしの実行: ファイルを再選択した後、「Transcribe」をクリックする [157]。
    1. 「Transcribing audio」というロード状態が表示される [159]。
    2. 音声からテキストへの処理が完了すると、完全な文字起こし結果が表示される [159]。
    3. モデルが検出された言語や音声の持続時間をサポートしている場合、それらも表示されるが、Whisperモデルはこれらをサポートしていないため、完全な文字起こしテキストのみが表示される [159]。
    4. この文字起こしは非常に正確であると確信できる [162]。
    5. ユーザーには、自分の文字起こしがどれほど正確であったかコメントセクションで知らせるよう促される [163]。

4.2. 応用可能性と考慮事項 [164]

  1. 音声文字起こしの応用例: 音声文字起こしは多くの可能性を開く [164]。
    1. 会議の議事録作成サービス、ポッドキャスト検索エンジン、ボイスノートからテキストへの変換、動画の字幕生成、インタビューの文字起こしツールなど、多岐にわたる [165]。
  2. 考慮事項:
    1. 音声ファイルはサイズが大きくなる可能性があるため、本番環境ではファイルサイズ制限を追加することを推奨する [166]。
    2. 長い音声ファイルは処理に時間がかかり、API使用料も高くなる [167]。
    3. テスト時には、数秒程度の短いクリップを使用することを推奨する [168]。

5. まとめと次のレッスン [169]

  1. これまでに構築したものの概要:
    1. フォームデータを通じて音声ファイルのアップロードを受け入れるルートハンドラを作成した [170]。
    2. AIが処理できるUint8Array形式にファイルを変換した [171]。
    3. OpenAIのWhisperモデルを使用して音声を文字起こしし、結果を返した [172]。
    4. UIについては、適切な状態管理を備えたファイルアップロードインターフェースを構築した [173]。
    5. 選択されたファイルを表示し、アップロードプロセスを処理し、言語や持続時間などのメタデータとともに文字起こし結果を表示した [174]。
  2. AI SDKの利点: AI SDKは音声文字起こしを驚くほどシンプルにする [175]。
    1. わずか数行のコードで、強力な音声からテキストへの機能をアプリに追加できる [176]。
  3. 次のレッスン: 次のレッスンでは、逆方向に進み、テキストから音声を生成する方法を学ぶ [177]。