Amazon TranscribeでKeyError: 'TranscriptFileUri'となったときの対処方法について

# はじめに
最近[AWSでつくるAIプログラミング入門](https://amzn.to/3uFcHT8)という本をやっています。
この本が書かれたのは、2019年末で当初は英語でしか動かなかったシステムも今では日本語で動くようになっているものがいくつかあります。
その中にAmazon Transcribeがあり、日本語で本書のコードの一部を変更して対応させていましたが、エラーに遭遇したので今回はまとめたいと思います。


# 問題

以下のコードを実行したところエラーになりました。
このコードは本書のコードを一部変更したものになります。

```
import boto3
import json
import pprint
import time
import uuid
import urllib

bucket = str(uuid.uuid1())
print('bucket:', bucket)
region = 'ap-northeast-1'
s3 = boto3.client('s3', region)
result = s3.create_bucket(
    Bucket=bucket,
    CreateBucketConfiguration={'LocationConstraint': region})

file = 'scribe_file_in.mp3'
key = 'input'
s3.upload_file(file, bucket, key)

transcribe = boto3.client('transcribe', region)
job = str(uuid.uuid1())
uri = 'https://s3-'+region+'.amazonaws.com/'+bucket+'/'+key
result = transcribe.start_transcription_job(
    TranscriptionJobName=job, Media={'MediaFileUri': uri},
    MediaFormat='mp3', LanguageCode='ja-JP')
print('start_transcription_job:')
pprint.pprint(result)

start = time.time()
while True:
    result = transcribe.get_transcription_job(TranscriptionJobName=job)
    status = result['TranscriptionJob']['TranscriptionJobStatus']
    if status != 'IN_PROGRESS':
        break
    time.sleep(10)
    print('time:', time.time()-start)
print('get_transcription_job:')
pprint.pprint(result)

uri = result['TranscriptionJob']['Transcript']['TranscriptFileUri']
print('uri:', uri)
with urllib.request.urlopen(uri) as file_in:
    transcripts = json.load(file_in)
with open('scribe_file_out.json', 'w', encoding='utf-8') as file_out:
    json.dump(transcripts, file_out, indent=4)

print('transcript:')
for transcript in transcripts['results']['transcripts']:
    print(transcript['transcript'])

transcribe.delete_transcription_job(TranscriptionJobName=job)
s3.delete_object(Bucket=bucket, Key=key)
s3.delete_bucket(Bucket=bucket)
```

変更点は以下の2点です。
1. regionが`us-east-2`から`ap-northeast-1`に変更
2. transcribe.start_transcription_jobのLanguageCodeが`en-US`から`ja-JP`に変更

同じディレクトリには、ボイスレコーダーで自分の声を録音した`scribe_file_in.m4a`を`scribe_file_in.mp3`にJupyterNotebook上で名前を変更しました。

すると、以下のエラーが出ます。

```
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-19-68aeffcf87d9> in <module>
     38 pprint.pprint(result)
     39 
---> 40 uri = result['TranscriptionJob']['Transcript']['TranscriptFileUri']
     41 print('uri:', uri)
     42 with urllib.request.urlopen(uri) as file_in:

KeyError: 'TranscriptFileUri'
```

# 解決方法

resultをみたところ、以下のようになっていました。

```
{'ResponseMetadata': {'HTTPHeaders': {'connection': 'keep-alive',
                                      'content-length': '562',
                                      'content-type': 'application/x-amz-json-1.1',
                                      'date': 'Mon, 31 May 2021 13:48:28 GMT',
                                      'x-amzn-requestid': 'fd6eb86c-4668-4f0d-8af6-6847b09f9c40'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'fd6eb86c-4668-4f0d-8af6-6847b09f9c40',
                      'RetryAttempts': 0},
 'TranscriptionJob': {'CreationTime': datetime.datetime(2021, 5, 31, 13, 48, 19, 219000, tzinfo=tzlocal()),
                      'FailureReason': 'The media format that you specified '
                                       "doesn't match the detected media "
                                       'format. Check the media format and try '
                                       'your request again.',
                      'LanguageCode': 'ja-JP',
                      'Media': {'MediaFileUri': 'https://s3-ap-northeast-1.amazonaws.com/da8910de-c216-11eb-8f7b-0242ac190002/input'},
                      'MediaFormat': 'mp3',
                      'Settings': {'ChannelIdentification': False},
                      'Transcript': {},
                      'TranscriptionJobName': 'db3498b4-c216-11eb-8f7b-0242ac190002',
                      'TranscriptionJobStatus': 'FAILED'}}
```

この中の以下の箇所をみると、`Transcript`が空({})となっていました。

```
                      'MediaFormat': 'mp3',
                      'Settings': {'ChannelIdentification': False},
                      'Transcript': {},
```

空になっているので、そもそも文字おこし自体が失敗しています。
これが、`KeyError: 'TranscriptFileUri'の原因でした。

ちなみに以下の箇所が`FAILED`になっているので失敗していることがわかります。

```
                      'TranscriptionJobStatus': 'FAILED'}}
```

この処理は、非同期で行われます。このコードでは`COMPLETED`になるまで繰り返し表示がありますが、そのようなコードにしていない場合は、何度か実行してCOMPLETEDになるまで待つ必要があります。

では、なぜ`FAILED`になっているのでしょうか。

それは、`.m4a`を`.mp3`に変えたことでした。
`.m4a`をアップロードしてFormatを`mp4`にしても動きます。

以下のコードで解決しました。

```
import boto3
import json
import pprint
import time
import uuid
import urllib

bucket = str(uuid.uuid1())
print('bucket:', bucket)
region = 'ap-northeast-1'
s3 = boto3.client('s3', region)
result = s3.create_bucket(
    Bucket=bucket,
    CreateBucketConfiguration={'LocationConstraint': region})

file = 'scribe_file_in.m4a'
key = 'input'
s3.upload_file(file, bucket, key)

transcribe = boto3.client('transcribe', region)
job = str(uuid.uuid1())
uri = 'https://s3-'+region+'.amazonaws.com/'+bucket+'/'+key
result = transcribe.start_transcription_job(
    TranscriptionJobName=job, Media={'MediaFileUri': uri},
    MediaFormat='mp4', LanguageCode='ja-JP')
print('start_transcription_job:')
pprint.pprint(result)

start = time.time()
while True:
    result = transcribe.get_transcription_job(TranscriptionJobName=job)
    status = result['TranscriptionJob']['TranscriptionJobStatus']
    if status != 'IN_PROGRESS':
        break
    time.sleep(10)
    print('time:', time.time()-start)
print('get_transcription_job:')
pprint.pprint(result)

uri = result['TranscriptionJob']['Transcript']['TranscriptFileUri']
print('uri:', uri)
with urllib.request.urlopen(uri) as file_in:
    transcripts = json.load(file_in)
with open('scribe_file_out.json', 'w', encoding='utf-8') as file_out:
    json.dump(transcripts, file_out, indent=4)

print('transcript:')
for transcript in transcripts['results']['transcripts']:
    print(transcript['transcript'])

transcribe.delete_transcription_job(TranscriptionJobName=job)
s3.delete_object(Bucket=bucket, Key=key)
s3.delete_bucket(Bucket=bucket)
```

データはボイスレコーダーでつくられたファイル形式そのままです。
これで動きました。

# さいごに
AWSのエラーは割と起きると解決までたどり着くのが難しい気がしています。
あまり、解決のための記事というのが日本語ではすくなく、全体としても少ないです。
直接問い合わせるのがよさそうですね。

# 参考記事
[AWSでつくるAIプログラミング入門](https://amzn.to/3uFcHT8)