import os import sys import subprocess import tkinter as tk from tkinter import messagebox from datetime import datetime from send2trash import send2trash # 需提前安装:pip install send2trash # ================================================================= # 请在这里修改为您的实际路径! # ================================================================= # F 盘:视频文件和此Python脚本所在的目录(自动获取,无需手动改) F_DISK_VIDEO_DIR = os.path.dirname(os.path.abspath(__file__)) # E 盘:与视频同名的文件夹所在的根目录(当前设为C盘根目录,需根据实际改) E_DISK_FOLDERS_ROOT_DIR = "E:\\" # 日志文件配置(保存在F盘,与脚本同级) PASS_LOG_FILE = "passed_files.txt" # 通过视频日志 FAIL_LOG_FILE = "rejected_files.txt" # 不通过视频日志 # 新增视频检测间隔(秒) DETECTION_INTERVAL = 5 # 每5秒检测一次新视频 # ================================================================= class VideoProcessorGUI: def __init__(self, root): """初始化GUI窗口和变量""" self.root = root self.root.title("视频批量标记工具 (支持实时新增视频)") self.root.geometry("550x220") self.root.resizable(False, False) # 检查E盘(当前是C盘)文件夹根目录是否存在 if not os.path.isdir(E_DISK_FOLDERS_ROOT_DIR): messagebox.showerror("错误", f"目标盘文件夹根目录不存在!\n请检查路径: {E_DISK_FOLDERS_ROOT_DIR}") self.root.destroy() return # 初始化视频文件列表(存储完整路径) self.video_files = self._get_video_files() self.current_index = 0 self.last_checked_time = datetime.now() # 记录最后一次检测时间 # 创建界面组件 self._create_widgets() # 开始处理第一个视频 self._process_current_video() # 启动新视频检测定时器 self._start_detection_timer() def _force_focus_timer(self): """定时强制获取焦点""" self.root.focus_force() # 强制获取焦点 self.root.after(500, self._force_focus_timer) def _get_video_files(self): """获取F盘当前目录下的所有视频文件(仅返回完整路径)""" video_extensions = ('.mp4', '.mkv', '.avi', '.mov', '.flv', '.wmv', '.webm', '.ts') video_files = [ os.path.join(F_DISK_VIDEO_DIR, f) for f in os.listdir(F_DISK_VIDEO_DIR) if os.path.isfile(os.path.join(F_DISK_VIDEO_DIR, f)) and f.lower().endswith(video_extensions) ] return video_files # 不排序,确保新增文件按系统顺序添加 def _create_widgets(self): """创建弹窗中的按钮、文字等组件""" # 新视频提示标签(红色提示新增文件) self.new_files_label = tk.Label(self.root, text="", fg="red", pady=5) self.new_files_label.pack() # 状态提示(如“正在处理第1/5个”) self.status_label = tk.Label(self.root, text="", pady=5) self.status_label.pack() # 当前视频文件名(蓝色加粗) self.filename_label = tk.Label(self.root, text="", font=("Arial", 12), fg="blue") self.filename_label.pack() # 按钮容器(让按钮横向排列) button_frame = tk.Frame(self.root) button_frame.pack(side=tk.BOTTOM, pady=20) self.root.bind('', self._handle_key_press) self.root.focus_force() # 强制获取焦点 self.root.bind('', lambda e: self.root.focus_force()) # 上一条/重播/通过/不通过/下一条按钮 self.prev_btn = tk.Button(button_frame, text="上一条", width=10, command=self._prev_video) self.replay_btn = tk.Button(button_frame, text="重播", width=10, command=self._replay_video) self.pass_btn = tk.Button(button_frame, text="通过", width=10, command=self._mark_as_pass, bg="green", fg="white") self.fail_btn = tk.Button(button_frame, text="不通过", width=10, command=self._mark_as_fail, bg="red", fg="white") self.next_btn = tk.Button(button_frame, text="下一条", width=10, command=self._next_video) # 按钮布局(横向均匀分布) self.prev_btn.pack(side=tk.LEFT, padx=5) self.replay_btn.pack(side=tk.LEFT, padx=5) self.pass_btn.pack(side=tk.LEFT, padx=5) self.fail_btn.pack(side=tk.LEFT, padx=5) self.next_btn.pack(side=tk.LEFT, padx=5) def _start_detection_timer(self): """启动定时器,定期检测新视频文件""" self._check_new_videos() # 立即执行一次检测 # 每隔DETECTION_INTERVAL秒重复检测 self.root.after(DETECTION_INTERVAL * 1000, self._start_detection_timer) def _check_new_videos(self): """检测并添加新增的视频文件,若之前无视频则自动启动播放""" if not os.path.isdir(F_DISK_VIDEO_DIR): return # 目录不存在则跳过检测 # 获取当前目录下的所有视频(最新状态) current_all_videos = self._get_video_files() # 找出不在当前处理列表中的新视频(通过路径对比) existing_paths = set(self.video_files) new_videos = [path for path in current_all_videos if path not in existing_paths] if new_videos: # 记录新增前的列表长度(判断是否之前无视频) was_empty = len(self.video_files) == 0 # 将新视频添加到处理列表末尾 self.video_files.extend(new_videos) # 提取新视频的文件名(用于提示) new_filenames = [os.path.basename(path) for path in new_videos] # 显示新增提示(3秒后自动清除提示) self.new_files_label.config(text=f"检测到 {len(new_videos)} 个新视频,已添加到列表:{', '.join(new_filenames[:2])}{'...' if len(new_filenames)>2 else ''}") self.root.after(3000, lambda: self.new_files_label.config(text="")) # 3秒后清除提示 print(f"新增视频:{new_filenames}") # 核心修复:若之前无视频,新增后自动从第一个新视频开始播放 if was_empty: self.current_index = 0 # 确保从第一个新增视频开始 self._process_current_video() # 触发播放 # 更新最后检测时间 self.last_checked_time = datetime.now() def _play_video(self, video_path): """用系统默认播放器打开视频""" try: if sys.platform == 'win32': # 针对Windows系统 os.startfile(video_path) else: # 针对Mac/Linux系统(备用) opener = 'open' if sys.platform == 'darwin' else 'xdg-open' subprocess.run([opener, video_path], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except Exception as e: # 播放失败时弹窗提示 error_msg = ( f"无法播放视频!\n\n" f"系统错误信息: {e}\n\n" f"**请检查:**\n" f"1. 您是否已安装视频播放器?\n" f"2. 您是否已将其设置为默认播放器?" ) messagebox.showerror("播放错误", error_msg) def _log_file(self, filename, log_type): """通用日志记录方法:根据log_type记录到对应的TXT文件""" try: # 选择日志文件(通过→passed_files.txt,不通过→rejected_files.txt) log_file = PASS_LOG_FILE if log_type == "pass" else FAIL_LOG_FILE # 日志文件路径:和脚本、视频在同一目录(F盘) log_file_path = os.path.join(F_DISK_VIDEO_DIR, log_file) # 追加写入(不覆盖历史记录),包含时间戳 with open(log_file_path, 'a', encoding='utf-8') as f: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") f.write(f"[{timestamp}] {filename}\n") return True # 记录成功 except Exception as e: print(f"写入日志失败: {e}") # 终端打印错误(不影响用户操作) return False # 记录失败 def _handle_key_press(self, event): """处理键盘按键""" if event.char == '2': # 按下1键 self._mark_as_pass() elif event.char == '3': # 按下2键 self._mark_as_fail() def _process_current_video(self): """更新弹窗状态,并自动播放当前视频""" if not self.video_files: # 没有视频可处理时 self.status_label.config(text="所有视频处理完毕!(将继续检测新视频...)", fg="green") self.filename_label.config(text="") return # 获取当前视频路径和文件名 current_video_path = self.video_files[self.current_index] current_video_name = os.path.basename(current_video_path) # 更新弹窗文字提示(覆盖之前的“处理完毕”提示) self.status_label.config(text=f"正在处理: 第 {self.current_index + 1}/{len(self.video_files)} 个") self.filename_label.config(text=current_video_name) # 自动播放当前视频 self._play_video(current_video_path) def _get_associated_folder(self, video_path): """获取E盘(当前是C盘)上与视频同名的文件夹路径(仅查询,不操作)""" video_basename = os.path.basename(video_path) folder_name = os.path.splitext(video_basename)[0] # 提取视频名(去后缀) folder_path = os.path.join(E_DISK_FOLDERS_ROOT_DIR, folder_name) if os.path.isdir(folder_path): return folder_path return None def _mark_as_pass(self): """处理“通过”:删除视频+记录到passed_files.txt""" if not self.video_files: return os.system('taskkill /f /im "vlc.exe"') # 获取当前视频信息(路径、带后缀名、去后缀名) current_video_path = self.video_files[self.current_index] video_basename = os.path.basename(current_video_path) video_filename = os.path.splitext(video_basename)[0] # 去后缀的文件名(如“视频1”) # 1. 记录“通过”日志 log_success = self._log_file(video_filename, "pass") # 2. 提示用户操作结果(包含日志记录情况) if log_success: messagebox.showinfo("操作结果", f"已标记为通过: {video_filename}\n" f"文件名已记录到 {PASS_LOG_FILE}") else: messagebox.showerror("操作结果", f"已标记为通过: {video_filename}\n" f"警告:文件名未成功记录到 {PASS_LOG_FILE}") # 3. 删除F盘的视频(移至回收站,安全删除) try: send2trash(current_video_path) print(f"通过视频已删除: {current_video_path}") except Exception as e: messagebox.showerror("操作失败", f"无法删除视频: {e}\n" f"请确保视频没有被播放器占用(关闭播放器后重试)") # 4. 处理下一个视频 self._move_to_next() def _mark_as_fail(self): """处理“不通过”:删除视频+记录到rejected_files.txt""" if not self.video_files: return os.system('taskkill /f /im "vlc.exe"') # 获取当前视频信息 current_video_path = self.video_files[self.current_index] video_basename = os.path.basename(current_video_path) video_filename = os.path.splitext(video_basename)[0] # 1. 记录“不通过”日志 log_success = self._log_file(video_filename, "fail") # 2. 提示用户操作结果 if log_success: messagebox.showinfo("操作结果", f"已标记为不通过: {video_filename}\n" f"文件名已记录到 {FAIL_LOG_FILE}") else: messagebox.showerror("操作结果", f"已标记为不通过: {video_filename}\n" f"警告:文件名未成功记录到 {FAIL_LOG_FILE}") # 3. 删除F盘的视频(移至回收站) try: send2trash(current_video_path) print(f"不通过视频已删除: {current_video_path}") except Exception as e: messagebox.showerror("操作失败", f"无法删除视频: {e}\n" f"请确保视频没有被播放器占用(关闭播放器后重试)") # 4. 处理下一个视频 self._move_to_next() def _move_to_next(self): """移除已处理的视频,切换到下一个""" if not self.video_files: return self.video_files.pop(self.current_index) # 避免删除最后一个视频后索引越界 if self.current_index >= len(self.video_files) and self.video_files: self.current_index = len(self.video_files) - 1 # 更新弹窗状态,播放下一个视频 self._process_current_video() def _replay_video(self): """重播当前视频""" if self.video_files: self._play_video(self.video_files[self.current_index]) def _next_video(self): """切换到下一个视频(不处理当前视频)""" if not self.video_files: return if self.current_index < len(self.video_files) - 1: self.current_index += 1 self._process_current_video() else: messagebox.showinfo("提示", "已经是最后一个视频了。") def _prev_video(self): """切换到上一个视频(不处理当前视频)""" if not self.video_files: return if self.current_index > 0: self.current_index -= 1 self._process_current_video() else: messagebox.showinfo("提示", "已经是第一个视频了。") if __name__ == "__main__": # 启动弹窗程序 root = tk.Tk() app = VideoProcessorGUI(root) root.mainloop()