Files
kenaito-media-picker/README.md
T
odboy 66faed75a2 feat: 增加 EXIF 旋转图片自动转正
- 解析 JPEG EXIF Orientation,方向 5/6/7/8 的竖拍横存图不再误入横屏目录
- 配置 ffmpeg 时用 ffmpeg 物理转正像素并保留原 EXIF;无 ffmpeg 时按显示方向归类
- 新增 ffmpeg.path / organize.rotateImages 配置项
- 补充 EXIF 解析与旋转幂等测试
2025-05-19 21:15:58 +08:00

187 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# kenaito-media-picker
媒体分辨率分类整理工具:一键扫描目录下所有图片与视频,按横屏 / 竖屏 / 等比和分辨率自动归类到对应子目录。
## 功能特性
- **图片 / 视频 / 音频**:可指定任意目录进行扫描(递归),自动识别图片、视频与音频文件;不填目录时默认扫描程序当前运行目录
- **标准分辨率归类**:720P、1080P、2K、2.5K、影院 2K、DCI 4K、8K 及带鱼屏(UltraWide 2K/3K/4K)等标准规格单独建目录
- **非标分辨率归类**:按像素区间(每 1024px 一档)自动归入 1K ~ 19K / 普通 / 原图(原画质)目录
- **方向区分**:横屏、竖屏、等比(正方形)分开整理,GIF 单独处理
- **EXIF 旋转修正**:手机/相机竖拍的图(文件里横着存、带 EXIF 旋转标记)自动识别并转正——找到 ffmpeg 时用 ffmpeg 把像素真正转正;找不到时至少按"显示方向"(宽高对调)归类,不会再误入横屏目录
- **明暗分类**:基于 ITU-R BT.601 亮度公式分析每张图片明暗程度,暗色(亮度 < 80)像素占比为 darkScore、亮色(亮度 > 175)像素占比为 lightScore;以两者差幅判定亮暗主导,在目录名末尾追加 `-dark_N` / `-mid` / `-bright_N`(N 为占比四舍五入到 10% 的档位)
- **音频归类**:音频文件单独归入「音频」目录
- **异常兜底**:读取失败、不支持的格式自动归入「读取异常」「已忽略」目录
- **自动整理**:无需人工干预,扫描 → 分析 → 移动一次完成,跨磁盘自动降级为复制后删除
- **全格式支持**:视频/音频以**内嵌的 ffprobe**(Windows 版)作为最终判定依据,**凡是 ffmpeg 支持的格式都能识别**,不再依赖扩展名白名单
- **媒体信息入库**:自动将文件的 MD5 与完整媒体信息(类型/宽高/时长/编码/容器等)存入根目录下的 `media-info.db`(SQLite,xorm 驱动),支持按字段查询与去重
## 支持的格式
| 类型 | 格式 |
|------|------|
| 图片 | JPG / JPEG / JPE / JFIF / PNG / GIF / BMP / WEBP / PSB,PSD 单独归入「图片_PSD」;Go 无法解码的图片格式(如 TIFF)由 ffprobe 兜底 |
| 视频 | 凡 ffmpeg 支持的容器/编码均可识别(MP4 / MKV / AVI / MOV / TS / FLV / WMV / RM / RMVB / WEBM / M2TS 等),以 ffprobe 探测结果为准 |
| 音频 | 凡 ffmpeg 支持的音频(MP3 / WAV / FLAC / AAC / M4A / OGG / WMA 等)归入「音频」目录 |
> 判断依据:图片按文件 MIME + 扩展名识别(扩展名大小写不敏感);视频/音频由 ffprobe 读取媒体流判定,支持范围与 ffmpeg 一致。
## 构建编译
```shell
sh build.sh
```
- 依赖 Go 1.25.0+(`go.mod` 指定 1.25.0)
- 构建时内嵌 **Windows 版 ffprobe**:`internal/probe/bin/ffprobe.exe`,需提前放置(未入库,见 `.gitignore`)
- `build.sh` 在 **Windows** 下先构建前端、再 `go build` + `upx` 压缩,输出 `kenaito-media-picker.exe`(Web 服务 + 内嵌前端)
- 编译所需工具(ffmpeg / upx)压缩包已放在 `doc/` 目录,按需解压使用(upx 放置于项目根目录 `upx`)
## 使用方法
```text
# 双击有风险, 双击需谨慎
将 kenaito-media-picker.exe 拖拽到需要整理的文件夹内,双击运行即可
```
程序启动后自动打开浏览器进入 **Web 管理界面**(默认 http://localhost:8080),
媒体整理不再是自动执行,而是在页面上点击 **【扫描整理】** 后开始(整理会移动文件,请先备份)。
## Web 管理界面
项目提供 **Web 管理界面**(Vue 2 + ElementUI,内嵌进程序二进制),负责触发扫描整理与浏览 `media-info.db` 媒体库。
### 运行服务
```shell
# 需要 Node.js 的机器上先构建前端(dist 会被 go:embed 打入程序)
cd frontend
npm install && npm run build
# 回到项目根目录编译(Windows 下输出 kenaito-media-picker.exe)
go build .
./kenaito-media-picker.exe
```
- 默认监听 `:8080`,启动后自动打开浏览器(配置 `server.autoOpen=false` 可关闭)
- 端口 / 数据库路径 / 自动打开浏览器可在 `application.properties` 配置:`server.port` / `database.dsn`(相对路径相对服务启动目录)/ `server.autoOpen`
### 开发模式(无需重复构建前端)
日常开发用 **Vite 开发服务器 + Go 源码运行**,前端改动实时热更新,不需要每次 `npm run build`:
```shell
# 终端 1:启动后端(源码运行,端口 8080)
go run .
# 终端 2:启动前端开发服务器(端口 3000,/api 自动代理到 8080)
cd frontend
npm install && npm run dev
```
- 浏览器访问 **http://localhost:3000** 即可联调
- 改前端文件 → Vite 热更新自动生效;改后端 → 重启 `go run .`
**只有发布时才需要重新构建前端**:执行 `npm run build` 把产物打进 `frontend/dist`,再 `go build .` 重新编译,前端改动才会进入二进制。
### 页面功能
| 页面 | 功能 |
|------|------|
| 扫描整理 | 点击「选择目录」浏览服务器磁盘(只列目录),也可手动输入;点击【开始扫描】触发扫描归类,实时显示运行日志与进度 |
| 统计看板 | 媒体总数/总体积/重复文件、类型/方向/分辨率/编码/格式分布、体积 TOP10 |
| 媒体库 | 按类型/方向/分辨率/编码/格式/文件名筛选,分页 + 列排序 |
> 分辨率统计与筛选复用归类规则(标准规格 / 1K~19K / 普通 / 原图),与归类结果一致。
## 整理规则
执行后,会在当前目录生成一批以 UUID 开头命名的子目录,文件按规则移动(或复制)进去:
### 图片
```text
{uid}-图片_横屏_720P-dark_100 ← 1280×720,暗色主导
{uid}-图片_横屏_1D5KP-mid ← 1600×900,明暗均衡
{uid}-图片_横屏_1080P-bright_100 ← 1920×1080,亮色主导
{uid}-图片_横屏_2DKP-dark_70 ← 2048×1080(影院 2K)
{uid}-图片_竖屏_... ← 竖屏按同样规则(普通 / 1K~19K / 原图)
{uid}-图片_等比_... ← 等比(正方形)按同样规则
{uid}-图片_GIF ← 所有 GIF 图片
{uid}-图片_PSD ← 所有 PSD 文件
{uid}-图片_读取异常 ← 无法读取尺寸的图片
{uid}-图片_已忽略 ← 不支持格式的图片
```
### 视频 / 音频
```text
{uid}-视频_横屏_720P / 1080P / 2DKP / 2D5KP / 4KP / 4KHP / 8KP
{uid}-视频_横屏_UltraWide2KP / UltraWide3KP / UltraWide4KP
{uid}-视频_横屏_1k ~ 19k / 普通 / 原画质
{uid}-视频_竖屏_1k ~ 19k / 普通 / 原画质
{uid}-视频_等比_1k ~ 19k / 普通 / 原画质
{uid}-视频_横屏_GIF / 视频_竖屏_GIF / 视频_等比_GIF
音频 ← 所有音频文件(ffmpeg 可识别的格式)
视频_读取异常 ← mime 为视频但 ffprobe 读取失败
视频_已忽略 ← 非媒体文件或无法识别
```
### 重复文件
```text
重复文件 ← 与库中已有记录 MD5 相同的图片/视频/音频(不去重入库,单独存放)
```
> 文件按 MD5 去重:已入库的同一内容文件(不论图片、视频还是音频)统一移入「重复文件」目录。
> 视频时长与分辨率通过内嵌 ffprobe 读取(处理时会临时释放到当前目录、完成后自动删除);凡 ffmpeg 支持的视频/音频格式均可识别。
## 明暗评分算法
```text
brightness = 0.299×R + 0.587×G + 0.114×B (ITU-R BT.601)
darkScore = 亮度 < 80 的像素占比
lightScore = 亮度 > 175 的像素占比
```
- **判定规则**:`lightScore` 高于 `darkScore` 15 个百分点以上归 `-bright_N`,反之归 `-dark_N`;差幅不足 15% 视为中性归 `-mid`(不强制二选一)
- **档位命名**:N 为占比四舍五入到 10% 的档位(10~100),如 darkScore 0.70 → `dark_70`,表示占比命中 [N-5%, N+5%) 区间
- **透明处理**:完全透明的像素(alpha=0)不计入统计,全透明图片归 `-mid`
- **性能**:大图(>100 万像素)先等比缩放为最长边 512px 的缩略图(纯内存)再统计明暗,内存占用与耗时大幅下降;超 4000 万像素的图片直接跳过明暗分析(按中性 mid 归类)避免 OOM
## 目录结构
```text
├── main.go # 程序入口(Web 服务:Gin + 内嵌前端产物)
├── internal/
│ ├── scanner/ # 文件扫描、MIME 读取、图片/媒体候选过滤
│ ├── probe/ # 媒体探测:图片尺寸 + ffprobe 解析 + 内嵌解码器释放
│ ├── classify/ # 纯函数归类规则(方向 + 标准分辨率 + K档 + 明暗等级)
│ ├── organize/ # 整理编排:探测→归类→收集→移动→记录去向
│ ├── mover/ # 文件移动(rename / 跨盘 copy+delete)
│ ├── store/ # SQLite 媒体信息持久化 + 查询/统计
│ ├── service/ # 后台任务编排(扫描整理任务 + 状态/日志)
│ ├── config/ # Web 服务配置加载(application.properties)
│ ├── api/ # HTTP 接口层(统一响应 / 媒体列表 / 统计 / 扫描触发)
│ └── digest/ # 文件 MD5 / 指纹采样
├── frontend/ # 前端工程(Vue 2 + ElementUI + Vite,dist 内嵌进程序)
├── application.properties # Web 服务配置(端口 / 数据库路径)
├── doc/ # 构建依赖(ffmpeg / upx 压缩包)
└── build.sh # 编译脚本(upx 压缩)
```
## 注意事项
- 默认使用**移动**模式,原文件会从源目录移走;同名文件跨磁盘时自动降级为复制 + 删除
- 已整理文件全部移动到 `KMP已整理` 白名单目录内,重复运行会跳过该目录,并将新文件并入已有的分类目录
- 扫描整理由页面点击触发,整理期间服务会短暂占用较多 CPU/磁盘,属正常现象
- 同一时间只允许一次扫描整理;重复点击会提示「正在运行中」
- GIF 图片单独归入「图片_GIF」;GIF 视频按方向归入「视频_*_GIF」
- ffprobe 内嵌(Windows),运行前释放到当前目录、结束后自动删除
- EXIF 旋转转正需要 ffmpeg:配置 `ffmpeg.path` 指定路径,或让 ffmpeg 在 PATH 里;找不到 ffmpeg 时不转正像素,仍按显示方向归类。可用 `organize.rotateImages=false` 关闭
## License
Apache 2.0