import os import re import argparse import shutil from datetime import datetime # ====================== 【配置区】 ====================== LOG_FILE = "./rename_log.txt" # 需要完全跳过的目录名称(basename,大小写敏感) SKIP_DIRS = [ "@eaDir", # 群晖隐含目录 ] # 需要跳过的隐含文件名称(完整文件名匹配) SKIP_FILES = { ".DS_Store", "Thumbs.db", "desktop.ini", } # 文件清洗规则 RULES_FILE = [ (r"^[A-Za-z] ", ""), (r"\[.*?\]", ""), (r"【.*?】", ""), (r"丨", ""), (r"(? str: name_no_ext, ext = os.path.splitext(name) name_no_ext = full2half(name_no_ext) for item in RULES_FILE: if len(item) == 3: pat, repl, flag = item name_no_ext = re.sub(pat, repl, name_no_ext, flags=flag) else: pat, repl = item name_no_ext = re.sub(pat, repl, name_no_ext) name_no_ext = name_no_ext.strip() if len(name_no_ext) > MAX_FILENAME_LEN: name_no_ext = name_no_ext[:MAX_FILENAME_LEN] return name_no_ext + ext def clean_dir_name(name: str) -> str: name = full2half(name) year_match = re.search(r"^\[(\d{4})\]$", name) year_str = "" if year_match: year_str = year_match.group(1) for item in RULES_DIR: if len(item) == 3: pat, repl, flag = item name = re.sub(pat, repl, name, flags=flag) else: pat, repl = item name = re.sub(pat, repl, name) name = name.strip() if year_str and name: name = f"{name}({year_str})" if len(name) > MAX_FILENAME_LEN: name = name[:MAX_FILENAME_LEN] return name def get_quality_tag(filename:str): m = re.search(r"(? {target_dir}") return #初始化日志 try: with open(log_path, "w", encoding="utf-8") as f: f.flush() except Exception as e: print(f"警告:无法初始化日志文件 {log_path},后续仅控制台输出日志,异常:{e}") write_log("===== 文件名+文件夹清洗【修复参数符号,过滤BD/HD/4K/双语/国语等标记】 =====", log_path) write_log(f"源目录:{target_dir}", log_path) write_log(f"输出镜像目录:{out_dir}", log_path) write_log(f"运行模式:{mode_info}", log_path) write_log(f"预览模式 DRY_RUN = {DRY_RUN}", log_path) if SKIP_DIRS: write_log(f"配置跳过目录名列表:{SKIP_DIRS}", log_path) write_log(f"过滤隐含系统文件:{SKIP_FILES}", log_path) stat = { "scan_dir_total":0, "scan_file_total":0, "media_file_total":0, "file_success":0, "file_fail":0, "dir_success":0, "dir_fail":0 } try: for root, dirs, files in os.walk(target_dir, topdown=False): #过滤跳过目录:@eaDir、配置SKIP_DIRS remove_idx = [] for idx, dname in enumerate(dirs): if dname in SKIP_DIRS: remove_idx.append(idx) for idx in reversed(remove_idx): del dirs[idx] stat["scan_dir_total"] += 1 rel = os.path.relpath(root, target_dir) if rel == ".": dst_current_dir = out_dir else: src_basename = os.path.basename(root) new_dir_name = clean_dir_name(src_basename) if len(new_dir_name) == 0: write_log(f"【警告】文件夹清洗后名称为空,使用原名保留:{src_basename}", log_path) new_dir_name = src_basename dst_current_dir = os.path.join(out_dir, rel.replace(src_basename, new_dir_name)) # 创建输出目录 if not DRY_RUN: try: os.makedirs(dst_current_dir, exist_ok=True) stat["dir_success"] +=1 except OSError as e: stat["dir_fail"] +=1 write_log(f"【目录创建失败】{dst_current_dir} | {e}", log_path) # 处理本目录全部文件 for fname in files: if fname in SKIP_FILES: continue stat["scan_file_total"] +=1 ext = os.path.splitext(fname)[1].lower() if ext in ALLOW_EXT: stat["media_file_total"] += 1 new_name = clean_file_name(fname) old_path = os.path.join(root, fname) base, ext_out = os.path.splitext(new_name) final_fname = resolve_conflict(dst_current_dir, base, ext_out, fname) dst_file = os.path.join(dst_current_dir, final_fname) if final_fname != fname: write_log(f"待修改文件-> {fname} → {final_fname}", log_path) if not DRY_RUN: try: shutil.copy2(old_path, dst_file) stat["file_success"] +=1 except OSError as e: stat["file_fail"] +=1 write_log(f"【文件复制失败】src:{old_path} dst:{dst_file} | 错误:{e}", log_path) # 处理子文件夹,只打印名称发生变更的文件夹 for dname in dirs: new_name = clean_dir_name(dname) if len(new_name) == 0: write_log(f"【警告】文件夹清洗后名称为空,跳过改名:{dname}", log_path) continue if new_name != dname: write_log(f"待修改文件夹-> {dname} → {new_name}", log_path) except OSError as walk_err: write_log(f"【严重】遍历目录发生IO异常,部分目录未扫描:{walk_err}", log_path) write_log("----------任务统计汇总----------", log_path) write_log(f"总扫描目录:{stat['scan_dir_total']}", log_path) write_log(f"总扫描文件:{stat['scan_file_total']},媒体文件:{stat['media_file_total']}", log_path) write_log(f"目录:成功创建 {stat['dir_success']},失败 {stat['dir_fail']}", log_path) write_log(f"文件:成功复制 {stat['file_success']},失败 {stat['file_fail']}", log_path) write_log("============================================================", log_path) if __name__ == "__main__": main()