文字起こしサービスwriteout.aiをDockerでセルフホストする

Docker

OpenAIの音声認識モデルWhisperを使ったテキスト文字起こしサービスwriteout.aiをDockerを使ってセルフホスト(自前でサービスを構築)してみました。


OpenAIの音声認識モデルWhisperは、高い精度を持ち、さまざまな言語に対応している高機能なSpeech-to-textモデルです。そのAPIを利用して、録音ファイルからテキストファイルを作成してくれるwriteout.aiというサービスがあります。このサービスは、GitHubアカウントがあれば無料で利用できますが、混雑時には利用できない場合もあります。

実は、このwriteout.aiはオープンソースであり、GitHub上で公開されています。リポジトリはbeyondcode/writeout.aiです。MITライセンスのため、ライセンスの範囲内で自由に利用できます。

このwriteout.aiはLaravelを使用して作成されているため、Laravelが動作する環境を準備し、セルフホストすれば、サービスの状況に関係なく、自分専用の文字起こしWebUIを構築することができます。

必要なものは2つだけです。

  • OpenAIのAPIキー(従量課金)
  • GitHubのOAuthアプリ(無料)
シップ
シップ

OpenAIのAPI使用料(音声ファイル1分あたり0.006ドル、日本円で約0.84円)は発生しますので注意してくださいね。

writeout.aiはGitHubアカウントを利用したユーザー登録を行っているため、GitHubのOAuthアプリを登録する必要があります。ただし、自身の認証システムを使用するように変更すれば、この手続きは不要です。

今回は、Dockerを使用して構築してみます。こちらのLaravel-Dockerリポジトリを使用してみます。

cd ~/project/
git clone https://github.com/shipwebdotjp/writeout.ai-docker.git writeout.ai
cd writeout.ai/
git clone https://github.com/beyondcode/writeout.ai backend

ただし、このリポジトリのLaravel on Dockerはやや古いバージョンですので、手直しが必要です。

修正したものは以下のリポジトリにあります。

Docker

ポート開放

Viteで開発する際に、JavaScriptやCSSをViteのサーバーから配信する必要がありますが、そのためにはポートを開放する必要があります(ただし、開発せずに最初からビルドする場合は不要です)。

  web:
    ports:
      - ${WEB_PORT:-80}:80
      - 5173:5173 #追加

PHPのバージョンアップ

Laravelのバージョンは9系ですが、PHP8.1以降が必要とされているため、infra/docker/php/Dockerfileを編集してPHPを8.1に変更する必要があります。また、必要な拡張機能であるpcntlもインストールする必要があります。

#修正前
FROM php:8.0-fpm-buster
...
docker-php-ext-install intl pdo_mysql zip bcmath && \
#修正後
FROM php:8.1-fpm-buster
...
docker-php-ext-install intl pdo_mysql zip bcmath pcntl && \

Nodeのバージョンアップ

FROM node:14.18-alpine as node

アップロードの最大サイズを上げる

server {
	client_max_body_size 25M;

各種キーの記入

OPENAI_API_KEYにOpenAIのAPIキーを、GITHUB_CLIENT_IDとGITHUB_CLIENT_SECRETにはGitHubで作成したOAuthアプリのCLIENT_IDとCLIENT_SECRETを記入します。
GitHubのOAuthアプリはSettings->Developer settings->OAuth Appsから作成することができます。

OPENAI_API_KEY=sk-...
GITHUB_CLIENT_ID=xxxxx
GITHUB_CLIENT_SECRET=xxxxx

Laravelプロジェクトの修正

backendディレクトリ配下のファイルを修正していきます。

フロントエンドの修正

ViteのサーバーへDockerから接続できるようにします。(フロントの開発をせず最初からビルドするだけなら不要です)

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
    //この下の部分を追加
    server: {
        host: true,
        hmr: {
            host: 'localhost',
        },
    },
});

バックエンド(Laravel)の変更

ファイルストレージの変更

ファイルの保存場所をAWS S3からローカルに変更します。
(doの部分をpublicと同一の設定に変更)

        'do' => [
            'driver' => 'local',
            'root' => storage_path('app/public'),
            'url' => env('APP_URL').'/storage',
            'visibility' => 'public',
            'throw' => false,
        ],

OAuth認証後のリダイレクト先を変更

フルパスが設定されているので相対パスへ変更します。

    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID'),
        'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('GITHUB_REDIRECT_URL', '/auth/callback'),
    ],

デモ部分を非表示

デモの変換済みスクリプトを表示している部分を表示しないようにします。

    @unless(empty(\App\Models\Transcript::firstWhere('public', true)))
    <div class="relative overflow-hidden bg-blue-600 py-32">
        <img src="/img/background-call-to-action.jpg"
             class="absolute top-1/2 left-1/2 max-w-none -translate-x-1/2 -translate-y-1/2"/>
        <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 relative grid md:grid-cols-2 gap-12">
...省略
            </div>
        </div>
    </div>
    @endunless

動作確認

DockerコンテナのビルドとLaravelのセットアップ、フロントエンドのビルドを行います。

make init
make yarn
make web
yarn build
http://localhost/にアクセスするとトップページが表示されます。
Transcribe for freeをクリックすると、音声ファイルのアップロード画面に進みます。
しばらく待つと文字起こしが行われます。

一応これで動くかと思いますが、変換したファイルへ後からアクセスするためページがないため、変換済みファイル一覧表示機能を追加します。

ファイル一覧表示追加

ルートの追加

use App\Http\Controllers\ShowTranscriptsController;

Route::group(['middleware' => 'auth'], function () {
    Route::get('transcribe', NewTranscriptionController::class);
    Route::post('transcribe', TranscribeAudioController::class);
    Route::get('transcripts', ShowTranscriptsController::class); //追加
});

Controllerの作成

<?php

namespace App\Http\Controllers;

use App\SendStack;
use App\Models\Transcript;

class ShowTranscriptsController extends Controller
{
    //return all transcripts for user
    public function __invoke(SendStack $sendStack)
    {
        //return user's transcripts
        $transcripts = Transcript::where('user_id', auth()->user()->id)->get();

        return view('transcripts', [
            'transcripts' => $transcripts
        ]);
    }
}

ビューの作成

@extends('layouts.app')

@section('content')
    <div class="max-w-7xl mx-auto my-24 w-full p-4">
        <ul class="list-disc">
            @foreach ($transcripts as $transcript)
                <li><a href="/transcript/{{$transcript->id}}">{{$transcript->hash}}</a> ({{$transcript->created_at}})</li>
            @endforeach
        </ul>
    </div>
@endsectionß

一覧ページへのリンク追加

<a class="group inline-flex items-center justify-center rounded-full py-2 px-4 text-sm font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 bg-slate-900 text-white hover:bg-slate-700 hover:text-slate-100 active:bg-slate-800 active:text-slate-300 focus-visible:outline-slate-900"
                href="/transcripts">Transcribed files</a>

これにより以前に変換したファイル一覧がhttp://localhost/transcript/にアクセスすると表示されるようになります。

以上、Whisperを使った文字起こしを行うためのWebUIをwriteout.aiのセルフホストで構築する方法を紹介しました。

この記事を書いた人

PHPが好物な個人開発プログラマ。フリーランスエンジニアとしてWebサービス作ったりしてます。15年の経験を生かしてMENTAでメンターもやってます。WordPressやPHPでお困りのことがあればご相談に乗りますのでDMください。

Follow on SNS
DockerWebService
SOHO MIND

Comments

タイトルとURLをコピーしました