博主 小野博客
小野博客 小野(Hirono)博客的个人网站,主要讲述关于小野的一些陈芝麻烂谷子事。网络、生活、我的主张,也是一个自留地
菜单

Python之造轮,Gif截图录制工具

[deepseek]小野博客分享了一款基于Python开发的Gif截图录制工具,实现了屏幕动态内容捕获与转换功能。
📟前端日记 共 13389 字 计 101 次阅读 需要 2 分钟
  • 区域选择录制:支持全屏或自定义区域录制
  • 实时预览:选择区域后可立即预览
  • 参数自定义:可调节FPS、录制时长
  • 友好界面:直观的GUI操作界面

安装依赖库

pip install pillow pyautogui pyinstaller

核心源码

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pyautogui
from PIL import Image, ImageTk, ImageGrab
import threading
import time
import os

class GIFRecorderWithRegion:
    def __init__(self, root):
        self.root = root
        self.root.title("GIF录制工具 - 带区域选择")
        self.root.geometry("500x400")
        
        self.recording = False
        self.selecting_region = False
        self.frames = []
        self.output_path = "favicon.ico"
        self.region = None  # (x1, y1, x2, y2)
        
        self.setup_ui()
        
    def setup_ui(self):
        """设置用户界面"""
        # 输出路径选择
        path_frame = ttk.Frame(self.root)
        path_frame.pack(pady=10, padx=20, fill="x")
        
        ttk.Label(path_frame, text="输出路径:").pack(side="left")
        self.path_var = tk.StringVar(value=self.output_path)
        ttk.Entry(path_frame, textvariable=self.path_var, width=30).pack(side="left", padx=5)
        ttk.Button(path_frame, text="浏览", command=self.browse_path).pack(side="left")
        
        # 区域选择
        region_frame = ttk.LabelFrame(self.root, text="录制区域/////LB5.NET-小野博客//////", padding=10)
        region_frame.pack(pady=10, padx=20, fill="x")
        
        self.region_var = tk.StringVar(value="全屏")
        ttk.Radiobutton(region_frame, text="全屏", variable=self.region_var, 
                       value="全屏", command=self.update_region_display).pack(anchor="w")
        ttk.Radiobutton(region_frame, text="自定义区域", variable=self.region_var,
                       value="自定义区域", command=self.update_region_display).pack(anchor="w")
        
        # 区域坐标显示和选择
        coord_frame = ttk.Frame(region_frame)
        coord_frame.pack(fill="x", pady=5)
        
        ttk.Label(coord_frame, text="区域坐标:").pack(side="left")
        self.coord_var = tk.StringVar(value="未选择")
        ttk.Label(coord_frame, textvariable=self.coord_var, foreground="green").pack(side="left", padx=5)
        ttk.Button(coord_frame, text="选择区域", command=self.start_region_selection).pack(side="left", padx=10)
        
        # 参数设置
        param_frame = ttk.LabelFrame(self.root, text="录制参数", padding=10)
        param_frame.pack(pady=10, padx=20, fill="x")
        
        ttk.Label(param_frame, text="FPS:").grid(row=0, column=0, sticky="w")
        self.fps_var = tk.StringVar(value="10")
        ttk.Spinbox(param_frame, from_=1, to=30, textvariable=self.fps_var, width=10).grid(row=0, column=1, padx=5)
        
        ttk.Label(param_frame, text="时长(秒):").grid(row=1, column=0, sticky="w", pady=5)
        self.duration_var = tk.StringVar(value="10")
        ttk.Spinbox(param_frame, from_=1, to=60, textvariable=self.duration_var, width=10).grid(row=1, column=1, padx=5)
        
        # 控制按钮
        btn_frame = ttk.Frame(self.root)
        btn_frame.pack(pady=20)
        
        self.record_btn = ttk.Button(btn_frame, text="开始录制", command=self.toggle_recording, width=15)
        self.record_btn.pack(pady=5)
        
        # 状态显示
        self.status_var = tk.StringVar(value="准备就绪")
        status_label = ttk.Label(self.root, textvariable=self.status_var, foreground="blue")
        status_label.pack(pady=10)
        
        # 预览区域
        preview_frame = ttk.LabelFrame(self.root, text="区域预览", padding=5)
        preview_frame.pack(pady=10, padx=20, fill="both", expand=True)
        
        self.preview_label = ttk.Label(preview_frame, text="区域预览将显示在这里")
        self.preview_label.pack(expand=True)
        
    def browse_path(self):
        """选择输出路径"""
        path = filedialog.asksaveasfilename(
            defaultextension=".gif",
            filetypes=[("GIF文件", "*.gif"), ("所有文件", "*.*")]
        )
        if path:
            self.path_var.set(path)
            
    def update_region_display(self):
        """更新区域显示"""
        if self.region_var.get() == "全屏":
            self.coord_var.set("全屏")
            self.update_preview()
        elif self.region and self.region_var.get() == "自定义区域":
            x1, y1, x2, y2 = self.region
            self.coord_var.set(f"({x1}, {y1}) - ({x2}, {y2})")
            self.update_preview()
            
    def start_region_selection(self):
        """开始区域选择"""
        self.status_var.set("请选择录制区域...")
        self.root.withdraw()  # 隐藏主窗口
        
        # 创建区域选择窗口
        self.region_selector = RegionSelector(self.root, self.region_selected)
        
    def region_selected(self, region):
        """区域选择回调"""
        self.region = region
        self.region_var.set("自定义区域")
        self.root.deiconify()  # 重新显示主窗口
        self.update_region_display()
        self.status_var.set("区域选择完成")
        
    def update_preview(self):
        """更新区域预览"""
        try:
            if self.region_var.get() == "全屏":
                # 全屏预览 - 缩小显示
                screenshot = pyautogui.screenshot()
                preview_size = (300, 200)
                screenshot.thumbnail(preview_size, Image.Resampling.LANCZOS)
                photo = ImageTk.PhotoImage(screenshot)
                self.preview_label.config(image=photo)
                self.preview_label.image = photo
            elif self.region and self.region_var.get() == "自定义区域":
                # 自定义区域预览
                x1, y1, x2, y2 = self.region
                region_screenshot = pyautogui.screenshot(region=(x1, y1, x2-x1, y2-y1))
                preview_size = (300, 200)
                region_screenshot.thumbnail(preview_size, Image.Resampling.LANCZOS)
                photo = ImageTk.PhotoImage(region_screenshot)
                self.preview_label.config(image=photo)
                self.preview_label.image = photo
        except Exception as e:
            self.preview_label.config(text=f"预览更新失败: {str(e)}")
            
    def toggle_recording(self):
        """开始/停止录制"""
        if not self.recording:
            self.start_recording()
        else:
            self.stop_recording()
            
    def start_recording(self):
        """开始录制"""
        self.output_path = self.path_var.get()
        fps = int(self.fps_var.get())
        duration = int(self.duration_var.get())
        
        if not self.output_path:
            messagebox.showerror("错误", "请选择输出路径")
            return
            
        self.recording = True
        self.frames = []
        self.record_btn.config(text="停止录制")
        self.status_var.set("录制中...")
        
        # 在新线程中录制
        self.record_thread = threading.Thread(
            target=self.record_screen,
            args=(fps, duration)
        )
        self.record_thread.daemon = True
        self.record_thread.start()
        
    def stop_recording(self):
        """停止录制"""
        self.recording = False
        self.record_btn.config(text="开始录制")
        self.status_var.set("正在保存GIF...")
        
    def record_screen(self, fps, duration):
        """录制屏幕"""
        start_time = time.time()
        interval = 1.0 / fps
        
        while self.recording and (time.time() - start_time) < duration:
            frame_start = time.time()
            
            try:
                # 根据选择的区域截屏
                if self.region_var.get() == "全屏":
                    screenshot = pyautogui.screenshot()
                else:
                    if self.region:
                        x1, y1, x2, y2 = self.region
                        screenshot = pyautogui.screenshot(region=(x1, y1, x2-x1, y2-y1))
                    else:
                        screenshot = pyautogui.screenshot()
                
                self.frames.append(screenshot)
                
                # 更新状态
                elapsed = time.time() - start_time
                remaining = duration - elapsed
                self.status_var.set(f"录制中... 剩余时间: {remaining:.1f}秒")
                
            except Exception as e:
                print(f"截图失败: {e}")
                
            # 控制帧率
            elapsed_frame = time.time() - frame_start
            sleep_time = interval - elapsed_frame
            if sleep_time > 0:
                time.sleep(sleep_time)
                
        # 保存GIF
        if self.frames:
            self.save_gif()
            
    def save_gif(self):
        """保存GIF文件"""
        try:
            if self.frames:
                self.frames[0].save(
                    self.output_path,
                    save_all=True,
                    append_images=self.frames[1:],
                    duration=1000//int(self.fps_var.get()),
                    loop=0,
                    optimize=True
                )
                self.status_var.set(f"GIF已保存: {self.output_path}")
                messagebox.showinfo("成功", f"GIF已保存到:\n{self.output_path}\n共{len(self.frames)}帧")
            else:
                self.status_var.set("没有录制到帧")
                messagebox.showwarning("警告", "没有录制到任何帧")
        except Exception as e:
            self.status_var.set("保存失败")
            messagebox.showerror("错误", f"保存失败: {str(e)}")


class RegionSelector:
    """区域选择器"""
    def __init__(self, parent, callback):
        self.parent = parent
        self.callback = callback
        self.start_x = None
        self.start_y = None
        self.current_x = None
        self.current_y = None
        self.region = None
        
        # 创建全屏透明窗口
        self.selector = tk.Toplevel(parent)
        self.selector.attributes('-fullscreen', True)
        self.selector.attributes('-alpha', 0.3)
        self.selector.configure(bg='black')
        self.selector.attributes('-topmost', True)
        
        # 创建画布用于绘制选择框
        self.canvas = tk.Canvas(self.selector, highlightthickness=0)
        self.canvas.pack(fill=tk.BOTH, expand=True)
        
        # 绑定事件
        self.canvas.bind('<Button-1>', self.on_button_press)
        self.canvas.bind('<B1-Motion>', self.on_mouse_drag)
        self.canvas.bind('<ButtonRelease-1>', self.on_button_release)
        self.selector.bind('<Escape>', self.cancel_selection)
        
        self.selector.focus_set()
        
    def on_button_press(self, event):
        """鼠标按下"""
        self.start_x = event.x
        self.start_y = event.y
        self.rect = self.canvas.create_rectangle(
            self.start_x, self.start_y, self.start_x, self.start_y,
            outline='red', width=2, fill='blue', stipple='gray50'
        )
        
    def on_mouse_drag(self, event):
        """鼠标拖动"""
        self.current_x = event.x
        self.current_y = event.y
        self.canvas.coords(self.rect, self.start_x, self.start_y, self.current_x, self.current_y)
        
    def on_button_release(self, event):
        """鼠标释放"""
        self.current_x = event.x
        self.current_y = event.y
        
        # 确保坐标正确(左上角到右下角)
        x1 = min(self.start_x, self.current_x)
        y1 = min(self.start_y, self.current_y)
        x2 = max(self.start_x, self.current_x)
        y2 = max(self.start_y, self.current_y)
        
        # 如果区域太小,认为是取消
        if abs(x2 - x1) < 10 or abs(y2 - y1) < 10:
            self.cancel_selection()
            return
            
        self.region = (x1, y1, x2, y2)
        self.selector.destroy()
        self.callback(self.region)
        
    def cancel_selection(self, event=None):
        """取消选择"""
        self.selector.destroy()
        self.callback(None)


class AdvancedGIFRecorder(GIFRecorderWithRegion):
    def __init__(self, root):
        super().__init__(root)
        self.root.title("GIF录制截图")
        
        # 添加更多功能按钮
        advanced_frame = ttk.Frame(self.root)
        advanced_frame.pack(pady=5)
        
        ttk.Button(advanced_frame, text="测试区域", command=self.test_region).pack(side="left", padx=5)
        ttk.Button(advanced_frame, text="清除区域", command=self.clear_region).pack(side="left", padx=5)
        
    def test_region(self):
        """测试选择的区域"""
        if self.region and self.region_var.get() == "自定义区域":
            try:
                x1, y1, x2, y2 = self.region
                # 短暂显示区域边框
                test_window = tk.Toplevel(self.root)
                test_window.attributes('-topmost', True)
                test_window.geometry(f"{x2-x1}x{y2-y1}+{x1}+{y1}")
                test_window.overrideredirect(True)
                test_window.configure(bg='red', highlightthickness=2, highlightbackground='yellow')
                
                # 3秒后自动关闭
                test_window.after(3000, test_window.destroy)
            except Exception as e:
                messagebox.showerror("错误", f"测试失败: {str(e)}")
        else:
            messagebox.showinfo("提示", "请先选择自定义区域")
            
    def clear_region(self):
        """清除区域选择"""
        self.region = None
        self.region_var.set("全屏")
        self.update_region_display()
        self.status_var.set("区域已清除")


if __name__ == "__main__":
    root = tk.Tk()
    app = AdvancedGIFRecorder(root)
    root.mainloop()
昵称
邮箱
站点
填写信息
返回
发表
返回留言
😎 此页面还没有评论呢,欢迎留下第一个脚印