- 区域选择录制:支持全屏或自定义区域录制
- 实时预览:选择区域后可立即预览
- 参数自定义:可调节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()