屏幕录制功能
This commit is contained in:
parent
f754072e08
commit
0e5923def3
297
backend/screen_recorder.py
Normal file
297
backend/screen_recorder.py
Normal file
@ -0,0 +1,297 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
屏幕录制工具
|
||||
支持录制当前屏幕并保存为视频文件
|
||||
"""
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import pyautogui
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
class ScreenRecorder:
|
||||
def __init__(self, output_dir="recordings", fps=20, quality=80, region=None):
|
||||
"""
|
||||
初始化屏幕录制器
|
||||
|
||||
Args:
|
||||
output_dir (str): 输出目录
|
||||
fps (int): 帧率
|
||||
quality (int): 视频质量 (1-100)
|
||||
region (tuple): 录制区域 (x, y, width, height),None表示全屏录制
|
||||
"""
|
||||
self.output_dir = output_dir
|
||||
self.fps = fps
|
||||
self.quality = quality
|
||||
self.recording = False
|
||||
self.paused = False
|
||||
self.video_writer = None
|
||||
self.thread = None
|
||||
self.region = region
|
||||
|
||||
# 创建输出目录
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
# 获取屏幕尺寸
|
||||
self.screen_size = pyautogui.size()
|
||||
print(f"屏幕尺寸: {self.screen_size}")
|
||||
|
||||
# 设置录制区域
|
||||
if self.region:
|
||||
x, y, width, height = self.region
|
||||
# 确保区域在屏幕范围内
|
||||
x = max(0, min(x, self.screen_size[0] - 1))
|
||||
y = max(0, min(y, self.screen_size[1] - 1))
|
||||
width = min(width, self.screen_size[0] - x)
|
||||
height = min(height, self.screen_size[1] - y)
|
||||
self.region = (x, y, width, height)
|
||||
self.record_size = (width, height)
|
||||
print(f"录制区域: {self.region}")
|
||||
else:
|
||||
self.record_size = self.screen_size
|
||||
print("录制模式: 全屏录制")
|
||||
|
||||
def start_recording(self, filename=None):
|
||||
"""
|
||||
开始录制
|
||||
|
||||
Args:
|
||||
filename (str): 输出文件名,如果为None则自动生成
|
||||
"""
|
||||
if self.recording:
|
||||
print("录制已在进行中")
|
||||
return
|
||||
|
||||
if filename is None:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"screen_record_{timestamp}.mp4"
|
||||
|
||||
self.output_path = os.path.join(self.output_dir, filename)
|
||||
|
||||
# 设置视频编码器
|
||||
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
||||
self.video_writer = cv2.VideoWriter(
|
||||
self.output_path,
|
||||
fourcc,
|
||||
self.fps,
|
||||
self.record_size
|
||||
)
|
||||
|
||||
self.recording = True
|
||||
self.paused = False
|
||||
|
||||
# 在新线程中开始录制
|
||||
self.thread = threading.Thread(target=self._record_loop)
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
print(f"开始录制: {self.output_path}")
|
||||
|
||||
def _record_loop(self):
|
||||
"""
|
||||
录制循环
|
||||
"""
|
||||
while self.recording:
|
||||
if not self.paused:
|
||||
# 截取屏幕
|
||||
if self.region:
|
||||
# 区域录制
|
||||
x, y, width, height = self.region
|
||||
screenshot = pyautogui.screenshot(region=(x, y, width, height))
|
||||
else:
|
||||
# 全屏录制
|
||||
screenshot = pyautogui.screenshot()
|
||||
|
||||
# 转换为numpy数组
|
||||
frame = np.array(screenshot)
|
||||
|
||||
# 转换颜色格式 (RGB -> BGR)
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
|
||||
|
||||
# 写入视频文件
|
||||
self.video_writer.write(frame)
|
||||
|
||||
# 控制帧率
|
||||
time.sleep(1.0 / self.fps)
|
||||
|
||||
def pause_recording(self):
|
||||
"""
|
||||
暂停录制
|
||||
"""
|
||||
if not self.recording:
|
||||
print("当前没有在录制")
|
||||
return
|
||||
|
||||
self.paused = not self.paused
|
||||
status = "暂停" if self.paused else "继续"
|
||||
print(f"录制{status}")
|
||||
|
||||
def stop_recording(self):
|
||||
"""
|
||||
停止录制
|
||||
"""
|
||||
if not self.recording:
|
||||
print("当前没有在录制")
|
||||
return
|
||||
|
||||
self.recording = False
|
||||
self.paused = False
|
||||
|
||||
# 等待录制线程结束
|
||||
if self.thread:
|
||||
self.thread.join()
|
||||
|
||||
# 释放视频写入器
|
||||
if self.video_writer:
|
||||
self.video_writer.release()
|
||||
self.video_writer = None
|
||||
|
||||
print(f"录制完成: {self.output_path}")
|
||||
|
||||
def set_region(self, region):
|
||||
"""
|
||||
设置录制区域
|
||||
|
||||
Args:
|
||||
region (tuple): 录制区域 (x, y, width, height),None表示全屏录制
|
||||
"""
|
||||
if self.recording:
|
||||
print("录制进行中,无法更改区域设置")
|
||||
return False
|
||||
|
||||
self.region = region
|
||||
|
||||
# 重新计算录制区域
|
||||
if self.region:
|
||||
x, y, width, height = self.region
|
||||
# 确保区域在屏幕范围内
|
||||
x = max(0, min(x, self.screen_size[0] - 1))
|
||||
y = max(0, min(y, self.screen_size[1] - 1))
|
||||
width = min(width, self.screen_size[0] - x)
|
||||
height = min(height, self.screen_size[1] - y)
|
||||
self.region = (x, y, width, height)
|
||||
self.record_size = (width, height)
|
||||
print(f"录制区域已设置: {self.region}")
|
||||
else:
|
||||
self.record_size = self.screen_size
|
||||
print("录制模式已设置: 全屏录制")
|
||||
|
||||
return True
|
||||
|
||||
def get_status(self):
|
||||
"""
|
||||
获取录制状态
|
||||
|
||||
Returns:
|
||||
dict: 包含录制状态信息的字典
|
||||
"""
|
||||
return {
|
||||
'recording': self.recording,
|
||||
'paused': self.paused,
|
||||
'output_path': getattr(self, 'output_path', None),
|
||||
'screen_size': self.screen_size,
|
||||
'record_size': self.record_size,
|
||||
'region': self.region,
|
||||
'fps': self.fps
|
||||
}
|
||||
|
||||
def main():
|
||||
"""
|
||||
主函数 - 命令行界面
|
||||
"""
|
||||
recorder = ScreenRecorder()
|
||||
|
||||
print("\n=== 屏幕录制工具 ===")
|
||||
print("命令:")
|
||||
print(" start [filename] - 开始录制")
|
||||
print(" pause - 暂停/恢复录制")
|
||||
print(" stop - 停止录制")
|
||||
print(" status - 查看状态")
|
||||
print(" region x y w h - 设置录制区域 (x, y, 宽度, 高度)")
|
||||
print(" fullscreen - 设置全屏录制")
|
||||
print(" center w h - 设置居中区域录制 (宽度, 高度)")
|
||||
print(" quit - 退出程序")
|
||||
print("\n输入命令:")
|
||||
|
||||
while True:
|
||||
try:
|
||||
command = input("> ").strip().split()
|
||||
|
||||
if not command:
|
||||
continue
|
||||
|
||||
cmd = command[0].lower()
|
||||
|
||||
if cmd == "start":
|
||||
filename = command[1] if len(command) > 1 else None
|
||||
recorder.start_recording(filename)
|
||||
|
||||
elif cmd == "pause":
|
||||
recorder.pause_recording()
|
||||
|
||||
elif cmd == "stop":
|
||||
recorder.stop_recording()
|
||||
|
||||
elif cmd == "status":
|
||||
status = recorder.get_status()
|
||||
print(f"录制状态: {'进行中' if status['recording'] else '已停止'}")
|
||||
print(f"暂停状态: {'是' if status['paused'] else '否'}")
|
||||
print(f"输出文件: {status['output_path']}")
|
||||
print(f"屏幕尺寸: {status['screen_size']}")
|
||||
print(f"录制尺寸: {status['record_size']}")
|
||||
print(f"录制区域: {status['region'] if status['region'] else '全屏'}")
|
||||
print(f"帧率: {status['fps']} FPS")
|
||||
|
||||
elif cmd == "region":
|
||||
if len(command) != 5:
|
||||
print("用法: region x y width height")
|
||||
continue
|
||||
try:
|
||||
x, y, w, h = map(int, command[1:5])
|
||||
if recorder.set_region((x, y, w, h)):
|
||||
print(f"录制区域已设置: ({x}, {y}, {w}, {h})")
|
||||
except ValueError:
|
||||
print("请输入有效的数字")
|
||||
|
||||
elif cmd == "fullscreen":
|
||||
if recorder.set_region(None):
|
||||
print("已设置为全屏录制")
|
||||
|
||||
elif cmd == "center":
|
||||
if len(command) != 3:
|
||||
print("用法: center width height")
|
||||
continue
|
||||
try:
|
||||
w, h = map(int, command[1:3])
|
||||
screen_w, screen_h = recorder.screen_size
|
||||
x = (screen_w - w) // 2
|
||||
y = (screen_h - h) // 2
|
||||
if recorder.set_region((x, y, w, h)):
|
||||
print(f"居中录制区域已设置: ({x}, {y}, {w}, {h})")
|
||||
except ValueError:
|
||||
print("请输入有效的数字")
|
||||
|
||||
elif cmd == "quit":
|
||||
if recorder.recording:
|
||||
recorder.stop_recording()
|
||||
print("程序退出")
|
||||
break
|
||||
|
||||
else:
|
||||
print("未知命令")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
if recorder.recording:
|
||||
recorder.stop_recording()
|
||||
print("\n程序退出")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"错误: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -13,7 +13,7 @@ api.interceptors.request.use(
|
||||
if (window.electronAPI) {
|
||||
config.baseURL = window.electronAPI.getBackendUrl()
|
||||
} else {
|
||||
config.baseURL = 'http://192.168.1.58:5000'
|
||||
config.baseURL = 'http://localhost:5000'
|
||||
}
|
||||
|
||||
// 只为需要发送数据的请求设置Content-Type
|
||||
@ -607,7 +607,7 @@ export const getBackendUrl = () => {
|
||||
if (window.electronAPI) {
|
||||
return window.electronAPI.getBackendUrl()
|
||||
} else {
|
||||
return 'http://192.168.1.58:5000'
|
||||
return 'http://localhost:5000'
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user