游戏测试 用 python 写一个类似行车记录仪可以循环录制、自动删除的录屏软件

碧晓寒枫 · 2023年05月19日 · 最后由 维生素 回复于 2024年02月02日 · 10839 次阅读

  作为一个测试,在跑游戏的时候偶然间遇到一些 BUG,都需要提交表单并附上重现操作,但有时候,我们也不记得之前进行了什么操作,所以在跑功能的时候,开启录屏就是一个好习惯了,遇到问题,可以通过回看视频,重复之前的操作步骤来尝试定位问题。常用的录屏软件也就那几个,游戏录屏的话可以使用 windows 自带的录屏软件(win+G),其他的市面上也有不少好用的录屏软件,但是这些录屏软件基本都不支持分段循环录制,每个视频录制下来都很大,不利于保存和传播,小伙伴们都想要一个能够像行车记录仪一样的视频录制软件,循环录制,单个视频时长 1 分钟,可以定义存储上限,不会无限保存视频,翻阅了网上的资料,决定自己试着写个。
  python 实现录屏主要用到了 PIL 和 cv2 库,然后还有 numpy 和 pygetwindow 等,首先声明这些库

import time
from datetime import datetime
from PIL import ImageGrab, ImageDraw
import numpy as np
import cv2
import pygetwindow as gw
import pyautogui as pag

  接下来是实现录像功能的方法:

def recode(window_title):
    """
    录制指定窗口的视频
    :param window_title: 窗口名称
    :return: 
    """
    # 1.首先根据窗口名称获取到对应的窗口
    window = gw.getWindowsWithTitle(window_title)[0]
    # 2.激活并将对应的窗口显示到最顶层
    window.restore()
    window.activate()
    # 3.根据当前时间生成录像文件的名字
    file_name = datetime.now().strftime('%Y-%m-%d %H-%M-%S')
    # 4.获取窗口的位置和大小
    left, top, width, height = window.left, window.top, window.width, window.height
    # 5.设置VideoWriter_fourcc录制类型
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    # 6.设置文件名,大小,帧率等, VIDEO_FILE_PATH是我设置的一个全局参数,是录像的保存目录,如VIDEO_FILE_PATH = 'E:\\record_files'
    #   FPS我也参数化了,FPS=15,这个帧率可能会影响录像的播放速度,可以根据自己的情况自行调整
    video = cv2.VideoWriter(f'{VIDEO_FILE_PATH}\\{file_name}.avi', fourcc, FPS, (width, height))
    # 7.记录开始录制视频的时间
    start_time = time.time()
    # 8.当录制时间不足1分钟时,循环写入到录像文件中
    while time.time() - start_time < 60:
        # 这里我每次写入都重新获取了窗口的顶点位置和大小,这样的用处是在你拖动对应的窗口后,录像的区域会跟随你的拖动重新选定,不会傻傻的还在录原来的位置
        left, top, width, height = window.left, window.top, window.width, window.height
        # 根据窗口区域截取对应的图像
        capture_image = ImageGrab.grab((left, top, left+width, top+height))
        # 生成图像帧
        frame = cv2.cvtColor(np.array(capture_image), cv2.COLOR_RGB2BGR)
        # 将图像帧写入到文件中
        video.write(frame)
    video.release()

  然后写一个简单的主函数,循环调用进行录制

def main():
    while True:
        recode('锚点降临')

  这样就可以将录制的视频文件循环保存到指定的文件夹中了。第一步成功!

  在使用过程中,我们发现录制的视频是没有包含鼠标的,也就是说我们只能看到视频,无法确定我们的鼠标怎么移动,点了哪里,然后又查阅资料,写了一个绘制鼠标轨迹的方法,这个方法是将窗口截图传入,根据当时鼠标的位置,在传入的截图中指定位置自己绘制一个鼠标(真正的鼠标图案有点复杂,我是以圆点代替的)

def record_mouse(img, mouse_x, mouse_y):
    """
    在图片上绘制鼠标位置
    :param img: 需要绘制的图片
    :param mouse_x: 鼠标在图片上的相对x坐标
    :param mouse_y: 鼠标在图片上的相对y坐标
    :return:
    """
    # 1.首先获取图片大小
    w, h = img.size
    # 2.加载图像
    draw = ImageDraw.Draw(img)
    # 判断鼠标位置,如果当时鼠标坐标在图片范围之内,则绘制鼠标
    if 0 < mouse_x < w and 0 < mouse_y < h:
        # 绘制一个圆点,以红色填充
        draw.ellipse((mouse_x-2, mouse_y-2, mouse_x+2, mouse_y+2), fill=250)
    # 返回添加了鼠标的截图图像
    return img

  然后修改一下原来的方法,在截图的时候绘制上鼠标位置

# # 根据窗口区域截取对应的图像
# capture_image = ImageGrab.grab((left, top, left + width, top + height))

# 将上面的方法修改为:

# 获取鼠标坐标,利用到了pyautogui库
mouse_x, mouse_y = pag.position()
# 将截图的图像传入record_mouse方法,获得绘制了鼠标位置之后的截图
capture_image = record_mouse(ImageGrab.grab((left, top, left+width, top+height)), mouse_x - left, mouse_y-top)

  这样,录制的视频就可以显示鼠标轨迹了。

  最后,就只剩定时删文件,以确保不会占用太多的硬盘空间。

def del_files():
    """
    判断文件数量是否超过设定值,如果超过,则删除一定数量的文件
    :return:
    """
    # 根据目录获取文件列表
    files = os.listdir(VIDEO_FILE_PATH)
    # 判断文件数量,如果超过了设定的最大值MAX_FILES_COUNT(自行定义),则删除最前面的几个文件
    if len(files) > MAX_FILES_COUNT:
        for i in files[:len(files)-MAX_FILES_COUNT]:
            os.remove(f'{VIDEO_FILE_PATH}\\{i}')

  然后再修改一下 main 函数:

def main():
    while True:
        del_files()
        recode('锚点降临')

  这样,一个模拟行车记录仪的视频录制软件就完成了,试了一下效果好像还可以?

  为了将其打包成一个工具,方便给组里的其他小伙伴使用,我用 PYQT5 给他添加了一个图形界面:

  点击开始录制按钮,倒计时 321 开始循环录制,按钮变成停止录制,可以点击停止录制按钮自行停止录制,当找不到对应名称的窗口时,也会 tips 提醒,以上就是这个录屏软件的全部功能,有兴趣的小伙伴欢迎探讨~

共收到 21 条回复 时间 点赞

系统资源的占用率如何

cpu 十几个点,内存 60~80m

仅楼主可见
回复

可以输入当前标签页的 title,如果是谷歌浏览器也可以输入 Google,你把鼠标放到任务栏的谷歌浏览器窗口图标上,悬停显示的内容都可以作为窗口关键字

能实现英伟达的 shadowplay 功能就厉害了,switch 能录制前 30 秒视频就是用的这个技术。游戏应该很适合这个,能减少不少资源消耗

好家伙,这样的注释对新手玩家真是极度的友好呀。😂

感谢楼主分享,小改了一下,但是感觉录屏的时长像是设置帧数 * 时长,类似于慢放,FPS 需要设置吗?

import os, time
from datetime import datetime
from PIL import ImageGrab, ImageDraw
import numpy as numpy
import cv2
import pygetwindow
import pyautogui

FPS = 5
MAX_FILES_COUNT = 10
PER_FILE_SECONDS = 5
VIDEO_FILE_PATH = r'D:\循环录屏'

def record_mouse(img, mouse_x, mouse_y):
    """
    在图片上绘制鼠标位置
    :param img: 需要绘制的图片
    :param mouse_x: 鼠标在图片上的相对x坐标
    :param mouse_y: 鼠标在图片上的相对y坐标
    :return:
    """
    # 1.首先获取图片大小
    w, h = img.size
    # 2.加载图像
    draw = ImageDraw.Draw(img)
    # 判断鼠标位置,如果当时鼠标坐标在图片范围之内,则绘制鼠标
    if 0 < mouse_x < w and 0 < mouse_y < h:
        # 绘制一个圆点,以红色填充
        draw.ellipse((mouse_x-2, mouse_y-2, mouse_x+2, mouse_y+2), fill=250)
    # 返回添加了鼠标的截图图像
    return img

def get_region_by_title(window_title):
    """
    window_title:特定窗口名称
    能找到窗口返回窗口对应区域边界坐标
    找不到窗口返回整个桌面边界坐标
    """
    if window_title:
        # 1.首先根据窗口名称获取到对应的窗口
        window_titles =  pygetwindow.getWindowsWithTitle(window_title)
        if window_titles:
            window = pygetwindow.getWindowsWithTitle(window_title)[0]
            # 2.激活并将对应的窗口显示到最顶层
            window.restore()
            window.activate()
            # left, top, width, height = window.left, window.top, window.width, window.height # 窗口区域坐标
            return window.left, window.top, window.width, window.height

    # left, top, width, height = 1,1,*pyautogui.size() #桌面区域坐标
    return 1,1,*pyautogui.size()


def recode(window_title = ''):
    """
    录制指定窗口的视频
    :param window_title: 窗口名称
    :return: 
    """
    left, top, width, height = get_region_by_title('')
    # 3.根据当前时间生成录像文件的名字
    file_name = datetime.now().strftime('%Y-%m-%d %H-%M-%S')
    print(datetime.now().strftime('%Y-%m-%d %H-%M-%S'))

    # 5.设置VideoWriter_fourcc录制类型
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    # 6.设置文件名,大小,帧率等, VIDEO_FILE_PATH是我设置的一个全局参数,是录像的保存目录,如VIDEO_FILE_PATH = 'E:\\record_files'
    #   FPS我也参数化了,FPS=15,这个帧率可能会影响录像的播放速度,可以根据自己的情况自行调整
    video = cv2.VideoWriter(f'{VIDEO_FILE_PATH}\\{file_name}.avi', fourcc, FPS, (width, height))
    # 7.记录开始录制视频的时间
    start_time = time.time()
    # 8.当录制时间不足1分钟时,循环写入到录像文件中
    while time.time() - start_time < PER_FILE_SECONDS:
        # 这里我每次写入都重新获取了窗口的顶点位置和大小,这样的用处是在你拖动对应的窗口后,录像的区域会跟随你的拖动重新选定,不会傻傻的还在录原来的位置
        left, top, width, height = get_region_by_title(window_title)
        # # 根据窗口区域截取对应的图像
        # capture_image = ImageGrab.grab((left, top, left + width, top + height))

        # 获取鼠标坐标,利用到了pyautogui库
        mouse_x, mouse_y = pyautogui.position()
        # 将截图的图像传入record_mouse方法,获得绘制了鼠标位置之后的截图
        capture_image = record_mouse(ImageGrab.grab((left, top, left+width, top+height)), mouse_x - left, mouse_y-top)

        # 生成图像帧
        frame = cv2.cvtColor(numpy.array(capture_image), cv2.COLOR_RGB2BGR)
        # 将图像帧写入到文件中
        video.write(frame)
    video.release()



def del_files():
    """
    判断文件数量是否超过设定值,如果超过,则删除一定数量的文件
    :return:
    """
    # 根据目录获取文件列表
    files = os.listdir(VIDEO_FILE_PATH)
    # 判断文件数量,如果超过了设定的最大值MAX_FILES_COUNT(自行定义),则删除最前面的几个文件
    if len(files) > MAX_FILES_COUNT:
        for i in files[:len(files)-MAX_FILES_COUNT]:
            os.remove(f'{VIDEO_FILE_PATH}\\{i}')


def main():
    while True:
        del_files()
        recode('锚点降临')

main()   
yeyu 回复

FPS 会影响播放速率,具体影响关系我还没有去查资料,但是经过我的测试,FPS=15 播放速度会比较正常

5t5 回复

哈哈,个人的一个习惯,在写东西的时候会标注一下代码的作用😂

有时候会出现时长为 0,请问是什么原因?

会出现卡死的情况

无人机 回复

是不是关闭了录制的窗口啥的?可以写个 try,加一些容错,在找不到窗口的时候自动停止

优秀

14楼 已删除
15楼 已删除

我感觉直接拿 obs 会不会好一点,拿 obs-websocket 直接操作

老哥,在你的启发下,我做了一个类似的,但是我们日常用两个屏幕,我发现无论怎么设置都只能截取主屏幕上的区域,截取扩展屏上的就是全黑的图片,有没有踩坑经验

我也是两个屏幕啊,但是我好像没有遇到你说的问题,你改了哪里吗?

sheeprazr 回复

这个链接点不开哎

这是全部的代码嘛

wlzr 回复

是啊

app 游戏能用吗。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册