使用pynput模块的keyboard和mouse监听键盘和鼠标,实现热键机制和鼠标选取
使用pywin32模块的win32api、win32gui和win32con捕捉当前窗口句柄,实现窗口的隐藏和显示
使用pillow模块的ImageGrab实现屏幕截图
使用imageio模块生成GIF或MP4文件
使用Python标准模块optparse构造linux风格的使用界面,遵循GNU/POSIX语法规则设置参数选项
使用批处理命令编写批处理文件,最终生成桌面快捷方式
1. 监听键盘和鼠标
pip install pynput
友情提示:不要在运行这段代码的命令行窗口中测试鼠标左键,因为点击左键会影响程序执行,导致反应迟滞。
from pynput import keyboard, mouse
def on_click(x, y, button, pressed):
"""鼠标按键"""
action = '按下' if pressed else '弹起'
if button == mouse.Button.left:
print('左键%s,(%d,%d)'%(action, x, y))
elif button == mouse.Button.right:
print('右键%s,(%d,%d)'%(action, x, y))
def on_press(key):
"""键按下"""
if key == keyboard.Key.ctrl_l or key == keyboard.Key.ctrl_r:
print('Ctr键按下')
def on_release(key):
"""键弹起"""
if key == keyboard.Key.ctrl_l or key == keyboard.Key.ctrl_r:
print('Ctr键弹起')
elif key == keyboard.Key.esc:
print('再见')
return False
monitor_m = mouse.Listener(on_click=on_click)
monitor_m.start()
monitor_k = keyboard.Listener(on_press=on_press, on_release=on_release)
monitor_k.start()
monitor_k.join()
2. 隐藏或显示控制台窗口
pip install pypiwin32
import win32gui, win32api, win32con
hwnd = win32gui.GetForegroundWindow() # 获取最前窗口句柄
win32gui.ShowWindow(hwnd, win32con.SW_HIDE) # 隐藏hwnd指定的窗口
win32gui.ShowWindow(hwnd, win32con.SW_SHOW) # 显示hwnd指定的窗口
3. 屏幕截图
from PIL import ImageGrab
im = ImageGrab.grab((1200,600,1920,1080)) # 截取大小为720×480的屏幕区域
im.show()
im = ImageGrab.grab() # 截取整个屏幕
im.show()
pip install pillow
4. 生成动画或视频文件
pip install imageio
pip install imageio-ffmpeg
# imageio.mimsave(out_file, pil_list, format='GIF', fps=fps, loop=loop)
# out_file - 输出文件名
# pil_list - 列表,元素类型为PIL对象
# format - 输出格式
# fps - 帧率(每秒播放的帧数)
# loop - 循环次数,0表示无限循环(视频格式不支持该参数)
# writer = imageio.get_writer(out_file, fps=fps) # gif格式可增加loop参数
# writer.append_data(im_pil) # im_pil为PIL对象
# writer.close()
5. 定时器
6. 完整代码
# -*- coding:utf-8 -*-
import os, time
import optparse
import threading
import imageio
import queue
import numpy as np
from PIL import Image, ImageGrab
import win32gui, win32api, win32con
from pynput import keyboard, mouse
class PyTimer:
"""定时器类"""
def __init__(self, func, *args, **kwargs):
"""构造函数"""
self.func = func
self.args = args
self.kwargs = kwargs
self.running = False
def _run_func(self):
"""运行定时事件函数"""
th = threading.Thread(target=self.func, args=self.args, kwargs=self.kwargs)
th.setDaemon(True)
th.start()
def _start(self, interval, once):
"""启动定时器的线程函数"""
if interval < 0.010:
interval = 0.010
if interval < 0.050:
dt = interval/10
else:
dt = 0.005
if once:
deadline = time.time() + interval
while time.time() < deadline:
time.sleep(dt)
# 定时时间到,调用定时事件函数
self._run_func()
else:
self.running = True
deadline = time.time() + interval
while self.running:
while time.time() < deadline:
time.sleep(dt)
deadline += interval # 更新下一次定时时间
if self.running: # 定时时间到,调用定时事件函数
self._run_func()
def start(self, interval, once=False):
"""启动定时器
interval - 定时间隔,浮点型,以秒为单位,最高精度10毫秒
once - 是否仅启动一次,默认是连续的
"""
th = threading.Thread(target=self._start, args=(interval, once))
th.setDaemon(True)
th.start()
def stop(self):
"""停止定时器"""
self.running = False
class ScreenRecorder:
"""屏幕记录器"""
def __init__(self, out, fps=10, nfs=1000, loop=0):
"""构造函数"""
self.format = ('.gif', '.mp4', '.avi', '.wmv')
ext = os.path.splitext(out)[1].lower()
if not ext in self.format:
raise ValueError('不支持的文件格式:%s'%ext)
self.out = out
self.ext = ext
self.fps = fps
self.nfs = nfs
self.loop = loop
self.cw = win32api.GetSystemMetrics(win32con.SM_CXSCREEN)
self.ch = win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
self.set_box((0, 0, self.cw, self.ch))
self.ctr_is_pressed = False
self.hidding = False
self.recording = False
self.pos_click = (0,0)
self.q = None
self.hwnd = self._find_self()
self.info = None
self.help()
self.status()
def _find_self(self):
"""找到当前Python解释器的窗口句柄"""
return win32gui.GetForegroundWindow() # 获取最前窗口句柄
def set_box(self, box):
"""设置记录区域"""
x0, y0, x1, y1 = box
dx, dy = (x1-x0)%16, (y1-y0)%16
dx0, dx1 = dx//2, dx-dx//2
dy0, dy1 = dy//2, dy-dy//2
self.box = (x0+dx0, y0+dy0, x1-dx1, y1-dy1)
def help(self):
"""热键提示"""
print('---------------------------------------------')
print('Ctr + 回车键:隐藏/显示窗口')
print('Ctr + 鼠标左键或右键拖拽:设置记录区域')
print('Ctr + PageUp/PageDown:更改记录格式')
print('Ctr + Up/Down:调整帧率')
print('Ctr + 空格键:开始/停止记录')
print('Esc:退出')
print()
def status(self):
"""当前状态"""
if self.info:
print('\r%s'%(' '*len(self.info.encode('gbk')),), end='', flush=True)
recording_text = '正在记录' if self.recording else '准备就绪'
if self.ext == 'gif':
loop_str = '循环%d次'%self.loop if self.loop > 0 else '循环'
else:
loop_str = '不循环'
self.info = '\r输出文件:%s | 帧率:%d | 区域:%s'%(self.out, self.fps, str(self.box))
print(self.info, end='', flush=True)
def start(self):
"""开始记录"""
self.q = queue.Queue(100)
self.timer = PyTimer(self.capture)
self.timer.start(1/self.fps)
th = threading.Thread(target=self.produce)
th.setDaemon(True)
th.start()
def stop(self):
"""停止记录"""
self.timer.stop()
def capture(self):
"""截屏"""
if not self.q.full():
im = ImageGrab.grab(self.box)
self.q.put(im)
def produce(self):
"""生成动画或视频文件"""
if self.ext == '.gif':
writer = imageio.get_writer(self.out, fps=self.fps, loop=self.loop)
else:
writer = imageio.get_writer(self.out, fps=self.fps)
n = 0
while self.recording and n < self.nfs:
if self.q.empty():
time.sleep(0.01)
else:
im = np.array(self.q.get())
writer.append_data(im)
n += 1
writer.close()
def on_press(self, key):
"""键按下"""
if key == keyboard.Key.ctrl_l or key == keyboard.Key.ctrl_r:
self.ctr_is_pressed = True
def on_release(self, key):
"""键释放"""
if key == keyboard.Key.ctrl_l or key == keyboard.Key.ctrl_r:
self.ctr_is_pressed = False
elif key == keyboard.Key.space and self.ctr_is_pressed:
if self.recording: # 停止记录
self.stop()
self.recording = False
if self.hidding:
win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW) # 显示窗口
self.hidding = False
else: # 开始记录
self.start()
self.recording = True
if not self.hidding:
win32gui.ShowWindow(self.hwnd, win32con.SW_HIDE) # 隐藏窗口
self.hidding = True
elif key == keyboard.Key.enter and self.ctr_is_pressed:
if self.hidding: # 显示窗口
win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW) # 显示窗口
self.hidding = False
self.status()
else: # 隐藏窗口
win32gui.ShowWindow(self.hwnd, win32con.SW_HIDE) # 隐藏窗口
self.hidding = True
elif (key == keyboard.Key.page_down or key == keyboard.Key.page_up) and self.ctr_is_pressed:
i = self.format.index(self.ext)
if key == keyboard.Key.page_down:
self.ext = self.format[(i+1)%len(self.format)]
else:
self.ext = self.format[(i-1)%len(self.format)]
folder = os.path.split(self.out)[0]
dt_str = time.strftime('%Y%m%d%H%M%S')
self.out = os.path.join(folder, '%s%s'%(dt_str, self.ext))
self.status()
elif key == keyboard.Key.left and self.ctr_is_pressed:
if self.fps > 1:
self.fps -= 1
self.status()
elif key == keyboard.Key.right and self.ctr_is_pressed:
if self.fps < 40:
self.fps += 1
self.status()
elif key == keyboard.Key.esc:
print('\n程序已结束')
return False
def on_click(self, x, y, button, pressed):
"""鼠标按键"""
if (button == mouse.Button.left or button == mouse.Button.right) and self.ctr_is_pressed:
if pressed:
self.pos_click = (x, y)
elif self.pos_click != (x, y):
x0, y0 = self.pos_click
self.set_box((min(x0,x), min(y0,y), max(x0,x), max(y0,y)))
self.status()
def parse_args():
"""获取参数"""
parser = optparse.OptionParser()
parser.add_option('-o', '--out', action='store', type='string', dest='out', default='', help='输出文件名')
parser.add_option('-f', '--fps', action='store', type='int', dest='fps', default='10', help='帧率')
parser.add_option('-n', '--nfs', action='store', type='int', dest='nfs', default='1000', help='最大帧数')
parser.add_option('-l', '--loop', action='store', type='int', dest='loop', default=0, help='循环')
return parser.parse_args()
if __name__ == '__main__':
options, args = parse_args()
if options.out:
out = options.out
folder = os.path.split(out)[0]
if folder and not os.path.isdir(folder):
raise ValueError('路径不存在:%s'%folder)
else:
dt_str = time.strftime('%Y%m%d%H%M%S')
out = os.path.join(os.getcwd(), '%s.mp4'%(dt_str,))
sr = ScreenRecorder(out, fps=options.fps, nfs=options.nfs, loop=options.loop)
monitor_m = mouse.Listener(on_click=sr.on_click)
monitor_m.start()
monitor_k = keyboard.Listener(on_press=sr.on_press, on_release=sr.on_release)
monitor_k.start()
monitor_k.join()
7. linux风格的使用界面
假如要录屏到文件d:\demo.mp4,帧率为25,下面的两种写法是等价的。
python .\ScreenRecorder.py -o d:\demp.mp4 -f 25
python .\ScreenRecorder.py --out=d:\demp.mp4 --fps=25
细心的同学很快就会发现,界面上的参数信息会实时更新,但屏幕却没有滚动。这是怎么实现的呢?有兴趣的同学可以去读一下代码,或者在我的博客首页搜索“必杀技”,就会找到答案。
8. 生成桌面快捷方式
@echo off
cd /d d:\XufiveGithub\ScreenRecorder
python ScreenRecorder.py
E N D
各位伙伴们好,詹帅本帅搭建了一个个人博客和小程序,汇集各种干货和资源,也方便大家阅读,感兴趣的小伙伴请移步小程序体验一下哦!(欢迎提建议)
推荐阅读
推荐阅读