近日做了個文字轉語音的小專案,主功能接百度智慧雲的API很順利,但3個小細節處理很費了些事。就像生活中往往一些不起眼的小角色更狠、更能給人添堵,做專案也是一樣。特分享記錄一下,先看看專案介面和執行效果:
主功能:在頂部輸入框中輸入英語單詞或語句,點選右側的三個按鈕,識別輸入的文字,轉換為語音,並進行播放。讀單詞模式時,中間灰色區域,同步顯示英語單詞和中文翻譯。
1、主功能文字轉語音試了三種方法,感覺百度智慧雲的語音技術甩別的幾條街,不知是不是百度曾經的all in ai留下的戰果,翻譯也就順帶用他家的了。
2、通過“檔案匯入”功能匯入文字時,開啟文字、讀取文字都不難,但遇到無法解碼的特殊字元,只一個就導致讀取失敗,字元解碼的細節容易被忽略,卻常常出來惹事。
3、“讀單詞”模式,通過分割符將文字分割成一個個單詞,再逐個進行語音轉換,播放。但由於playsound播放檔案後,資源不會被放棄,導致無法逐個播放,這是本專案最硬的茬,通過修改源庫程式碼方解決。
4、“讀單詞”模式,播放語音時,中間同步顯示英語和翻譯,實際卻是等全部讀完了,才顯示最後一個單詞及翻譯,後通過新增執行緒解決。
詳細過程:
一、主功能,接百度語音技術解決文字轉語音。首先,需要註冊百度智慧雲,新增語音技術應用,獲得ID、key,如下圖:
接下來寫碼就ok了。
def text_to_voice(str, cs):
APP_ID = '************************'
API_KEY = '************************'
SECRET_KEY = '************************'
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
result = client.synthesis(str, 'zh', 1, cs)
if not isinstance(result, dict):
with open('auido.mp3', 'wb') as f:
f.write(result)
if __name__ == "__main__":
cs = {'vol': 5, 'per': 1, 'spd': 8}
text_to_voice('goodmorning', cs)
程式碼很簡單,頭三行***為從前面獲得的ID、key。‘str’為要讀取的文字內容,‘cs’為播放聲音的引數,'vol'音調,'per'1為男聲,0為女聲,'spd'為語速。作為引數帶入函式塊,利於介面互動。
二、檔案匯入寫入文字def open_file():
global word
path = './'
filename = filedialog.askopenfilename(
title=u'選擇檔案',
filetypes=[("記事本", ".txt")],
initialdir=(os.path.expanduser(path)))
if filename != '':
f = open(filename, 'rb')
lines = f.readlines()
n = 1
for line in lines:
line = line.strip().decode('utf-8')
if line != '':
word = word + line
n += 1
e.delete(0, 'end')
e.insert(0, word)
e.insert(0, word)將word裡的內容顯示在文字框裡。
讀取生成word內容時,line = line.strip().decode('utf-8')用decode解碼,可以應付多數情況了。
三、解決不能迴圈播放mp3檔案的問題匯入模組from playsound import playsound
直接播放playsound('auido.mp3')
但在for迴圈裡批量播放時報錯,因為playsound庫原始碼裡沒有關閉檔案的程式碼,從網上借鑑到的做法,修改原始碼,增加關閉功能。
開啟python安裝路徑,找到playsound.py檔案開啟,
在下面的位置,增加紅框內的程式碼
增加的程式碼為:
while True:
if winCommand('status', alias, 'mode').decode() == 'stopped':
winCommand('close', alias)
break
儲存後,再迴圈執行playsound('auido.mp3')就沒問題了。
將一組單詞逐個進行閱讀的程式碼:
def read_text(text):
cs['vol'] = s1.get()
cs['spd'] = s2.get()
cs['per'] = v.get()
ttv.text_to_voice(text, cs)
playsound('auido.mp3')
def one_by_one():
tt = re.split(',|\\.|\\?|。|,', content.get())
if tt != '':
for t in tt:
if t != '':
label22['text'] = t.strip() + '\\n' + '\\n' + en_to_zh.fy(t)
read_text(t)
time.sleep(1)
四、多執行緒同步顯示單詞及翻譯
上面label22['text'] = t.strip() + '\\n' + '\\n' + en_to_zh.fy(t)中en_to_zh.fy(t)即為翻譯的中文,首先匯入自己寫的含翻譯功能py檔案en_to_zh,再利用檔案裡的fy函式塊進行翻譯得到中文。en_to_zh.py程式碼為百度翻譯api提供的程式碼,只修改為自己的id和key,然後設定return語句返回獲得的中文即可:
import http.client
import hashlib
import urllib
import random
import json
def fy(txt):
appid = '*************************' # 填寫你的appid
secretKey = '*************************' # 填寫你的金鑰
httpClient = None
myurl = '/api/trans/vip/translate'
fromLang = 'en' #原文語種
toLang = 'zh' #譯文語種
salt = random.randint(32768, 65536)
q = txt#'apple'
sign = appid + q + str(salt) + secretKey
sign = hashlib.md5(sign.encode()).hexdigest()
myurl = myurl + '?appid=' + appid + '&q=' + urllib.parse.quote(
q) + '&from=' + fromLang + '&to=' + toLang + '&salt=' + str(
salt) + '&sign=' + sign
try:
httpClient = http.client.HTTPConnection('api.fanyi.baidu.com')
httpClient.request('GET', myurl)
# response是HTTPResponse物件
response = httpClient.getresponse()
result_all = response.read().decode("utf-8")
result = json.loads(result_all)
return result['trans_result'][0]['dst']
except Exception as e:
print(e)
finally:
if httpClient:
httpClient.close()
在原始碼裡僅加了“return result['trans_result'][0]['dst']”語句就好。該處的id和key是百度翻譯裡獲取的,與前面的語音技術id是兩碼事,但註冊操作方法一樣。
上面獲得了內容,但我們需要在for迴圈裡動態顯示在label框裡,卻無法實現,只能在for迴圈完成後,再顯示最後一次的結果,顯然這不是我們要的。這是因為單執行緒不能同時幹兩件事導致的,解決方法是增加執行緒,讓同時處理兩件事。
def read_one():
read_text('請跟我讀:')
t = threading.Thread(target=one_by_one, args=(), name='thread-refresh')
t.setDaemon(True)
t.start()
真正的執行閱讀程式碼寫在函式one_by_one()裡。
if __name__ == "__main__":
cs = {'vol': 8, 'per': 0, 'spd': 4}
root = tkinter.Tk()
root.title("英語領讀")
root.geometry('660x420-20+30')
# 第0行
ft0 = tkFont.Font(family='楷體', size=15)
titlelabel = tkinter.Label(root, text='輸入或通過檔案匯入閱讀內容', width=40, font=ft0, anchor='s')
titlelabel.grid(row=0, column=0, columnspan=6, pady=10, sticky='S')
# 第1行
pic_11 = tkinter.PhotoImage(file='pic/99.png')
bt11 = tkinter.Button(root, text='從檔案匯入', image=pic_11, width=65, height=25, command=open_file)
bt11.grid(row=1, column=0, pady=8, padx=5, columnspan=1, sticky='NW')
content = tkinter.StringVar()
e = tkinter.Entry(root, textvariable=content, font=ft0, width=46)
e.grid(row=1, column=1, columnspan=4, pady=8, sticky='NW')
content.set('hai, nice to meet you!')
pic_1 = tkinter.PhotoImage(file='pic/12.png')
bt12 = tkinter.Button(root, text='清空', width=8,
command=clear) #, image=pic_1
bt12.grid(row=1, column=5, pady=8, padx=5)
# 第2行
ft = tkFont.Font(family='楷體', size=38, weight=tkFont.BOLD)
label22 = tkinter.Label(root,
width=17,
height=5,
bg='DarkGray',#DarkSeaGreen DarkGray
fg='black',
justify='left',
relief='sunken',
font=ft) #wraplength=120,
label22.grid(row=2, column=1, rowspan=3, columnspan=4, sticky='NW')
# 第3行
pic_31 = tkinter.PhotoImage(file='pic/one11.png')
bt31 = tkinter.Button(root,
text='讀單詞',
width=60,
image=pic_31,
command=read_one)
bt31.grid(row=2, column=5, padx=15)
pic_32 = tkinter.PhotoImage(file='pic/all11.png')
bt32 = tkinter.Button(root,
text='讀整段',
width=60,
image=pic_32,
command=read_all)
bt32.grid(row=3, column=5, padx=15)
pic_33 = tkinter.PhotoImage(file='pic/tx1.png')
bt33 = tkinter.Button(root,
text='聽寫',
width=60,
image=pic_33,
command=dictation)
bt33.grid(row=4, column=5, padx=15)
ft2 = tkFont.Font(family='Fixdsys', size=8)
label8 = tkinter.Label(root,
text='版本: bmy-001',
width=20,
font=ft2)
label8.grid(row=5, column=5)
s1 = tkinter.Scale(root,
label='音調',
from_=0,
to=10,
orient=tkinter.HORIZONTAL,
length=200,
showvalue=0,
tickinterval=10,
resolution=1)
s1.grid(row=5, column=1, columnspan=2, padx=15)
s1.set(5)
s2 = tkinter.Scale(root,
label='語速',
from_=0,
to=10,
orient=tkinter.HORIZONTAL,
length=200,
showvalue=0,
tickinterval=10,
resolution=1)
s2.grid(row=5, column=3, columnspan=2, padx=15, pady=1, sticky='N')
s2.set(5)
v = tkinter.IntVar()
boy = tkinter.PhotoImage(file='pic/男1.png')
tkinter.Radiobutton(
root,
text='男聲',
variable=v,
image=boy,
value=1,
).grid(row=2, column=0, sticky='S')
girl = tkinter.PhotoImage(file='pic/女1.png')
tkinter.Radiobutton(
root,
text='女聲',
variable=v,
image=girl,
value=0,
).grid(row=3, column=0)
v.set(0)
photo = tkinter.PhotoImage(file="./pic/古代兒童.png") # file:t圖片路徑
imgLabel = tkinter.Label(root, image=photo)
imgLabel.grid(row=4, column=0, rowspan=2, sticky='S')
root.mainloop()
六、預留功能:隨時暫停播放過程的功能暫未設定,因為用生澀的底層控制程式碼去實現一個普通不過的mp3檔案暫停播放功能,感覺價效比真的好差,所以......