若是有一個工具能識別音視中的語音並轉換成文字輸出,因爲能夠複製粘貼而不須要逐字逐句地打,那咱們進行爲音頻配字幕工做時將會事半功倍。html
其中的關鍵點是音文轉換,音文轉換其實在不少地方均可以看到好比qq,百度搜索,訊飛輸入法等等,具體到技術而言前述的三個場景其背後的技術都是同樣的,都是利用AI進行語音識別。並且騰訊、百度、訊飛當前都開放了自家的語音識別接口無償使用,本文就是利用的百度語音識別接口實現的轉換。python
其實就配字幕這種工做而言,當前應該能夠實現經過寫代碼自動給音頻文件配上字幕,複製粘貼這步均可以省了,固然這不在本文的實現範圍。git
另外AI識別有必定的錯誤率,在咱們本身使用qq、百度、訊飛時應該都有體會,更況且開放出來的接口通常都不會是公司產品百分百的能力,因此確定仍是要人工介入修正。github
官方文檔:http://ai.baidu.com/docs#/ASR-API/topjson
語音識別接口:http://vop.baidu.com/server_apiapi
接口要求:只接受pcm格式音頻,請求次數不限但每一個音視不能超過60秒app
系統環境:Windows-7 X6四、Python-3.6.5 X64(miniconda)。minicoda安裝可參見「PyCharm+Miniconda3安裝配置教程」第二大點,若是直接安裝python網上搜搜教程便可。另外須要requests模塊因此裝完python後須要執行pip install requests安裝一下。ide
gihub地址:https://github.com/PrettyUp/BaiduAI。這裏將程序和以前寫的程序都放在了BaiduAI項目錄,只管其中的vtt目錄便可。函數
程序支持:僅支持mp3格式文件,僅支持英語(要支持普通話將post的dev_pid參數修改成1536便可,參見官方文檔)工具
程序流程:獲取video目錄下的全部mp3文件並逐個進行處理----將當前要處理的mp3文件使用ffmpeg轉換成pcm格式----將生成的pcm文件使用speech-vad-demo切割----將切割後的pcm文件逐個進行音文轉換
運行操做:安裝好系統環境----將壓縮包解壓到任意目錄----將要轉換的mp3文件複製到video文件夾下----使用python運行baiduvi.py文件----程序執行完後在video文件夾下會爲各mp3文件生成其同名.srt文件其中便是語音轉換成的文字。(首版代碼生成的是txt文件,按建議改成直接生成.srt格式字幕文件,操做等使用方式都沒改變)以下所示
程序本身寫的只有一個文件,我這裏命名爲baiduvi.py
import base64 import json import os import time import shutil import requests class BaiduVoiceToTxt(): # 初始化函數 def __init__(self): # 定義要進行切割的pcm文件的位置。speech-vad-demo固定好的,沒的選 self.pcm_path = ".\\speech-vad-demo\\pcm\\16k_1.pcm" # 定義pcm文件被切割後,分割成的文件輸出到的目錄。speech-vad-demo固定好的,沒的選 self.output_pcm_path = ".\\speech-vad-demo\\output_pcm\\" # 百度AI接口只接受pcm格式,因此須要轉換格式 # 此函數用於將要識別的mp3文件轉換成pcm格式,並輸出爲.\speech-vad-demo\pcm\16k_1.pcm def change_file_format(self,filepath): file_name = filepath # 若是.\speech-vad-demo\pcm\16k_1.pcm文件已存在,則先將其刪除 if os.path.isfile(f"{self.pcm_path}"): os.remove(f"{self.pcm_path}") # 調用系統命令,將文件轉換成pcm格式,並輸出爲.\speech-vad-demo\pcm\16k_1.pcm change_file_format_command = f".\\ffmpeg\\bin\\ffmpeg.exe -y -i {file_name} -acodec pcm_s16le -f s16le -ac 1 -ar 16000 {self.pcm_path}" os.system(change_file_format_command) # 百度AI接口最長只接受60秒的音視,因此須要切割 # 此函數用於將.\speech-vad-demo\pcm\16k_1.pcm切割 def devide_video(self): # 若是切割輸出目錄.\speech-vad-demo\output_pcm\已存在,那其中極可能已有文件,先將其清空 # 清空目錄的文件是先刪除,再建立 if os.path.isdir(f"{self.output_pcm_path}"): shutil.rmtree(f"{self.output_pcm_path}") time.sleep(1) os.mkdir(f"{self.output_pcm_path}") # vad-demo.exe使用相對路徑.\pcm和.\output_pcm,因此先要將當前工做目錄切換到.\speech-vad-demo下否則vad-demo.exe找不到文件 os.chdir(".\\speech-vad-demo\\") # 直接執行.\vad-demo.exe,其默認會將.\pcm\16k_1.pcm文件切割並輸出到.\output_pcm目錄下 devide_video_command = ".\\vad-demo.exe" os.system(devide_video_command) # 切換回工做目錄 os.chdir("..\\") # 此函數用於將.\speech-vad-demo\output_pcm\下的文件的文件名的時間格式化成0:00:00,000形式 def format_time(self, msecs): # 一個小時毫秒數 hour_msecs = 60 * 60 * 1000 # 一分鐘對應毫秒數 minute_msecs = 60 * 1000 # 一秒鐘對應毫秒數 second_msecs = 1000 # 文件名的時間是毫秒須要先轉成秒。+500是爲了四捨五入,//是整除 # msecs = (msecs + 500) // 1000 # 小時 hour = msecs // hour_msecs if hour < 10: hour = f"0{hour}" # 扣除小時後剩餘毫秒數 hour_left_msecs = msecs % hour_msecs # 分鐘 minute = hour_left_msecs // minute_msecs # 若是不足10分鐘那在其前補0湊成兩位數格式 if minute < 10: minute = f"0{minute}" # 扣除分鐘後剩餘毫秒數 minute_left_msecs = hour_left_msecs % minute_msecs # 秒 second = minute_left_msecs // second_msecs # 若是秒數不足10秒,同樣在其前補0湊足兩位數格式 if second < 10: second = f"0{second}" # 扣除秒後剩餘毫秒數 second_left_msecs = minute_left_msecs % second_msecs # 若是不足10毫秒或100毫秒,在其前補0湊足三位數格式 if second_left_msecs < 10: second_left_msecs = f"00{second_left_msecs}" elif second_left_msecs < 100: second_left_msecs = f"0{second_left_msecs}" # 格式化成00:00:00,000形式,並返回 time_format = f"{hour}:{minute}:{second},{second_left_msecs}" return time_format # 此函數用於申請訪問ai接口的access_token def get_access_token(self): # 此變量賦值成本身API Key的值 client_id = 'f3wT23Otc8jXlDZ4HGtS4jfT' # 此變量賦值成本身Secret Key的值 client_secret = 'YPPjW3E0VGPUOfZwhjNGVn7LTu3hwssj' auth_url = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=' + client_id + '&client_secret=' + client_secret response_at = requests.get(auth_url) # 以json格式讀取響應結果 json_result = json.loads(response_at.text) # 獲取access_token access_token = json_result['access_token'] return access_token # 此函數用於將.\speech-vad-demo\output_pcm\下的單個文件由語音轉成文件 def transfer_voice_to_srt(self,access_token,filepath): # 百度語音識別接口 url_voice_ident = "http://vop.baidu.com/server_api" # 接口規範,以json格式post數據 headers = { 'Content-Type': 'application/json' } # 打開pcm文件並讀取文件內容 pcm_obj = open(filepath,'rb') pcm_content_base64 = base64.b64encode(pcm_obj.read()) pcm_obj.close() # 獲取pcm文件大小 pcm_content_len = os.path.getsize(filepath) # 接口規範,則體函義見官方文件,值得注意的是cuid和speech兩個參數的寫法 post_data = { "format": "pcm", "rate": 16000, "dev_pid": 1737, "channel": 1, "token": access_token, "cuid": "1111111111", "len": pcm_content_len, "speech": pcm_content_base64.decode(), } proxies = { 'http':"127.0.0.1:8080" } # 調用接口,進行音文轉換 response = requests.post(url_voice_ident, headers=headers, data=json.dumps(post_data)) # response = requests.post(url_voice_ident,headers=headers,data=json.dumps(post_data),proxies=proxies) return response.text if __name__ == "__main__": # 實例化 baidu_voice_to_srt_obj = BaiduVoiceToTxt() # 本身要進行音文轉換的音視存放的文件夾 video_dir = ".\\video\\" all_video_file =[] all_file = os.listdir(video_dir) subtitle_format = "{\\fscx75\\fscy75}" # 只接受.mp3格式文件。由於其餘格式沒研究怎麼轉成pcm纔是符合接口要求的 for filename in all_file: if ".mp3" in filename: all_video_file.append(filename) all_video_file.sort() i = 0 video_file_num = len(all_video_file) print(f"當前共有{video_file_num}個音頻文件須要轉換,即將進行處理請稍等...") # 此層for循環是逐個mp3文件進行處理 for video_file_name in all_video_file: i += 1 print(f"當前轉換{video_file_name}({i}/{video_file_num})") # 將音視翻譯成的內容輸出到同目錄下同名.txt文件中 video_file_srt_path = f".\\video\\{video_file_name[:-4]}.srt" # 以覆蓋形式打開.txt文件 video_file_srt_obj = open(video_file_srt_path,'w+') filepath = os.path.join(video_dir, video_file_name) # 調用change_file_format將mp3轉成pcm格式 baidu_voice_to_srt_obj.change_file_format(filepath) # 將轉換成的pcm文件切割成多個小於60秒的pcm文件 baidu_voice_to_srt_obj.devide_video() # 獲取token access_token = baidu_voice_to_srt_obj.get_access_token() # 獲取.\speech-vad-demo\output_pcm\目錄下的文件列表 file_dir = baidu_voice_to_srt_obj.output_pcm_path all_pcm_file = os.listdir(file_dir) all_pcm_file.sort() j = 0 pcm_file_num = len(all_pcm_file) print(f"當前所轉文件{video_file_name}({i}/{video_file_num})被切分紅{pcm_file_num}塊,即將逐塊進行音文轉換請稍等...") # 此層for是將.\speech-vad-demo\output_pcm\目錄下的全部文件逐個進行音文轉換 for filename in all_pcm_file: j += 1 filepath = os.path.join(file_dir, filename) if (os.path.isfile(filepath)): # 獲取文件名上的時間 time_str = filename[10:-6] time_str_dict = time_str.split("-") time_start_str = baidu_voice_to_srt_obj.format_time(int(time_str_dict[0])) time_end_str = baidu_voice_to_srt_obj.format_time(int(time_str_dict[1])) print(f"當前轉換{video_file_name}({i}/{video_file_num})-{time_start_str}-{time_end_str}({j}/{pcm_file_num})") response_text = baidu_voice_to_srt_obj.transfer_voice_to_srt(access_token, filepath) # 以json形式讀取返回結果 json_result = json.loads(response_text) # 將音文轉換結果寫入.srt文件 video_file_srt_obj.writelines(f"{j}\r\n") video_file_srt_obj.writelines(f"{time_start_str} --> {time_end_str}\r\n") if json_result['err_no'] == 0: print(f"{time_start_str}-{time_end_str}({j}/{pcm_file_num})轉換成功:{json_result['result'][0]}") video_file_srt_obj.writelines(f"{subtitle_format}{json_result['result'][0]}\r\n") elif json_result['err_no'] == 3301: print(f"{time_start_str}-{time_end_str}({j}/{pcm_file_num})音頻質量過差沒法識別") video_file_srt_obj.writelines(f"{subtitle_format}音頻質量過差沒法識別\r\n") else: print(f"{time_start_str}-{time_end_str}({j}/{pcm_file_num})轉換過程遇到其餘錯誤") video_file_srt_obj.writelines(f"{subtitle_format}轉換過程遇到其餘錯誤\r\n") video_file_srt_obj.writelines(f"\r\n") video_file_srt_obj.close()
參考: