python-script-library/videos质检程序.py

325 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()