325 lines
14 KiB
Python
325 lines
14 KiB
Python
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('<Key>', self._handle_key_press)
|
||
self.root.focus_force() # 强制获取焦点
|
||
self.root.bind('<FocusOut>', 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() |