ChatGPT Whisper を使用し、音声ファイルを文字起こしする機能を作成
背景
前回、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
CarrierWave
音声ファイルのサンプル
コード
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