Wallpaper中动态壁纸的转移

Wallpaper中动态壁纸的转移

背景

Wallpaper是Steam上的一个优秀的动态壁纸软件,其创意工坊中存在大量制作精良的壁纸(懂得都懂😉),但是订阅下载后会占用本机大量空间,尽管可以通过Steam的转移功能转移,但是依旧还是不太方便,最近在折腾家庭NAS,就想将Wallpaper的壁纸文件转移到NAS上并取消订阅,释放本地空间。
本次使用Python3来编写转移脚本,平常我用Python用的少,也算是提升一下熟练度吧。

分析

首先,Wallpaper中的壁纸结构基本上都是一个文件夹中三个文件的形式,这三个文件分别是视频文件、预览图、json文件,同时文件夹命名是一串很长的数字,我怀疑这串数字是唯一对应各个壁纸的,估计是Wallpaper数据库里壁纸的编号。
壁纸文件存在位置
壁纸文件结构
所以讲道理直接拷贝这个文件夹中的文件也问题不大,但是这样就很难直观的知道这个文件夹内部是什么,最好能拷贝到NAS中时再把文件夹名字改掉。
其次,要写脚本一次性转移这么多文件,很有可能中途就中断了,最好增加一些检查机制防止重复拷贝(最后我的脚本检查机制可以说是拉满了)
总之,我要实现一个拷贝的脚本,1、需要可以拷贝的时候重命名文件夹。2、有一些检查机制保证不重复拷贝。

实现

实现工具Python、Sqlite
编写两个脚本分别实现不同功能:

  1. 转移脚本,基本功能:
    • 收集Wallpaper中有哪些文件
    • 检查这些文件和Sqlite中的对不对的上
    • 检查这些文件在目标文件夹中有没有
    • 转移文件
  2. 检查脚本,基本功能:
    • 收集目标文件夹中有哪些文件
    • 检查目标文件夹文件是否与Sqlite中对的上
    • 如果传入参数–update就更新数据库文件使数据库记录与目标文件夹文件一致,否则就报差别在哪里
      转移脚本效果图

脚本内容

使用了通义千问辅助开发(有一说一,通义千问挺强)
只要搭建好数据库,改一下脚本里的地址,也挺通用的,反正我用的感觉还行
脚本中有很多输出和log记录,还有文件名的防错,管控还比较到位

一、转移脚本

# 用来转移下载好的Wallpaper内容的脚本

import os
import json
import sqlite3
import chardet
import datetime
import shutil
import pprint

def detectEncoding(filePath):
    """
    检测给定文件的编码。

    :param filePath: 文件路径
    :return: 文件的编码类型
    """
    # """检测文件的编码"""
    with open(filePath, 'rb') as f:
        result = chardet.detect(f.read())
    return result['encoding']

def getCurrentWallpaper():
    """
    获取当前的壁纸信息。

    :return: 壁纸信息列表,每个元素包含标题、索引和原始目录路径
    """
    objects = []
    # 原始地址
    sourceDir = "F:/SteamLibrary/steamapps/workshop/content/431960/"
    dirs = os.listdir(sourceDir)
    for dir in dirs:
        if not os.path.isdir(sourceDir + dir):
            continue
        files = os.listdir(sourceDir + dir)
        if "project.json" not in files:
            print("project.json 不存在于 " + dir)
            continue
        # 检测文件编码
        jsonPath = os.path.join(sourceDir, dir, "project.json")
        encoding = detectEncoding(jsonPath)
        try:
            with open(jsonPath, 'r', encoding=encoding, errors='ignore') as file:
                jsonInfo = json.load(file)
                title = jsonInfo["title"]
            object = {
                "title": title,
                "index": dir,
                "origDir": os.path.join(sourceDir, dir)
            }
            objects.append(object)
        except Exception as e:
            print(f"Failed to read {jsonPath}: {e}")
            continue
    return objects

def checkSqlInfo(objects):
    """
    检查数据库中是否已存在相同的壁纸信息,将新信息添加到数据库。

    :param objects: 壁纸信息列表
    :return: 新的壁纸信息列表,不包含已存在于数据库中的信息
    """
    newObjects = []
    errorMessages = []
    sqlFile = "Y:/WallpaperDate/transRecord.db"
    conn = sqlite3.connect(sqlFile)
    cursor = conn.cursor()
    for object in objects:
        index = object["index"]
        cursor.execute("SELECT COUNT(*) FROM currentFiles WHERE title=?", (object["title"],))
        result = cursor.fetchone()
        if result is None:
            newObjects.append(object)
        else:
            status = result[0]
            if status == 0:
                newObjects.append(object)
            else:
                message = f"{object['index']} {object['title']} 数据库中已存在\n"
                curTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                errorMessages.append((message,curTime))
                print(f"{message} {curTime}")
    if errorMessages:
        cursor.executemany(
            "INSERT INTO record (message, time) VALUES (?, ?)",
            errorMessages
        )
        conn.commit()
        message = "checkSqlInfo报错信息更新完毕"
        curTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"{message} {curTime}")
    cursor.close()
    conn.close()
    return newObjects

def transferFiles(objects):
    """
    将壁纸文件从原始目录转移到目标目录,并在数据库中记录相关信息。

    :param objects: 壁纸信息列表
    """
    errorMessages = []
    modifys = []
    transferMessages = []
    sqlFile = "Y:/WallpaperDate/transRecord.db"
    destBaseDir = "Y:/WallpaperDate"
    conn = sqlite3.connect(sqlFile)
    cursor = conn.cursor()
    for object in objects:
        origDir = object["origDir"]
        index = object["index"]
        title = object["title"]

        # 定义一个集合,包含所有非法的文件名字符
        illegalChars = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'}

        # 使用列表推导式来构建一个只包含合法字符的新字符串
        safeTitle = "".join(c for c in title if c not in illegalChars)

        destDir = os.path.join(destBaseDir, safeTitle + "_" + index)
        if os.path.exists(destDir):
            message = f"{index} {title} 文件夹已存在"
            curTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            errorMessages.append((message, curTime))
            print(f"{message} {curTime}")
            continue
        shutil.copytree(origDir, destDir)
        if os.path.exists(destDir):
            message = f"{index} {title} 已完成拷贝"
            curTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            modifys.append((f"{message} {curTime}"))
            transferMessages.append((index, title))
            print(f"{message} {curTime}")
    cursor.executemany(
        "INSERT INTO record (message, time) VALUES (?, ?)",
        errorMessages
    )
    conn.commit()
    message = "transferFiles报错信息更新完毕"
    curTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    print(f"{message} {curTime}")
    cursor.executemany(
        "INSERT INTO record (modify, time) VALUES (?, ?)",
        modifys
    )
    conn.commit()
    message = "transferFiles操作信息更新完毕"
    curTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    print(f"{message} {curTime}")
    cursor.executemany(
        "INSERT INTO currentFiles ([index], title) VALUES (?, ?)",
        transferMessages
    )
    conn.commit()
    message = "transferFiles转入信息记录完毕"
    curTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    print(f"{message} {curTime}")
    cursor.close()
    conn.close()

def main():
    """
    主函数,程序的入口点。
    """
    objects = getCurrentWallpaper()
    objects = checkSqlInfo(objects)
    transferFiles(objects)

main()

二、检查脚本

import os
import sqlite3
import argparse

def readDatabaseRecords(sqlFile):
    """
    从数据库中读取所有记录,并清洗标题。

    :param sqlFile: 数据库文件路径
    :return: 清洗后的数据库记录列表
    """
    records = []
    illegalChars = {'<', '>', ':', '"', '/', '\\', '|', '?', '*'}
    
    with sqlite3.connect(sqlFile) as connection:
        cursor = connection.cursor()
        cursor.execute("SELECT [index], title FROM currentFiles")
        rawRecords = cursor.fetchall()
        
        for index, title in rawRecords:
            # 使用列表推导式来构建一个只包含合法字符的新字符串
            safeTitle = "".join(c for c in title if c not in illegalChars)
            records.append((index, safeTitle))
            
    return records

def scanFileSystem(baseDirectory):
    """
    扫描文件系统,获取所有子目录信息。

    :param baseDirectory: 文件系统根目录
    :return: 文件系统中的目录信息列表
    """
    directories = []
    for entry in os.scandir(baseDirectory):
        if entry.is_dir():
            directoryName = entry.name
            parts = directoryName.rsplit('_', 1)  # 从右向左分割,保留最后一段作为索引
            if len(parts) == 2:
                directories.append({"index": parts[1], "title": parts[0]})
    return directories

def compareRecords(databaseRecords, fileSystemDirectories):
    """
    比较数据库记录与文件系统中的目录,找出不一致的地方。

    :param databaseRecords: 数据库记录列表
    :param fileSystemDirectories: 文件系统目录信息列表
    :return: 缺失和多余的条目列表
    """
    missingInDb = []
    missingInFs = []
    dbSet = {(record[0], record[1]) for record in databaseRecords}
    fsSet = {(directory["index"], directory["title"]) for directory in fileSystemDirectories}

    missingInFs = list(dbSet - fsSet)
    missingInDb = list(fsSet - dbSet)

    return missingInDb, missingInFs

def logDifferences(missingInDb, missingInFs, logFile):
    """
    将不一致的地方记录到日志文件中。

    :param missingInDb: 文件系统中有但在数据库中没有的条目列表
    :param missingInFs: 数据库中有的但在文件系统中没有的条目列表
    :param logFile: 日志文件路径
    """
    with open(logFile, 'w',encoding='utf-8') as log:
        log.write("Missing in database:\n")
        for item in missingInDb:
            log.write(f"{item[0]} {item[1]}\n")

        log.write("\nMissing in filesystem:\n")
        for item in missingInFs:
            log.write(f"{item[0]} {item[1]}\n")

def updateDatabaseToMatchFileSystem(sqlFile, databaseRecords ,fileSystemDirectories):
    """
    更新数据库中的记录,使其与文件系统中的目录信息相匹配。
    包括更新现有记录和插入新记录。

    :param sqlFile: 数据库文件路径
    :param baseDirectory: 文件系统根目录
    """
    # # 首先读取数据库中的记录
    # databaseRecords = readDatabaseRecords(sqlFile)

    # # 然后扫描文件系统,获取所有子目录信息
    # fileSystemDirectories = scanFileSystem(baseDirectory)

    # 创建数据库连接
    with sqlite3.connect(sqlFile) as connection:
        cursor = connection.cursor()

        # 创建一个集合,存储数据库中已有的索引
        existingIndices = set(record[0] for record in databaseRecords)

        # 遍历文件系统中的目录,检查并更新/插入记录
        for fsDirectory in fileSystemDirectories:
            index, title = fsDirectory["index"], fsDirectory["title"]
            if index not in existingIndices:
                # 如果数据库中没有这个索引,则插入新记录
                cursor.execute("INSERT INTO currentFiles ([index], title) VALUES (?, ?)", (index, title))
                existingIndices.add(index)
            else:
                # 如果数据库中有这个索引,检查标题是否需要更新
                for dbRecord in databaseRecords:
                    if dbRecord[0] == index and dbRecord[1] != title:
                        cursor.execute("UPDATE currentFiles SET title=? WHERE index=?", (title, index))

        # 清理数据库中不再存在的记录
        for index in existingIndices:
            if not any(directory["index"] == index for directory in fileSystemDirectories):
                cursor.execute("DELETE FROM currentFiles WHERE index=?", (index,))

        connection.commit()

def main():

    parser = argparse.ArgumentParser(description="Check and optionally update the consistency between database records and filesystem.")
    parser.add_argument('--update', action='store_true', help="Update database records to match filesystem directories.")
    args = parser.parse_args()

    sqlFile = "Y:/WallpaperDate/transRecord.db"
    baseDirectory = "Y:/WallpaperDate"
    logFile = "Y:/WallpaperDate/consistencyCheck.log"

    databaseRecords = readDatabaseRecords(sqlFile)
    fileSystemDirectories = scanFileSystem(baseDirectory)
    missingInDb, missingInFs = compareRecords(databaseRecords, fileSystemDirectories)
    logDifferences(missingInDb, missingInFs, logFile)

    # 如果指定了 --update 参数,则更新数据库
    if args.update:
        updateDatabaseToMatchFileSystem(sqlFile, databaseRecords ,fileSystemDirectories)

if __name__ == "__main__":
    main()
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值