Wallpaper Engine 有一类网页壁纸,如图:
心血来潮想着备份一下,毕竟蛮多图都挺有艺术性(?)。正好发现这些壁纸目录有一定的规律,图片文件都是保存在 壁纸目录文件/directories/customdirectory/ 下,如图:
(壁纸来自:铁板烧鬼舞w 欧根亲王)
于是在DeepSeek的帮助下实现了备份文件的功能,线程池的使用对我来说有点超纲,毕竟没有深入接触过。代码就直接Ctrl+v过来了,如有错误和需要改善的地方还望指正。
"""
Wallpaper Engine 创意工坊壁纸批量导出工具
功能:多线程复制文件 + MD5重复校验
"""
import os
import shutil
import hashlib
from concurrent.futures import ThreadPoolExecutor # 线程池模块
import threading
# -------------------- 全局配置 --------------------
SOURCE_PATH = r'D:\steam\steamapps\workshop\content\431960' # Steam创意工坊壁纸根目录
TARGET_PATH = r'D:\wallpaper\images' # 目标输出目录
MAX_WORKERS = 4 # 线程池最大线程数(建议设为CPU核心数)
# -------------------- 全局状态 --------------------
IMAGES_PATH_DICT = {} # 存储壁纸ID与对应目录的映射 { "壁纸ID": "完整路径" }
existing_hashes = set() # 存储已存在文件的MD5哈希(用于去重)
lock = threading.Lock() # 线程锁(确保哈希集合操作的原子性)
def create_target_path():
"""创建目标目录(如果不存在)"""
if not os.path.exists(TARGET_PATH):
os.makedirs(TARGET_PATH) # 递归创建多级目录
def get_directories():
"""
扫描源目录结构,收集有效的壁纸目录
目录结构示例:
D:\steam\...\431960\123456\directories\customdirectory
"""
# 遍历创意工坊根目录下的所有子目录(每个子目录对应一个壁纸ID)
for item in os.listdir(SOURCE_PATH):
# 构建完整路径:SOURCE_PATH/item
item_dir = os.path.join(SOURCE_PATH, item)
# 检查是否存在 "directories" 子目录
directories_path = os.path.join(item_dir, 'directories')
if os.path.isdir(directories_path):
# 检查目标壁纸目录是否存在
custom_dir = os.path.join(directories_path, 'customdirectory')
if os.path.isdir(custom_dir):
# 记录有效路径:壁纸ID -> 自定义目录路径
IMAGES_PATH_DICT[item] = custom_dir
def calculate_file_hash(file_path):
"""
计算文件的MD5哈希值(用于重复校验)
参数:
file_path : 文件完整路径
返回:
32位十六进制哈希字符串
"""
hash_md5 = hashlib.md5() # 创建MD5哈希对象
with open(file_path, "rb") as f:
# 分块读取文件(避免大文件内存溢出)每次读取4KB 遇到空字节表示文件结束
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk) # 更新哈希值
return hash_md5.hexdigest() # 返回十六进制字符串
def copy_single_file(args):
"""
单个文件复制任务(线程池工作单元)
参数:
args : 元组 (壁纸ID, 源目录, 文件名)
"""
# 解包参数
item_id, custom_dir, filename = args
src_path = os.path.join(custom_dir, filename)
# 跳过非文件对象(如子目录)
if not os.path.isfile(src_path):
return
# ---------- 重复校验模块 ----------
file_hash = calculate_file_hash(src_path)
# 使用线程锁确保原子操作(防止哈希冲突)
with lock:
# 如果哈希已存在,跳过复制
if file_hash in existing_hashes:
print(f"跳过重复文件: {filename}")
return
# 记录新文件的哈希
existing_hashes.add(file_hash)
# ---------- 文件复制模块 ----------
# 生成带壁纸ID前缀的目标文件名(防重名)
dst_filename = f"{item_id}_{filename}"
dst_path = os.path.join(TARGET_PATH, dst_filename)
try:
# 执行文件复制(shutil.copy自动处理不同文件系统)
shutil.copy(src_path, dst_path)
print(f"成功复制: {dst_filename}")
except Exception as e:
# 捕获并打印异常(如权限不足、磁盘已满等)
print(f"复制失败 {filename}: {e}")
def copy_images():
"""主复制逻辑(包含多线程调度)"""
# ---------- 初始化阶段 ----------
# 预加载目标目录现有文件的哈希(实现增量备份)
if os.path.exists(TARGET_PATH):
print("正在加载已有文件哈希...")
for filename in os.listdir(TARGET_PATH):
file_path = os.path.join(TARGET_PATH, filename)
if os.path.isfile(file_path):
existing_hashes.add(calculate_file_hash(file_path))
# ---------- 任务准备阶段 ----------
# 生成所有待复制文件的任务列表
tasks = []
for item_id, custom_dir in IMAGES_PATH_DICT.items():
if os.path.isdir(custom_dir):
# 遍历目录下的所有文件
for filename in os.listdir(custom_dir):
# 每个文件生成一个任务参数元组
tasks.append( (item_id, custom_dir, filename) )
# ---------- 多线程执行阶段 ----------
print(f"开始复制,共 {len(tasks)} 个文件待处理...")
# 创建线程池(自动管理线程资源)
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
# 提交所有任务并等待完成
executor.map(copy_single_file, tasks)
def main():
"""主程序流程"""
# 1. 创建输出目录
create_target_path()
# 2. 扫描有效壁纸目录
get_directories()
# 3. 执行复制操作
copy_images()
if __name__ == '__main__':
# 程序入口
main()