From d74b66b95e6580fac202bf99df40981f3dfcc810 Mon Sep 17 00:00:00 2001 From: Admin <2187762716@qq.com> Date: Tue, 28 Oct 2025 16:02:55 +0800 Subject: [PATCH] 1028new --- videos质检程序.py | 325 ++++++++++++++++++++++++++++++++++++ x时间后按下x按键.py | 177 ++++++++++++++++++++ 一直按着x按键.py | 44 +++++ 可视化质检.py | 389 ++++++++++++++++++++++++++++++++++++++++++++ 战神修改文件名.py | 84 ++++++++++ 脚本管理器.py | 61 +++++++ 转移文件non.py | 167 +++++++++++++++++++ 递归删除文件夹.py | 146 +++++++++++++++++ 鼠标旋转往左移动.py | 82 ++++++++++ 9 files changed, 1475 insertions(+) create mode 100644 videos质检程序.py create mode 100644 x时间后按下x按键.py create mode 100644 一直按着x按键.py create mode 100644 可视化质检.py create mode 100644 战神修改文件名.py create mode 100644 脚本管理器.py create mode 100644 转移文件non.py create mode 100644 递归删除文件夹.py create mode 100644 鼠标旋转往左移动.py diff --git a/videos质检程序.py b/videos质检程序.py new file mode 100644 index 0000000..39b707c --- /dev/null +++ b/videos质检程序.py @@ -0,0 +1,325 @@ +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() \ No newline at end of file diff --git a/x时间后按下x按键.py b/x时间后按下x按键.py new file mode 100644 index 0000000..96b5b36 --- /dev/null +++ b/x时间后按下x按键.py @@ -0,0 +1,177 @@ +import time +import threading +from pynput import keyboard +from pynput.keyboard import Key, Controller + + +class AutoKeyPresser: + # =============== 全局配置 =============== + CONTROL_KEY = 'p' # 控制定时器启动/暂停的快捷键 + TARGET_KEY = Key.f10 # 要自动按下的目标键(默认为F10) + INTERVAL = 60 + 52 # 定时器间隔时间(1分钟52秒 = 112秒) + + # =============== 结束配置 =============== + + def __init__(self): + self.timer = None + self.is_active = False + self.listener = None + self.keyboard_controller = Controller() + self.ignore_next_key = False # 标志位,用于忽略程序自动按下的目标键 + self.timer_lock = threading.Lock() # 线程锁确保状态同步 + self.last_activity_time = 0 # 记录最后一次活动时间 + + def simulate_key_press(self): + """模拟按下配置的目标键""" + try: + self.ignore_next_key = True # 设置标志,忽略接下来的目标键事件 + # 使用正确的键模拟 + self.keyboard_controller.press(self.TARGET_KEY) + time.sleep(0.05) # 短暂延迟模拟真实按键 + self.keyboard_controller.release(self.TARGET_KEY) + current_time = time.strftime("%Y-%m-%d %H:%M:%S") + key_name = self._get_key_name(self.TARGET_KEY) + print(f"[{current_time}] 已自动按下 {key_name} 键") + except Exception as e: + print(f"模拟按下 {self._get_key_name(self.TARGET_KEY)} 时发生错误: {e}") + + def _get_key_name(self, key): + """获取按键的可读名称""" + if hasattr(key, 'name'): + return key.name.upper() + elif hasattr(key, 'char'): + return key.char.upper() + return str(key) + + def start_timer(self): + """启动定时器(总是重新开始112秒计时)""" + with self.timer_lock: + if self.is_active: + return + + self.is_active = True + self.last_activity_time = time.time() # 重置开始时间 + current_time = time.strftime("%Y-%m-%d %H:%M:%S") + key_name = self._get_key_name(self.TARGET_KEY) + print(f"[{current_time}] 定时器已启动,将在{self.INTERVAL}秒后按下 {key_name} 键") + print(f"按 {self.CONTROL_KEY.upper()} 键可以暂停定时器") + + def timer_loop(): + # 记录本次循环的开始时间 + cycle_start_time = time.time() + + while self.is_active: + # 计算已经过去的时间 + elapsed_time = time.time() - cycle_start_time + remaining_time = self.INTERVAL - elapsed_time + + # 如果已经达到或超过间隔时间,执行按键操作 + if remaining_time <= 0: + if self.is_active: # 再次检查状态 + self.simulate_key_press() + # 重置循环开始时间,开始新的周期 + cycle_start_time = time.time() + continue + + # 分段等待,便于及时响应暂停命令 + wait_interval = min(0.5, remaining_time) # 每次最多等待0.5秒 + if wait_interval > 0: + time.sleep(wait_interval) + + self.timer = threading.Thread(target=timer_loop) + self.timer.daemon = True + self.timer.start() + + def pause_timer(self): + """暂停定时器""" + with self.timer_lock: + if not self.is_active: + return + + self.is_active = False + current_time = time.strftime("%Y-%m-%d %H:%M:%S") + # 计算已过去的时间(用于显示信息) + if self.last_activity_time > 0: + elapsed = time.time() - self.last_activity_time + remaining = max(0, self.INTERVAL - elapsed) + print(f"[{current_time}] 定时器已暂停(已等待{elapsed:.1f}秒,剩余{remaining:.1f}秒)") + else: + print(f"[{current_time}] 定时器已暂停") + + def on_press(self, key): + """键盘按键事件处理""" + try: + # 检测控制键按下,用于控制定时器暂停/恢复 + if hasattr(key, 'char') and (key.char.lower() == self.CONTROL_KEY.lower()): + if not self.is_active: + # 按下控制键,启动定时器(重新开始计时) + self.start_timer() + else: + # 定时器运行中按下控制键,暂停定时器 + self.pause_timer() + + # 处理Esc键事件 - 不再退出程序,只显示信息 + elif key == Key.esc: + current_time = time.strftime("%Y-%m-%d %H:%M:%S") + status = '运行中' if self.is_active else '已暂停' + print(f"[{current_time}] 检测到按下Esc键(定时器状态:{status})") + print("提示:Esc键已禁用退出功能,程序继续运行") + + # 处理目标键事件(仅用于显示,不影响定时器状态) + elif key == self.TARGET_KEY: + if self.ignore_next_key: + # 如果是程序自动按下的键,忽略它 + self.ignore_next_key = False + else: + # 用户手动按下的目标键,只显示信息不影响定时器 + current_time = time.strftime("%Y-%m-%d %H:%M:%S") + status = '运行中' if self.is_active else '已暂停' + key_name = self._get_key_name(self.TARGET_KEY) + print(f"[{current_time}] 检测到手动按下 {key_name} 键(定时器状态:{status})") + + except AttributeError: + # 处理特殊功能键(没有char属性的键) + if key == Key.esc: + current_time = time.strftime("%Y-%m-%d %H:%M:%S") + status = '运行中' if self.is_active else '已暂停' + print(f"[{current_time}] 检测到按下Esc键(定时器状态:{status})") + print("提示:Esc键已禁用退出功能,程序继续运行") + elif key == self.TARGET_KEY: + if self.ignore_next_key: + self.ignore_next_key = False + else: + current_time = time.strftime("%Y-%m-%d %H:%M:%S") + status = '运行中' if self.is_active else '已暂停' + key_name = self._get_key_name(self.TARGET_KEY) + print(f"[{current_time}] 检测到手动按下 {key_name} 键(定时器状态:{status})") + + def on_release(self, key): + """键盘释放事件处理 - 移除Esc键退出功能""" + pass + + def start_listening(self): + """开始监听键盘事件""" + key_name = self._get_key_name(self.TARGET_KEY) + print(f"{key_name}自动按下程序已启动") + print(f"按 {self.CONTROL_KEY.upper()} 键开始定时器,再次按 {self.CONTROL_KEY.upper()} 键暂停定时器") + print(f"定时器间隔时间:{self.INTERVAL}秒") + print(f"目标按键:{key_name}") + print("提示:定时器暂停后重新启动将重新开始计时") + print("提示:Esc键退出功能已禁用,如需退出程序请使用Ctrl+C") + + # 同时监听按下和释放事件 + with keyboard.Listener(on_press=self.on_press, on_release=self.on_release) as self.listener: + try: + self.listener.join() + except Exception as e: + print(f"监听器错误: {e}") + + +if __name__ == "__main__": + try: + auto_presser = AutoKeyPresser() + auto_presser.start_listening() + except KeyboardInterrupt: + print("\n程序被用户中断(Ctrl+C)") + except Exception as e: + print(f"程序运行错误: {e}") \ No newline at end of file diff --git a/一直按着x按键.py b/一直按着x按键.py new file mode 100644 index 0000000..f46133e --- /dev/null +++ b/一直按着x按键.py @@ -0,0 +1,44 @@ +import keyboard +import time +import sys + +# 可配置参数 +START_STOP_KEY = '-' # 启动/停止按键 +HOLD_KEY = 'w' # 程序运行时持续按下的键 +EXIT_KEY = 'esc' # 注意:虽然你要求ESC不退出,但我们会忽略它 + +# 状态变量 +is_running = False +should_exit = False + +def on_p_pressed(e): + global is_running + if e.event_type == keyboard.KEY_DOWN: + is_running = not is_running + if not is_running: # 当停止时立即释放按键 + keyboard.release(HOLD_KEY) + print(f"程序 {'已启动' if is_running else '已停止'}") + +def main(): + print(f"程序已启动,按 {START_STOP_KEY} 键开始/停止,按 Ctrl+C 退出") + print(f"程序运行时将持续按下 {HOLD_KEY} 键") + + keyboard.hook_key(START_STOP_KEY, on_p_pressed) + + try: + while not should_exit: + if is_running: + keyboard.press(HOLD_KEY) + # 保持按键状态,但允许其他事件处理 + time.sleep(0.1) + else: + time.sleep(0.1) + except KeyboardInterrupt: + print("\n程序通过 Ctrl+C 退出") + finally: + if is_running: + keyboard.release(HOLD_KEY) + keyboard.unhook_all() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/可视化质检.py b/可视化质检.py new file mode 100644 index 0000000..fd25f6f --- /dev/null +++ b/可视化质检.py @@ -0,0 +1,389 @@ +import os +import tkinter as tk +from tkinter import messagebox +import subprocess +import sys +import time + +# =============== 全局命令配置 =============== +COMMANDS_TEMPLATE = [ + "conda activate aiden-p3d", + "d:", + "cd aiden-p3d", +] + +# 扫描路径配置(默认F盘根目录) +SCAN_PATH = "E:\\" # 修改为您需要的默认扫描路径 + +# 新增文件夹检测间隔(秒) +DETECTION_INTERVAL = 5 # 每5秒检测一次新文件夹 + +# 游戏配置映射 +GAME_COMMANDS = { + "赛博朋克2077": { + "command": "python load_point_cloud_saibo_new.py \"{}\"", + "description": "赛博朋克2077 - python load_point_cloud_saibo_new.py" + }, + "巫师3": { + "command": "python load_point_cloud_wushi_new.py \"{}\"", + "description": "巫师3 - python load_point_cloud_wushi_new.py" + }, + "孤岛危机": { + "command": "python load_point_cloud_crysis_new.py \"{}\"", + "description": "孤岛危机 - python load_point_cloud_crysis_new.py" + }, + "地平线零之曙光": { + "command": "python load_point_horizon_new.py \"{}\"", + "description": "地平线零之曙光 - python load_point_horizon_new.py" + }, + "原子之心": { + "command": "python debug_atomicHeart.py -d \"{}\"", + "description": "原子之心 - python debug_atomicHeart.py -d" + }, + "巴士模拟18": { + "command": "python debug_bus18.py -d \"{}\"", + "description": "巴士模拟18 - python debug_bus18.py -d" + }, + "超自然车旅": { + "command": "python debug_pacificdrive.py -d \"{}\"", + "description": "超自然车旅 - python debug_pacificdrive.py -d" + }, + "印蒂卡": { + "command": "python debug_indika.py -d \"{}\"", + "description": "印蒂卡 - python debug_indika.py -d" + }, + "黑神话:悟空": { + "command": "python restruct_b1.py --data-dir \"{}\"", + "description": "黑神话:悟空 - python restruct_b1.py --data-dir" + }, + "新游戏选项": { + "command": "conda activate worldgen && E: && cd datacheck && run.bat \"{}\"", + "description": "新游戏选项 - E: && cd datacheck && run.bat" + } +} + +# MeshLab路径 +MESHLAB_PATH = r"C:\Program Files\VCG\MeshLab\meshlab.exe" + +# 输出目录 +OUTPUT_DIR = r"E:\datacheck\outputs" + +# =============== 结束配置 =============== + +class FolderPlayerApp: + def __init__(self, root): + self.root = root + self.root.title("CV Saved Folders Player") + self.root.geometry("700x250") # 增加窗口宽度以适应新按钮 + self.root.resizable(False, False) + + self.selected_game = tk.StringVar(value="赛博朋克2077") + self.scan_folders() + + if not self.folders: + messagebox.showerror("错误", f"在路径 {SCAN_PATH} 中没有找到以'cv_saved_'或'25'为前缀的文件夹") + sys.exit(1) + + self.current_index = 0 + self.process = None + self.last_scan_time = time.time() + + self.create_ui() + self.update_display() + self.start_detection_timer() + self._force_focus_timer() + + def _force_focus_timer(self): + self.root.focus_force() + self.root.after(500, self._force_focus_timer) + + def start_detection_timer(self): + self.check_new_folders() + self.root.after(DETECTION_INTERVAL * 1000, self.start_detection_timer) + + def check_new_folders(self): + current_dir = SCAN_PATH + try: + all_items = os.listdir(current_dir) + except Exception as e: + print(f"检测新文件夹时出错: {e}") + return + + processed_folders = set() + for filename in ["passed_files.txt", "rejected_files.txt"]: + if os.path.exists(filename): + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line and not line.startswith('#'): + processed_folders.add(line) + + new_folders = [ + os.path.join(current_dir, item) + for item in all_items + if ((item.startswith("cv_saved_") or item.startswith("25")) and + os.path.isdir(os.path.join(current_dir, item)) and + item not in processed_folders and + os.path.join(current_dir, item) not in self.folders) + ] + + if new_folders: + new_folders.sort() + was_empty = len(self.folders) == 0 + self.folders.extend(new_folders) + + if was_empty and self.folders: + self.current_index = 0 + self.update_display() + + messagebox.showinfo("提示", f"检测到 {len(new_folders)} 个新文件夹,已添加到列表") + + def scan_folders(self): + current_dir = SCAN_PATH + try: + all_items = os.listdir(current_dir) + except FileNotFoundError: + messagebox.showerror("错误", f"指定的扫描路径不存在: {SCAN_PATH}") + sys.exit(1) + except PermissionError: + messagebox.showerror("错误", f"没有权限访问指定路径: {SCAN_PATH}") + sys.exit(1) + + processed_folders = set() + for filename in ["passed_files.txt", "rejected_files.txt"]: + if os.path.exists(filename): + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line and not line.startswith('#'): + processed_folders.add(line) + + self.folders = [ + os.path.join(current_dir, item) + for item in all_items + if ((item.startswith("cv_saved_") or item.startswith("25")) and + os.path.isdir(os.path.join(current_dir, item)) and + item not in processed_folders) + ] + self.folders.sort() + + def create_ui(self): + game_frame = tk.Frame(self.root) + game_frame.pack(fill=tk.X, padx=10, pady=5) + + tk.Label(game_frame, text="选择游戏:", font=("Arial", 10)).pack(side=tk.LEFT) + game_dropdown = tk.OptionMenu(game_frame, self.selected_game, *GAME_COMMANDS.keys()) + game_dropdown.pack(side=tk.LEFT, padx=5) + + self.status_label = tk.Label(self.root, text="", pady=5) + self.status_label.pack() + + self.folder_label = tk.Label(self.root, text="", font=("Arial", 12), fg="blue") + self.folder_label.pack() + + button_frame = tk.Frame(self.root) + button_frame.pack(side=tk.BOTTOM, pady=20) + + self.prev_button = tk.Button( + button_frame, + text="上一条(<-)", + width=10, + command=self.prev_folder, + state=tk.DISABLED if self.current_index == 0 else tk.NORMAL + ) + self.prev_button.pack(side=tk.LEFT, padx=5) + + self.play_button = tk.Button( + button_frame, + text="播放(空格)", + width=10, + command=self.play_current + ) + self.play_button.pack(side=tk.LEFT, padx=5) + + self.pass_button = tk.Button( + button_frame, + text="通过(p)", + width=10, + command=self.mark_as_passed, + bg="green", + fg="white" + ) + self.pass_button.pack(side=tk.LEFT, padx=5) + + self.reject_button = tk.Button( + button_frame, + text="不通过(r)", + width=10, + command=self.mark_as_rejected, + bg="red", + fg="white" + ) + self.reject_button.pack(side=tk.LEFT, padx=5) + + self.next_button = tk.Button( + button_frame, + text="下一条(->)", + width=10, + command=self.next_folder, + state=tk.DISABLED if self.current_index == len(self.folders) - 1 else tk.NORMAL + ) + self.next_button.pack(side=tk.LEFT, padx=5) + + # 新增打开pts.ply文件按钮 + self.open_ply_button = tk.Button( + button_frame, + text="打开PLY(o)", + width=10, + command=self.open_ply_file, + bg="orange", + fg="white" + ) + self.open_ply_button.pack(side=tk.LEFT, padx=5) + + self.root.bind('', lambda event: self.prev_folder()) + self.root.bind('', lambda event: self.next_folder()) + self.root.bind('', lambda event: self.play_current()) + self.root.bind('

', lambda event: self.mark_as_passed()) + self.root.bind('', lambda event: self.mark_as_rejected()) + self.root.bind('', lambda event: self.open_ply_file()) # 绑定o键 + + def update_display(self): + if not self.folders: + self.status_label.config(text="所有文件夹已处理完毕") + self.folder_label.config(text="") + return + + current_path = self.folders[self.current_index] + folder_name = os.path.basename(current_path) + + self.status_label.config(text=f"正在处理: 第 {self.current_index + 1}/{len(self.folders)} 个") + self.folder_label.config(text=folder_name) + + self.prev_button.config(state=tk.NORMAL if self.current_index > 0 else tk.DISABLED) + self.next_button.config(state=tk.NORMAL if self.current_index < len(self.folders) - 1 else tk.DISABLED) + + def prev_folder(self): + if self.current_index > 0: + self.current_index -= 1 + self.update_display() + + def next_folder(self): + if self.current_index < len(self.folders) - 1: + self.current_index += 1 + self.update_display() + + def play_current(self): + if not self.folders: + return + + current_path = self.folders[self.current_index] + selected_game_name = self.selected_game.get() + game_config = GAME_COMMANDS.get(selected_game_name) + + if not game_config: + messagebox.showerror("错误", f"未找到游戏 {selected_game_name} 的配置") + return + + try: + self.close_command_prompt() + time.sleep(0.5) + + if selected_game_name == "新游戏选项": + # 执行新游戏选项命令(不使用COMMANDS_TEMPLATE) + game_command = game_config["command"] + formatted_command = game_command.format(current_path) + + # 启动命令(不等待) + self.process = subprocess.Popen(f'start cmd /k "{formatted_command}"', shell=True) + else: + # 其他游戏使用原有的COMMANDS_TEMPLATE + game_command = game_config["command"] + formatted_command = game_command.format(current_path) + commands = COMMANDS_TEMPLATE.copy() + commands.append(formatted_command) + full_command = " && ".join(commands) + self.process = subprocess.Popen(f'start cmd /k "{full_command}"', shell=True) + + except Exception as e: + messagebox.showerror("错误", f"执行命令时出错:\n{str(e)}") + + def open_ply_file(self): + """打开output目录中的pts.ply文件""" + ply_file = os.path.join(OUTPUT_DIR, "pts.ply") + + if os.path.exists(ply_file): + try: + subprocess.Popen([MESHLAB_PATH, ply_file]) + messagebox.showinfo("成功", f"已用MeshLab打开文件: {ply_file}") + except Exception as e: + messagebox.showerror("错误", f"打开文件时出错:\n{str(e)}") + else: + messagebox.showwarning("警告", f"未找到文件: {ply_file}") + + def close_command_prompt(self): + try: + if self.process: + self.process.terminate() + + subprocess.Popen('taskkill /f /im cmd.exe', shell=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + creationflags=subprocess.CREATE_NO_WINDOW) + except Exception as e: + print(f"关闭命令行时出错: {e}") + + def mark_as_passed(self): + self._mark_file("passed_files.txt") + + def mark_as_rejected(self): + self._mark_file("rejected_files.txt") + + def _mark_file(self, filename): + if not self.folders: + return + + current_path = self.folders[self.current_index] + folder_name = os.path.basename(current_path) + + try: + if not os.path.exists(filename): + with open(filename, 'w', encoding='utf-8') as f: + pass + + with open(filename, 'a', encoding='utf-8') as f: + if os.path.getsize(filename) == 0: + f.write(f"{folder_name}\n") + else: + needs_newline = True + with open(filename, 'rb+') as f_check: + f_check.seek(-1, os.SEEK_END) + last_char = f_check.read(1) + needs_newline = (last_char != b'\n') + + if needs_newline: + f.write('\n') + f.write(f"{folder_name}\n") + + status = "通过" if filename == "passed_files.txt" else "不通过" + messagebox.showinfo("成功", f"已标记为{status}: {folder_name}") + + self.folders.pop(self.current_index) + + if self.current_index >= len(self.folders): + if len(self.folders) > 0: + self.current_index = len(self.folders) - 1 + else: + messagebox.showinfo("完成", "所有文件夹已处理完毕!") + self.update_display() + return + + self.update_display() + + except Exception as e: + messagebox.showerror("错误", f"写入文件时出错:\n{str(e)}") + + +if __name__ == "__main__": + root = tk.Tk() + app = FolderPlayerApp(root) + root.mainloop() \ No newline at end of file diff --git a/战神修改文件名.py b/战神修改文件名.py new file mode 100644 index 0000000..dd994ba --- /dev/null +++ b/战神修改文件名.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +""" +fix_commas_and_pad_multi.py +批量修复多个文件夹中含逗号的数字文件名并补齐到6位,例如: + "01,000.png" -> "001000.png" +支持扫描指定文件夹中所有以"25"开头的子文件夹 +""" + +import os +import argparse + +VALID_EXTS = {'.png', '.exr'} + +def safe_rename(src, dst, dry_run=False): + if os.path.exists(dst): + print(f"[跳过] 目标已存在,未重命名:{os.path.basename(src)} -> {os.path.basename(dst)}") + return False + print(f"[重命名] {os.path.basename(src)} -> {os.path.basename(dst)}") + if not dry_run: + os.rename(src, dst) + return True + +def fix_folder(root_folder, dry_run=False): + changed = 0 + for root, dirs, files in os.walk(root_folder): + for fname in files: + name, ext = os.path.splitext(fname) + if ext.lower() not in VALID_EXTS: + continue + + # 若文件名中含逗号,则移除逗号 + if ',' in name: + candidate = name.replace(',', '') + else: + candidate = None + + if candidate is None or not candidate.isdigit(): + continue + + padded = candidate.zfill(6) + if padded == name: + continue + + src_path = os.path.join(root, fname) + dst_path = os.path.join(root, padded + ext) + if safe_rename(src_path, dst_path, dry_run=dry_run): + changed += 1 + + print(f"[完成] {root_folder} 共重命名 {changed} 个文件。\n") + +def find_25_folders(root_folder): + """查找所有以'25'开头的子文件夹""" + folders = [] + for item in os.listdir(root_folder): + item_path = os.path.join(root_folder, item) + if os.path.isdir(item_path) and item.startswith('25'): + folders.append(item_path) + return folders + +def main(): + p = argparse.ArgumentParser( + description="修复含逗号的数字文件名并补齐到6位,例如 01,000.png -> 001000.png(扫描指定文件夹中所有以25开头的子文件夹)" + ) + p.add_argument("root_folder", help="包含多个以25开头的子文件夹的根目录") + p.add_argument("--dry-run", action="store_true", help="仅模拟显示将要做的重命名,不执行") + args = p.parse_args() + + if not os.path.isdir(args.root_folder): + print(f"❌ 路径无效:{args.root_folder}") + return + + target_folders = find_25_folders(args.root_folder) + if not target_folders: + print(f"❌ 在 {args.root_folder} 中未找到以'25'开头的子文件夹") + return + + print(f"\n=== 在 {args.root_folder} 中找到 {len(target_folders)} 个以'25'开头的子文件夹 ===") + + for folder in target_folders: + print(f"\n=== 处理文件夹:{folder} ===") + fix_folder(folder, dry_run=args.dry_run) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/脚本管理器.py b/脚本管理器.py new file mode 100644 index 0000000..d4250fa --- /dev/null +++ b/脚本管理器.py @@ -0,0 +1,61 @@ +import os +import subprocess +from PyQt5.QtWidgets import (QApplication, QMainWindow, QListWidget, + QVBoxLayout, QLineEdit, QMessageBox, QWidget) +from PyQt5.QtCore import Qt + +class ScriptRunner(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Python脚本启动器") + self.setGeometry(100, 100, 600, 400) + self.script_dir = r"D:\test\集合" + self.init_ui() + self.load_scripts() + + def init_ui(self): + central = QWidget() + layout = QVBoxLayout(central) + + self.search_box = QLineEdit() + self.search_box.setPlaceholderText("搜索脚本...") + self.search_box.textChanged.connect(self.filter_scripts) + layout.addWidget(self.search_box) + + self.script_list = QListWidget() + self.script_list.itemDoubleClicked.connect(self.run_script) + layout.addWidget(self.script_list) + + self.setCentralWidget(central) + + def load_scripts(self): + self.script_list.clear() + if os.path.exists(self.script_dir): + for f in os.listdir(self.script_dir): + if f.endswith('.py'): + self.script_list.addItem(f) + else: + QMessageBox.warning(self, "错误", f"目录不存在: {self.script_dir}") + + def filter_scripts(self): + keyword = self.search_box.text().lower() + for i in range(self.script_list.count()): + item = self.script_list.item(i) + item.setHidden(keyword not in item.text().lower()) + + def run_script(self, item): + script_name = item.text() + script_path = os.path.join(self.script_dir, script_name) + + try: + # 使用subprocess在新窗口中运行 + subprocess.Popen(f'start cmd /k python "{script_path}"', shell=True) + self.statusBar().showMessage(f"正在运行: {script_name}") + except Exception as e: + QMessageBox.critical(self, "错误", f"运行失败: {str(e)}") + +if __name__ == "__main__": + app = QApplication([]) + window = ScriptRunner() + window.show() + app.exec_() \ No newline at end of file diff --git a/转移文件non.py b/转移文件non.py new file mode 100644 index 0000000..d68f5c7 --- /dev/null +++ b/转移文件non.py @@ -0,0 +1,167 @@ +import os +import time +import shutil +import subprocess +from pathlib import Path +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +import concurrent.futures +import threading + +# ====== 配置 ====== +SOURCE_FOLDER = Path(r"E:\ELDEN RING\Game\cv_saved") # 监控的源目录 +DEST_FOLDER = Path(r"E:") # 目标目录(必须是具体路径) +FOLDER_PREFIX = "25" # 需要监控的文件夹前缀 +MAX_WORKERS = 8 # 最大线程数 +STABLE_TIME = 30 # 文件夹稳定检查时间(秒) +# ================= + +def is_folder_stable(folder_path): + """检查文件夹是否稳定(连续两次修改时间相同)""" + try: + initial_mtime = os.path.getmtime(folder_path) + time.sleep(STABLE_TIME) + current_mtime = os.path.getmtime(folder_path) + return initial_mtime == current_mtime + except Exception as e: + print(f" [错误] 检查稳定性失败: {e}") + return False + +def process_folder(folder_path): + """使用 robocopy 移动文件夹""" + start_time = time.time() + folder_name = folder_path.name + + print(f"\n[处理] 开始移动文件夹: '{folder_name}'") + print(f" 源路径: {folder_path}") + print(f" 目标路径: {DEST_FOLDER / folder_name}") + + try: + # 检查目标目录权限 + if not os.access(DEST_FOLDER, os.W_OK): + raise PermissionError(f"无写入权限: {DEST_FOLDER}") + + # 确保目标目录存在 + DEST_FOLDER.mkdir(parents=True, exist_ok=True) + + # 构造 robocopy 命令 + cmd = [ + "robocopy", + str(folder_path), # 源目录 + str(DEST_FOLDER / folder_name), # 目标目录 + "/MIR", # 镜像目录树 + "/MT:8", # 多线程(8线程) + "/R:2", # 失败重试2次 + "/W:5", # 重试等待5秒 + "/NFL", "/NDL", "/NJH", "/NJS" # 减少日志输出 + ] + + print(f" 执行命令: {' '.join(cmd)}") + result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8') + + # 处理 robocopy 返回码 + if result.returncode >= 8: + raise RuntimeError(f"Robocopy 失败 (代码 {result.returncode}): {result.stderr}") + + # 删除源目录(robocopy /MIR 已处理,此处作为二次确认) + if (DEST_FOLDER / folder_name).exists(): + print(f" 验证: 目标文件夹已存在,正在清理源文件夹...") + shutil.rmtree(folder_path, ignore_errors=True) + print(f" [完成] '{folder_name}' 移动成功") + else: + raise FileNotFoundError("目标文件夹未创建,终止删除源文件夹") + + except Exception as e: + print(f" [错误] 处理失败: {e}") + finally: + duration = time.time() - start_time + print(f" 耗时: {duration:.2f} 秒") + +class FolderMonitor(FileSystemEventHandler): + def __init__(self, executor): + super().__init__() + self.executor = executor + self.active_tasks = set() # 跟踪正在处理的任务 + + def on_created(self, event): + """处理新创建的文件夹""" + self._handle_folder(event.src_path, "创建") + + def on_moved(self, event): + """处理移动/重命名的文件夹""" + self._handle_folder(event.dest_path, "移动") + + def _handle_folder(self, path, action_type): + """统一处理文件夹事件""" + folder_path = Path(path) + if not folder_path.is_dir(): + return + + if folder_path.parent != SOURCE_FOLDER or not folder_path.name.startswith(FOLDER_PREFIX): + return + + print(f"\n[发现] 通过 {action_type} 事件检测到文件夹: '{folder_path.name}'") + + # 避免重复处理 + if folder_path.name in self.active_tasks: + print(f" 警告: 文件夹 '{folder_path.name}' 已在处理中") + return + + self.active_tasks.add(folder_path.name) + self.executor.submit(self._process_with_stability_check, folder_path) + + def _process_with_stability_check(self, folder_path): + """带稳定性检查的处理流程(无限等待直到稳定)""" + folder_name = folder_path.name + + try: + while True: + if is_folder_stable(folder_path): + print(f" [稳定] 文件夹 '{folder_name}' 已稳定,开始移动") + process_folder(folder_path) + break + else: + print(f" [监控] 文件夹 '{folder_name}' 仍在修改中,继续等待...") + except Exception as e: + print(f" [错误] 处理 '{folder_name}' 时发生异常: {e}") + finally: + self.active_tasks.discard(folder_name) + +def main(): + print("\n===== 文件夹监控转移程序 =====") + print(f"监控目录: {SOURCE_FOLDER}") + print(f"目标目录: {DEST_FOLDER}") + print(f"文件夹前缀: '{FOLDER_PREFIX}'") + print(f"稳定性检查间隔: {STABLE_TIME}秒") + print(f"最大线程数: {MAX_WORKERS}\n") + + # 验证目录 + if not SOURCE_FOLDER.is_dir(): + print(f"[错误] 源目录不存在: {SOURCE_FOLDER}") + return + try: + DEST_FOLDER.mkdir(parents=True, exist_ok=True) + except Exception as e: + print(f"[错误] 无法创建目标目录: {e}") + return + + # 启动监控 + with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: + event_handler = FolderMonitor(executor) + observer = Observer() + observer.schedule(event_handler, str(SOURCE_FOLDER), recursive=False) + observer.start() + print("[系统] 监控已启动 (按 Ctrl+C 停止)...") + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + print("\n[系统] 正在停止监控...") + finally: + observer.stop() + observer.join() + print("[系统] 监控已停止") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/递归删除文件夹.py b/递归删除文件夹.py new file mode 100644 index 0000000..1a2c5d3 --- /dev/null +++ b/递归删除文件夹.py @@ -0,0 +1,146 @@ +import os +import shutil +import argparse + +# =============== 全局路径配置 =============== +REJECT_ROOT = r"D:\videos" # 包含rejected_files.txt的根路径 +DELETE_ROOT = "E:" # 要删除文件夹的根路径 + + +# =============== 结束配置 =============== + +def find_rejected_files(root_path): + """在指定根路径下递归查找所有名为rejected_files.txt的文件""" + rejected_files = [] + for root, dirs, files in os.walk(root_path): + if 'rejected_files.txt' in files: + rejected_files.append(os.path.join(root, 'rejected_files.txt')) + return rejected_files + + +def read_folder_names(file_paths): + """读取多个TXT文件中的文件夹名称,提取'] '后的字符(严格去空格,确保名称完全匹配)""" + folder_names = [] + unique_names = set() # 用于去重 + + for file_path in file_paths: + if not os.path.exists(file_path): + print(f"警告:文件 {file_path} 不存在,已跳过") + continue + + with open(file_path, 'r', encoding='utf-8') as f: + for line_num, line in enumerate(f, 1): + raw_line = line.strip() + if not raw_line or raw_line.startswith('#'): + continue + + if "] " in raw_line: + folder_name = raw_line.split("] ", 1)[1].strip() + else: + folder_name = raw_line.strip() + + if folder_name and folder_name not in unique_names: + unique_names.add(folder_name) + folder_names.append(folder_name) + print(f"从 {os.path.basename(file_path)} 提取有效名称(第{line_num}行):'{folder_name}'") + elif not folder_name: + print(f"警告:{os.path.basename(file_path)} 第{line_num}行提取后为空,已忽略(原始内容:{raw_line})") + + return folder_names + + +def find_matching_folders_recursively(root_path, target_names): + """在指定根路径下递归查找所有名称与目标列表完全一致的文件夹""" + matching_folders = [] + for root, dirs, files in os.walk(root_path): + for dir_name in dirs: + if dir_name in target_names: + folder_path = os.path.join(root, dir_name) + matching_folders.append(folder_path) + return matching_folders + + +def delete_matching_folders(root_path, names_to_delete): + """递归删除指定根路径下所有名称与不通过列表完全一致的文件夹""" + if not os.path.isdir(root_path): + print(f"错误:源路径 {root_path} 不是有效目录,无法删除") + return 0 + + folders_to_delete = find_matching_folders_recursively(root_path, names_to_delete) + + if not folders_to_delete: + print("没有找到匹配的文件夹需要删除") + return 0 + + print("\n找到以下匹配的文件夹:") + for i, folder in enumerate(folders_to_delete, 1): + print(f"{i}. {folder}") + + # 询问用户确认删除 + while True: + user_input = input("\n确定要删除以上所有文件夹吗?(y/n): ").strip().lower() + if user_input == 'y': + break + elif user_input == 'n': + print("取消删除操作") + return 0 + else: + print("请输入 'y' 或 'n'") + + deleted = 0 + for folder_path in folders_to_delete: + try: + shutil.rmtree(folder_path) + deleted += 1 + print(f"删除成功:{folder_path}") + except Exception as e: + print(f"删除失败 {folder_path}:{str(e)}") + + print(f"\n共成功删除 {deleted} 个文件夹(在 {root_path} 下递归查找)") + return deleted + + +def main(): + parser = argparse.ArgumentParser(description='根据rejected_files.txt删除匹配的文件夹') + parser.add_argument('--reject_root', default=REJECT_ROOT, + help=f'包含rejected_files.txt的根路径(默认:{REJECT_ROOT})') + parser.add_argument('--delete_root', default=DELETE_ROOT, + help=f'要删除文件夹的根路径(默认:{DELETE_ROOT})') + + args = parser.parse_args() + + print("=" * 60) + print("操作说明:") + print(f"1. 将在 {args.reject_root} 下递归查找所有 rejected_files.txt 文件") + print(f"2. 收集这些文件中的所有条目作为要删除的文件夹名称") + print(f"3. 在 {args.delete_root} 下递归查找并删除匹配的文件夹") + print("=" * 60) + + # 查找所有rejected_files.txt文件 + rejected_files = find_rejected_files(args.reject_root) + if not rejected_files: + print(f"\n错误:在 {args.reject_root} 下未找到任何 rejected_files.txt 文件") + return + + print(f"\n找到 {len(rejected_files)} 个 rejected_files.txt 文件:") + for file in rejected_files: + print(f" - {file}") + + # 读取所有不通过的文件夹名称 + print("\n----- 读取不通过列表 -----") + fail_folders = read_folder_names(rejected_files) + if not fail_folders: + print("\n错误:未从rejected_files.txt文件中提取到任何有效文件夹名称") + return + + print(f"\n共提取到 {len(fail_folders)} 个需要删除的文件夹名称") + + # 在删除根路径下递归查找并删除匹配的文件夹 + print(f"\n----- 在 {args.delete_root} 中执行删除操作 -----") + total_deleted = delete_matching_folders(args.delete_root, fail_folders) + + print(f"\n总共成功删除 {total_deleted} 个文件夹") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/鼠标旋转往左移动.py b/鼠标旋转往左移动.py new file mode 100644 index 0000000..d5f94e9 --- /dev/null +++ b/鼠标旋转往左移动.py @@ -0,0 +1,82 @@ +import pyautogui +import time +import keyboard +import random + +# =========================== +# 🎮 配置区域(可调参数) +# =========================== + +pyautogui.FAILSAFE = True # 启用安全机制:鼠标快速移到左上角可紧急停止! + +SCREEN_WIDTH, SCREEN_HEIGHT = pyautogui.size() +SCREEN_HEIGHT_MIDDLE = SCREEN_HEIGHT // 2 # 鼠标保持在屏幕竖直中央(Y轴不变) + +MOVE_STEP = 50 * 2 # 每次移动的像素距离(越小越慢越自然,比如 1~3) +MOVE_DURATION = 0.5 * 2 * 2 # 每次移动持续时间(单位:秒,越大越慢越平滑,推荐 0.2~1.0) +MOVE_DELAY = 0 # 每次移动后的停顿时间(可调整,推荐 0.02~0.1) +USE_RANDOM_OFFSET = False # 是否加入随机偏移(你不需要,保持 False 即可) +CONTROL_KEY = 'F1' # 控制鼠标移动的快捷键(可修改为F1-F12或其他键) + +# =========================== +# 🧠 状态控制 +# =========================== + +is_moving_left = False # 控制是否正在向左移动鼠标 + +# =========================== +# 🖱️ 鼠标向左移动函数 +# =========================== + +def auto_move_left(): + global is_moving_left + print(f"🖱️ 【鼠标自动左移】已启动:鼠标将缓慢向左移动...") + + while is_moving_left: + current_x, current_y = pyautogui.position() # 获取当前鼠标 x, y 坐标 + target_x = max(current_x - MOVE_STEP, 0) # 往左移动,但不能小于 0(屏幕最左边) + target_y = current_y # Y 坐标保持不变,保持在屏幕中间或当前位置 + + # 移动鼠标到目标位置(向左),使用平滑动画 + pyautogui.moveTo(target_x, target_y, duration=MOVE_DURATION) + + # 可选的停顿,模拟自然观察 + time.sleep(MOVE_DELAY) + + print(f"🛑 【鼠标自动左移】已停止。") + +# =========================== +# ⌨️ 按键监听:启动 / 停止 左移控制 +# =========================== + +def toggle_left_movement(): + global is_moving_left + if is_moving_left: + is_moving_left = False + print(f"⏹️ {CONTROL_KEY} 被按下:鼠标左移已停止。") + else: + is_moving_left = True + print(f"▶️ {CONTROL_KEY} 被按下:鼠标开始缓慢向左移动!") + # 在新线程中运行,避免阻塞主线程和键盘监听 + import threading + threading.Thread(target=auto_move_left, daemon=True).start() + +# 注册热键监听 +keyboard.add_hotkey(CONTROL_KEY, toggle_left_movement) + +# =========================== +# 🚀 启动脚本 & 保持运行 +# =========================== + +print("🎮 脚本已启动!") +print(f"🔘 按下 {CONTROL_KEY} 键:启动 / 停止 鼠标缓慢向左移动") +print("💡 鼠标会从当前位置开始,向左缓缓移动,不会重置到屏幕边缘") +print("⚠️ 安全提示:你仍然可以用鼠标快速移动到屏幕左上角来紧急停止(FAILSAFE 机制)") +print("ℹ️ 当前设置:缓慢、平滑、自然向左移动,无随机抖动") + +try: + keyboard.wait() # 保持程序运行,监听按键 +except KeyboardInterrupt: + print("\n🛑 脚本已通过 Ctrl+C 停止。") +finally: + is_moving_left = False # 确保退出时停止移动 \ No newline at end of file