博主

2小时前在线

小野博客
小野(Hirono)博客的个人网站,主要讲述关于小野的一些陈芝麻烂谷子事。网络、生活、我的主张,也是一个自留地
歌曲封面 未知作品
  • 歌曲封面Algaxhi har阿来
  • 歌曲封面野人孟维来

网站已运行 2 年 200 天 4 小时 51 分

Powered by Typecho & Sunny

5 online · 60 ms

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

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

小野

·

📟前端日记

·

Article
  • 区域选择录制:支持全屏或自定义区域录制
  • 实时预览:选择区域后可立即预览
  • 参数自定义:可调节FPS、录制时长
  • 友好界面:直观的GUI操作界面

安装依赖库

♾️ bash 代码:
pip install pillow pyautogui pyinstaller

核心源码

♾️ python 代码:
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()
现在已有 2 次阅读,0 条评论,0 人点赞
Comment:共0条
发表
搜 索 消 息 足 迹
你还不曾留言过..
你还不曾留下足迹..
博主 网站设置有【CDN】缓存,留言等评论内容需要12小时才能显示! 不再显示
博主
博主 立即安装