目錄
目錄
應用平臺
屏幕錄制部分
計算視頻最優(yōu)fps及使用numpy計算中間幀數組
使用pynput監(jiān)聽鍵盤按鍵
如何保存MP4格式視頻
源碼
總結
最近有在使用屏幕錄制軟件錄制桌面,在用的過程中突發(fā)奇想,使用python能不能做屏幕錄制工具,也鍛煉下自己的動手能力示惊。接下準備寫使用python如何做屏幕錄制工具的系列文章:
錄制屏幕制作視頻
錄制音頻
合成視頻,音頻
基于pyqt5制作可視化窗口
大概上述四個部分,希望自己能夠盡快完善垄琐,接下來開始使用python制作屏幕錄制部分。
應用平臺
windows 10
python 3.7
屏幕錄制部分
屏幕錄制可以簡單地理解為將屏幕快照以動圖的形式播放经柴,這里我選用PIL下的ImageGrab來截取屏幕畫面狸窘,首先
pip install Pillow
之后需要將截取到的快照數組合成為視頻,使用cv2模塊
pip install opencv-python
ImageGrab類不能直接存儲為視頻坯认,使用numpy模塊進行數組化翻擒,再通過cv2.COLOR_BGR2RGB轉換為cv2色彩通道氓涣。
pip install numpy
屏幕錄制主要代碼:
import?numpy?as?np
from?PIL?import?ImageGrab
import?cv2
im?=?ImageGrab.grab()
width,?high?=?im.size??#?獲取屏幕的寬和高
fourcc?=?cv2.VideoWriter_fourcc(*'I420')??#?設置視頻編碼格式
fps?=?15??#?設置幀率
video?=?cv2.VideoWriter('test.avi',?fourcc,?fps,?(width,?high))
while?True:??#?開始錄制
????im?=?ImageGrab.grab()
????im_cv?=?cv2.cvtColor(np.array(im),?cv2.COLOR_BGR2RGB)
????#?圖像寫入
????video.write(im_cv)
????if?xx:??#?當某某條件滿足中斷循環(huán)
????????break
video.release()??#?釋放緩存,持久化視頻
測試運行可以保存屏幕快照為視頻陋气,但操作起來不優(yōu)雅劳吠,也不利于后續(xù)的操作。
封裝成類巩趁,繼承線程父類痒玩,方便使用鍵盤來控制視頻錄制的結束。
from?threading?import?Thread
class?ScreenshotVideo(Thread):
????def?__init__(self):
?????"""初始化參數"""
????????super().__init__()
詳細代碼將在文末給出议慰。
計算視頻最優(yōu)fps及使用numpy計算中間幀數組
實際操作中視頻錄制在不同電腦中會出現(xiàn)不一樣的幀率蠢古,導致視頻播放或快或慢,需要根據不同的電腦計算出相應的最優(yōu)fps值别凹。
def?video_best_fps(self,?path):
????"""獲取電腦錄制視頻的最優(yōu)幀率"""
????video?=?cv2.VideoCapture(path)??#?讀取視頻
????fps?=?video.get(cv2.CAP_PROP_FPS)??#?獲取當前視頻的幀率
????count?=?video.get(cv2.CAP_PROP_FRAME_COUNT)??#?獲取視頻幀數草讶,即該視頻有多少幅畫面
????self.best_fps?=?int(fps?*?((int(count)?/?fps)?/?self.spend_time))???#?計算播放時間與錄制時間對比得到最優(yōu)幀率
????video.release()
再調整幀率參數進行錄制視頻就減弱了視頻播放太快或者太慢。也可以給視頻增加幀數從而延長播放時間炉菲,這里我采用一種很簡單的方法增加視頻幀到涂,僅供參考。
from?numba?import?jit
#?使用numpy計算相鄰兩幀圖像且更接近于后一幀的圖像
#?調用jit方法加速數組計算
@jit(nopython=True)
def?average_n(x,?y):
????"""Numpy計算趨近值"""
????return?((x?+?y?+?y)?//?3).astype(x.dtype)
該方法僅針對于設置的fps比最優(yōu)fps要高時颁督,處理后的視頻觀感践啄,視頻還是較為急促,但是細節(jié)幀增多沉御,所以播放時長會比未處理前的要長屿讽,略有殘影。
使用pynput監(jiān)聽鍵盤按鍵
在視頻錄制中吠裆,并不知道視頻何時結束伐谈,所以用while循環(huán)包裹錄制代碼,但也不可能讓代碼無休止的運行下去试疙,在此使用監(jiān)聽鍵盤模塊來中斷錄制代碼的運行诵棵。
from?pynput?import?keyboard??#?pip?install?pynput
def?hotkey(self):
????"""熱鍵監(jiān)聽"""
????with?keyboard.Listener(on_press=self.on_press)?as?listener:
????????listener.join()
def?on_press(self,?key):
????try:
????????if?key.char?==?'t':??#?錄屏結束,保存視頻
????????????self.flag?=?True
????????elif?key.char?==?'k':??#?錄屏中止祝旷,刪除文件
????????????self.flag?=?True
????????????self.kill?=?True
????except?Exception?as?e:
????????print(e)
按下鍵盤“T”鍵時履澳,結束錄制,保存視頻怀跛【啻“K”鍵則是停止錄制,刪除緩存文件吻谋。
如何保存MP4格式視頻
視頻編碼格式應該為('a', 'v', 'c', '1')忠蝗,文件后綴為'.mp4',在錄制前先去下下載對應平臺的dll.bz2文件漓拾,將壓縮包解壓放在項目文件夾下阁最。
源碼
本文實現(xiàn)的源碼如下:
import?time
from?PIL?import?ImageGrab
import?cv2
from?pathlib?import?Path
import?numpy?as?np
from?numba?import?jit
from?pynput?import?keyboard
from?threading?import?Thread
@jit(nopython=True)
def?average_n(x,?y):
????"""Numpy計算趨近值"""
????return?((x?+?y?+?y)?//?3).astype(x.dtype)
class?ScreenshotVideo(Thread):
????def?__init__(self,?width,?high,?path='',?fps=15):
????????"""初始化參數"""
????????super().__init__()
????????self.save_file?=?path
????????self.best_fps?=?fps
????????self.fps?=?fps
????????self.width?=?width
????????self.high?=?high
????????self.spend_time?=?1
????????self.flag?=?False
????????self.kill?=?False
????????self.video?=?None
????def?__call__(self,?path):
????????"""重載視頻路徑戒祠,便于類的二次調用"""
????????self.save_file?=?Path(path)
????????self.video?=?self.init_videowriter(self.save_file)
????@staticmethod
????def?screenshot():
????????"""靜態(tài)方法,屏幕截圖速种,并轉換為np.array數組"""
????????return?np.array(ImageGrab.grab())
????@staticmethod
????def?get_fourcc(name):
????????"""視頻編碼字典"""
????????fourcc_maps?=?{'.avi':?'I420',
???????????????????????'.m4v':?'mp4v',
???????????????????????'.mp4':?'avc1',
???????????????????????'.ogv':?'THEO',
???????????????????????'.flv':?'FLV1',
???????????????????????}
????????return?fourcc_maps.get(name)
????def?init_videowriter(self,?path):
????????"""獲取視頻編碼并新建視頻文件"""
????????if?not?path:
????????????raise?Exception('視頻路徑未設置姜盈,請設置\nvideo?=?ScreenshotVideo(fps,width,high)\nvideo?=?video(video_path)')
????????path?=?Path(path)?if?isinstance(path,?str)?else?path
????????fourcc?=?cv2.VideoWriter_fourcc(*self.get_fourcc(path.suffix))
????????return?cv2.VideoWriter(path.as_posix(),?fourcc,?self.fps,?(self.width,?self.high))
????def?video_record_doing(self,?img):
????????"""將BGR數組轉換為RGB數組"""
????????im_cv?=?cv2.cvtColor(img,?cv2.COLOR_BGR2RGB)
????????self.video.write(im_cv)
????def?video_record_end(self):
????????"""錄制結束,根據條件判斷文件是否保存"""
????????self.video.release()
????????cv2.destroyAllWindows()
????????if?self.save_file?and?self.kill:
????????????Path(self.save_file).unlink()
????def?video_best_fps(self,?path):
????????"""獲取電腦錄制視頻的最優(yōu)幀率"""
????????video?=?cv2.VideoCapture(path)
????????fps?=?video.get(cv2.CAP_PROP_FPS)
????????count?=?video.get(cv2.CAP_PROP_FRAME_COUNT)
????????self.best_fps?=?int(fps?*?((int(count)?/?fps)?/?self.spend_time))
????????video.release()
????def?pre_video_record(self):
????????"""預錄制哟旗,以獲取最佳fps值"""
????????self.video?=?self.init_videowriter('test.mp4')
????????start_time?=?time.time()
????????for?_?in?range(10):
????????????im?=?self.screenshot()
????????????self.video_record_doing(im)
????????self.spend_time?=?round(time.time()?-?start_time,?4)
????????self.video_record_end()
????????time.sleep(2)
????????self.video_best_fps('test.mp4')
????????Path('test.mp4').unlink()
????def?insert_frame_array(self,?frame_list):
????????"""Numpy增強截圖信息"""
????????fps_n?=?round(self.fps?/?self.best_fps)
????????if?fps_n?<=?0:
????????????return?frame_list
????????times?=?int(np.log2(fps_n))??#?倍率
????????for?_?in?range(times):
????????????frame_list2?=?map(average_n,?[frame_list[0]]?+?frame_list[:-1],?frame_list)
????????????frame_list?=?[[x,?y]?for?x,?y?in?zip(frame_list2,?frame_list)]
????????????frame_list?=?[j?for?i?in?frame_list?for?j?in?i]
????????return?frame_list
????def?frame2video_run(self):
????????"""使用opencv將連續(xù)型截圖轉換為視頻"""
????????self.video?=?self.init_videowriter(self.save_file)
????????start_time?=?time.time()
????????frame_list?=?[]
????????while?True:
????????????frame_list.append(self.screenshot())
????????????if?self.flag:
????????????????break
????????self.spend_time?=?round(time.time()?-?start_time,?4)
????????if?not?self.kill:??#?視頻錄制不被終止將逐幀處理圖像
????????????frame_list?=?self.insert_frame_array(frame_list)
????????????for?im?in?frame_list:
????????????????self.video_record_doing(im)
????????self.video_record_end()
????def?hotkey(self):
????????"""熱鍵監(jiān)聽"""
????????with?keyboard.Listener(on_press=self.on_press)?as?listener:
????????????listener.join()
????def?on_press(self,?key):
????????try:
????????????if?key.char?==?'t':??#?錄屏結束贩据,保存視頻
????????????????self.flag?=?True
????????????elif?key.char?==?'k':??#?錄屏中止,刪除文件
????????????????self.flag?=?True
????????????????self.kill?=?True
????????except?Exception?as?e:
????????????print(e)
????def?run(self):
????????#?運行函數
????????#?設置守護線程
????????Thread(target=self.hotkey,?daemon=True).start()
????????#?運行截圖函數
????????self.frame2video_run()
screen?=?ImageGrab.grab()
width,?high?=?screen.size
video?=?ScreenshotVideo(width,?high,?fps=60)
video.pre_video_record()??#?預錄制獲取最優(yōu)fps
video('test1.mp4')
video.run()
總結
本文目前使用了opencv和相關模塊對屏幕進行錄制并轉換為視頻保存闸餐,學習將多個函數封裝為類饱亮,方便后續(xù)功能開發(fā)。學習的道路是無止境的舍沙,大膽的邁步走吧近上!
小伙伴們,快快用實踐一下吧拂铡!如果在學習過程中壹无,有遇到任何問題,歡迎關注我感帅,我拉你進Python學習交流群共同探討學習斗锭。