#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 图片资源批量处理脚本 用法: python3 2_rename_assets.py """ import os import json import hashlib import shutil from pathlib import Path from typing import Dict, List, Set import re # 颜色输出 class Colors: GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' BLUE = '\033[94m' END = '\033[0m' # 配置 PROJECT_ROOT = "/Users/edwinqqq/Local/Company Projects/peko-ios" ASSETS_DIR = os.path.join(PROJECT_ROOT, "YuMi/Assets.xcassets") SOURCE_DIR = os.path.join(PROJECT_ROOT, "YuMi") BACKUP_DIR = os.path.join(PROJECT_ROOT, "issues/temp_rename/assets_backup") MAPPING_FILE = os.path.join(PROJECT_ROOT, "issues/temp_rename/asset_name_mapping.json") # 存储映射关系 asset_mapping: Dict[str, str] = {} def print_header(text): """打印标题""" print(f"\n{Colors.GREEN}{'='*50}{Colors.END}") print(f"{Colors.GREEN}{text}{Colors.END}") print(f"{Colors.GREEN}{'='*50}{Colors.END}\n") def print_info(text): """打印信息""" print(f"{Colors.BLUE}ℹ️ {text}{Colors.END}") def print_warning(text): """打印警告""" print(f"{Colors.YELLOW}⚠️ {text}{Colors.END}") def print_error(text): """打印错误""" print(f"{Colors.RED}❌ {text}{Colors.END}") def print_success(text): """打印成功""" print(f"{Colors.GREEN}✓ {text}{Colors.END}") def generate_new_name(old_name: str, index: int) -> str: """ 生成新的资源名称 策略:使用语义化的随机名称,而不是完全随机 """ # 提取原名称中的关键信息 parts = old_name.lower().split('_') # 常见的UI元素映射 element_map = { 'icon': ['symbol', 'mark', 'sign', 'emblem'], 'button': ['btn', 'action', 'control', 'press'], 'background': ['bg', 'backdrop', 'layer', 'base'], 'avatar': ['profile', 'face', 'user', 'photo'], 'banner': ['header', 'top', 'promo', 'ad'], 'gift': ['present', 'reward', 'prize', 'bonus'], 'room': ['space', 'zone', 'area', 'hall'], 'home': ['main', 'index', 'start', 'feed'], 'message': ['msg', 'chat', 'talk', 'dialog'], 'mine': ['profile', 'me', 'personal', 'user'], 'login': ['signin', 'auth', 'entry', 'access'], 'arrow': ['pointer', 'indicator', 'direction', 'nav'], 'close': ['dismiss', 'exit', 'cancel', 'remove'], 'back': ['return', 'previous', 'prev', 'retreat'], 'next': ['forward', 'proceed', 'continue', 'advance'], 'play': ['start', 'run', 'launch', 'begin'], 'pause': ['stop', 'halt', 'freeze', 'wait'], 'more': ['additional', 'extra', 'plus', 'expand'], } # 尝试替换关键词 new_parts = [] for part in parts: replaced = False for key, alternatives in element_map.items(): if key in part: # 使用 index 作为种子选择一个替代词 alt_index = (index + hash(part)) % len(alternatives) new_part = part.replace(key, alternatives[alt_index]) new_parts.append(new_part) replaced = True break if not replaced: # 保留数字和常见后缀 if part.isdigit() or part in ['selected', 'normal', 'disabled', 'active', 'inactive']: new_parts.append(part) else: # 使用简单的字符替换 new_part = ''.join([chr((ord(c) - 97 + 13) % 26 + 97) if c.isalpha() else c for c in part]) new_parts.append(new_part) new_name = '_'.join(new_parts) # 如果生成的名称和原名称一样,添加一个后缀 if new_name == old_name: new_name = f"asset_{hashlib.md5(old_name.encode()).hexdigest()[:8]}" return new_name def backup_assets(): """备份原始资源""" print_header("第1步:备份原始资源") if os.path.exists(BACKUP_DIR): print_warning("备份目录已存在,将覆盖...") shutil.rmtree(BACKUP_DIR) print_info(f"正在备份 {ASSETS_DIR} 到 {BACKUP_DIR} ...") shutil.copytree(ASSETS_DIR, BACKUP_DIR) print_success(f"备份完成!") def scan_imagesets() -> List[str]: """扫描所有 .imageset 目录""" print_header("第2步:扫描图片资源") imagesets = [] for root, dirs, files in os.walk(ASSETS_DIR): for dir_name in dirs: if dir_name.endswith('.imageset'): full_path = os.path.join(root, dir_name) imagesets.append(full_path) print_success(f"找到 {len(imagesets)} 个图片资源") return imagesets def rename_imageset(imageset_path: str, index: int) -> tuple: """ 重命名单个 imageset 返回 (old_name, new_name) """ dir_name = os.path.basename(imageset_path) old_name = dir_name.replace('.imageset', '') # 跳过 AppIcon if 'AppIcon' in old_name: print_info(f"跳过 AppIcon: {old_name}") return (old_name, old_name) # 生成新名称 new_name = generate_new_name(old_name, index) new_dir_name = f"{new_name}.imageset" new_path = os.path.join(os.path.dirname(imageset_path), new_dir_name) # 重命名目录 try: os.rename(imageset_path, new_path) # 更新 Contents.json contents_json = os.path.join(new_path, 'Contents.json') if os.path.exists(contents_json): with open(contents_json, 'r', encoding='utf-8') as f: data = json.load(f) # 更新图片文件名引用 for image in data.get('images', []): if 'filename' in image: old_filename = image['filename'] if old_name in old_filename: new_filename = old_filename.replace(old_name, new_name) # 重命名实际的图片文件 old_file_path = os.path.join(new_path, old_filename) new_file_path = os.path.join(new_path, new_filename) if os.path.exists(old_file_path): os.rename(old_file_path, new_file_path) image['filename'] = new_filename # 写回 Contents.json with open(contents_json, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) return (old_name, new_name) except Exception as e: print_error(f"重命名失败 {old_name}: {str(e)}") return (old_name, old_name) def rename_all_imagesets(): """批量重命名所有图片资源""" print_header("第3步:批量重命名图片资源") imagesets = scan_imagesets() total = len(imagesets) success_count = 0 for index, imageset_path in enumerate(imagesets, 1): old_name, new_name = rename_imageset(imageset_path, index) if old_name != new_name: asset_mapping[old_name] = new_name success_count += 1 # 显示进度 if index % 100 == 0: print_info(f"进度: {index}/{total} ({index*100//total}%)") print_success(f"完成!成功重命名 {success_count} 个资源") # 保存映射关系 os.makedirs(os.path.dirname(MAPPING_FILE), exist_ok=True) with open(MAPPING_FILE, 'w', encoding='utf-8') as f: json.dump(asset_mapping, f, indent=2, ensure_ascii=False) print_success(f"映射关系已保存到: {MAPPING_FILE}") def update_code_references(): """更新代码中的图片资源引用""" print_header("第4步:更新代码引用") print_warning("这一步需要手动处理,因为图片引用方式多样") print_info("常见的引用方式:") print(" 1. [UIImage imageNamed:@\"old_name\"]") print(" 2. UIImage(named: \"old_name\")") print(" 3. @\"old_name\"") print("") # 生成替换脚本 replace_script = os.path.join(PROJECT_ROOT, "issues/temp_rename/replace_image_refs.sh") with open(replace_script, 'w') as f: f.write("#!/bin/bash\n") f.write("# 自动生成的图片引用替换脚本\n\n") f.write(f"SOURCE_DIR=\"{SOURCE_DIR}\"\n\n") for old_name, new_name in asset_mapping.items(): # 转义特殊字符 old_escaped = old_name.replace('"', '\\"') new_escaped = new_name.replace('"', '\\"') f.write(f"# {old_name} → {new_name}\n") f.write(f"find \"$SOURCE_DIR\" -type f \\( -name \"*.m\" -o -name \"*.h\" -o -name \"*.swift\" \\) -exec sed -i '' 's/@\"{old_escaped}\"/@\"{new_escaped}\"/g' {{}} +\n") f.write(f"find \"$SOURCE_DIR\" -type f \\( -name \"*.m\" -o -name \"*.h\" -o -name \"*.swift\" \\) -exec sed -i '' 's/\"{old_escaped}\"/\"{new_escaped}\"/g' {{}} +\n\n") os.chmod(replace_script, 0o755) print_success(f"替换脚本已生成: {replace_script}") print_warning("请手动执行该脚本进行代码替换") def generate_report(): """生成替换报告""" print_header("第5步:生成报告") report_file = os.path.join(PROJECT_ROOT, "issues/temp_rename/assets_rename_report.txt") with open(report_file, 'w', encoding='utf-8') as f: f.write("=" * 60 + "\n") f.write("图片资源批量重命名报告\n") f.write("=" * 60 + "\n\n") f.write(f"执行时间: {os.popen('date').read()}\n") f.write(f"总共处理: {len(asset_mapping)} 个资源\n\n") f.write("=" * 60 + "\n") f.write("重命名映射(前50个):\n") f.write("=" * 60 + "\n\n") count = 0 for old_name, new_name in asset_mapping.items(): f.write(f"{old_name:40} → {new_name}\n") count += 1 if count >= 50: f.write(f"\n... 还有 {len(asset_mapping) - 50} 个资源\n") break f.write("\n" + "=" * 60 + "\n") f.write("下一步操作:\n") f.write("=" * 60 + "\n\n") f.write("1. 执行生成的替换脚本更新代码引用\n") f.write("2. 在 Xcode 中清理并重新构建项目\n") f.write("3. 运行应用,检查所有图片是否正常显示\n") f.write("4. 使用 UI 测试验证关键页面\n\n") f.write("备份位置:\n") f.write(f" {BACKUP_DIR}\n\n") f.write("映射文件:\n") f.write(f" {MAPPING_FILE}\n\n") f.write("替换脚本:\n") f.write(f" {os.path.join(PROJECT_ROOT, 'issues/temp_rename/replace_image_refs.sh')}\n\n") print_success(f"报告已生成: {report_file}") # 打印简要统计 print("") print(f" 📊 总共重命名: {len(asset_mapping)} 个资源") print(f" 📁 备份位置: {BACKUP_DIR}") print(f" 📄 映射文件: {MAPPING_FILE}") print("") def main(): """主函数""" print_header("白牌项目 - 图片资源批量重命名工具") # 确认操作 print_warning("此脚本将重命名所有图片资源!") print_warning("请确保:") print(" 1. 已经运行了类名替换脚本") print(" 2. 已经提交了所有更改") print(" 3. 已经创建了新的 Git 分支") print("") confirm = input("确认继续?(y/N): ") if confirm.lower() != 'y': print("已取消操作") return try: # 执行步骤 backup_assets() rename_all_imagesets() update_code_references() generate_report() print_header("✅ 图片资源批量重命名完成!") print_warning("记得执行生成的替换脚本更新代码引用!") except Exception as e: print_error(f"执行过程中出错: {str(e)}") print_warning("你可以从备份恢复:") print(f" rm -rf {ASSETS_DIR}") print(f" cp -r {BACKUP_DIR} {ASSETS_DIR}") if __name__ == "__main__": main()