😇

ChatGPT Whisper を使用し、音声ファイルを文字起こしする機能を作成

2023/03/26に公開

背景

前回、rubyのChatGPTライブラリを用いて、入力した値からChatGPTの返信が返ってくるアプリの作成に関する記事を作成しました。

今回は、音声ファイルをもとにテキストを生成することができる「Whisper」という機能を用いて、アップロードした音声ファイルの内容を文字起こししテキスト表示させる機能を作成しました。

rubyやrailsを用いて自動文字起こしする機能やアプリを作成することを検討している方のご参考になれば幸いです。

完成動画

Whisperとは?

Ruby ChatGPTの「Whisper」とは、APIを介して送信されたテキストメッセージを自然言語処理によって解析し、返信メッセージを生成する機能です。

「Whisper」は、ChatGPT APIの一部であり、OpenAIの自然言語処理技術を活用して、人間とコンピュータの間の対話を実現することができます。

この機能を使用するには、まずChatGPT APIを呼び出す必要があります。APIは、ユーザーからの入力テキストを受け取り、自然言語処理技術を使用して、そのテキストに応じた返信を生成します。

返信は、元のテキストに関連するものであり、ユーザーにとって有用な情報や回答を提供することができます。ChatGPTのWhisper機能を使用することで、人間とコンピュータの間でより自然な対話を実現し、よりスムーズなコミュニケーションを行うことができます。

解説

今回ははじめにCarrieWave というファイルアップロードを簡単に行うことができるgem を用いて、音声ファイルをアップロードする機能を実装しました。

CarrieWaveを用いたアップロード機能の作成はこの記事を参考にしました。

(カラムを作成するときは body:string だけでなく、transcribe_data: string も作成しておきましょう。作成したコードは一番下の「コード」を参考にしていただけると幸いです。)

次に、ChatGPTのWhisperを用いて音声ファイルの自動文字起こしをしてから、その音声ファイルをデータベースに保存させ、詳細ページに音声ファイルの内容を表示させるようにしました。

下記のコードは、ChatGPTのWhisperを用いて音声ファイルを文字起こし(transcribeメソッド)し、データベースに保存している箇所(createアクション)となります。

app/controllers/tweets_controller.rb

class TweetsController < ApplicationController
  before_action :set_tweet, only: %i[ show edit update destroy ]
  skip_before_action :verify_authenticity_token

  # トップページ
  def index
    @tweets = Tweet.all
  end

  # 詳細ページ
  def show
  end

  def new
    @tweet = Tweet.new
  end

  --- 省略 ---

  # 音声ファイルをアップロードし、データベースに保存
  def create
    @tweet = Tweet.new(tweet_params)

    # transcribeメソッドのレスポンスを変数に代入
    transcribe_data = transcribe(@tweet.file.path)

    # 音声ファイルからWhisperのレスポンス内容をカラムに追加
    @tweet.update(transcribe_data: transcribe_data)

    respond_to do |format|
      if @tweet.save
        format.html { redirect_to tweet_url(@tweet), notice: "Tweet was successfully created." }
        format.json { render :show, status: :created, location: @tweet }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @tweet.errors, status: :unprocessable_entity }
      end
    end
  end

  --- 省略 ---

  private
    def set_tweet
      @tweet = Tweet.find(params[:id])
    end

    # 文字起こししたデータを保存するための「transcribe_data」カラムとアップロードしたファイルを保存するための「file」カラムを追加
    def tweet_params
      params.require(:tweet).permit(:body, :transcribe_data, :file)
    end

    # 音声ファイルを文字起こしし、データを返却
    def transcribe(path)
      response = @client.transcribe(
      parameters: {
          model: "whisper-1",
          file: File.open(params[:tweet][:file].tempfile, 'rb'),
      })

      response.parsed_response['text']

    end
end

今回注目していただきたいのは、transcribeメソッドの部分です。

この箇所で音声ファイルを文字起こしし、文字起こししたデータを返却しています。

ドキュメントで response の中の file: 部分が、「file: File.open('path_to_file', 'rb')」となっており、第一引数を「 params[:tweet][:file].tempfile 」とすることで、アップロードしたファイルを値として取得しました。

また、parsed_response メソッドは、HTTPリクエストを送信してレスポンスを受信した後に、レスポンスボディをパースしてRubyのオブジェクトに変換するメソッドです。

parsed_response メソッドは、レスポンスのContent-Typeヘッダーに応じて自動的に解釈を行います。

JSONの場合は、JSON文字列をRubyのハッシュや配列に変換します。

下記は、response のデータ形式を記載した内容です。

    78: def transcribe(path)
    79:   response = @client.transcribe(
    80:   parameters: {
    81:       model: "whisper-1",
    82:       file: File.open(params[:tweet][:file].tempfile, 'rb'),
    83:   })
    84:   response.parsed_response['text']
 => 85:   binding.pry
    86: end

[1] pry(#<TweetsController>)> response
=> {"text"=>"まだまだだね"}

[2] pry(#<TweetsController>)> response.parsed_response['text']
=> "まだまだだね"

上記の response.parsed_response['text'] を returnし、createアクションの中にある transcribe_data という変数に代入し、その内容をTweetモデルのtranscribe_dataカラムに入れ updateメソッドでカラムの中に値を追加し、データベースに保存しました。

以上が、ChatGPT Whisper を使用した音声ファイルを文字起こしする機能の説明となります。

まとめ

今回は、ChatGPT Whisper を使用した音声ファイルを文字起こしする機能を作成する方法についてご紹介いたしました。

音声ファイルをアップロードしたことがなく、CarrierWaveというgemの使用方法をドキュメントを見ながら実装することができたので、良い経験になりました。

また、railsアプリ内で自動文字起こしができるということを今回の実装で知ることができたので他のことに応用できないか検討していきたいです。

参考

ChatGPT Whisper

https://github.com/alexrudall/ruby-openai

CarrierWave

https://github.com/carrierwaveuploader/carrierwave

音声ファイルのサンプル
http://pro-video.jp/voice/announce/

コード

app/views/tweets/index.html.erb (トップページ、音声ファイル一覧ページ)

<div class="main">
<p id="notice"><%= notice %></p>

<h1>Tweets</h1>

<div class="table-container">
<table>
  <thead>
    <tr>
      <th>Body</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @tweets.each do |tweet| %>
      <tr>
        <td><%= tweet.body %></td>
        <td><%= link_to 'Show', tweet %></td>
        <td><%= link_to 'Edit', edit_tweet_path(tweet) %></td>
        <td><%= link_to 'Destroy', tweet, method: :delete, data: { confirm: 'Are you sure?' } %></td>
        <td>
          <audio src= "<%= "#{tweet.file}" %>" controls="">
          <a src="<%= "#{tweet.file}" %>">ダウンロード</a>
          </audio>
       </td>
      </tr>
    <% end %>
  </tbody>
</table>
</div>
<br>

<%= link_to 'New Tweet', new_tweet_path %>
</div>

app/views/tweets/new.html.erb(音声ファイルをアップロードするページ)

<div class="main">
	<h1>New Tweet</h1>
	
	<%= render 'form', tweet: @tweet %>
	<br>
	<%= link_to 'Back', tweets_path %>
</div>

app/views/tweets/_form.html.erb

<%= form_with(model: @tweet) do |form| %>
  <% if tweet.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(tweet.errors.count, "error") %> prohibited this tweet from being saved:</h2>

      <ul>
        <% tweet.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>
  <br>
  <div class="field">
    <%= form.label :file %>
    <%= form.file_field :file , :size => 140 %>
  </div>
  <br>
  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

app/views/tweets/show.html.erb(アップロードした音声ファイルの詳細を確認できるページ)

<div class="main">
<p id="notice"><%= notice %></p>

<p>
  <strong>タイトル:</strong>
  <%= @tweet.body %>
</p>

<p>
  <strong>文字起こし:</strong>
  <%= @tweet.transcribe_data %>
</p>

<%= link_to 'Edit', edit_tweet_path(@tweet) %> |
<%= link_to 'Back', tweets_path %>
</div>

app/controllers/tweets_controller.rb

class TweetsController < ApplicationController
  before_action :set_tweet, only: %i[ show edit update destroy ]
  skip_before_action :verify_authenticity_token

  # トップページ
  def index
    @tweets = Tweet.all
  end

  # 詳細ページ
  def show

  end

  # GET /tweets/new
  def new
    @tweet = Tweet.new
  end

  # GET /tweets/1/edit
  def edit
  end

  # 音声ファイルをアップロードし、データベースに保存
  def create
    @tweet = Tweet.new(tweet_params)
    transcribe_data = transcribe(@tweet.file.path)

    # 音声ファイルからWhisperのレスポンス内容をカラムに追加
    @tweet.update(transcribe_data: transcribe_data)

    respond_to do |format|
      if @tweet.save
        format.html { redirect_to tweet_url(@tweet), notice: "Tweet was successfully created." }
        format.json { render :show, status: :created, location: @tweet }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @tweet.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /tweets/1 or /tweets/1.json
  def update
    respond_to do |format|
      if @tweet.update(tweet_params)
        format.html { redirect_to tweet_url(@tweet), notice: "Tweet was successfully updated." }
        format.json { render :show, status: :ok, location: @tweet }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @tweet.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /tweets/1 or /tweets/1.json
  def destroy
    @tweet.destroy

    respond_to do |format|
      format.html { redirect_to tweets_url, notice: "Tweet was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_tweet
      @tweet = Tweet.find(params[:id])
    end

    # 文字起こししたデータを保存するための「transcribe_data」カラムとアップロードしたファイルを保存するための「file」カラムを追加
    def tweet_params
      params.require(:tweet).permit(:body, :transcribe_data, :file)
    end

    # 音声ファイルを文字起こしし、データを返却
    def transcribe(path)
      response = @client.transcribe(
      parameters: {
          model: "whisper-1",
          file: File.open(params[:tweet][:file].tempfile, 'rb'),
      })
      response.parsed_response['text']
    end
end

Discussion