首頁>技術>

近日做了個文字轉語音的小專案,主功能接百度智慧雲的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檔案暫停播放功能,感覺價效比真的好差,所以......

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Android app反除錯與程式碼保護的一些基本方案