天我想學(xué)習(xí)記錄的內(nèi)容是?——如何用python實(shí)現(xiàn)錄屏秋冰。
1
寫在前面
作為一名測(cè)試,有時(shí)候經(jīng)常會(huì)遇到需要錄屏記錄自己操作坚踩,方便后續(xù)開發(fā)同學(xué)定位呈础。以前都是用ScreenToGif來(lái)錄屏制作成動(dòng)態(tài)圖,偶爾的機(jī)會(huì)看到python也能實(shí)現(xiàn)渊胸。那就趕緊學(xué)習(xí)下旬盯。
!
2
效果展示
?
3
知識(shí)串講(敲黑板啦)
這次要講的東西可能比較多了,涉及到pyqt5 GUI軟件的制作胖翰、QThread多線程的使用频丘、Sikuli庫(kù)的圖形操作、win32庫(kù)的模擬鍵盤操作泡态、cv2庫(kù)的寫視頻文件等甥材。下面我們一點(diǎn)點(diǎn)來(lái)蠶食我這次寫的代碼蚕涤。
(1)GUI界面制作?
這次我用的是現(xiàn)成的Pyqt5界面布局類,QVBoxLayout。這個(gè)類可以快速協(xié)助我完成按鈕的垂直分布贵涵,而且按鈕添加也更方便。
button1 = QPushButton("自定義錄屏")layout.addWidget(button1)
兩行代碼就完成了按鈕的命名和添加从诲。我之前玩qt時(shí)果元,用的都是qt的UI界面,對(duì)應(yīng)生成的組件代碼也比較復(fù)雜腾降。因此拣度,在開發(fā)一些少量按鈕、簡(jiǎn)單布局時(shí)可以用QVBoxLayout類螃壤。如果喜歡水平布局抗果,可以用QHBoxLayout類,使用方法是一樣的奸晴。
另外冤馏,在按鈕點(diǎn)擊關(guān)聯(lián)的功能函數(shù),即work()方法時(shí)寄啼,如果想帶參數(shù)逮光,可以通過(guò)lambda匿名函數(shù)來(lái)實(shí)現(xiàn)。這 也是個(gè)小技巧墩划。
#?不帶參數(shù)button1.clicked.connect(self.work)# 帶參數(shù)button1.clicked.connect(lambda: self.work(1))
(2)QThread類的多線程使用
因?yàn)殇浧凉ぞ哂虚_始和停止兩個(gè)功能涕刚,一開始時(shí)我用的是單線程,發(fā)現(xiàn)工具就會(huì)卡死乙帮。查了一些資料杜漠,發(fā)現(xiàn)針對(duì)這種情況,應(yīng)該要使用多線程來(lái)實(shí)現(xiàn)蚣旱,而QT庫(kù)中本身就有多線程類--QThread碑幅。
使用方法是通過(guò)繼承QThread類,重寫run方法來(lái)實(shí)現(xiàn)的塞绿。
(但是其實(shí)這種使用方法沟涨,QT大神們是不贊成這樣使用的,我會(huì)在第2篇文章中再簡(jiǎn)單說(shuō)明更好的多線程使用方法)
這 里要注意异吻,work()函數(shù)必須是Ui_Mainwindow類方法裹赴,因?yàn)槿绻皇穷惙椒ㄏ才樱瑫?huì)在運(yùn)行GUI時(shí)導(dǎo)致生命周期直接結(jié)束,導(dǎo)致錄屏代碼沒見運(yùn)行就報(bào)錯(cuò)退出棋返。
class WorkThread(QThread):? ? def __init__(self, n):? ? ? ? super(WorkThread, self).__init__()? ? ? ? self.n = n? ? def run(self):????????XXXXX
(3)sikuli庫(kù)圖形識(shí)別
由于這個(gè)庫(kù)的使用方法和介紹延都,我在之前的博客里已經(jīng)提過(guò) 了。因此只簡(jiǎn)單地呈現(xiàn)下代碼睛竣。這段代碼主要是為了自定義錄屏?xí)r晰房,可以獲取選擇范圍的坐標(biāo)值,并傳值給recording函數(shù)射沟,從而完成自定義錄屏功能殊者。
def SelectRegion():? ? jvmPath = jpype.get_default_jvm_path()? ? jpype.startJVM(jvmPath, '-ea', '-Djava.class.path=F:\\sikuli\\1\\sikulixapi.jar') #加載jar包路徑? ? Screen = jpype.JClass('org.sikuli.script.Screen')? ? myscreen = Screen()? ? region = myscreen.selectRegion() # 自定義獲取屏幕范圍? ? return region
(4)win32庫(kù)模擬鍵盤操作
其實(shí)這個(gè)庫(kù)不用也是可以的,我為什么要用呢验夯?主要是為了方便用戶在進(jìn)行錄屏?xí)r猖吴,能自動(dòng)將工具界面縮小。一切為了用戶嘛挥转!
以下這段代碼 是為了縮小工具窗口海蔽,其中91表示左win鍵,40表示方向向下鍵绑谣。即win+向下鍵是可以實(shí)現(xiàn)窗口縮小功能的党窜。keybd_event(91, 0, 0, 0)表示按下win鍵,
keybd_event(91, 0, win32con.KEYEVENTF_KEYUP, 0)則是松開win鍵域仇。
另外刑然,這里為什么要加 上sleep(0.5)?這是因?yàn)樵诎聪聎in鍵后要延遲按方向鍵暇务,不然是 不起作用的。
def Minimize_Window():? ? win32api.keybd_event(91, 0, 0, 0)? ? time.sleep(0.5)? ? win32api.keybd_event(40, 0, 0, 0)? ? time.sleep(0.5)? ? win32api.keybd_event(91, 0, win32con.KEYEVENTF_KEYUP, 0)? ? win32api.keybd_event(40, 0, win32con.KEYEVENTF_KEYUP, 0)
(5)錄屏主代碼
這段代碼其實(shí)網(wǎng)上已經(jīng)有很多類似的代碼怔软,并且我已經(jīng)加了注釋垦细,相信大家應(yīng)該能理解。這里我想注明下的是:如何停止錄屏挡逼。
如果大家有去?網(wǎng)上查如何停止錄屏的方法括改,很多人都會(huì)寫以下代碼:
???????
if cv2.waitKey(1) & 0xFF == ord('q'):? ? break
然后告訴你,按q鍵就會(huì)停止錄屏家坎。但是你會(huì)發(fā)現(xiàn)嘱能,實(shí)際情況根本停止不了,為什么呢虱疏?因?yàn)檫€ 有一句屏幕顯示的代碼:
cv2.imshow('imm', img_bgr)if cv2.waitKey(1) & 0xFF == ord('q'):? ? break
如果你不親自執(zhí)行一次惹骂,你以為會(huì)萬(wàn)事大吉,但你錯(cuò)了做瞪。這樣寫对粪,會(huì)導(dǎo)致你的電腦屏幕被每一幀畫面給撐暴右冻!因?yàn)橛玫膚hile True,因此每一幀畫面都會(huì)顯示著拭,即1S 25幀畫面會(huì)不停地顯示在你桌面上纱扭!
因此,綜上的問題儡遮,我采用了一種取巧的方法:在錄屏開始時(shí)生成一個(gè)標(biāo)記文件乳蛾,通過(guò)標(biāo)記文件是否被刪除來(lái)判斷是否要停止錄屏功能。
4
示例代碼
1鄙币、工具GUI界面代碼:
# coding=utf-8# @Auther : "鵬哥賊優(yōu)秀"# @Date : 2019/11/27# @Software : PyCharm import sysfrom PyQt5.QtCore import *from PyQt5.QtWidgets import *import timeimport win32api,win32confrom recording import *class WorkThread(QThread):? ? def __init__(self, n):? ? ? ? super(WorkThread, self).__init__()? ? ? ? self.n = n? ? def run(self):? ? ? ? if self.n == 1:? ? ? ? ? ? Minimize_Window()? ? ? ? ? ? Recording(1)? ? ? ? elif self.n == 2:? ? ? ? ? ? Minimize_Window()? ? ? ? ? ? Recording(2)? ? ? ? else:? ? ? ? ? ? StopRecording()def Minimize_Window():? ? win32api.keybd_event(91, 0, 0, 0)? ? time.sleep(0.5)? ? win32api.keybd_event(40, 0, 0, 0)? ? time.sleep(0.5)? ? win32api.keybd_event(91, 0, win32con.KEYEVENTF_KEYUP, 0)? ? win32api.keybd_event(40, 0, win32con.KEYEVENTF_KEYUP, 0)class Ui_Mainwindow():? ? def setupUi(self, top):? ? ? ? # 垂直布局類QVBoxLayout? ? ? ? layout = QVBoxLayout(top)? ? ? ? # 添加錄屏相關(guān)按鈕? ? ? ? button1 = QPushButton("自定義錄屏")? ? ? ? layout.addWidget(button1)? ? ? ? button2 = QPushButton("全屏錄屏")? ? ? ? layout.addWidget(button2)? ? ? ? button3 = QPushButton("停止錄屏")? ? ? ? layout.addWidget(button3)? ? ? ? self.text = QPlainTextEdit('歡迎使用肃叶!By 鵬哥賊優(yōu)秀')? ? ? ? layout.addWidget(self.text)? ? ? ? button1.clicked.connect(lambda: self.work(1))? ? ? ? button2.clicked.connect(lambda: self.work(2))? ? ? ? button3.clicked.connect(lambda: self.work(3))? ? def work(self, n):? ? ? ? if n == 1 :? ? ? ? ? ? print('已選擇自定義錄屏:')? ? ? ? ? ? self.text.setPlainText('正在錄屏中,請(qǐng)等待……')? ? ? ? elif n == 2 :? ? ? ? ? ? print('已選擇全屏錄屏:')? ? ? ? ? ? self.text.setPlainText('正在錄屏中爱榔,請(qǐng)等待……')? ? ? ? else:? ? ? ? ? ? print('已選擇結(jié)束錄屏:')? ? ? ? ? ? self.text.setPlainText('錄屏結(jié)束被环!(點(diǎn)擊關(guān)閉按鈕,可退出程序详幽!)')? ? ? ? self.workThread = WorkThread(n)? ? ? ? self.workThread.start()if __name__ == "__main__":? ? app = QApplication(sys.argv)? ? top = QWidget()? ? top.setWindowTitle('錄屏小工具')? ? top.resize(300, 170)? ? ui = Ui_Mainwindow()? ? ui.setupUi(top)? ? top.show()????sys.exit(app.exec_())
2筛欢、錄屏函數(shù)
# coding=utf-8# @Auther : "鵬哥賊優(yōu)秀"# @Date : 2019/11/27# @Software : PyCharmfrom PIL import ImageGrabimport numpy as npimport cv2import osimport jpypedef Recording(tag=1):? ? # 錄屏開始時(shí)創(chuàng)建test.txt,作為結(jié)束錄屏的條件? ? if not os.path.exists('test.txt'):? ? ? ? f = open('test.txt', 'w')? ? ? ? f.close()? ? # 根據(jù)tag值判斷自定義錄屏或全錄屏? ? if tag == 1:? ? ? ? r = SelectRegion()? ? ? ? record_region = (r.x, r.y, r.w + r.x, r.h + r.y) # 自定義錄屏的范圍(左上坐標(biāo)、右下坐標(biāo))? ? elif tag == 2:? ? ? ? record_region = None? ? image = ImageGrab.grab(record_region)? # 獲取指定范圍的屏幕對(duì)象? ? width, height = image.size? ? fourcc = cv2.VideoWriter_fourcc(*'XVID')? ? video = cv2.VideoWriter('test.avi', fourcc, 25, (width, height)) # 默認(rèn)視頻為25幀? ? while True:? ? ? ? captureImage = ImageGrab.grab(record_region)? # 抓取指定范圍的屏幕? ? ? ? frame = cv2.cvtColor(np.array(captureImage), cv2.COLOR_RGB2BGR)? ? ? ? video.write(frame) # 將每幀畫面寫視頻文件? ? ? ? # 停止錄屏的條件:test.txt被刪除? ? ? ? if not os.path.exists('test.txt'):? ? ? ? ? ? break? ? video.release()? ? cv2.destroyAllWindows()def SelectRegion():? ? jvmPath = jpype.get_default_jvm_path()? ? jpype.startJVM(jvmPath, '-ea', '-Djava.class.path=F:\\sikuli\\1\\sikulixapi.jar') #加載jar包路徑? ? Screen = jpype.JClass('org.sikuli.script.Screen')? ? myscreen = Screen()? ? region = myscreen.selectRegion() # 自定義獲取屏幕范圍? ? return regiondef StopRecording():? ? os.remove('test.txt') #停止錄屏的觸發(fā)條件if __name__ == "__main__":????Recording()