Ruby/AWS S3/AWS Lambda/Amazon Transcribeで動画の文字起こしをする

はじめに

RubyとS3とLambdaとTranscribeを使ってS3にアップロードされた動画を自動的に文字起こしするフローを作ってみました。 この記事はその時の手順などをまとめたものになります。

何故やったかというと、妻が「中国語の動画の字幕が欲しい」と言っていたのがきっかけ。 良い感じに動画の文字起こしができるものがないかと探してたところ、S3とLambdaとTranscribeで行けそうな感じだったのでやってみた感じです

やったこと

S3にアップロードされた動画をLambdaで受け取る

まずはS3にアップロードされた動画をLambdaで受け取ることができるようにします。

AWS Lambdaで以下のように関数を作成します。

f:id:gamelinks007:20210515172027p:plain

作成する関数名はs3-upload-hookとします。 また「ランタイム」はRuby 2.7、「デフォルトの実行ロールの変更」は「基本的な Lambda アクセス権限で新しいロールを作成」を選択します。

最後に「関数の作成」をクリックしてLambdaの関数を作成します。

関数の作成後、使用するコードを以下のように変更します。

require 'json'
require 'logger'

def lambda_handler(event:, context:)
    logger = Logger.new($stdout)
    
    logger.info("Hook Event")
    logger.info(event)
    
    record = event["Records"][0]
    s3 = record["s3"]
    region = record["awsRegion"]

    bucket = s3["bucket"]["name"]
    object_key = s3["object"]["key"]
    path = "https://#{bucket}.s3-#{region}.amazonaws.com/#{object_key}"
    job_name = "TranscriptionJobName_#{object_key.sub(/movie\//, "")}"
    output_key = object_key.sub(/movie/, "text").sub(/\.mp4/, "")

    logger.info("S3 info")
    logger.info(bucket)
    logger.info(object_key)
    logger.info(path)
end

コードの内容としては、S3から受け取ったバケットなどの情報を最終的に出力しています。

次に、S3でファイルがアップロードされたイベントをフックできるようにします。

f:id:gamelinks007:20210515172523p:plain

バケット」には動画がアップロードされるバケットを指定します。特定のディレクトリを指定したい場合はプレフィックスmovie/のようにディレクトリ名を指定します

最後に「追加」をクリックしてトリガーを追加します。

ここまでの段階で、S3にアップロードされた動画ファイルをLambdaで受け取ることができるようになります。

Transcribeで文字起こしをするジョブを作成

次に、LambdaからTranscribeへと文字起こしをするジョブの処理を追加します。

Lambdaのコードに以下の部分を追加します。

require 'json'
+ require 'aws-sdk-transcribeservice'
require 'logger'

def lambda_handler(event:, context:)
    logger = Logger.new($stdout)
    
    logger.info("Hook Event")
    logger.info(event)
    
    record = event["Records"][0]
    s3 = record["s3"]
    region = record["awsRegion"]

    bucket = s3["bucket"]["name"]
    object_key = s3["object"]["key"]
    path = "https://#{bucket}.s3-#{region}.amazonaws.com/#{object_key}"
    job_name = "TranscriptionJobName_#{object_key.sub(/movie\//, "")}"
    output_key = object_key.sub(/movie/, "text").sub(/\.mp4/, "")

    logger.info("S3 info")
    logger.info(bucket)
    logger.info(object_key)
    logger.info(path)

+    client = Aws::TranscribeService::Client.new(region: region)
    
+    response = client.start_transcription_job({
+        transcription_job_name: job_name,
+        language_code: "ja-JP",
+        media_format: "mp4",
+        media: {
+            media_file_uri: path
+        },
+        output_bucket_name: bucket,
+        output_key: "#{output_key}.txt"
+    })
+
+    logger.info("TranscriptionJob start #{response.transcription_job.transcription_job_name}")
end

client = Aws::TranscribeService::Client.new(region: region)でTranscribeへ投げるジョブを作成するためのクライアントを生成します。

次に、以下の部分でTranscribeへ投げるジョブを生成しています。

    response = client.start_transcription_job({
        transcription_job_name: job_name,
        language_code: "ja-JP",
        media_format: "mp4",
        media: {
            media_file_uri: path
        },
        output_bucket_name: bucket,
        output_key: "#{output_key}.txt"
    })

transcription_job_nameは実行するジョブの名前を指定しています。ジョブの名前は一意でないといけないため、同じ名前のジョブがあるとエラーになるので注意が必要です。

language_codeでは文字起こしする動画ファイルの言語を指定し、media_formatでは変換元のファイルの種類を指定します。

media_file_uriには変換元の動画ファイルのパスを渡しています。また、output_bucket_nameoutput_keyで文字起こししたテキストをS3のどこに保存するかを指定しています。

ここまでの段階で、動画をS3にアップロードすると自動的に文字起こしされたファイルがS3に作成されるようになります。

おわりに

意外と簡単に実装できたので、このフローをベースに海外の動画の文字起こしサービスとか作っても面白そうかなと思いました。