亚马逊AWS官方博客

采用 Amazon Transcribe 服务快速为大型线上会议视频添加字幕

背景介绍

去年,突如其来的疫情给我们的生活和学习,带来了极大的影响。一方面需要减少人员聚集、降低疫情传播风险,同时又需要兼顾好日常工作推进,很多客户把线下会议转移到线上举办,亚马逊云科技也不例外。尤其是去年底的re:Invent 2020, 史无前例采用免费线上会议的方式举办,同时,为了中国客户更好的观看体验,我们也推出了本地化的亚马逊re:Invent国内站点,方便国内的客户观看,相信不少的粉丝已经通过国内的站点观看了最新的fable和技术画。

由于re:Invent在全球主要采用英语进行直播,尤其是来自不同国家的英语口音对国内的开发者去学习和了解re:Invent的技术内容会有较大的挑战。在收到全球站点传来视频之后,我们既需要第一时间在国内站点播出,又希望能够为中国客户和开发者的体验增加英文字幕,这就需要我们能极短的时间内为每天新增的数十个甚至上百个视频增加英文字幕。在以往,为一个1小时的英文视频增加字幕,就需要翻译人员数小时的工作,去听取英文文本,同时还需要借助一些专业的工具,为听录的文本增加时间轴,这个过程成本极高,也无法满足我们第一时间希望能够发布的需求。

细心的小伙伴们可能已经发现了,在国内站点的视频英文字幕前面都有一句话:“字幕由Amazon Transcribe服务提供”,那么这是一个怎样神奇的服务,又是如何快速帮助我们解决视频的英文字幕问题呢?

Amazon Transcribe 是一项自动语音识别 (ASR) 服务,让开发人员能够轻松地为其应用程序添加语音转文本功能,他可以支持多种语言的语音识别服务,其中包括英文和中国普通话的支持,而且在北京区域(BJS)和宁夏区域(ZHY)也已支持该项服务。

为了让国内的客户和粉丝能够第一时间看到re:Invent视频,很多时候从拿到数十个视频到发布,只有几个小时,这么短啊的时间,我们是如何借助Amazon Transcribe来为每天上线的英文视频增加字幕呢?下面我就介绍一下,这个简单易用,无服务器架构的自动生成字幕的解决方案。如果您也刚好有一个同样的需求,可以直接用起来噢!

体系架构

示例的总体架构如下图所示:

  1. 上传视频文件到Amazon S3存储桶,可以选定一个特定的文件夹;
  2. Amazon S3监测到存储桶中某个文件夹有新增的文件,触发lambda函数;
  3. Lambda函数调用Amazon Transcribe服务,生成视频对应的文本(json格式);
  4. 对字幕文本进行格式转换,生成支持播放器的字幕文件格式(srt);
  5. 上传字幕文件到存储桶指定的文件夹中。

实现过程

1. 创建S3存储桶

首先在AWS管理控制台进入”S3“服务,点击“创建存储桶”, 输入存储桶的名称,点击“创建”按钮创建一个s3存储桶。并且在新创建的桶里,点击“创建文件夹“,创建一个名字叫做“videos”的文件夹用于存放我们的视频文件。

注意这里我们新建一个文件夹存放需要加载字幕的视频,而不是直接把视频放到桶里,这样可以避免我们的Lambda函数被自己生成的字幕文件循环调用。

 

2. 创建IAM角色

每个Lambda函数都有一个与之关联的IAM角色。此角色定义允许该功能与其进行交互的其他Amazon Web Services服务。在本示例中,您需要创建一个IAM角色,授予您的Lambda函数权限,以便与Transcribe服务以及在上一步中创建的S3服务进行交互。

在Amazon Web Services管理控制台中,单击“服务”,然后选择“IAM”。在左侧导航栏中选择“角色”,然后选择“创建角色”,依次选择“AWS产品”,“Lambda”作为角色类型,然后单击“下一步:权限”按钮,在“筛选策略”选择“AmazonS3FullAccess”,“ AWSLambda_FullAccess”和“ AmazonTranscribeFullAccess”,点击“下一步:审核”,中角色名称中输入“addSubtitleRole”,点击“创建角色”。

3. 创建Lambda函数

在Amazon Web Services管理控制台进入“Lambda”服务,点击“创建函数”按钮。在“函数名称”中填写函数名称,在“运行时”的选择框中选择“Python 2.7”。这里要特别注意,我们选择更改默认执行角色,在“执行角色”中选择“使用现有角色”,然后选择刚刚创建的角色名称,点击“创建函数”按钮完成函数的创建。在此示例中,我们选择了Python 2.7作为开发环境,并为该Lambda函数赋予了上一步创建的角色。

4. 配置触发条件

在Lambda的函数配置页面,点击“添加触发器”按钮添加触发条件。选择“s3”。

在触发条件配置页面,在“存储桶”下拉列表中选择刚刚创建的存储桶名称,在“事件类型”下拉列表中选择“Put”,在“前缀”中输入“videos/”(注意这里要有“/”),在“后缀”中输入“.mp4”,然后点击“添加”按钮,完成触发条件的添加。该触发条件设置监视刚刚创建存储桶的videos目录中扩展名为.mp4的文件,如果新增一个视频,将触发该lambda函数。

5. Lambda内存和超时配置

在刚创建的Lambda函数中,我们需要配置了内存的大小和执行超时。由于Lambda函数会调用Transcribe服务进行文字提取,因此不需要修改内容的大小,默认值为128MB。示例中我们采用的视频文件的时长均在半小时内,Transcribe的处理时间通常不会超过10分钟,在这里我们设置超时时长“Timeout”为10分钟。

6. 导入Lambda函数

打开附件中的python文件,将其内容粘贴在Lambda函数实现的区域,点击右上角的“Deploy”按钮。


Lambda的实现主要包括以下几个步骤:

6.1. 参数获

从event对象中和系统变量中获取相关参数信息。

  • region:当前区域,示例中使用的是区域是宁夏区域
  • bucket_name:存储桶名称,您刚刚创建的存储桶名称
  • sourceS3Key:视频文件的key值。这里是我们刚才所设置的监测videos目录下的mp4类型的文件,key值为videos/***.mp4
  • fn:根据sourceS3Key提取文件名。
  • dir:根据sourceS3Key提取目录名。

6.2. 调用Transcribe

  • 为每个任务job_name创建唯一的标识
  • 调用starttranscriptionjob,下面的代码中介绍了每个参数以及含义
  • 由于调用的job是异步任务,我们通过轮训的方法检测job的返回结果

#生成转换任务的时间戳

now=int(time.time())

job_name = "conv-"+str(now)

    启动转换任务
    MediaFileUri:媒体路径,示例采用s3的路径
    MediaFormat:媒体格式,目前支持mp3,mp4,wav,flac
    LanguageCode:媒体的语言编码,我们的视频是英文的,设置为en-US
    ”’

       transcribe = boto3.client('transcribe')
    transcribe.start_transcription_job(
        TranscriptionJobName=job_name,
        Media={'MediaFileUri': job_uri},
        MediaFormat='mp4',
        LanguageCode='en-US'
    )

    #转换需要一定的时间,这里进行轮训检测处理结果。您也可以通过控制台查看任务状态。

while True:
        status = transcribe.get_transcription_job(TranscriptionJobName=job_name)
        if status['TranscriptionJob']['TranscriptionJobStatus'] in ['COMPLETED', 'FAILED']:
            break
        print("Transcribe is processing...")
        time.sleep(5)

6.3. 生成srt字幕文件

Transcribe所抓取转化的字幕json数组中,包含每个字(或者词语)的开始时间,结束时间,置信度等信息。下面我们需要把json数组转换成字幕文件。常见的字幕格式是SRT格式。SRT 的格式非常简单:一句时间代码 + 一句字幕。

if (status['TranscriptionJob']['TranscriptionJobStatus']=='COMPLETED'):
        url=status['TranscriptionJob']['Transcript']['TranscriptFileUri']
        text=downloadJson(url)
        sJson = json.loads(text)
        output=process(sJson["results"]['items'])
        uploadResult(region,bucket_name,dir+'/output/'+fn, output)
    return {
        'statusCode': 200,
        'body': json.dumps('Process complete')
    }

6.4. 转换为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=''
    return output

6.5. 上传结果到S3

最后我们将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) #remove .mp4

7. 测试

在管理控制台点击“S3”服务,打开刚创建的存储桶,进入“videos”目录,点击“上传”“添加文件”从本地电脑里选择一个视频文件,点击“上传”。此时就会触发我们刚刚创建的Lambda函数。我们可以在“Amazon Transcribe”观察job的执行情况。点击任何一个job的名称,可以显示job的详细信息。当job的状态显示为“完成”,进入到S3存储桶的“输出”目录,您会惊喜的发现,字幕文件已经生成了。

 

下面我们看一下效果吧。看起来效果还是不错的,除了一些技术词汇可能有些偏差,大部分的对话都可以完整的听录下来。如果需要字幕内容需要翻译,这也是一个很好的起点,可以极大的降低翻译人员的工作量。

成本分析

做了这么多,我们需要花费多少钱呢?AWS官网很贴心的为大家做了一个常用案例的成本分析:

按照上面的假设,Amazon Transcribe 处理一个半小时左右的网络研讨会,大概只需要人民币4.86元。

总结

通过使用无服务器架构的Amazon Transcribe,结合Amazon Lambda和Amazon S3,用户可以方便的在视频处理的各种场景中,无需购买服务器,方便快速的构建自己的视频字幕转换应用。上面的案例是一个我们在实际场景应用的简化版,适合短视频的处理。但是,在实际的运用中,由于部分视频时间较长,比如keynote可能长达3-4个小时,如果我们采用现在的架构,就会出现Lambda超时的问题。对于长视频的处理,我们就需要优化架构。限于篇幅,具体的方法在本博客中不再赘述,但是小伙伴们可以动起来手来,改进现在的架构,比如把我们的Lambda应用分成两个部分,现在的部分仅仅用来启动Transcribe的任务,不再轮询去等待任务的完成。而采用CloudWatch event来监听Transcribe job状态的变化,从而触发另一个lambda处理生成的JSON格式的字幕文件转化成一个SRT的格式,这样既可以避免超时的发生,同时也降低了Lambda服务轮询所产生的成本。

本篇作者

Leon Li (李昕)

亚马逊云科技资深产品市场经理。全面负责亚马逊云科技在人工智能,机器学习,数据库,大数据和物联网等有关的云服务的产品营销和推广。在加入亚马逊云科技之前,曾服务于Apple,Oracle,Lenovo 等世界知名跨国企业,涵盖产品战略规划和行业应用等多个领域。

本篇作者

王凯

亚马逊云科技解决方案架构师,负责基于 AWS 云计算方案架构的咨询和设计,推广 AWS 云平台技术和各种解决方案。在加入亚马逊云科技之前,曾服务于Huawei,Alibaba 等世界知名企业,负责视频云、边缘计算解决方案和架构设计,在直播、音视频通信、CDN 领域有丰富经验。