赛博红兔的科技博客

CyberHongTu shares news, insights, and musings on fascinating technology subjects.


用Python录制指定窗口并生成GIF动图

大家好,欢迎回到“Python五分钟”,我是赛博红兔。前两天,我在教家里人怎么用一款软件。就很简单几个步骤,但是因为家人年纪大了老是记不住。我突然想到这么一个问题:要是我能录制屏幕上某个特定窗口,然后把它保存为一个小小的GIF动图。那么他们就可以随时打开跟着操作,比文字更加直观,也不用录制一个大视频。结果你们猜怎么着?我用Python写了一段录制GIF的脚本,随时随地打开就能录,还是挺有用的。所以忍不住想给大伙分享一下,也当做一个学习Python上手的项目。

首先,我已经把代码和exe应用程序都打包好了,上传到了GitHub

网盘(链接: https://pan.baidu.com/s/1AWd9uYrquuWIAKqz6ScV0A 提取码: gifs)。只需运行Python脚本或者像我这样懒一点直接点开应用程序,直接点击你要录制窗口的左上角和右下角,然后程序就开始自动录制了。想要停下来的时候,就按一下键盘左上角的“ESC”键来结束。耐心等一段时间,GIF动图就会保存到程序脚本的文件夹里,随时可以分享给朋友、家人或者同学同事。

接下来打开脚本,我来简单介绍一下这个小程序。首先,我是用现在很火的UV管理工具来创建项目的,所以也推荐大伙用UV来复刻和启动这个项目。想要了解UV的小伙伴,我会把教学链接放在下面。这里比如说,你就可以用uv sync来同步整个项目需要的所有库,不需要你一个个用pip install来安装了。说起来,这里我们会用到三个第三方的库:

  1. pyautogui(pip install pyautogui):可以用来截屏;
  2. PIL(pip install pillow):可以用来抓取屏幕实际像素;
  3. pynput(pip install pynput):可以监听键盘按键和鼠标点击;
import os
import time
import pyautogui
from PIL import ImageGrab
from pynput import keyboard
from pynput.mouse import Listener

output_file = "output.gif"
frame_duration = 0.2

stop_recording = False
window = {'x': 0, 'y': 0, 'width': 0, 'height': 0}


def on_click(x, y, button, pressed):
    if pressed:
        click_positions.append((x, y))
    if len(click_positions) == 1:
        print('请点击窗口右下角来完成选区。')
    if len(click_positions) == 2:
        return False


def get_two_click_coordinates():
    global click_positions
    click_positions = []

    clicks = 0
    print('请点击窗口左上角以开始选区。')
    while clicks < 2:
        with Listener(on_click=on_click) as listener:
            listener.join()
        clicks += 1

    return click_positions


def calculate_rectangle_dimensions(coord1, coord2):
    global window

    width = abs(coord2[0] - coord1[0])
    height = abs(coord2[1] - coord1[1])

    window.update(x=int(coord1[0]), y=int(coord1[1]),
                  width=int(width), height=int(height))

    return width, height


def on_press(key):
    global stop_recording

    if key == keyboard.Key.esc:
        stop_recording = True


def record_gif(top_left, window, output_file, duration=0.1):
    frames = []

    print(f"录制中... 每帧间隔: {duration:.2f}s, 按 'ESC' 键结束录制。")

    with keyboard.Listener(on_press=on_press) as listener:
        while not stop_recording:
            screenshot = pyautogui.screenshot(
                region=(top_left[0], top_left[1], window['width'], window['height']))
            frames.append(screenshot)
            time.sleep(duration)

    if os.path.exists(output_file):
        print(f"警告:文件 '{output_file}' 已存在,将被覆盖。")

    print(f"正在保存 GIF 到 {output_file}")
    frames[0].save(
        output_file,
        save_all=True,
        append_images=frames[1:],
        duration=int(duration * 1000),
        loop=0,
    )
    print("GIF 已保存。")


def main():
    global window

    coordinates = get_two_click_coordinates()
    print(f"第一次点击: {coordinates[0]}, 第二次点击: {coordinates[1]}")

    width, height = calculate_rectangle_dimensions(
        coordinates[0], coordinates[1])
    print(f"选区尺寸: 宽 = {width}, 高 = {height}")

    virtual_screen_width, virtual_screen_height = pyautogui.size()
    screenshot = ImageGrab.grab()
    screen_width, screen_height = screenshot.size
    pixel_ratio = int(screen_width / virtual_screen_width)

    window.update(x=int(window['x'] * pixel_ratio), y=int(window['y'] * pixel_ratio),
                  width=int(window['width'] * pixel_ratio), height=int(window['height'] * pixel_ratio))

    print(
        f"位置: x={window['x']}, y={window['y']}, 宽={window['width']}, 高={window['height']}")

    top_left = (window['x'], window['y'])
    record_gif(top_left, window, output_file, frame_duration)


if __name__ == "__main__":
    main()

然后是几个函数的功能:

  1. on_click函数会在你点鼠标的时候被触发。它是一个回调函数,被 pynput.mouse.Listener在监听时调用。用户第一次点击,录制窗口左上角的坐标被记录,用户第二次点击后,右下角坐标被记录,然后返回False告诉Listener:“监听结束,用户选好了”。
  2. get_two_click_coordinates函数的作用是调用监听器然后返回这两个点的坐标。首先启动监听器,让on_click()接管鼠标点击事件,两次点完后,它就把两个坐标作为一对返回。
  3. 咱们刚才不是点了两个位置嘛?这个函数就是用来根据那两个点算出你想要录的矩形区域有多大。然后把这些值填进window这个字典里,表示说“我要录这个区域”;最后它把宽和高也返回了,方便在后面打印出来看下尺寸。
  4. on_press函数专门监听你按的键。如果你按的是ESC键,那么就把stop_recording设置成True。这样主程序就知道“哎,用户想停了,我不录了”;
  5. record_gif函数是整个程序的核心。它用pyautogui.screenshot不断地截图,把每一帧都存在frames这个列表里。我们在循环里加了time.sleep函数,这样就不会太快截图,是用来控制“帧率”的。一旦用户按下ESC,整个循环也就停了。然后它就把所有帧合成一个GIF动图。
  6. 在main()函数里除了刚才介绍的记录两坐标位置和计算矩形区域的函数之外,这一段有点“黑科技”的意思,是为了处理高DPI屏幕,比如4K显示器下的截图区域可能对不上的问题。操作系统为了让文字和界面元素在高分辨率屏幕上不至于太小,会启用一种叫屏幕缩放(DPI Scaling) 的功能。pyautogui.size() 拿到的是逻辑分辨率(比如说1080p),ImageGrab拿到的是实际像素(比如说4K);如果两者不一样,说明你屏幕缩放了;所以我们用这个比值pixel_ratio来修正坐标,把录制区域的坐标和尺寸“拉伸”一下,确保录屏时不会有偏移或者缩放错误。最后,用我们之前讲过的record_gif(),让它开始真正录制画面,直到按下ESC停止。

就这么简单!用这个脚本,你就可以轻松录制屏幕上任意窗口,然后保存为GIF动图,分享给你的亲朋好友。你学会了吗?



Leave a comment