语音识别技术,也被称为自动语音识别(Automatic Speech Recognition,简称ASR),其目标是将人类的语音中的词汇内容转换为计算机可读的输入,例如按键、二进制编码或者字符序列。语音识别技术已经发展了几十年,直到2009年,Hinton把人工智能深度学习解决方案引入语音识别中,语音识别才取得了巨大突破。
Amazon Transcribe 是一项自动语音识别 (ASR) 服务,使开发人员能够轻松地为其应用程序添加语音转文本功能。自从在 re:Invent 2017 发布以来,越来越多的用户将语音识别功能添加到其应用程序和设备中。2019年8月,Amazon Transcribe推出对中文普通话的支持。更加另用户兴奋的是,在中国的北京区域(BJS)和宁夏区域(ZHY)也已支持该项服务。
在日常工作学习中,我们往往需要对一个视频文件增加字幕。传统的方法需要将视频中的对话用文字记录下来,通常采用记事本等工具保存文字记录,然后借助一些工具软件为文字加上时间轴,然后进行人工校对,整个过程需要耗费大量的时间和精力。是否有更快捷的方法呢?下面我们分享一个使用Amazon Transcribe为视频自动增加字幕的示例。
示例的总体架构如下图所示:
监测到S3存储桶中的文件变化,触发lambda函数;
lambda函数调用Transcribe服务,生成视频对应的文本(json格式);
对文本进行格式转换,生成字幕文件格式(srt);
上传字幕文件到存储桶。
首先在AWS管理控制台进入”S3“服务,点击“Create bucket”, 输入存储桶的名称,点击“Create”按钮创建一个s3存储桶。
在刚建立的存储桶中,点击“Create folder”按钮,输入文件目录名称“video”,然后点击“Save”按钮。然后进入到“video”目录,点击“Create folder”按钮,输入文件目录名称“output”,点击“Save”按钮。此时,您在存储桶中创建了“video”目录,后面的lambda函数将监测video目录中的文件变化。在“video”目录下的“output”目录用来存储生成的字幕文件。
每个Lambda函数都有一个与之关联的IAM角色。此角色定义允许该功能与其进行交互的其他AWS服务。在本示例中,您需要创建一个IAM角色,授予您的Lambda函数权限,以便与Transcribe服务以及在上一步中创建的S3服务进行交互。
在AWS管理控制台中,单击“Services”,然后选择“IAM”。在左侧导航栏中选择“Role”,然后选择“Create new role”,依次选择“AWS Service”,“Lambda”作为角色类型,然后单击“Next:Permissions”按钮,在“Filter”文本框中键入“AWSLambdaBasicExecutionRole”,然后选中该角色旁边的复选框,然后在“”中输入“AmazonTranscribeFullAccess”,选中该角色旁边的复选框,点击“Next:Review”,中角色名称中输入“extractSubtitleRole”,点击“Create role”。
在AWS管理控制台进入“Lambda”服务,点击“Create Function”按钮。
在“Function name”中填写函数名称,在“Runtime”的选择框中选择“Python 2.7”,在“Permissions”中选择“Use an existing role”,然后选择刚刚创建的角色名称,点击“Create function”按钮完成函数的创建。在此示例中,我们选择了Python 2.7作为开发环境,并为该Lambda函数赋予了上一步创建的角色。
在Lambda的函数配置页面,点击“Add Trigger”按钮添加触发条件。
在触发条件配置页面,在“Bucket”下拉列表中选择刚刚创建的存储桶名称,在“Event”下拉列表中选择“Put”,在“Prefix”中输入“videos/”,在“Suffix”中输入“.mp4”,然后点击“add”按钮,完成触发条件的添加。该触发条件设置监视刚刚创建存储桶的video目录中扩展名为.mp4的文件,如果是put操作,将触发该lambda函数。
5. Lambda内存和超时配置
在刚创建的Lambda函数中,我们需要配置了内存的大小和执行超时。由于Lambda函数会调用Transcribe服务进行文字提取,因此不需要修改内容的大小,默认值为128MB。示例中我们采用的视频文件的时长均在一分钟内,Transcribe的处理时间通常不会超过一分钟,在这里我们设置超时时长“Timeout”为10分钟。
6. 导入Lambda函数
打开附件中的python文件,将其内容粘贴在Lambda函数实现的区域,点击右上角的“Save”按钮。
Lambda的实现主要包括以下几个步骤:
从event对象中和系统变量中获取相关参数信息。
region:当前区域,示例中使用的是区域是us-east-1
bucket_name:存储桶名称,您刚刚创建的存储桶名称
sourceS3Key:视频文件的key值。示例中监测video目录下的mp4类型的文件,key值为video/sample.mp4
fn:根据sourceS3Key提取文件名。示例中fn为mp4
dir:根据sourceS3Key提取目录名。示例中dir为video
为每个任务job_name创建唯一的标识
调用starttranscriptionjob,下面的代码中介绍了每个参数以及含义
由于调用的job是异步任务,我们通过轮训的方法检测job的返回结果
#生成转换任务的时间戳
now=int(time.time())
job_name = “conv-“+str(now)
”’
启动转换任务
MediaFileUri:媒体路径,示例采用s3的路径
MediaFormat:媒体格式,目前支持mp3,mp4,wav,flac
LanguageCode:媒体的语言编码,我们的视频是中文的,设置为zh-CN
”’
transcribe = boto3.client('transcribe')
transcribe.start_transcription_job(
TranscriptionJobName=job_name,
Media={'MediaFileUri': job_uri},
MediaFormat='mp4',
LanguageCode='zh-CN'
)
#转换需要一定的时间,这里进行轮训检测处理结果。您也可以通过控制台查看任务状态。
while True:
status = transcribe.get_transcription_job(TranscriptionJobName=job_name)
if status['TranscriptionJob']['TranscriptionJobStatus'] in ['COMPLETED', 'FAILED']:
break
print("Processing...")
time.sleep(5)
job的返回结果
我们可以看到,在json数组中,包含每个字(或者词语)的开始时间,结束时间,置信度等信息。下面我们需要把json数组转换成字幕文件。常见的字幕格式有srt, ssa, ass, idx+sub,其中srt, ssa, ass是文本格式,idx+sub是图形格式。本示例中,我们将使用srt格式。srt的格式非常简单:一句时间代码 + 一句字幕。
函数process()将上一步的json文件转换成srt格式的字幕文件,具体处理过程如下。
def process(items):
i=1
output=''
isStart=False
isEnd=False
start_time=0
end_time=0
msg=''
for index, item in enumerate(items):
if (not item.has_key('start_time')):
msg=msg+item['alternatives'][0]['content']
else:
end_time=float(item['end_time'])
if (end_time-start_time>4.0 or index+1==len(items)):
isEnd=True
if (not isStart and item.has_key('start_time')):
isStart=True
start_time=float(item['start_time'])
msg=msg+item['alternatives'][0]['content']
output=output+str(i)+'\n'
continue
if (isStart and not isEnd and item.has_key('start_time')):
msg=msg+item['alternatives'][0]['content']
if (isStart and isEnd):
hour=int(start_time/60/60)
min=int(start_time/60)-hour*60
sec=int(start_time)-min*60-hour*60*60
msec=int((start_time-sec)*1000)
e_hour=int(end_time/60/60)
e_min=int(end_time/60)-e_hour*60
e_sec=int(end_time)-e_min*60-e_hour*60*60
e_msec=int((end_time-sec)*1000)
msg1='{}:{}:{},{} --> {}:{}:{},{}'.format(hour,min,sec,msec, e_hour,e_min,e_sec,e_msec)+'\n'
output=output+msg1+msg+item['alternatives'][0]['content']+'\n\n'
i=i+1
isStart=False
isEnd=False
start_time=end_time
msg=''
最后我们将srt文件上传到s3,本示例中,我们设置了video/output作为其输出的存储路径。
def uploadResult(region,bucket_name,fn,body):
s3 = boto3.client(service_name='s3',region_name=region)
s3.put_object(Bucket=bucket_name, Key=fn[0:-4]+'.srt', Body=body)
在AWS管理控制台点击“S3”服务,打开刚创建的存储桶,进入“video”目录,点击“Upload”“Add files”从本地电脑里选择一个视频文件,点击“Upload”。此时就会触发我们刚刚创建的Lambda函数。我们可以在“Amazon Transcribe”观察job的执行情况。点击任何一个job的名称,可以显示job的详细信息。当job的状态显示为“Complete”,进入到S3存储桶的“output”目录,您会惊喜的发现,字幕文件已经生成了。注意,由于Lambda函数有600秒的执行超时限制,尽量不要处理时间超过15分钟的视频。
下面我们看一下效果吧。通过一些测试,语音的识别正确率还是比较高的,值得一提的是,Transcribe能够根据语音的停顿自动加上标点。但在人名的识别上,正确率还有待提高。
很多播放软件都支持自动加载字幕,需要把字幕文件和原始的视频文件放在同一目录。如果需要把字幕合成到视频中,可以使用ffmpeg为视频嵌入字幕,如果您使用的是Mac操作系统,也可以使用subler图形化工具把字幕文件嵌入到视频中。
在这里,一些读者一定会由于疏忽遗漏了上面的某些步骤,从而导致Lambda函数执行失败。在CloudWatch中,可以快速查询到Lambda函数的执行日志,方便读者进行调试。
成本分析
最后我们分析一下成本,以美东弗吉尼亚区域(us-east-1)为例:
Lambda实例采用128M内存,每月有3,200,000秒的免费用量,假设处理一段视频需要600秒,免费额度内您可以处理近5000个视频。
Memory (MB) | Free tier seconds per month | Price per 100ms ($) |
128 | 3,200,000 | 0.000000208 |
假设采用S3标准存储,假设处理5000个视频文件,每个文件大小100M,大约需要500G的存储空间,大约需要12$(5000*100/1024*0.023)。字幕文件为文本格式,大小可以忽略。
Volume Size | Price |
First 50 TB / Month | $0.023 per GB |
Next 450 TB / Month | $0.022 per GB |
Over 500 TB / Month | $0.021 per GB |
Amazon Transcribe API的计价方式是$0.0004/秒,按照上面的假设,每个视频长度为600秒,费用大约需要1200$(5000*600*0.0004)。
按照上面的假设,处理每个视频需要的费用仅仅需要0.24$ ((1200+12)/5000)),折算成人民币,成本不到2块钱。
通过使用Amazon Transcribe,用户可以方便的集成在各种场景中。用户不需要购买服务器,不需要算法实现,仅通过Lambda或者API调用的方式,方便快速的构建自己的ASR应用。
https://aws.amazon.com/cn/blogs/china/amazon-transcribe-now-supports-mandarin-and-russian/
https://docs.aws.amazon.com/zh_cn/transcribe/latest/dg/what-is-transcribe.html
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/transcribe.html
https://awschinablog.s3.cn-north-1.amazonaws.com.cn/appx/sourcecode.zip
完
“欢迎来访~”