项目架构分析:C-Sorting

tyrone的头像 发布于 2026-03-19 37 次阅读 预计阅读时间: 10 分钟 最后更新于 2026-03-19


目录


整体架构概览

这是一个完全本地运行的桌面媒体文件整理工具,采用四层架构设计:

层级技术选型核心职责
界面层PyQt6目录选择、模式切换、进度展示、历史记录、主题/托盘
业务层Python原生文件扫描、元数据读取、分组逻辑、文件搬运
AI层ONNX Runtime + Chinese-CLIP图像特征提取、零样本分类
工程层PyInstaller + 包管理跨平台打包、Debian/Arch安装包构建

项目入口src/main.py

  • 创建 QApplication
  • 设置应用图标和Windows AppUserModelID
  • 实例化主窗口 App(定义在 src/gui/app.py

技术栈详解

核心依赖(来自 requirements.txt

依赖用途
PyQt6桌面GUI框架
Pillow图片打开、EXIF读取、预处理
numpy张量运算、向量计算
onnxruntime本地CPU推理Chinese-CLIP
piexif依赖中存在,但主流程以Pillow为主
requests存在但未参与当前离线流程

AI模型资源

路径assets/models/chinese-clip-vit-base-patch16

  • vit-b-16.img.fp16.onnx:图像编码器
  • vit-b-16.txt.fp16.onnx:文本编码器
  • vocab.txt:中文词表

架构特点:Chinese-CLIP拆分为两个独立的ONNX模型,全部在本地CPU运行,无云端依赖。


核心执行流程

1. 用户触发整理

起点src/gui/app.py#L2062 start_sorting()

执行步骤

  1. 读取源目录并校验
  2. 获取用户选择的分类模式:
    • date(按天)
    • month(按月)
    • city(按城市)
    • ai(AI语义分类)
  3. AI模式特殊处理:合并两类标签
    • UI预设复选框(12个预设标签)
    • 用户自定义输入(逗号分隔)

UI标签定义src/gui/app.py#L1230

预设:鹦鹉、猫狗、自拍、双人、合照、食物、海边、夜景、烟花、绿植、文档、花

  1. 创建 SortWorker 线程(src/gui/app.py#L535
  2. 连接信号:
    • progress:状态文本
    • progress_val:数值进度
    • total_count_ready:总文件数
    • finished:完成结果
    • error:错误信息
  3. 启动线程 self.worker.start()

2. 工作线程处理

实现src/gui/app.py#L555 SortWorker.run()

流程

  1. 发送“扫描中”状态
  2. 调用 scan_folder() 扫描目录
  3. 分离照片和视频
  4. 根据模式调用分组函数
  5. 计算目标目录名
  6. 调用 move_grouped_items() 执行文件搬运
  7. 返回结果字典

目标目录命名规则

  • 照片:<源目录名>_photos_sorted_by_<mode>
  • 视频:<源目录名>_videos_sorted_by_<mode>

例:源目录 trip + AI模式 → trip_photos_sorted_by_aitrip_videos_sorted_by_ai


3. 文件扫描

实现src/sorter.py#L94 scan_folder()

逻辑

  • 递归扫描:rglob('*')(勾选“读取子文件夹”时)
  • 单层扫描:glob('*')(未勾选时)

支持格式

  • 图片.jpg .jpeg .jfif .png .heic .heif .tif .tiff .webp .bmp .gif
  • 视频.mp4 .mov .avi .mkv .wmv .flv .webm .m4v .mpg .mpeg .3gp

数据封装src/sorter.py#L17 MediaItem

  • 原始路径
  • 媒体类型(image/video)
  • 元数据缓存 meta
  • AI预测结果 _ai_tag

4. 元数据提取

实现src/sorter.py#L24 _get_metadata()

图片处理

  • 调用 src/exif_utils.py#L58 get_photo_metadata()
  • 优先读取 EXIF DateTimeOriginal
  • 若无EXIF,回退到文件修改时间

GPS处理链

视频处理

  • 不读取EXIF,直接使用文件时间

5. 非AI分类逻辑

日期/月份分组

城市分组

入口src/sorter.py#L88 city()

实现src/geocode.py#L31 latlon_to_city()

特点

  • 完全离线实现
  • 内置中国城市中心点坐标库
  • 采用“最近城市”匹配算法
  • 精度:地级行政区级别,非真实逆地理编码

6. AI分类核心

6.1 调用链

group_by_ai() [sorter.py#L128]
    → MediaItem.ai_tag()
        → Recognizer.predict() [recognition.py]

6.2 模型组件

路径src/models/recognition.py

  • SimpleChineseCLIPTokenizer:自定义中文分词器
  • Recognizer:模型封装类

模型加载recognition.py#L94 load_model()

ort.InferenceSession(img_model_path, providers=['CPUExecutionProvider'])
ort.InferenceSession(txt_model_path, providers=['CPUExecutionProvider'])

固定使用CPU,未配置CUDA

6.3 文本处理:提示词集成

核心recognition.py#L190 _get_text_features_ensemble()

模板示例

  • “一张{标签}的照片”
  • “这张图里有{标签}”
  • “典型的{标签}场景”
  • “高质量的{标签}图像”
  • “关于{标签}的截图或照片”

流程

  1. 每个标签应用多个模板
  2. 分别编码、推理得到文本向量
  3. 同一标签的多向量取平均
  4. 获得稳定的标签语义表示

分词器recognition.py#L25

  • 中文字符前后补空格
  • WordPiece子词切分
  • 添加 [CLS]/[SEP]
  • Padding到 max_length=52

6.4 图像处理:多视角采样

实现recognition.py#L152 _get_multi_views()

10视角生成

  • 缩放至短边256
  • 5个位置裁剪:中心、左上、右上、左下、右下
  • 每个位置2个版本:原图 + 水平翻转

预处理

  1. 除以255.0归一化
  2. CLIP风格标准化(mean/std)
  3. HWC → CHW
  4. 增加batch维度

6.5 图文比对与决策

相似度计算

  • 图像向量L2归一化
  • 文本向量矩阵L2归一化
  • 点积 = cosine similarity

阈值处理

  • 最高分 < 0.18 → 返回“其他”
  • 否则返回最高分标签

分类器允许“都不像”,有兜底机制


7. 文件搬运

入口src/sorter.py#L142 move_grouped_items()

输入格式

{
  "海边": [img1, img2, img3],
  "自拍": [img4, img5],
  "其他": [img6]
}

执行逻辑

  1. 确保目标根目录存在
  2. 遍历每个分组
  3. 将组名中的 / 替换为 _(防路径冲突)
  4. 创建子目录 target_base / safe_name
  5. 遍历组内文件:
    • 目标文件名默认同原名
    • 重名则追加 _1, _2, _3...

最终目录结构

trip_photos_sorted_by_ai/
├── 海边/
│   └── IMG001.jpg
├── 自拍/
│   └── IMG002.jpg
└── 其他/
    └── IMG003.jpg

复制/移动策略

  • 保留原文件(复选框选中):shutil.copy2()(保留元数据)
  • 否则shutil.move()

8. 视频处理策略

SortWorker.run() 中:

  • 扫描结果拆分为 photosvideos
  • 日期/月/城市模式:视频与图片统一规则
  • AI模式
    • 图片 → group_by_ai()
    • 视频 → 固定分组(如“视频”)

AI模式本质是“图片语义分类 + 视频单独归档”


工程化实现

配置与历史

数据目录src/gui/app.py#L813 get_user_data_dir()

  • 开发模式:项目根目录
  • 打包后:平台用户配置目录

配置文件config.json

历史记录history.json

系统托盘

实现src/gui/app.py#L689 init_tray_icon()

效果:关闭窗口时程序可继续后台运行

进度反馈

特点:视觉优化 > 严格精确

实现src/gui/app.py#L2118 update_progress_bar()

算法

  1. 第一张图处理完成时记录耗时 t1
  2. 估算剩余时间:t_fake = t1 * (total - 1.2)
  3. 使用 QPropertyAnimation 平滑动画
  4. 最终完成时跳转到终点值

目的:提供连续、丝滑的视觉反馈


打包与发布

Windows打包

配置C-SORTING.spec

  • 入口:src/main.py
  • 资源:包含整个 assets 目录
  • 控制台:console=False
  • 图标:assets/app_icon.ico

Linux打包

ARM64构建build-arm64.sh

  1. 安装Qt6依赖
  2. 创建虚拟环境
  3. pip install -r requirements.txt
  4. PyInstaller打包
  5. 组装Debian包
  6. dpkg-deb --build

发行版支持

  • packaging/arch/:Arch Linux
  • packaging/debian/:Debian/Ubuntu

设计总结

优点

  1. 完全本地化:不依赖云端API,隐私安全
  2. 架构清晰:UI/业务/AI三层分离,职责明确
  3. AI设计合理
    • CLIP架构天然支持自定义标签
    • 提示词集成提升分类稳定性
    • 多视角采样增强图像表征
  4. 安全机制
    • 分类阈值兜底
    • 独立输出目录,不破坏原结构
  5. 用户体验
    • 视频/图片分流处理
    • 进度条视觉优化
    • 托盘后台运行

待改进点

  1. AI推理:当前仅支持CPU,可考虑GPU加速
  2. 异常处理move_grouped_items() 中单文件失败直接 continue,可能导致部分文件遗漏
  3. 线程安全:关闭程序时使用 QThread.terminate(),文件搬运中不够安全
此作者没有提供个人介绍。
最后更新于 2026-03-19