備忘錄_20160105(定位) 修改 回首頁

程式 2023-01-24 04:27:25 1674505645 100
語音辨識 speech / voice recognition。pocketsphinx。cmusphinx。python。vosk。

語音辨識 speech / voice recognition。pocketsphinx。cmusphinx。python。vosk。

  1. 英文語音關鍵詞識別。長時間聆聽。pocketsphinx。LiveSpeech。kws_threshold範圍從1~1e-50。
  2. 中文語音詞句識別。限定秒數。SpeechRecognition+vosk。(https://alphacephei.com/vosk/ (vosk引擎,回傳簡體中文,辨識率不錯))
  3. 簡體中文換成正體中文。使用 OpenCC 進行簡繁互換。
  4. 使用 urllib 進行關鍵字搜尋並擷取網頁內文
  5. 使用 pyttsx3 進行文字轉語音(可接受中文)
  6. 使用 pygetwindow+pyautogui 來進行視窗操作(如關閉頁籤,按空白鍵等)
  7. 使用 webbrowser 開啟指定 url 的網頁
  8. playsound 若和 threading 搭配,可以不鎖住主執行緒。

import os
import webbrowser
from pocketsphinx import LiveSpeech # pip install pocketsphinx
from playsound import playsound # pip install playsound==1.2.2
import threading
import pyttsx3 # 記得也要安裝 sudo apt install/remove espeak-ng

import urllib.request
import urllib.parse
import time
import pyautogui
import pygetwindow
# pip install pyaudio

from opencc import OpenCC # pip install opencc-python-reimplemented
# 轉換模式
# 
#     hk2s: 繁體中文 (香港) -> 簡體中文
#     s2hk: 簡體中文 -> 繁體中文 (香港)
#     s2t: 簡體中文 -> 繁體中文
#     s2tw: 簡體中文 -> 繁體中文 (台灣)
#     s2twp: 簡體中文 -> 繁體中文 (台灣, 包含慣用詞轉換)
#     t2hk: 繁體中文 -> 繁體中文 (香港)
#     t2s: 繁體中文 -> 簡體中文
#     t2tw: 繁體中文 -> 繁體中文 (台灣)
#     tw2s: 繁體中文 (台灣) -> 簡體中文
#     tw2sp: 繁體中文 (台灣) -> 簡體中文 (包含慣用詞轉換 )

import speech_recognition # pip install SpeechRecognition; pip install vosk; 下載 vosk 的 中文 model(資料夾名稱須為 model)

import sounddevice # pip install sounddevice
from scipy.io.wavfile import write # pip install scipy
# pip install pydub
#
# ●ffmpeg for windows
#  https://www.ffmpeg.org/download.html 下載 windows 版本的 ffmpeg 後,解壓縮(某資料夾,不能刪除),
#  環境變數 path ,增加一個路徑到其 bin 的位置,之後再安裝 pip install ffmpeg
#
# ●ffmpeg for debian
#  sudo apt install ffmpeg
#
from pydub import AudioSegment 

# 若錄音有遇到問題,如 SystemError: ffi_prep_closure(): bad user_data (it seems that the version of the libffi library seen at runtime is different from the 'ffi.h' file seen at compile-time)
# 則試試下面兩行(改為舊版本)
# pip uninstall cffi
# pip install cffi==1.15.0

def printMenu():
  
  print('')
  print('')
  print('')
  print('')
  print('●語音助理 ver 0.22●')
  print('')
  print('說出 ' + strKeyPhrase + ' 即可進入指令模式,指令如下')
  print('------')
  print('自動音樂 [影片關鍵字] ← 搜尋youtube,關閉含YouTube字眼頁籤,開啟網頁,按下空白鍵')
  print('尋找音樂 [影片關鍵字] ← 搜尋youtube,開啟網頁')
  print('再見 ← 離開語音助理')
  print('論語、冰雪奇緣、夜空中最亮的星、我可能不會愛你、小幸運 ← 任一名稱,播放資料夾中對應的mp3檔案')
  print('立刻給我關機 ← 如字面所說,若工作無存檔,那就慘了!')
  print('我要錄音 ← 錄音30秒')
  print('搜尋網路 [搜尋關鍵字] ← 搜尋google,開啟網頁')
  print('------')
  print('其他註記')
  print('1.(一些歌名供參考 ^^) 傳奇、紅豆、約定、風箏、聽海、光年之外、那些年、明天會更好、隱形的翅膀、陳淑樺-問')
  print('2.youtube 的 autoplay 要自己手動關掉。')
  print('')
  print('')
  print('')
  print('')

def searchYoutube(strKW, booTryAutoPlay):
  
  print("搜尋關鍵字:" + strKW)
  
  oFP = urllib.request.urlopen("https://www.youtube.com/results?search_query=" + urllib.parse.quote_plus(strKW))
  strBytes = oFP.read()
  strUtf8 = strBytes.decode("utf8")
  oFP.close()
  
  #f=open('debug.log', mode='w', encoding='utf-8')
  #f.write(soup.prettify())
  #f.close()
  
  straSearchResult=[]
  
  iIdxStart=0
  strKWTitle='"title":{"runs":[{"text":"'
  strKWId='"videoId":"'
  strKWEnd='"'
  
  while True:
    
    iIdxTitleL=-1
    iIdxTitleR=-1
    iIdxIdL=-1
    iIdxIdR=-1
    
    iIdxTitleL = strUtf8.find(strKWTitle, iIdxStart)
    if iIdxTitleL==-1: break
    
    iIdxTitleR = strUtf8.find(strKWEnd, iIdxTitleL+len(strKWTitle))
    if iIdxTitleR==-1: break
    
    iIdxIdL = strUtf8.find(strKWId, iIdxTitleR+len(strKWEnd))
    if iIdxIdL==-1: break
    
    iIdxIdR = strUtf8.find(strKWEnd, iIdxIdL+len(strKWId))
    if iIdxIdR==-1: break
    
    strSRTitle = strUtf8[iIdxTitleL+len(strKWTitle) : iIdxTitleR]
    strSRId = strUtf8[iIdxIdL+len(strKWId) : iIdxIdR]
    straSearchResult.append(strSRTitle + "\t" + strSRId)
    
    iIdxStart = iIdxIdR+len(strKWEnd)
    if iIdxStart>=len(strUtf8): break
  
  if len(straSearchResult)<1:
    
    print("找不到符合關鍵字的音樂")
    oTTSEngine.say("找不到符合關鍵字的音樂")
    oTTSEngine.runAndWait()
    
  else:
    
    if booTryAutoPlay==True:
      print('盡可能關閉有 YouTube 字眼的頁籤,使用 Ctrl+W。')
      try:
        oOldWin = pygetwindow.getWindowsWithTitle("YouTube")[0]
        oOldWin.maximize()
        oOldWin.activate()
        pyautogui.hotkey('ctrl','w')
        print('已嘗試關閉')
      except:
        print('沒有找到可能的視窗')
    
    strWantedTitle, strWantedId = straSearchResult[0].split("\t")
    webbrowser.open("https://www.youtube.com/watch?autoplay=0&v=" + strWantedId)
    
    print("開啟:(" + strWantedId + ")-" + strWantedTitle)
    oTTSEngine.say("開啟" + strWantedTitle)
    oTTSEngine.runAndWait()
    
    if booTryAutoPlay==True:
      time.sleep(5) # 等待瀏覽器開啟,暫定5秒
      print('準備按下空白鍵')
      try:
        oNewWin = pygetwindow.getWindowsWithTitle("YouTube")[0]
        oNewWin.maximize()
        oNewWin.activate()
        pyautogui.press('space')
        print('已嘗試按下空白鍵')
      except:
        print('自動化失敗,請自行點選播放。')
        oTTSEngine.say("自動化失敗,請自行點選播放。")
        oTTSEngine.runAndWait()

strKeyPhrase = 'listen to me'

# 初始化文字轉語音
oTTSEngine = pyttsx3.init()
oTTSVoice = oTTSEngine.getProperty('voices')
fTTSRate = oTTSEngine.getProperty('rate')
fTTSVolume = oTTSEngine.getProperty('volume')
oTTSEngine.setProperty('rate', fTTSRate-20)
oTTSEngine.setProperty('volume', fTTSVolume-0.1)

printMenu()
for oEnglishPhrase in LiveSpeech(keyphrase=strKeyPhrase, kws_threshold=1e-20):
  # print(str(oEnglishPhrase))
  
  # 從麥克風讀取一段聲音(中文)
  oRecognizer = speech_recognition.Recognizer()
  strZhCn = ""
  
  with speech_recognition.Microphone() as oMic:
    
    playsound('try6_5secs.mp3')
    oRecognizer.adjust_for_ambient_noise(oMic, duration=0.5) # 抓前面0.5秒作為背景雜音的分析
    print("請在五秒內說出指令,指令長度最多七秒。")
    
    try:
      oAudio = oRecognizer.listen(oMic, 5, 7) # timeout=5, commandtimeout=7
      print("辨認中,請稍候。")
      playsound('try6_recognize.mp3')
      strZhCn = oRecognizer.recognize_vosk(oAudio)
    except:
      strZhCn = "error!"
  
  # 把 { "text":"......" } 變成 ......
  strZhCn = strZhCn.replace("{","").replace("}","").replace('"text" : "','').replace('"','').replace("\r","").replace("\n","")
  
  # 簡體中文轉換為正體中文
  oConverter = OpenCC('s2twp')
  strZhTw = oConverter.convert(strZhCn)
  strZhTw = strZhTw.replace(' ','')
  strZhCn = strZhCn.strip()
  
  try:
    
    print("指令(簡體中文):" + strZhCn)
    print("指令(正體中文):" + strZhTw)
    
    if strZhTw.find('自動音樂')!=-1:
      searchYoutube(strZhTw.replace("自動音樂",""), True)
      
    elif strZhTw.find('尋找音樂')!=-1:
      searchYoutube(strZhTw.replace("尋找音樂",""), False)
      
    elif strZhTw.find('再見')!=-1:
      print('See you next time.')
      playsound('try6_seeyou.mp3')
      break
      
    # 直接呼叫 playsound 時,要等播放完畢,才能接受下一道語音指令。
    # 若是透過 threading.Thread 來呼叫 playsound,則能馬上接受下一道語音指令。
    
    elif strZhTw.find('論語')!=-1:
      playsound('try6_20180809_luen2ju3_7th_a.mp3')
    elif strZhTw.find('冰雪奇緣')!=-1:
      threading.Thread(target=playsound, args=('try6_letitgo.mp3',), daemon=True).start()
    elif strZhTw.find('夜空中最亮的星')!=-1:
      threading.Thread(target=playsound, args=('try6_star.mp3',), daemon=True).start()
    elif strZhTw.find('我可能不會愛你')!=-1 or strZhTw.find('我可能不會愛妳')!=-1:
      threading.Thread(target=playsound, args=('try6_imaynotloveyou.mp3',), daemon=True).start()
    elif strZhTw.find('小幸運')!=-1:
      threading.Thread(target=playsound, args=('try6_littlelucky.mp3',), daemon=True).start()
    
    elif strZhTw.strip().find('立刻給我關機')==0:
      if (str(os.name)).startswith('nt'):
        os.system('shutdown -s -f -t 0')
      elif (str(os.name)).startswith('posix'):
        os.system('systemctl poweroff')
      else:
        print('unknown os')

    elif strZhTw.strip().find('我要錄音')==0:
      strFNRecWav='try6_rec.wav'
      strFNRecMp3='try6_rec.mp3'
      iSampleRate = 44100
      iSeconds = 30
      print('開始錄音'+str(iSeconds)+'秒')
      oTTSEngine.say('開始錄音'+str(iSeconds)+'秒')
      oTTSEngine.runAndWait()
      oMyRecording = sounddevice.rec(int(iSeconds * iSampleRate), samplerate=iSampleRate, channels=2)
      sounddevice.wait()
      write(strFNRecWav, iSampleRate, oMyRecording)
      print(strFNRecWav+'錄音完畢!')
      oTTSEngine.say('錄音完畢')
      oTTSEngine.runAndWait()
      print('準備轉為mp3')
      oTTSEngine.say('準備轉為mp3')
      oTTSEngine.runAndWait()
      oSound=AudioSegment.from_wav(strFNRecWav)
      oSound.export(strFNRecMp3, format='mp3')
      print('轉為mp3完成('+strFNRecMp3+')')
      oTTSEngine.say('轉為mp3完成')
      oTTSEngine.runAndWait()
      print('開始播放剛剛錄製的mp3('+strFNRecMp3+')')
      playsound('try6_rec.mp3')
      
    elif strZhTw.find('搜尋網路')!=-1:
      webbrowser.open("https://www.google.com/search?q=" + urllib.parse.quote_plus(strZhTw.replace("搜尋網路","")))
      
    elif len(strZhTw)>0:
      print("無法辨識的指令:" + strZhTw)
      oTTSEngine.say(strZhTw)
      oTTSEngine.runAndWait()
    
  except speech_recognition.UnknownValueError:
    print("未知的錯誤!speech_recognition.UnknownValueError!")
    
  except speech_recognition.RequestError as e:
    print("無法取得 vosk 服務; {0}".format(e))
    
  printMenu()