【GPT写代码】动作视频切截图研究器

发布于:2025-04-05 ⋅ 阅读:(12) ⋅ 点赞:(0)

背景

用python写一个windows环境运行的动作视频切截图研究器,用路径浏览的方式指定待处理的视频文件,然后点击分析按钮,再预览区域显示视频预览画面,然后拖动时间轴,可以在预览区域刷新显示相应的画面,时间轴要能够显示视频总时长和当前按钮所在位置的,输入想要截取的视频时间范围和间隔的毫秒数,最后再视频所在路径保存所有截取的视频画面图片。

遇到CV2中文路径无法保存的问题
filename = os.path.join(output_dir, f"frame_{i:04d}.jpg")
cv2.imwrite(filename, frame)
filename = os.path.normpath(os.path.join(output_dir, self.video_name+f"_{i:04d}.jpg"))

源代码

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import cv2
import os
from PIL import Image, ImageTk
import threading
import queue


class VideoSlicerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("动作视频切片研究器")

        # 窗口设置
        screen_width = root.winfo_screenwidth()
        screen_height = root.winfo_screenheight()
        self.root.geometry(f"{int(screen_width * 0.7)}x{int(screen_height * 0.7)}")
        self.root.minsize(800, 600)

        # 初始化变量
        self.video_path = tk.StringVar()
        self.video_name = "video"
        self.cap = None
        self.total_frames = 0
        self.fps = 0
        self.current_frame = 0
        self.original_size = (0, 0)
        self.preview_size = (800, 450)
        self.progress_queue = queue.Queue()

        # 创建界面
        self.create_widgets()
        self.root.bind("<Configure>", self.on_window_resize)

        # 定期检查进度
        self.root.after(100, self.check_progress)

    def create_widgets(self):
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill='both', expand=True, padx=10, pady=5)

        # 文件选择
        file_frame = ttk.Frame(main_frame)
        file_frame.pack(fill='x', pady=5)

        ttk.Label(file_frame, text="视频文件:").pack(side='left')
        ttk.Entry(file_frame, textvariable=self.video_path, width=40).pack(side='left', padx=5, fill='x', expand=True)
        ttk.Button(file_frame, text="浏览", command=self.browse_file).pack(side='left')

        # 分析按钮
        self.analyze_button = ttk.Button(main_frame, text="分析视频", command=self.analyze_video)
        self.analyze_button.pack(pady=5)

        # 预览区域
        self.preview_container = ttk.Frame(main_frame)
        self.preview_container.pack(fill='both', expand=True)

        self.preview_label = ttk.Label(self.preview_container)
        self.preview_label.pack(fill='both', expand=True)

        # 时间轴
        control_frame = ttk.Frame(main_frame)
        control_frame.pack(fill='x', pady=5)

        self.time_label = ttk.Label(control_frame, text="00:00:00 / 00:00:00")
        self.time_label.pack(side='bottom', fill='x')

        self.time_scale = ttk.Scale(control_frame, from_=0, to=100, command=self.update_frame)
        self.time_scale.pack(fill='x', padx=5)

        # 参数输入
        param_frame = ttk.Frame(main_frame)
        param_frame.pack(fill='x', pady=5)

        ttk.Label(param_frame, text="开始时间(ms):").grid(row=0, column=0, padx=5)
        self.start_time = ttk.Entry(param_frame, width=10)
        self.start_time.grid(row=0, column=1, padx=5)

        ttk.Label(param_frame, text="结束时间(ms):").grid(row=0, column=2, padx=5)
        self.end_time = ttk.Entry(param_frame, width=10)
        self.end_time.grid(row=0, column=3, padx=5)

        ttk.Label(param_frame, text="间隔(ms):").grid(row=0, column=4, padx=5)
        self.interval = ttk.Entry(param_frame, width=10)
        self.interval.grid(row=0, column=5, padx=5)

        # 进度条
        self.progress = ttk.Progressbar(main_frame, orient='horizontal', mode='determinate')
        self.progress.pack(fill='x', pady=5)

        # 保存按钮
        self.save_button = ttk.Button(main_frame, text="保存切片", command=self.save_slices)
        self.save_button.pack(pady=5)

    def browse_file(self):
        file_path = filedialog.askopenfilename(filetypes=[("视频文件", "*.mp4 *.avi *.mov")])
        if file_path:
            self.video_path.set(file_path)

    def analyze_video(self):
        if not self.video_path.get():
            messagebox.showerror("错误", "请先选择视频文件")
            return

        self.video_name = os.path.splitext(os.path.basename(repr(self.video_path.get())))[0]
        print(self.video_name)
        self.cap = cv2.VideoCapture(self.video_path.get())
        if not self.cap.isOpened():
            messagebox.showerror("错误", "无法打开视频文件")
            return

        self.fps = self.cap.get(cv2.CAP_PROP_FPS)
        self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.time_scale.config(to=self.total_frames)

        self.original_size = (
            int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
            int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        )

        self.update_preview_size()
        self.show_frame(0)

    def update_preview_size(self):
        container_width = self.preview_container.winfo_width()
        container_height = self.preview_container.winfo_height()

        max_width = max(container_width - 20, 100)
        max_height = max(container_height - 20, 100)

        ratio = min(max_width / self.original_size[0], max_height / self.original_size[1])
        self.preview_size = (
            int(self.original_size[0] * ratio),
            int(self.original_size[1] * ratio)
        )

    def on_window_resize(self, event):
        if self.cap and self.cap.isOpened():
            self.update_preview_size()
            self.show_frame(self.current_frame)

    def show_frame(self, frame_num):
        self.current_frame = frame_num
        self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
        ret, frame = self.cap.read()
        if ret:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = Image.fromarray(frame)
            img = img.resize(self.preview_size, Image.Resampling.LANCZOS)

            img_tk = ImageTk.PhotoImage(img)
            self.preview_label.config(image=img_tk)
            self.preview_label.image = img_tk

            current_time = frame_num / self.fps
            total_time = self.total_frames / self.fps
            self.time_label.config(text=f"{self.format_time(current_time)} / {self.format_time(total_time)}")

    def format_time(self, seconds):
        ms = int((seconds - int(seconds)) * 1000)
        seconds = int(seconds)
        h = seconds // 3600
        m = (seconds % 3600) // 60
        s = seconds % 60
        return f"{h:02d}:{m:02d}:{s:02d}.{ms:03d}"

    def update_frame(self, value):
        frame_num = int(float(value))
        self.show_frame(frame_num)

    def save_slices(self):
        if not self.cap:
            messagebox.showerror("错误", "请先分析视频")
            return

        try:
            start = int(self.start_time.get())
            end = int(self.end_time.get())
            interval = int(self.interval.get())
            if start >= end or interval <= 0:
                raise ValueError
        except ValueError:
            messagebox.showerror("输入错误", "请输入有效的时间范围\n(开始<结束,间隔>0)")
            return

        # 禁用按钮防止重复点击
        self.save_button["state"] = "disabled"
        self.analyze_button["state"] = "disabled"

        # 计算帧数范围
        start_frame = int(start * self.fps / 1000)
        end_frame = int(end * self.fps / 1000)
        interval_frames = int(interval * self.fps / 1000)

        total_saves = (end_frame - start_frame) // interval_frames
        self.progress['maximum'] = total_saves
        self.progress['value'] = 0

        save_thread = threading.Thread(target=self.process_slices,
                                       args=(start_frame, end_frame, interval_frames))
        save_thread.daemon = True
        save_thread.start()

    def process_slices(self, start_frame, end_frame, interval_frames):
        try:
            # 使用独立视频实例
            cap = cv2.VideoCapture(self.video_path.get())
            output_dir = os.path.join(os.path.dirname(self.video_path.get()),
                                      self.video_name+"_slices")

            if not os.path.exists(output_dir):
                os.makedirs(output_dir)

            frame_count = 0
            total = (end_frame - start_frame) // interval_frames

            for i in range(total):
                frame_num = start_frame + i * interval_frames
                cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
                ret, frame = cap.read()

                if ret:
                    filename = os.path.normpath(os.path.join(output_dir, self.video_name+f"_{i:04d}.jpg"))
                    print(filename)
                    # cv2.imwrite(filename, frame)
                    img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    img_pil = Image.fromarray(img)
                    img_pil.save(filename)
                    self.progress_queue.put(("progress", i + 1))
                else:
                    self.progress_queue.put(("error", f"无法读取 {frame_num} 帧"))

            cap.release()
            self.progress_queue.put(("done", total))

        except Exception as e:
            self.progress_queue.put(("error", str(e)))

    def check_progress(self):
        try:
            while not self.progress_queue.empty():
                msg_type, msg_data = self.progress_queue.get_nowait()

                if msg_type == "error":
                    messagebox.showerror("保存错误", msg_data)
                    self.reset_controls()
                elif msg_type == "done":
                    self.progress['value'] = self.progress['maximum']
                    messagebox.showinfo("完成", f"成功保存{msg_data}张切片")
                    self.reset_controls()
                elif msg_type == "progress":
                    self.progress['value'] = msg_data

        except queue.Empty:
            pass

        self.root.after(100, self.check_progress)

    def reset_controls(self):
        self.save_button["state"] = "normal"
        self.analyze_button["state"] = "normal"


if __name__ == "__main__":
    root = tk.Tk()
    app = VideoSlicerApp(root)
    root.mainloop()

end