import math import os import tkinter as tk from tkinter import filedialog, messagebox import cv2 class VideoFrameExtractorApp: def __init__(self, root): self.root = root self.root.title("视频拆分图片工具") self.root.geometry("760x520") self.video_path = "" self.output_dir = "" self.video_info = {} self._build_ui() def _build_ui(self): main = tk.Frame(self.root, padx=14, pady=14) main.pack(fill="both", expand=True) source_frame = tk.LabelFrame(main, text="视频源", padx=10, pady=10) source_frame.pack(fill="x", pady=(0, 10)) self.video_path_var = tk.StringVar() tk.Entry(source_frame, textvariable=self.video_path_var).pack(side="left", fill="x", expand=True) tk.Button(source_frame, text="添加视频", width=12, command=self.choose_video).pack(side="left", padx=(8, 0)) info_frame = tk.LabelFrame(main, text="视频信息", padx=10, pady=10) info_frame.pack(fill="x", pady=(0, 10)) self.info_labels = {} fields = [ ("file_name", "文件名"), ("resolution", "分辨率"), ("fps", "视频帧率"), ("frame_count", "总帧数"), ("duration", "总时长"), ("codec", "编码格式"), ("size", "文件大小"), ] for row, (key, title) in enumerate(fields): tk.Label(info_frame, text=f"{title}:", width=12, anchor="w").grid(row=row, column=0, sticky="w", pady=2) label = tk.Label(info_frame, text="未加载", anchor="w") label.grid(row=row, column=1, sticky="w", pady=2) self.info_labels[key] = label settings_frame = tk.LabelFrame(main, text="抽帧设置", padx=10, pady=10) settings_frame.pack(fill="x", pady=(0, 10)) tk.Label(settings_frame, text="每秒抽取图片数:", width=14, anchor="w").grid(row=0, column=0, sticky="w") self.target_fps_var = tk.StringVar(value="5") tk.Entry(settings_frame, textvariable=self.target_fps_var, width=12).grid(row=0, column=1, sticky="w") tk.Label(settings_frame, text="输出文件夹:", width=14, anchor="w").grid(row=1, column=0, sticky="w", pady=(10, 0)) self.output_dir_var = tk.StringVar() tk.Entry(settings_frame, textvariable=self.output_dir_var).grid(row=1, column=1, sticky="ew", pady=(10, 0)) tk.Button(settings_frame, text="选择输出", width=12, command=self.choose_output_dir).grid(row=1, column=2, padx=(8, 0), pady=(10, 0)) settings_frame.grid_columnconfigure(1, weight=1) action_frame = tk.Frame(main) action_frame.pack(fill="x", pady=(0, 10)) tk.Button(action_frame, text="开始拆分", width=14, command=self.extract_frames).pack(side="left") self.progress_var = tk.StringVar(value="等待开始") tk.Label(main, textvariable=self.progress_var, anchor="w", justify="left", fg="darkgreen").pack(fill="x") def choose_video(self): video_path = filedialog.askopenfilename( title="选择视频文件", filetypes=[("Video files", "*.mp4 *.avi *.mov *.mkv *.flv *.wmv"), ("All files", "*.*")], ) if not video_path: return self.video_path = video_path self.video_path_var.set(video_path) self.load_video_info(video_path) if not self.output_dir_var.get().strip(): default_name = os.path.splitext(os.path.basename(video_path))[0] + "_frames" self.output_dir = os.path.join(os.path.dirname(video_path), default_name) self.output_dir_var.set(self.output_dir) def choose_output_dir(self): output_dir = filedialog.askdirectory(title="选择输出文件夹") if not output_dir: return self.output_dir = output_dir self.output_dir_var.set(output_dir) def load_video_info(self, video_path): cap = cv2.VideoCapture(video_path) if not cap.isOpened(): messagebox.showerror("错误", "无法打开该视频文件。") return width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 0) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 0) fps = float(cap.get(cv2.CAP_PROP_FPS) or 0.0) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0) duration_seconds = frame_count / fps if fps > 0 else 0 fourcc_value = int(cap.get(cv2.CAP_PROP_FOURCC) or 0) cap.release() self.video_info = { "file_name": os.path.basename(video_path), "resolution": f"{width} x {height}", "fps": f"{fps:.3f}" if fps else "未知", "frame_count": str(frame_count) if frame_count else "未知", "duration": self.format_duration(duration_seconds), "codec": self.decode_fourcc(fourcc_value), "size": self.format_size(os.path.getsize(video_path)), } for key, value in self.video_info.items(): self.info_labels[key].config(text=value) self.progress_var.set("视频信息已读取,可以设置抽帧参数。") def decode_fourcc(self, value): if value <= 0: return "未知" chars = [chr((value >> 8 * i) & 0xFF) for i in range(4)] codec = "".join(chars).strip() return codec if codec else "未知" def format_duration(self, seconds): seconds = max(0, int(round(seconds))) hours = seconds // 3600 minutes = (seconds % 3600) // 60 secs = seconds % 60 return f"{hours:02d}:{minutes:02d}:{secs:02d}" def format_size(self, size_bytes): units = ["B", "KB", "MB", "GB", "TB"] size = float(size_bytes) index = 0 while size >= 1024 and index < len(units) - 1: size /= 1024 index += 1 return f"{size:.2f} {units[index]}" def extract_frames(self): video_path = self.video_path_var.get().strip() output_dir = self.output_dir_var.get().strip() target_fps_text = self.target_fps_var.get().strip() if not video_path: messagebox.showwarning("提示", "请先添加视频文件。") return if not os.path.exists(video_path): messagebox.showwarning("提示", "视频文件不存在,请重新选择。") return if not output_dir: messagebox.showwarning("提示", "请选择输出文件夹。") return try: target_fps = float(target_fps_text) except ValueError: messagebox.showwarning("提示", "每秒抽取图片数必须是数字。") return if target_fps <= 0: messagebox.showwarning("提示", "每秒抽取图片数必须大于 0。") return os.makedirs(output_dir, exist_ok=True) cap = cv2.VideoCapture(video_path) if not cap.isOpened(): messagebox.showerror("错误", "无法打开视频文件。") return original_fps = float(cap.get(cv2.CAP_PROP_FPS) or 0.0) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0) if original_fps <= 0: cap.release() messagebox.showerror("错误", "无法读取视频帧率。") return if target_fps >= original_fps: interval = 1 actual_fps = original_fps else: interval = max(1, int(round(original_fps / target_fps))) actual_fps = original_fps / interval frame_index = 0 saved_count = 0 base_name = os.path.splitext(os.path.basename(video_path))[0] self.progress_var.set( f"开始拆分:原始 FPS={original_fps:.3f},目标={target_fps:.3f},实际约={actual_fps:.3f} 张/秒" ) self.root.update_idletasks() while True: success, frame = cap.read() if not success: break if frame_index % interval == 0: timestamp = frame_index / original_fps output_name = f"{base_name}_{saved_count:06d}_{timestamp:09.3f}s.jpg" output_path = os.path.join(output_dir, output_name) cv2.imwrite(output_path, frame) saved_count += 1 frame_index += 1 if frame_count and frame_index % 100 == 0: percent = frame_index / frame_count * 100 self.progress_var.set(f"处理中:{frame_index}/{frame_count} 帧,约 {percent:.1f}%") self.root.update_idletasks() cap.release() self.progress_var.set( f"拆分完成:共导出 {saved_count} 张图片,输出目录:{output_dir}" ) messagebox.showinfo( "完成", f"视频拆分完成。\n\n导出图片:{saved_count} 张\n输出目录:{output_dir}\n实际抽取频率:约 {actual_fps:.3f} 张/秒", ) def main(): root = tk.Tk() VideoFrameExtractorApp(root) root.mainloop() if __name__ == "__main__": main()