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