用Python來做一個屏幕錄製工具

来源:https://www.cnblogs.com/chengxyuan/archive/2020/01/17/12207537.html
-Advertisement-
Play Games

一、寫在前面 作為一名測試,有時候經常會遇到需要錄屏記錄自己操作,方便後續開發同學定位。以前都是用ScreenToGif來錄屏製作成動態圖,偶爾的機會看到python也能實現。那就趕緊學習下。 二、效果展示 三、知識串講 這次要講的東西可能比較多了,涉及到pyqt5 GUI軟體的製作、QThread ...


一、寫在前面

作為一名測試,有時候經常會遇到需要錄屏記錄自己操作,方便後續開發同學定位。以前都是用ScreenToGif來錄屏製作成動態圖,偶爾的機會看到python也能實現。那就趕緊學習下。

二、效果展示

三、知識串講

這次要講的東西可能比較多了,涉及到pyqt5 GUI軟體的製作、QThread多線程的使用、Sikuli庫的圖形操作、win32庫的模擬鍵盤操作、cv2庫的寫視頻文件等。下麵我們一點點來蠶食我這次寫的代碼。

1、GUI界面製作

這次我用的是現成的Pyqt5界面佈局類,QVBoxLayout。這個類可以快速協助我完成按鈕的垂直分佈,而且按鈕添加也更方便。

button1 = QPushButton("自定義錄屏")
layout.addWidget(button1)

 

兩行代碼就完成了按鈕的命名和添加。我之前玩qt時,用的都是qt的UI界面,對應生成的組件代碼也比較複雜。因此,在開發一些少量按鈕、簡單佈局時可以用QVBoxLayout類。如果喜歡水平佈局,可以用QHBoxLayout類,使用方法是一樣的。

另外,在按鈕點擊關聯的功能函數,即work()方法時,如果想帶參數,可以通過lambda匿名函數來實現。這 也是個小技巧。

# 不帶參數
button1.clicked.connect(self.work)
# 帶參數
button1.clicked.connect(lambda: self.work(1))

 

2、QThread類的多線程使用

因為錄屏工具有開始和停止兩個功能,一開始時我用的是單線程,發現工具就會卡死。查了一些資料,發現針對這種情況,應該要使用多線程來實現,而QT庫中本身就有多線程類--QThread。

使用方法是通過繼承QThread類,重寫run方法來實現的。

(但是其實這種使用方法,QT大神們是不贊成這樣使用的,我會在第2篇文章中再簡單說明更好的多線程使用方法)

這 里要註意,work()函數必須是Ui_Mainwindow類方法,因為如果不是類方法,會在運行GUI時導致生命周期直接結束,導致錄屏代碼沒見運行就報錯退出。

class WorkThread(QThread):
    def __init__(self, n):
        super(WorkThread, self).__init__()
        self.n = n

    def run(self):
        XXXXX

 

3、sikuli庫圖形識別

由於這個庫的使用方法和介紹,我在之前的博客里已經提過 了。因此只簡單地呈現下代碼。這段代碼主要是為了自定義錄屏時,可以獲取選擇範圍的坐標值,並傳值給recording函數,從而完成自定義錄屏功能。

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庫模擬鍵盤操作

其實這個庫不用也是可以的,我為什麼要用呢?主要是為了方便用戶在進行錄屏時,能自動將工具界面縮小。一切為了用戶嘛!

以下這段代碼 是為了縮小工具視窗,其中91表示左win鍵,40表示方向向下鍵。****即win+向下鍵是可以實現視窗縮小功能的。****keybd_event(91, 0, 0, 0)表示按下win鍵,

keybd_event(91, 0, win32con.KEYEVENTF_KEYUP, 0)則是鬆開win鍵。

另外,這裡為什麼要加 上sleep(0.5)?這是因為在按下win鍵後要延遲按方向鍵,不然是 不起作用的。

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、錄屏主代碼

這段代碼其實網上已經有很多類似的代碼,並且我已經加了註釋,相信大家應該能理解。這裡我想註明下的是:如何停止錄屏。

如果大家有去 網上查如何停止錄屏的方法,很多人都會寫以下代碼:

if cv2.waitKey(1) & 0xFF == ord('q'):
    break

 

然後告訴你,按q鍵就會停止錄屏。但是你會發現,實際情況根本停止不了,為什麼呢?因為還 有一句屏幕顯示的代碼:

cv2.imshow('imm', img_bgr)
if cv2.waitKey(1) & 0xFF == ord('q'):
    break

 

如果你不親自執行一次,你以為會萬事大吉,但你錯了。這樣寫,會導致你的電腦屏幕被每一幀畫面給撐暴!因為用的while True,因此每一幀畫面都會顯示,即1S 25幀畫面會不停地顯示在你桌面上!

因此,綜上的問題,我採用了一種取巧的方法:在錄屏開始時生成一個標記文件,通過標記文件是否被刪除來判斷是否要停止錄屏功能。

四、示例代碼

1、工具GUI界面代碼:

# coding=utf-8
# @Software : PyCharm
#Python學習群827513319



import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import time
import win32api,win32con
from 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)
        # 添加錄屏相關按鈕
        button1 = QPushButton("自定義錄屏")
        layout.addWidget(button1)
        button2 = QPushButton("全屏錄屏")
        layout.addWidget(button2)
        button3 = QPushButton("停止錄屏")
        layout.addWidget(button3)
        self.text = QPlainTextEdit('歡迎使用!')
        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('正在錄屏中,請等待……')
        elif n == 2 :
            print('已選擇全屏錄屏:')
            self.text.setPlainText('正在錄屏中,請等待……')
        else:
            print('已選擇結束錄屏:')
            self.text.setPlainText('錄屏結束!(點擊關閉按鈕,可退出程式!)')
        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_())# coding=utf-8

 

2、錄屏函數

# coding=utf-8
# @Software : PyCharm

from PIL import ImageGrab
import numpy as np
import cv2
import os
import jpype

def Recording(tag=1):
    # 錄屏開始時創建test.txt,作為結束錄屏的條件
    #Python學習群827513319
    if not os.path.exists('test.txt'):
        f = open('test.txt', 'w')
        f.close()
    # 根據tag值判斷自定義錄屏或全錄屏
    if tag == 1:
        r = SelectRegion()
        record_region = (r.x, r.y, r.w + r.x, r.h + r.y) # 自定義錄屏的範圍(左上坐標、右下坐標)
    elif tag == 2:
        record_region = None
    image = ImageGrab.grab(record_region)  # 獲取指定範圍的屏幕對象
    width, height = image.size
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    video = cv2.VideoWriter('test.avi', fourcc, 25, (width, height)) # 預設視頻為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 region

def StopRecording():
    os.remove('test.txt') #停止錄屏的觸發條件

if __name__ == "__main__":
    Recording()

 

五、總結

至此,基本實現了錄屏小工具的代碼開發。但是如果你是對代碼中的相關庫不熟悉,或者都沒下載相關的庫,那我相信你還會遇到很多坑。因此,為了方便一些小伙伴能快速把代碼跑起來,我將在下一篇文章中講講我在開發時遇到的一些坑,方便大家能避免這些問題。好了,今天就先到這裡!Bye!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 因為該WPS插件使用NPAPI機制來和瀏覽器交互,故要求使用插件的瀏覽器必須支持NPAPI機制且必須開啟NPAPI機制。以下是支持的常見的瀏覽器及其版本: FireFox瀏覽器52及小於52的版本(高於52的版本不再支持NPAPI)Chrome瀏覽器45及小於45的版本(高於45的版本不再支持NPA ...
  • 樣式:在style裡面寫的,用css來操作 屬性:在裡面裡面寫的,用attr方法操作 //設置單個屬性 //attr(name, value) $("img").attr("alt", "圖破了"); $("img").attr("title", "錯錯錯錯"); //設置多個屬性 $("img") ...
  • //編輯過敏史 if(iToolbar == 'editGMS'){ lstype="gms"; var gms=""; if(gmstype=="0"){ gms=$('#GMSInfo').html(); $('#GMSDIV').html('<textarea class="form-cont ...
  • 從準備引進微服務這套技術棧的想法開始,到一個微服務架構的新系統部署上線,這大概需要經過哪些關鍵步驟呢? ...
  • 這兩天開始讀由Edward Crawley(愛德華 克勞利)、Bruce Cameron(布魯斯 卡梅隆)、Daniel Selva(丹尼爾 塞爾瓦)著作的系統架構,一開始看目錄以為是介紹系統軟體架構的書,也是我買它的緣由,但粗略看完序和第一章第二章後不經肅然起敬,這本書是真的好,該書面向對象是適用 ...
  • 對於這場比賽,我真的是有點划水了,做了倆題,做第三題的時候實在是不知道什麼地方卡住了,然後我家來了客人,被帶出去吃飯去了,ε=(´ο`*)))唉!!! B - Just Eat It! 這道題是個經典的DP題,我對於遞推還不是特別熟悉,得找到題目的狀態轉移方程。 B[ i ] = max{ A[ i ...
  • 一、字元串相關的常用方法簡介​ package com.bjpowernode.java_learning; ​ public class D74_1_StringCommonMethod { public static void main(String[] args) { //1.轉為大寫 Sys ...
  • import wave r = r"D:\沫沫醬 - 舊傷口.wav" # 一個.wav格式文件 with wave.open(r, "rb") as f: # 讀取文件格式等 params = f.getparams() nchannels, sampwidth, framerate, nfram ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...