目录
整体架构概览
这是一个完全本地运行的桌面媒体文件整理工具,采用四层架构设计:
| 层级 | 技术选型 | 核心职责 |
|---|---|---|
| 界面层 | 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()
执行步骤:
- 读取源目录并校验
- 获取用户选择的分类模式:
date(按天)month(按月)city(按城市)ai(AI语义分类)
- AI模式特殊处理:合并两类标签
- UI预设复选框(12个预设标签)
- 用户自定义输入(逗号分隔)
UI标签定义:src/gui/app.py#L1230
预设:鹦鹉、猫狗、自拍、双人、合照、食物、海边、夜景、烟花、绿植、文档、花
- 创建
SortWorker线程(src/gui/app.py#L535) - 连接信号:
progress:状态文本progress_val:数值进度total_count_ready:总文件数finished:完成结果error:错误信息
- 启动线程
self.worker.start()
2. 工作线程处理
实现:src/gui/app.py#L555 SortWorker.run()
流程:
- 发送“扫描中”状态
- 调用
scan_folder()扫描目录 - 分离照片和视频
- 根据模式调用分组函数
- 计算目标目录名
- 调用
move_grouped_items()执行文件搬运 - 返回结果字典
目标目录命名规则:
- 照片:
<源目录名>_photos_sorted_by_<mode> - 视频:
<源目录名>_videos_sorted_by_<mode>
例:源目录
trip+ AI模式 →trip_photos_sorted_by_ai和trip_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处理链:
- src/exif_utils.py#L20
_get_exif() - src/exif_utils.py#L33
_get_gps_info() - src/exif_utils.py#L43
_convert_to_degrees():将有理数元组转为浮点经纬度
视频处理:
- 不读取EXIF,直接使用文件时间
5. 非AI分类逻辑
日期/月份分组
- src/sorter.py#L73
date_key():YYYY:MM:DD HH:MM:SS→YYYY-MM-DD - src/sorter.py#L81
month_key():截取YYYY-MM
城市分组
入口: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 模型组件
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()
模板示例:
- “一张{标签}的照片”
- “这张图里有{标签}”
- “典型的{标签}场景”
- “高质量的{标签}图像”
- “关于{标签}的截图或照片”
流程:
- 每个标签应用多个模板
- 分别编码、推理得到文本向量
- 同一标签的多向量取平均
- 获得稳定的标签语义表示
- 中文字符前后补空格
- WordPiece子词切分
- 添加
[CLS]/[SEP] - Padding到
max_length=52
6.4 图像处理:多视角采样
实现:recognition.py#L152 _get_multi_views()
10视角生成:
- 缩放至短边256
- 5个位置裁剪:中心、左上、右上、左下、右下
- 每个位置2个版本:原图 + 水平翻转
预处理:
- 除以255.0归一化
- CLIP风格标准化(mean/std)
- HWC → CHW
- 增加batch维度
6.5 图文比对与决策
相似度计算:
- 图像向量L2归一化
- 文本向量矩阵L2归一化
- 点积 = cosine similarity
阈值处理:
- 最高分
< 0.18→ 返回“其他” - 否则返回最高分标签
分类器允许“都不像”,有兜底机制
7. 文件搬运
入口:src/sorter.py#L142 move_grouped_items()
输入格式:
{
"海边": [img1, img2, img3],
"自拍": [img4, img5],
"其他": [img6]
}
执行逻辑:
- 确保目标根目录存在
- 遍历每个分组
- 将组名中的
/替换为_(防路径冲突) - 创建子目录
target_base / safe_name - 遍历组内文件:
- 目标文件名默认同原名
- 重名则追加
_1,_2,_3...
最终目录结构:
trip_photos_sorted_by_ai/
├── 海边/
│ └── IMG001.jpg
├── 自拍/
│ └── IMG002.jpg
└── 其他/
└── IMG003.jpg
复制/移动策略:
- 保留原文件(复选框选中):
shutil.copy2()(保留元数据) - 否则:
shutil.move()
8. 视频处理策略
在 SortWorker.run() 中:
- 扫描结果拆分为
photos和videos - 日期/月/城市模式:视频与图片统一规则
- AI模式:
- 图片 →
group_by_ai() - 视频 → 固定分组(如“视频”)
- 图片 →
AI模式本质是“图片语义分类 + 视频单独归档”
工程化实现
配置与历史
数据目录:src/gui/app.py#L813 get_user_data_dir()
- 开发模式:项目根目录
- 打包后:平台用户配置目录
配置文件:config.json
- 存储:主题色、深色模式、语言
- 读写:app.py#L792 / app.py#L801
历史记录:history.json
- 每次成功处理记录结果
- 最多保留50条
- 读写:app.py#L847 / app.py#L856
系统托盘
实现:src/gui/app.py#L689 init_tray_icon()
效果:关闭窗口时程序可继续后台运行
进度反馈
特点:视觉优化 > 严格精确
实现:src/gui/app.py#L2118 update_progress_bar()
算法:
- 第一张图处理完成时记录耗时
t1 - 估算剩余时间:
t_fake = t1 * (total - 1.2) - 使用
QPropertyAnimation平滑动画 - 最终完成时跳转到终点值
目的:提供连续、丝滑的视觉反馈
打包与发布
Windows打包
- 入口:
src/main.py - 资源:包含整个
assets目录 - 控制台:
console=False - 图标:
assets/app_icon.ico
Linux打包
ARM64构建:build-arm64.sh
- 安装Qt6依赖
- 创建虚拟环境
pip install -r requirements.txt- PyInstaller打包
- 组装Debian包
dpkg-deb --build
发行版支持:
packaging/arch/:Arch Linuxpackaging/debian/:Debian/Ubuntu
设计总结
优点
- 完全本地化:不依赖云端API,隐私安全
- 架构清晰:UI/业务/AI三层分离,职责明确
- AI设计合理:
- CLIP架构天然支持自定义标签
- 提示词集成提升分类稳定性
- 多视角采样增强图像表征
- 安全机制:
- 分类阈值兜底
- 独立输出目录,不破坏原结构
- 用户体验:
- 视频/图片分流处理
- 进度条视觉优化
- 托盘后台运行
待改进点
- AI推理:当前仅支持CPU,可考虑GPU加速
- 异常处理:
move_grouped_items()中单文件失败直接continue,可能导致部分文件遗漏 - 线程安全:关闭程序时使用
QThread.terminate(),文件搬运中不够安全

Comments NOTHING