经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 大数据/云/AI » 人工智能基础 » 查看文章
教你1分钟搞定2小时字幕
来源:cnblogs  作者:华为云开发者联盟  时间:2023/5/26 9:56:13  对本文有异议
摘要:本文将介绍如何使用录音文件识别极速版给无字幕视频自动生成字幕。

本文分享自华为云社区《利用录音文件极速版为视频生成字幕》,作者:戈兀。

引言

越来越多的人们使用抖音、B站等视频app,记录、分享日常生活,随之互联网上产生了大量的长、短视频。字幕是影响视频观看体验的重要因素。以日常分享为主的视频创作者往往没有时间为视频制作字幕,在创作者发音不清楚的前提下,没有字幕的视频可能会让观众困惑甚至产生理解偏差。而带字幕的视频让观众有更好的观看体验,“一气呵成”顺畅地看完。

语音识别技术(Automatic Speech Recognition)是一种将人的语音转换为文本的技术。随着深度学习的发展,端到端语音识别技术也取得了巨大的突破。将原始的音频数据,经过分帧、加窗、FFT等操作后,得到描述音频在时、频域信息的梅尔特征或是Fbank特征。将特征送入transformer等神经网络,输出对应的文本信息。此外,由大量文本训练的语言模型(language model)能够纠正语音识别输出文本不通顺的问题,改善阅读体验。而热词技术也被用来解决语音识别的领域适配问题,如同音不同字。

本文将介绍如何使用录音文件识别极速版给无字幕视频自动生成字幕。

录音文件识别极速版采用同步接口,利用GPU加速模型的推理过程。对于两个小时内的音、视频文件,可以在1分钟内返回识别结果,满足准实时字幕、音频质检等对识别速度有要求的场景。感兴趣的读者可以点击录音文件识别极速版文档,了解详情。
:本文同步发布至华为云AI Gallery Notebook,可以在AI Gallery上运行:利用录音文件极速版为视频生成字幕

原理讲解

给无字幕视频生成字幕,就是从视频中的提取音频流,将音频流送入录音文件识别极速版,得到识别文字,和对应的时间戳信息。然后将其转换为视频字幕文件格式,如srt文件。得到srt字幕文件后,在播放视频时,载入字幕文件,就可以看到字幕了。

因此,整个流程如下:

1、利用ffmpeg工具,从视频中提取音频流

2、设置适合的参数,使用录音文件识别极速版,催音频文件进行识别

3、对识别结果,包括文字和时间戳信息,进行处理,得到视频字幕文件

4、将命名相同的视频文件与 srt 文件放在同一目录下,用播放器打开,即可得到有字幕的视频。或者利用ffmpeg,以硬字幕的形式,将字幕嵌入到视频中。

注:SRT(SubRip 文件格式)是以 SubRip 文件格式保存的简单字幕文件,扩展名为 .srt。每个字幕在 SRT 文件中有四个部分:

  1. 指示字幕编号或位置的数字计数器;
  2. 字幕的开始和结束时间;
  3. 一行或多行的字幕文本;
  4. 表示字幕结束的空行。

代码开发

步骤一:提取音频流

采用ffmpeg从视频文件中提取音频流,并保存为音频文件output.wav

  1. ffmpeg -i input.mp4 -ar 16000 -ac 1 output.wav

-ar指定保存音频文件的采样率,这里16000表示1秒钟,保存16000个采样点数据;-ac指定保存音频的通道数,这里1表示保存为单通道音频。

步骤二:安装语音识别python SDK

在安装python3后,用pip安装其他依赖依赖包

  1. pip install setuptools
  2. pip install requests
  3. pip install websocket-client

下载最新版python sdk源码:https://sis-sdk-repository.obs.cn-north-1.myhuaweicloud.com/python/huaweicloud-python-sdk-sis-1.8.1.zip
进入下载的Python SDK目录,在setup.py所在层目录执行 python setup.py install 命令,完成SDK安装。

步骤三:调用录音文件极速版

  • 导入依赖包
  1. from huaweicloud_sis.client.flash_lasr_client import FlashLasrClient
  2. from huaweicloud_sis.bean.flash_lasr_request import FlashLasrRequest
  3. from huaweicloud_sis.exception.exceptions import ClientException
  4. from huaweicloud_sis.exception.exceptions import ServerException
  5. from huaweicloud_sis.bean.sis_config import SisConfig
  6. import json
  • 初始化客户端
  1. config = SisConfig()
  2. config.set_connect_timeout(50)
  3. config.set_read_timeout(50)
  4. client = FlashLasrClient(ak=ak, sk=sk, region=region, project_id=project_id, sis_config=config)
  • 构造请求
  1. asr_request = FlashLasrRequest()
  2. asr_request.set_obs_bucket_name(obs_bucket_name) # 设置存放音频的桶名,必选
  3. asr_request.set_obs_object_key(obs_object_key) # 设置OBS桶中的对象的键值,必选
  4. asr_request.set_audio_format(audio_format) # 音频格式,必选
  5. asr_request.set_property(property) # property,比如:chinese_16k_conversation
  6. asr_request.set_add_punc('yes')
  7. asr_request.set_digit_norm('no')
  8. asr_request.set_need_word_info('yes')
  9. asr_request.set_first_channel_only('yes')

为视频产生字幕文件时,不仅需要文字,也需要文字对应的时间戳信息。当一句话过长,屏幕无法完整显示时,就需要对这句话进行切分。因此,仅仅根据每个句子的起始和截止时间,无法准确的确定切分后两句话的起始和截止时间。因此我们需要字级别的时间信息。而将need_word_info配置为‘yes’,就可以输出字级别的时间戳信息。如下:

  1. "word_info": [
  2. {
  3. "start_time": 590,
  4. "word": "",
  5. "end_time": 630
  6. },
  7. {
  8. "start_time": 830,
  9. "word": "",
  10. "end_time": 870
  11. },
  12. {
  13. "start_time": 950,
  14. "word": "",
  15. "end_time": 990
  16. },
  17. {
  18. "start_time": 1110,
  19. "word": "",
  20. "end_time": 1150
  21. },
  22. ]
  • 接下里发送识别请求
  1. result = client.get_flash_lasr_result(asr_request)
  • 拿到带有详细时间戳信息的识别结果result:
  1. "result": {
  2. "score": 0.9358551502227783,
  3. "word_info": [
  4. {
  5. "start_time": 590,
  6. "word": "",
  7. "end_time": 630
  8. },
  9. {
  10. "start_time": 830,
  11. "word": "",
  12. "end_time": 870
  13. },
  14. {
  15. "start_time": 950,
  16. "word": "",
  17. "end_time": 990
  18. },
  19. {
  20. "start_time": 1110,
  21. "word": "",
  22. "end_time": 1150
  23. },
  24. {
  25. "start_time": 1750,
  26. "word": "",
  27. "end_time": 1790
  28. },
  29. {
  30. "start_time": 1910,
  31. "word": "",
  32. "end_time": 1950
  33. },
  34. {
  35. "start_time": 2070,
  36. "word": "",
  37. "end_time": 2110
  38. },
  39. {
  40. "start_time": 2190,
  41. "word": "",
  42. "end_time": 2230
  43. },
  44. {
  45. "start_time": 2350,
  46. "word": "",
  47. "end_time": 2390
  48. },
  49. {
  50. "start_time": 2870,
  51. "word": "",
  52. "end_time": 2910
  53. },
  54. {
  55. "start_time": 3030,
  56. "word": "",
  57. "end_time": 3070
  58. },
  59. {
  60. "start_time": 3190,
  61. "word": "",
  62. "end_time": 3230
  63. },
  64. {
  65. "start_time": 3350,
  66. "word": "",
  67. "end_time": 3390
  68. },
  69. {
  70. "start_time": 3590,
  71. "word": "",
  72. "end_time": 3630
  73. },
  74. {
  75. "start_time": 3750,
  76. "word": "",
  77. "end_time": 3790
  78. },
  79. {
  80. "start_time": 3950,
  81. "word": "",
  82. "end_time": 3990
  83. },
  84. {
  85. "start_time": 4830,
  86. "word": "",
  87. "end_time": 4870
  88. },
  89. {
  90. "start_time": 4990,
  91. "word": "",
  92. "end_time": 5030
  93. },
  94. {
  95. "start_time": 5350,
  96. "word": "",
  97. "end_time": 5390
  98. },
  99. {
  100. "start_time": 5550,
  101. "word": "",
  102. "end_time": 5590
  103. },
  104. {
  105. "start_time": 5750,
  106. "word": "",
  107. "end_time": 5790
  108. },
  109. {
  110. "start_time": 5870,
  111. "word": "",
  112. "end_time": 5910
  113. },
  114. {
  115. "start_time": 6070,
  116. "word": "",
  117. "end_time": 6110
  118. },
  119. {
  120. "start_time": 6310,
  121. "word": "",
  122. "end_time": 6350
  123. },
  124. {
  125. "start_time": 6390,
  126. "word": "",
  127. "end_time": 6470
  128. },
  129. {
  130. "start_time": 6510,
  131. "word": "",
  132. "end_time": 6550
  133. },
  134. {
  135. "start_time": 6670,
  136. "word": "",
  137. "end_time": 6710
  138. },
  139. {
  140. "start_time": 6830,
  141. "word": "",
  142. "end_time": 6870
  143. },
  144. {
  145. "start_time": 7430,
  146. "word": "",
  147. "end_time": 7470
  148. },
  149. {
  150. "start_time": 7630,
  151. "word": "",
  152. "end_time": 7670
  153. },
  154. {
  155. "start_time": 7830,
  156. "word": "",
  157. "end_time": 7870
  158. },
  159. {
  160. "start_time": 8030,
  161. "word": "",
  162. "end_time": 8070
  163. },
  164. {
  165. "start_time": 8950,
  166. "word": "",
  167. "end_time": 8990
  168. },
  169. {
  170. "start_time": 9190,
  171. "word": "",
  172. "end_time": 9230
  173. },
  174. {
  175. "start_time": 9350,
  176. "word": "",
  177. "end_time": 9390
  178. },
  179. {
  180. "start_time": 9470,
  181. "word": "",
  182. "end_time": 9510
  183. }
  184. ],
  185. "text": "哎,大家好,我是你们的音乐老师康老师。那么这几系列呢,我们来到了发声练习三十五讲。"
  186. },
  187. "start_time": 510,
  188. "end_time": 9640
  189. }

步骤四:将识别结果转为srt字幕格式文件

由于视频播放界面的宽度有限,当一句话包含的文字数过多时,会存在一行放不下的问题。因此我们在生成srt文件时,需要将文字数量过长的一句话切分为两句话,分别在不同的时间段显示。企切分后的第一句话的起始时间不变,截止时间为最后一个字的截止时间;第二句话的起始时间为第一个字的起始时间,截止时间不变。这样就保证切分后两句话的时间戳也是正确的,进而在合适的视频帧中显示正确的文本内容。

  1. def json2srt(json_result):
  2. results = ""
  3. count = 1
  4. max_word_in_line = 15
  5. min_word_in_line = 3
  6. punc = ["", "", "", ""]
  7. segments = json_result['flash_result'][0]['sentences']
  8. for i in range(len(segments)):
  9. current_result = segments[i]
  10. current_sentence = current_result["result"]["text"]
  11. if len(current_result["result"]["word_info"]) > max_word_in_line:
  12. srt_result = ""
  13. srt_result_len = 0
  14. current_segment = ""
  15. cnt = 0
  16. start = True
  17. for i in range(len(current_sentence)):
  18. if current_sentence[i] not in punc:
  19. if start:
  20. start_time = current_result["result"]["word_info"][cnt]['start_time']
  21. start = False
  22. else:
  23. end_time = current_result["result"]["word_info"][cnt]['end_time']
  24. current_segment += current_sentence[i]
  25. srt_result_len += 1
  26. cnt += 1
  27. else:
  28. if srt_result_len < min_word_in_line:
  29. srt_result += current_segment + current_sentence[i]
  30. current_segment = ""
  31. else:
  32. srt_result += current_segment + current_sentence[i]
  33. current_segment = ""
  34. start_time = time_format(start_time)
  35. end_time = time_format(end_time)
  36. if srt_result[-1] == "":
  37. srt_result = srt_result[:-1]
  38. results += str(count) + "\n" + start_time + "-->" + end_time + "\n" + srt_result + "\n" + "\n"
  39. count += 1
  40. start = True
  41. srt_result = ""
  42. else:
  43. start_time = time_format(current_result["start_time"])
  44. end_time = time_format(current_result["end_time"])
  45. if current_sentence[-1] == "":
  46. current_sentence = current_sentence[:-1]
  47. results += str(count) + "\n" + start_time + "-->" + end_time + "\n" + current_sentence + "\n" + "\n"
  48. count += 1
  49. return results

得到srt格式的字幕文件

步骤五:播放视频,载入字幕

修改文件名,保证srt文件和原始视频文件命名相同,然后用播放器播放视频:

步骤六:使用ffmpeg给视频添加硬字幕(可选)

  1. ffmpeg -i input.mp4 -vf subtitles=subtitle.srt output_srt.mp4

注: 硬字幕是将字幕渲染到视频的纹理上,然后将其编码成独立于视频格式的一个完整视频。硬字幕与视频是一个整体,不能更改或删除。

 

点击关注,第一时间了解华为云新鲜技术~

原文链接:https://www.cnblogs.com/huaweiyun/p/17431369.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号