回复
22
查看
1327
收藏
33

0

赠楼

0%

赠楼率

267

蒸汽

39

主题

352

帖子

1680

积分
发表于 2024-9-20 11:08:37 · 江西 | 显示全部楼层 |阅读模式
本帖最后由 267646 于 2024-9-20 13:31 编辑

起因
Steam测试版开放了录制功能,可以在玩Steam游戏时自动录制,体验了几天,发现录像会有卡顿。例如我录了几小时真女神转生5复仇,游玩时非常流畅,但是录像几秒一卡。这点让我很不舒服,但是目前英伟达、OBS或其它录制软件并不支持自动录制,因此我萌生了用python写一个自动录制游戏程序的想法。
由于自己写一个录制软件是不现实的,因此该python程序的主要内容是:1. 自动监测需要录制的游戏;2. 当需要录制的游戏启动或停止时通知录像软件开始录制或停止录制(通过模拟快捷键实现)。
当然,录制软件需要开启,因为该Python程序并不负责录制的过程,只负责监测和通知录制软件。
英伟达和Windows自带的录制软件是默认开启的,OBS、bandicam等需要手动开启。

实现思路
1. 该python程序可以检测进程,当特定目录下(例如Steam游戏目录)的exe进程启动时,开始录制。当且仅当该进程结束时,停止录制。
2. 无关的进程名会被排除(加入黑名单),例如UnityCrashHandler32.exe或者一些不想录制的游戏(例如NBA 2K25)。
3. 除黑名单外,还需要有白名单,用于允许特定的进程触发录制。
4. 以上规则支持自行配置,配置内容放在一个txt文档中。
5. 开始录制和停止录制,利用python模拟按键实现。此时录制软件需要开启。
6. 该程序需要持续运行,每隔5秒就检测一次进程。

规则配置
基于以上思路,首先需要配置好规则,即什么游戏需要录制,什么游戏不需要录制。用什么快捷键去通知录制程序。
这些配置放在RecordingList.txt中,下面是一个示例的RecordingList.txt:

  1. [Hotkey]
  2. start, alt, f9
  3. stop, alt, f10
  4. [FilePath]
  5. D:\Steam\steamapps\common, 2
  6. D:\XboxGames, 2
  7. [WhiteList]
  8. D:\Steam\steamapps\common\Inari\UnityCrashHandler32.exe
  9. [BlackList]
  10. UnityCrashHandler32.exe
复制代码
[Hotkey]下面的内容是录制程序的开始录制和结束录制的快捷键。支持单个按键或者组合键。例如英伟达的录制程序,开始录制快捷键是alt+f9,那就填start, alt, f9

[FilePath]下面的内容用于匹配目录。
D:\Steam\steamapps\common, 2会匹配D:\Steam\steamapps\common的孙文件夹下的exe文件,例如D:\Steam\steamapps\common\SMT5V\SMT5V.exe。
这是Steam游戏目录的结构决定的,根据你自己的游戏目录灵活调整。同理D:\XboxGames, 2也会匹配D:\XboxGames的孙文件夹下的exe文件。

[WhiteList]下面的内容用于匹配白名单,[BlackList]下面的内容用于匹配黑名单。支持进程名和特定文件夹下的进程名。

GameRecording.py代码放在文章末尾,可以自行改动,如果不会也可以问GPT,我就是这么问来的

实现步骤
下面是实现该功能的详细步骤:
1. 你需要安装python并将其加入系统环境变量中,从python官网下载后安装会自动帮你配置好,不用担心。
2. 该python程序用到了2个python库,psutil和ctypes,可以在控制台执行下面的命令来安装这两个库:
  1. pip install psutil
  2. pip install ctypes
复制代码
3. 选择一个文件夹,在该文件夹下新建2个文件:GameRecording.py和RecordingList.txt。GameRecording.py代码放在文章末尾。
4. 按照需求配置RecordingList.txt。如果改动了RecordingList.txt需要重新运行GameRecording.py
5. 运行GameRecording.py。你可以在控制台中运行它,例如
  1. python D:\Code\Python\GameRecording\GameRecording.py
复制代码

详细代码及细节设置
1. 你可以将这个GameRecording.py加入开机自动启动列表,并前台运行,具体实现方式是在这个python程序下新建一个批处理文件,名字可以是start.bat,内容如下
  1. @echo off
  2. start "" "python.exe" "GameRecording.py"
复制代码
(如果要后台运行将上面代码中的python.exe改成pythonw.exe就可以了。)
然后按Windows+R,输入shell:startup


点击确定,在打开的文件夹中,右键新建快捷方式,点击浏览,选择刚才的新建的start.bat,然后一直下一步。


这样这个批处理就会开机自动启动运行GameRecording.py了。
2. 录制软件你可以使用任何录制软件,例如英伟达、Windows自带录制、bandicam等。
下面我以OBS为例,分享一下我的OBS设置。
使用OBS的原因是它免费,且支持NVENC HEVC录制,用到了N卡的硬件加速比较省资源,同时HEVC的编码体积较小,20Mbps的码率就很清晰了。如果是40系N卡可以尝试用AV1编码录制。
下面是我的OBS设置截图:


另外第一次用需要在OBS中设置采集为游戏采集,这样会只采集游戏内容,即便切出游戏也不会去采集桌面内容。


3. GameRecording.py代码
  1. import os
  2. import time
  3. import psutil
  4. import ctypes

  5. # Windows API 按键代码映射,包括 Windows 键
  6. key_map = {
  7.     'alt': 0x12,
  8.     'ctrl': 0x11,
  9.     'shift': 0x10,
  10.     'win': 0x5B,  # 左侧 Windows 键
  11.     'f1': 0x70, 'f2': 0x71, 'f3': 0x72, 'f4': 0x73,
  12.     'f5': 0x74, 'f6': 0x75, 'f7': 0x76, 'f8': 0x77,
  13.     'f9': 0x78, 'f10': 0x79, 'f11': 0x7A, 'f12': 0x7B,
  14.     'a': 0x41, 'b': 0x42, 'c': 0x43, 'd': 0x44, 'e': 0x45,
  15.     'f': 0x46, 'g': 0x47, 'h': 0x48, 'i': 0x49, 'j': 0x4A,
  16.     'k': 0x4B, 'l': 0x4C, 'm': 0x4D, 'n': 0x4E, 'o': 0x4F,
  17.     'p': 0x50, 'q': 0x51, 'r': 0x52, 's': 0x53, 't': 0x54,
  18.     'u': 0x55, 'v': 0x56, 'w': 0x57, 'x': 0x58, 'y': 0x59,
  19.     'z': 0x5A, '0': 0x30, '1': 0x31, '2': 0x32, '3': 0x33,
  20.     '4': 0x34, '5': 0x35, '6': 0x36, '7': 0x37, '8': 0x38,
  21.     '9': 0x39, 'space': 0x20, 'enter': 0x0D, 'esc': 0x1B,
  22.     'tab': 0x09, 'backspace': 0x08, 'insert': 0x2D,
  23.     'delete': 0x2E, 'home': 0x24, 'end': 0x23,
  24.     'pageup': 0x21, 'pagedown': 0x22, 'left': 0x25,
  25.     'up': 0x26, 'right': 0x27, 'down': 0x28,
  26. }

  27. # 调用 Windows API 发送按键组合
  28. def press_key_combination(keys):
  29.     for key in keys:
  30.         ctypes.windll.user32.keybd_event(key, 0, 0, 0)  # 按下按键
  31.     time.sleep(0.05)
  32.     for key in keys:
  33.         ctypes.windll.user32.keybd_event(key, 0, 2, 0)  # 释放按键

  34. # 检查进程是否在运行
  35. def is_process_running(exe_name):
  36.     for proc in psutil.process_iter(['pid', 'name', 'exe']):
  37.         try:
  38.             if proc.info['name'] == exe_name or proc.info['exe'] == exe_name:
  39.                 return True
  40.         except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
  41.             pass
  42.     return False

  43. # 检查是否被列入黑名单并不在白名单中
  44. def is_blacklisted(exe_path, whitelist, blacklist):
  45.     exe_name = os.path.basename(exe_path)
  46.     if exe_path in whitelist:
  47.         return False  # 白名单优先
  48.     if exe_name in blacklist:
  49.         return True
  50.     return False

  51. # 获取文件夹及其子文件夹中的所有 .exe 文件
  52. def get_exe_files(folder_path, depth):
  53.     exe_files = []
  54.     for root, dirs, files in os.walk(folder_path):
  55.         if root[len(folder_path):].count(os.sep) < depth:
  56.             for file in files:
  57.                 if file.endswith(".exe"):
  58.                     exe_files.append(os.path.join(root, file))
  59.     return exe_files

  60. # 动态获取脚本所在的目录
  61. script_dir = os.path.dirname(os.path.abspath(__file__))

  62. # 使用相对路径,基于脚本所在的目录查找 RecordingList.txt
  63. recording_list_path = os.path.join(script_dir, 'RecordingList.txt')

  64. # 解析 RecordingList.txt 文件,读取文件夹路径、白名单、黑名单和快捷键
  65. def load_recording_list():
  66.     folder_paths = []
  67.     whitelist = []
  68.     blacklist = []
  69.     hotkeys = {
  70.         'start': [],
  71.         'stop': []
  72.     }

  73.     # 打开基于相对路径的 RecordingList.txt 文件
  74.     with open(recording_list_path, 'r') as file:
  75.         lines = file.readlines()
  76.         current_section = None

  77.         for line in lines:
  78.             line = line.strip()
  79.             if not line or line.startswith('#') or line.startswith(';'):  # 跳过注释
  80.                 continue

  81.             if line == "[FilePath]":
  82.                 current_section = "FilePath"
  83.             elif line == "[WhiteList]":
  84.                 current_section = "WhiteList"
  85.             elif line == "[BlackList]":
  86.                 current_section = "BlackList"
  87.             elif line == "[Hotkey]":
  88.                 current_section = "Hotkey"
  89.             elif current_section == "FilePath" and line:
  90.                 parts = line.split(',')
  91.                 folder = parts[0].strip()
  92.                 depth = int(parts[1].strip())
  93.                 folder_paths.append((folder, depth))
  94.             elif current_section == "WhiteList" and line:
  95.                 whitelist.append(line)
  96.             elif current_section == "BlackList" and line:
  97.                 blacklist.append(line)
  98.             elif current_section == "Hotkey" and line:
  99.                 parts = line.split(',')
  100.                 if parts[0].strip() == 'start':
  101.                     hotkeys['start'] = parse_hotkey(parts[1:])
  102.                 elif parts[0].strip() == 'stop':
  103.                     hotkeys['stop'] = parse_hotkey(parts[1:])

  104.     return folder_paths, whitelist, blacklist, hotkeys

  105. # 解析快捷键
  106. def parse_hotkey(keys):
  107.     parsed_keys = []
  108.     for key in keys:
  109.         key = key.strip().lower()
  110.         if key in key_map:
  111.             parsed_keys.append(key_map[key])
  112.     return parsed_keys

  113. # 主函数
  114. def monitor_folders(folder_paths, whitelist, blacklist, hotkeys):
  115.     running_status = {}
  116.     active_process = None  # 当前触发 F9 的进程

  117.     # 获取所有文件夹下的可执行文件
  118.     for folder_path, depth in folder_paths:
  119.         exe_files = get_exe_files(folder_path, depth)
  120.         for exe in exe_files:
  121.             if not is_blacklisted(exe, whitelist, blacklist):
  122.                 running_status[exe] = False

  123.     while True:
  124.         if active_process is None:  # 没有进程在活动状态
  125.             for exe in running_status:
  126.                 process_running = is_process_running(exe)

  127.                 if process_running and not running_status[exe]:
  128.                     print(f"{exe} 已启动,按下快捷键组合...")
  129.                     press_key_combination(hotkeys['start'])  # 按下开始的快捷键组合
  130.                     running_status[exe] = True
  131.                     active_process = exe  # 设置当前活动进程
  132.                     break

  133.         else:  # 当前有一个进程在活动状态
  134.             if not is_process_running(active_process):
  135.                 print(f"{active_process} 已停止,按下结束的快捷键组合...")
  136.                 press_key_combination(hotkeys['stop'])  # 按下停止的快捷键组合
  137.                 running_status[active_process] = False
  138.                 active_process = None  # 重置活动进程

  139.         time.sleep(5)  # 每5秒检测一次

  140. if __name__ == "__main__":
  141.     # 读取 RecordingList.txt 获取文件夹路径、白名单、黑名单和快捷键
  142.     folder_paths, whitelist, blacklist, hotkeys = load_recording_list()

  143.     if folder_paths:
  144.         monitor_folders(folder_paths, whitelist, blacklist, hotkeys)
  145.     else:
  146.         print("RecordingList.txt 中没有找到任何文件夹路径。")
复制代码
有任何问题欢迎问我或者问GPT


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

×
回复

使用道具 举报

浏览本版块需要:
1. 初阶会员或更高等级;
2. (点击此处)绑定Steam账号
您需要登录后才可以回帖 登录 | 注册

本版积分规则

欢迎发帖参与讨论 o(*≧▽≦)ツ,请注意
1. 寻求帮助或答案的帖子请发到问题互助版块,悬赏有助于问题解决的速度。发错可能失去在该板块发布主题的权限(了解更多
2. 表达观点可以,也请务必注意语气和用词,以免影响他人浏览,特别是针对其他会员的内容。如觉得违规可使用举报功能 交由管理人员处理,请勿引用对方的内容。
3. 开箱晒物交易中心游戏互鉴福利放送版块请注意额外的置顶版规。
4. 除了提问帖和交易帖以外,不确认发在哪个版块的帖子可以先发在谈天说地

  作为民间站点,自 2004 年起为广大中文 Steam 用户提供技术支持与讨论空间。历经二十余载风雨,如今已发展为国内最大的正版玩家据点。

列表模式 · · 微博 · Bilibili频道 · Steam 群组 · 贴吧 · QQ群 
Keylol 其乐 ©2004-2024 Chinese Steam User Fan Site.
Designed by Lee in Balestier, Powered by Discuz!
推荐使用 ChromeMicrosoft Edge 来浏览本站
广告投放|手机版|广州数趣信息科技有限公司 版权所有|其乐 Keylol ( 粤ICP备17068105号 )
GMT+8, 2024-11-21 20:15
快速回复 返回顶部 返回列表