文章目錄
- 坤坤音效鍵盤說明
- 坤坤音效鍵盤效果展示
- 代碼實作
- 安裝第三方庫
- 準備音頻
- 監聽鍵盤
- 播放音頻
- 編寫邏輯
- 引入線程
- 打包成exe程式
坤坤音效鍵盤說明
坤坤音效鍵盤說明:
- 單獨按下
- 連續按下
- 連續按下
- 按下Esc按鍵,會觸發 “雞你太美版《澎湖灣》” 的長音效。
- 按下左Ctrl鍵,會觸發 “雞你太美版《想某人》” 的超長音效。
- 按下小鍵盤上的數字鍵或小數點鍵,停止播放音頻并終止程式。
說明一下: 對于連續按鍵觸發的音效,不要求快速連續按下,隻要連續即可。
坤坤音效鍵盤效果展示
說明一下: 為了讓大家知道我按下了哪些按鍵,視訊中将我按下的按鍵進行了列印。
代碼實作
安裝第三方庫
該程式需要用到以下兩個第三方庫:
- playsound子產品: 使用該子產品中的playsound函數來播放音頻。
- pynput子產品: 使用該子產品中的Listener對象來監聽鍵盤按鍵。
在指令行或終端中輸入以下指令進行安裝:
pip install pynput==1.6.8
pip install playsound==1.2.2
說明一下: 這裡下載下傳第三方庫時最好不要下載下傳最新版本的。
準備音頻
準備幾個想要播放的音頻,在Python程式所在目錄下建立一個子目錄,将這些音頻檔案放到這個子目錄當中。比如:
說明一下: 部落客的這些音頻檔案是在B站上找到的,大家可以去各個資源網站上下載下傳音頻檔案,也可以自行錄音。
監聽鍵盤
監聽鍵盤
監聽鍵盤的步驟如下:
- 通過from關鍵字導入pynput子產品中的keyboard子產品。
- 建立一個listener對象,建立對象時為其設定一個回調函數。
- 調用listener對象的start方法,讓listener開始監聽鍵盤按鍵。
- 調用listener對象的join方法,防止程式直接退出(listener本質是一個線程)。
代碼如下:
from pynput import keyboard
def onRelease(key):
print(f'使用者輸入: {key}')
listener = keyboard.Listener(on_release=onRelease)
listener.start()
listener.join()
此時每當按鍵被敲擊時,listener就會自動調用我們設定的回調函數,進而列印出被敲擊的按鍵。
說明一下:
- Listener的構造函數主要有兩個參數,一個是on_press,另一個是on_release,設定給on_press的回調函數會在按鍵被按下時調用,而設定給on_release的回調函數會在按鍵被釋放時調用。
- 構造Listener對象時,設定給on_press和on_release的回調函數必須有一個參數,該參數在按鍵被按下或釋放時由Listener自動傳入,表示被按下或釋放的按鍵。
普通鍵和特殊鍵
雖然在回調函數中通過print能夠直接列印出被按下的按鍵,但實際這個參數并不是字元串類型的,我們不能将該參數直接與字元串進行比較,這樣得不到正确的比較結果。
正确的做法如下:
- 如果使用者按下的是普通鍵(鍵盤上所有的字母、數字、符号),可以通過參數對象的char成員變量得知使用者按下的是哪個按鍵,這個char成員變量的類型是字元串str類型的。
- 如果使用者按下的是特殊鍵(普通鍵以外的按鍵),可以通過參數對象的name成員變量得知使用者按下的是哪個按鍵,這個name成員變量的類型是字元串str類型的。
但實際我們并不知道使用者本次按鍵按下的是普通鍵還是特殊鍵,并且如果使用者按下的是普通鍵,那麼參數對象是沒有name成員變量的,反之,如果使用者按下的是特殊鍵,那麼參數對象是沒有char成員變量的。如果通路了不存在的成員變量,那麼程式就會抛出異常
AttributeError
。
這時可以借助異常來進行處理:
- 将處理普通鍵的代碼邏輯放到try塊中,将處理特殊鍵的代碼邏輯放到except塊中。
- 當使用者按下按鍵後,會先執行try塊中的代碼邏輯,如果使用者按下的是普通鍵,那麼程式不會抛出異常,正常執行。
- 如果使用者按下的是特殊鍵,那麼當通路參數對象的char成員變量時就會抛出異常
,但由于我們對異常AttributeError
進行了捕捉,是以程式不會終止,此時執行流會跳轉到except塊中,執行except塊中處理特殊鍵的代碼邏輯。AttributeError
代碼如下:
def onRelease(key):
try:
print(f'使用者輸入: {key.char}')
print(type(key.char)) # <class 'str'>
except AttributeError:
print(f'使用者輸入: {key.name}')
print(type(key.name)) # <class 'str'>
判斷特殊鍵的另一種方式
當使用者按下的是特殊鍵時,除了通過參數對象的name成員變量得知使用者按下的是哪個按鍵之外,還可以通過如下方式進行比較:
# 下面兩種比較方式都可以
if key.name == 'ctrl_l':
print('使用者按下的是左Ctrl鍵')
if key == keyboard.Key.ctrl_l:
print('使用者按下的是左Ctrl鍵')
說明一下: Key是keyboard子產品中的一個枚舉類,Key中枚舉出了各個特殊鍵。
播放音頻
播放音頻
播放音頻的步驟如下:
- 通過from關鍵字導入playsound子產品中的playsound函數。
- 調用playsound函數時,傳入需要播放的音頻的路徑。
代碼如下:
from playsound import playsound
playsound('sound/j.mp3')
編寫邏輯
建立映射關系
為了能夠快速獲得一個字元串對應的音頻路徑,可以使用字典建立字元串與對應音頻的映射關系。
代碼如下:
# 建立字元串與對應音頻的映射
letterToAudio = {
'j': 'sound/j.mp3',
'n': 'sound/n.mp3',
't': 'sound/t.mp3',
'm': 'sound/m.mp3',
'jntm': 'sound/jntm.mp3',
'ngm': 'sound/ngm.mp3',
'esc': 'sound/phw.mp3',
'ctrl_l': 'sound/xmr.mp3'
}
編寫邏輯
代碼邏輯的編寫如下:
- 将處理普通鍵的代碼邏輯放到try塊中,将處理特殊鍵的代碼邏輯放到except塊中。
- 為了實作特定連續按鍵觸發特定音頻的功能,需要用history變量記錄曆史敲擊過的字母,每當按鍵被敲擊時就可以通過history變量來判斷是否觸發連續字母音效了。
- 在處理普通鍵時,需要優先判斷是否觸發連續字母音效,如果沒有觸發連續字母音效再判斷是否觸發單字母音效,因為觸發連續字母音效的最後一個字母可能也會觸發單字母音效。
- 在處理特殊鍵時,直接判斷使用者按下的按鍵是否會觸發音效即可。
代碼如下:
history = '' # 記錄曆史敲擊過的字母
def onRelease(key):
global history
audio = ''
try:
print(f'使用者輸入: {key.char}')
# 記錄敲擊過的字母
if len(history) < 4:
history += key.char
else:
history = history[1:] + key.char
# 優先判斷是否觸發連續字母音效,再判斷是否觸發單字母音效
if history == 'jntm':
audio = letterToAudio[history]
elif history[-3:] == 'ngm':
audio = letterToAudio[history[-3:]]
elif key.char in 'jntm':
audio = letterToAudio[key.char]
except AttributeError:
print(f'使用者輸入: {key.name}')
# 按下的不是普通鍵,可以把history清空
history = ''
# 判斷是否觸發音效
if key == keyboard.Key.esc:
audio = letterToAudio['esc']
elif key == keyboard.Key.ctrl_l:
audio = letterToAudio['ctrl_l']
# 判斷是否本次敲擊按鍵是否觸發音效
if audio != '':
playsound(audio)
說明一下:
- 變量history沒必要将曆史敲擊過的字母全部記錄下來,因為這裡觸發連續字母音效的最長連續字母就是
,長度為4,是以history隻需要記錄最近4次敲擊過的字母即可。'jntm'
- 在判斷是否觸發
的連續音效時,history的長度可能為3,也可能為4,這時需要通過負索引的方式對history進行切片操作,保證是在用history中的後三個字母在進行判斷。'ngm'
引入線程
目前程式存在的問題
現在我們編寫的代碼已經可以運作了,但目前的效果體驗并不好:
- 在播放音頻的時候我們打字會卡頓,并且在目前音頻未播放完之前的按鍵無法觸發其他音頻。
- 根本原因就是因為此時監聽鍵盤按鍵和播放音頻都是由同一個線程處理的,是以線程在播放音頻的時候無法監聽鍵盤按鍵。
為了解決這個問題,我們可以在調用playsound播放音頻的時候建立一個線程,讓該線程去執行播放音頻的動作,而讓目前線程繼續進行按鍵監聽操作。
引入線程
引入線程的步驟如下:
- 通過from關鍵字導入threading子產品中的Thread類(threading是标準庫中的子產品,不需要額外安裝)。
- 建立線程時需要建立一個Thread對象,然後調用Thread對象的start方法啟動線程。
- 在建立Thread對象時,需要通過target參數指定該線程啟動後要執行的程式例程,通過args參數指定調用該程式例程時需要傳入的參數。
代碼如下:
# 建立線程對象,并指定其要執行的程式例程
t = Thread(target=playsound, args=(audio, ))
# 啟動線程
t.start()
說明一下:
- 建立Thread對象時,傳入的args參數的類型是元組類型,是以如果隻需要傳入一個參數,就需要以
的方式傳入,這後面這個逗号是不可省略的,否則就不是元組類型了。(arg, )
- Python預設建立線程後,不管主線程是否執行完畢,都會等待子線程執行完畢才一起退出,是以主線程是否join子線程結果都一樣。
- 如果這裡調用了join,那麼監聽鍵盤按鍵的線程就會被阻塞,直到子線程将音頻播放完畢,此時音頻的播放過程變成了串行的。
完整代碼
引入線程後的完整代碼如下,此時在播放音頻的時候敲鍵盤就不會存在卡頓現象,并且在音頻播放期間能夠再次觸發其他音頻。
from pynput import keyboard
from playsound import playsound
from threading import Thread
# 建立字元串與對應音頻的映射
letterToAudio = {
'j': 'sound/j.mp3',
'n': 'sound/n.mp3',
't': 'sound/t.mp3',
'm': 'sound/m.mp3',
'jntm': 'sound/jntm.mp3',
'ngm': 'sound/ngm.mp3',
'esc': 'sound/phw.mp3',
'ctrl_l': 'sound/xmr.mp3'
}
history = '' # 記錄曆史敲擊過的字母
def onRelease(key):
global history
audio = ''
try:
print(f'使用者輸入: {key.char}')
# 記錄敲擊過的字母
if len(history) < 4:
history += key.char
else:
history = history[1:] + key.char
# 優先判斷是否觸發連續字母音效,再判斷是否觸發單字母音效
if history == 'jntm':
audio = letterToAudio[history]
elif history[-3:] == 'ngm':
audio = letterToAudio[history[-3:]]
elif key.char in 'jntm':
audio = letterToAudio[key.char]
except AttributeError:
print(f'使用者輸入: {key.name}')
# 按下的不是普通鍵,可以把history清空
history = ''
# 判斷是否觸發音效
if key == keyboard.Key.esc:
audio = letterToAudio['esc']
elif key == keyboard.Key.ctrl_l:
audio = letterToAudio['ctrl_l']
# 判斷是否本次敲擊按鍵是否觸發音效
if audio != '':
# 建立線程對象,并指定其要執行的程式例程
t = Thread(target=playsound, args=audio)
# 啟動線程
t.start()
listener = keyboard.Listener(on_release=onRelease)
listener.start()
listener.join()
打包成exe程式
一、打包資源檔案夾
目前項目播放音頻時需要用到的音頻檔案就叫做資源檔案,部落客将這些資源檔案放在了一個名為sound的檔案夾中。如下:
二、修改KunKunKeyboard.py檔案
我們需要在KunKunKeyboard.py檔案中加入如下函數,該函數是用于生成資源檔案的通路路徑的。
# 生成資源檔案通路路徑
def resource_path(relative_path):
if getattr(sys, 'frozen', False): # 是否Bundle Resource
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
代碼中所有使用資源檔案路徑的地方,都需要通過調用該函數來生成資源檔案的通路路徑,然後再通過這個生成的路徑來通路資源檔案,我們隻需要将字典中的内容更改一下即可。如下:
# 建立字元串與對應音頻的映射
letterToAudio = {
'j': resource_path(os.path.join('sound', 'j.mp3')),
'n': resource_path(os.path.join('sound', 'n.mp3')),
't': resource_path(os.path.join('sound', 't.mp3')),
'm': resource_path(os.path.join('sound', 'm.mp3')),
'jntm': resource_path(os.path.join('sound', 'jntm.mp3')),
'ngm': resource_path(os.path.join('sound', 'ngm.mp3')),
'esc': resource_path(os.path.join('sound', 'phw.mp3')),
'ctrl_l': resource_path(os.path.join('sound', 'xmr.mp3'))
}
說明一下: join是os.path子產品中的一個函數,它的作用是将多個路徑進行拼接。
三、準備圖示檔案
如果你想要修改生成的exe程式的圖示的話,那麼你需要準備一個像素的圖檔檔案,圖檔檔案需要為ico格式,可以使用百度的JPG線上轉ICO:https://www.aconvert.com/cn/icon/jpg-to-ico/
将生成的圖示檔案KunKunKeyboard.py的同級目錄下。如下:
三、生成KunKunKeyboard.spec檔案并修改
在指令行或終端中輸入以下指令,生成KunKunKeyboard.spec檔案:
pyi-makespec -F -i logo.ico KunKunKeyboard.py
此時在KunKunKeyboard.py的同級目錄下會生成一個KunKunKeyboard.spec檔案。如下:
此時,打開KunKunKeyboard.spec檔案,并将做如下修改:
說明一下:
- 在修改之前,datas的值為一個空清單,即
。datas=[]
- 這裡修改的意思是,将KunKunKeyboard.py目錄下的sound目錄及其該目錄中的檔案加入生成的exe程式中,在運作時放在臨時檔案的根目錄下,名稱為sound。
四、生成exe程式
最後在指令行或終端中輸入以下指令:
pyinstaller KunKunKeyboard.spec
此時在KunKunKeyboard.py的同級目錄下會生成一個dict目錄,生成的exe程式就在該目錄中。如下:
拓展内容(非必須)
如果在修改KunKunKeyboard.spec時,同時将console的值設定為False。如下:
那麼此時生成的exe程式在運作時将不會彈出視窗,程式運作後也不會在工作列顯示。需要注意的是,如果要這樣做,需要将程式中兩處調用print函數的地方注釋掉,因為此時沒有視窗供print輸出,如果print被調用那麼程式将會抛異常。
- 打開任務管理器,找到該程序将其終止。
- 關機重新開機。
- 按下小鍵盤上的數字鍵,讓程式因抛異常而終止。