Files
kenaito-media-picker/README.md
T
odboy 957dfe1134 feat: 断点续传与色情识别提速/修复,支持 Linux 交叉编译
- feat: 新增续传表 scan_log,重扫跳过已归类文件(尺寸/头尾指纹校验)
- fix: 色情识别记录批量入库超 SQLite 999 变量上限,改每 100 条一批
- perf: NudeNet 检测改为多线程推理池(每线程独立模型实例)
- perf: 明暗分析 JPEG(YCbCr) 快路径;SQLite 读并发放开;加阶段耗时统计
- fix: 前端色情识别状态由后端如实上报(配置开关+检测服务是否起来)
- feat: ffprobe 内嵌按平台拆分,Linux 构建回退系统 ffprobe,支持交叉编译
- docs: README 补充断点续传与 CentOS 交叉编译步骤
- chore: organize.workers 默认 8
2025-06-19 21:07:35 +08:00

217 lines
13 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
媒体文件自动分类整理工具:一键扫描目录下的图片 / 视频 / 音频,按 **方向(横屏 / 竖屏 / 等比)+ 分辨率 + 明暗** 自动归入对应子目录,并提供 Web 管理界面(实时进度、运行日志、媒体库、统计看板、可选色情识别)。
## 功能特性
- **全类型识别**:图片、视频、音频均可扫描(递归);视频/音频以内嵌 **ffprobe** 探测媒体流为准,**凡 ffmpeg 支持的格式都能识别**,不依赖扩展名白名单
- **方向区分**:横屏、竖屏、等比(正方形)分开整理
- **标准分辨率归类**:720P、1D5KP、1080P、2DKP、2D5KP、UltraWide 2K/3K/4K、4KP、4KHP、8KP 等精确匹配单独建目录
- **非标分辨率归类**:按长边每 1024px 一档归入 `1K` ~ `19K`,不足一档归「普通」,超出归「原图」(视频为「原画质」)
- **明暗分类**:基于 ITU-R BT.601 亮度公式,在分辨率目录下追加 `dark_N` / `mid` / `bright_N` 子目录(N 取 20/40/60/80/100 共 5 档)
- **EXIF 旋转修正**:带旋转标记的竖拍图自动处理——有 ffmpeg 时物理转正像素,否则至少按显示方向(宽高对调)归类;可在配置中整体关闭
- **色情识别(可选)**:本机 Python + NudeNet 离线鉴黄,命中的图片移入 `图片_敏感_*` 目录并记录检测分,Web 端可逐张预览复核
- **异常兜底**:读取失败、不支持格式自动归入「读取异常」「已忽略」目录;GIF、PSD 单独归类
- **断点续传**:每个文件归类结果实时写入续传表(`scan_log`),扫描中断/出错后重跑时,已归类过的文件直接复用上次分类,不再重新探测尺寸 / 算 MD5 / 分析明暗 / 色情识别
- **媒体信息入库**:自动把文件 MD5 与完整媒体信息(类型 / 宽高 / 时长 / 编码 / 容器等)写入 `media-info.db`(SQLite),支持筛选查询与去重
- **Web 管理界面**:页面点击触发扫描整理,WebSocket 实时推送进度与运行日志;支持中止
- **并行处理**:探测 / 归类 / 移动各阶段并发 worker 数可配置(默认 4)
## 支持的格式
| 类型 | 格式 |
|------|------|
| 图片 | JPG / JPEG / JPE / JFIF / PNG / GIF / BMP / WEBP / PSD 等;Go 无法解码的格式由 ffprobe 兜底 |
| 视频 | 凡 ffmpeg 支持的容器/编码(MP4 / MKV / AVI / MOV / TS / FLV / WMV / RM / RMVB / WEBM / M2TS 等),以 ffprobe 探测结果为准 |
| 音频 | 凡 ffmpeg 支持的音频(MP3 / WAV / FLAC / AAC / M4A / OGG / WMA 等),统一归入「音频」目录 |
> 图片按 MIME + 扩展名识别(大小写不敏感);视频/音频由 ffprobe 读取媒体流判定。
## 快速开始
### 运行
```text
运行编译出的 exe(如 kenaito-media-picker.exe),程序启动后自动打开浏览器进入
Web 管理界面(默认 http://localhost:8080)。
```
整理不再自动执行——在页面上点击 **【扫描整理】** 后开始(会移动文件,请先备份)。
### Web 页面
| 页面 | 功能 |
|------|------|
| 扫描整理 | 选择/输入扫描目录与存放目录,点击开始扫描,实时查看进度、运行日志,可中止 |
| 统计看板 | 媒体总数 / 总体积 / 平均·最大·最小文件 / 总时长,以及类型、方向、分辨率、编码、格式、扩展名等分布与体积 TOP10 |
| 媒体库 | 按类型 / 方向 / 分辨率 / 编码 / 格式 / 文件名筛选,分页 + 列排序 |
| 色情识别 | 展示最近一次开启识别扫描命中的图片:检测分 + 完整路径 + 缩略图预览,便于人工复核 |
## 配置(application.properties)
程序运行目录下的 `application.properties`,用记事本即可修改:
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `server.port` | `8080` | Web 服务端口 |
| `server.autoOpen` | `true` | 启动后是否自动打开浏览器 |
| `database.dsn` | `media-info.db` | 媒体数据库路径(相对路径相对运行目录) |
| `ffmpeg.path` | 空 | ffmpeg 路径,用于 EXIF 旋转转正;留空自动在 PATH 找 |
| `organize.rotateImages` | `true` | 是否处理 EXIF 旋转:`true` 转正/按显示方向归类;`false` 完全不处理,按存储方向原样归类 |
| `organize.workers` | `4` | 探测/归类/移动的并发 worker 数(`0` = 用满全部 CPU 核) |
| `nsfw.enabled` | `false` | 是否开启色情图片识别 |
| `nsfw.python` | 空 | python 可执行文件路径;留空自动在 PATH 找 |
| `nsfw.threshold` | `0.5` | 判定阈值(0~1),敏感部位检测分达到即判为色情 |
## 整理规则
整理结果统一放进输出目录下的 **`KMP已整理`** 目录(默认在扫描目录下创建,也可在页面指定存放目录)。文件名会加一长串随机前缀(如 `a1b2..._照片.jpg`),防止重名覆盖。
```text
KMP已整理/
├── 图片_横屏_1080P/
│ ├── bright_20/ ← 偏亮
│ ├── mid/ ← 明暗均衡
│ └── dark_40/ ← 偏暗
├── 图片_竖屏_2K/
├── 图片_等比_普通/ ← 非标分辨率:普通 / 1K~19K / 原图
├── 图片_横屏_UltraWide3KP/
├── 图片_GIF/
├── 图片_PSD/ ← PSD 设计稿原样收走
├── 图片_读取异常/ 图片_已忽略/
├── 图片_敏感_横屏_1080P/bright_20/ ← 判为色情的图片(明暗子目录照常)
├── 视频_横屏_1080P/ 视频_竖屏_1K/ 视频_等比_原画质/
├── 视频_横屏_GIF/ 视频_竖屏_GIF/ 视频_等比_GIF/
├── 视频_读取异常/ 视频_已忽略/
└── 音频/
```
标准分辨率档位:`720P / 1D5KP / 1080P / 2DKP / 2D5KP / UltraWide2KP / UltraWide3KP / UltraWide4KP / 4KP / 4KHP / 8KP`。
### 明暗评分算法
```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 为占比四舍五入到 20% 的档位(20/40/60/80/100 共 5 档),如 darkScore 0.70 → `dark_80`
- **透明处理**:完全透明的像素(alpha=0)不计入统计,全透明图片归 `mid`
- **性能**:超过 25 万像素的图先等比缩放为最长边 256px 的缩略图(纯内存)再统计;超过 4000 万像素直接跳过明暗分析(按 `mid` 归类),避免内存暴涨
### 去重
与数据库中已有记录 **MD5 相同且源路径相同** 的文件视为"同一文件被重复扫到",原地忽略(不移动、不重复入库)。MD5 相同但路径不同视为不同文件,照常整理。
### 断点续传(扫描表)
每次归类一个文件,程序就会把它「源路径 → 目标目录」记进数据库的续传表(`scan_log`)。之后**扫描中断、出错、甚至中途手动中止再重跑**时:
- 已归类过且内容没变的文件 → **跳过全部探测/识别(尺寸、MD5、明暗、色情识别都不再重做)**,直接复用上次的目标目录,没搬走的这次搬走;
- 内容变化(文件大小或头尾指纹变了)→ 当作新文件重新探测归类;
- 大文件(>1MB)用「大小 + 头尾 256KB 指纹」校验内容,小文件只比对大小。
所以重扫基本只花在"剩余没处理的文件"上,不会把整批几小时的工作重做一遍。
### 色情图片识别(可选)
基于本机 **Python + NudeNet** 的离线鉴黄,判为色情的图片移入 `图片_敏感_*` 分类目录(明暗子目录照常保留,如 `图片_敏感_横屏_1080P/bright_20`),并可在 Web「色情识别」页面查看每张图的检测分、路径与预览。
- **开启**:`application.properties` 设 `nsfw.enabled=true`
- **依赖**:本机安装 Python 并 `pip install nudenet`
- **模型**:优先加载程序运行目录下 `lib/320n.onnx`(本仓库已附带,约 11.6MB,离线可用);本地缺失时自动下载到该位置,下载失败再回退用 nudenet 包自带模型
- **可调**:`nsfw.python`(python 路径)、`nsfw.threshold`(判定阈值)
- **敏感类**:`FEMALE_GENITALIA_EXPOSED` / `MALE_GENITALIA_EXPOSED` / `ANUS_EXPOSED` / `FEMALE_BREAST_EXPOSED`,任一分数达标即判定
- **降级**:未装 python / nudenet 时打印警告并跳过识别,图片按普通流程整理,不影响其它功能
## 构建编译
```shell
sh build.sh
```
- 依赖 Go 1.25.0+(`go.mod` 指定)
- `build.sh` 先构建前端,再 `go build` 输出 `kenaito-media-picker-release.exe`(upx 压缩已注释,可自行启用)
- 前端产物(`frontend/dist`)由 `go:embed` 打进二进制;`internal/probe/bin/ffprobe.exe`(Windows 版,约 145MB)已随仓库入库并内嵌
- 项目使用纯 Go 的 SQLite 驱动(无 CGO),因此**可以跨平台交叉编译**
### 在 Windows 10 x64 上交叉编译 CentOS x64(Linux amd64)可执行程序
```shell
# 方式一:Git Bash / MSYS 终端
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o kenaito-media-picker-linux-amd64 .
# 方式二:PowerShell
$env:CGO_ENABLED="0"; $env:GOOS="linux"; $env:GOARCH="amd64"
go build -o kenaito-media-picker-linux-amd64 .
# 编完恢复环境变量
Remove-Item Env:CGO_ENABLED,Env:GOOS,Env:GOARCH
```
> `CGO_ENABLED=0` 很关键:本项目 SQLite 用的是纯 Go 驱动,关掉 CGO 才能交叉编译;若用默认开启 CGO 会尝试调 Linux 的 C 编译器而失败。
**把二进制拷到 CentOS 后,还要注意:**
1. **ffprobe(视频/音频探测)**:Linux 构建**不内嵌** ffprobe(仓库只带了 Windows 版),程序会回退用系统 PATH 里的 `ffprobe`。CentOS 上先装:
```shell
# CentOS/RHEL 需要先装 EPEL 再装 ffmpeg(自带 ffprobe)
sudo yum install -y epel-release
sudo yum install -y ffmpeg
# 验证:ffprobe -version
```
没装 ffprobe 时视频/音频整理会跳过,图片照常处理。
2. **色情识别(可选)**:Linux 上同样要装 Python 与 nudenet,并在 `application.properties` 里配 `nsfw.python`(如 `/usr/bin/python3`):
```shell
sudo yum install -y python3
pip3 install nudenet
```
模型文件 `lib/320n.onnx` 需与二进制放在同一目录(没有会自动下载)。
3. 其余步骤与 Windows 一致:`application.properties`、`media-info.db` 随运行目录自动创建。
### 前端开发模式(无需重复构建)
```shell
# 终端 1:后端源码运行(端口 8080)
go run .
# 终端 2:前端开发服务器(端口 3000,/api 代理到 8080,热更新)
cd frontend
npm install && npm run dev
```
浏览器访问 http://localhost:3000 联调。**只有发布时才需要** `npm run build` 重新构建前端。
## 目录结构
```text
├── main.go # 程序入口(Gin + 内嵌前端产物)
├── application.properties # 运行配置(端口 / 数据库 / 旋转开关 / 色情识别)
├── lib/320n.onnx # 色情识别模型(随仓库分发,离线可用)
├── build.sh # 编译脚本
├── internal/
│ ├── scanner/ # 文件扫描、MIME 读取、候选过滤
│ ├── probe/ # 媒体探测:图片尺寸 / ffprobe / EXIF / 明暗分析(含内嵌 ffprobe)
│ ├── classify/ # 纯函数归类规则(方向 + 标准分辨率 + K档 + 明暗等级)
│ ├── organize/ # 整理编排:探测 → 归类 → 收集 → 移动 → 入库
│ ├── mover/ # 文件移动(rename / 跨盘 copy+delete)
│ ├── store/ # SQLite 媒体信息 / 色情记录持久化 + 查询/统计
│ ├── service/ # 后台任务编排(扫描整理任务 + 状态/日志/WebSocket)
│ ├── nsfw/ # 色情图片识别(内嵌 python 服务 + NudeNet 本地 HTTP)
│ ├── api/ # HTTP 接口层(扫描 / 媒体库 / 统计 / 色情识别 / 目录浏览)
│ └── digest/ # 文件 MD5 / 指纹采样
└── frontend/ # 前端工程(Vue 2 + ElementUI + Vite)
```
## 注意事项
- 默认使用**移动**模式,原文件会从源目录移走;跨磁盘时自动降级为复制 + 删除
- 已整理文件全部进入 `KMP已整理` 白名单目录,重复运行会跳过它,并把新文件并入已有分类目录
- 扫描会自动跳过 `KMP已整理` 输出目录、`media-info.db` 数据库与 ffprobe 临时文件
- 同一时间只允许一次扫描整理;整理期间会占用较多 CPU/磁盘,属正常现象
- EXIF 旋转转正需要 ffmpeg:配置 `ffmpeg.path` 指定路径,或让 ffmpeg 在 PATH 里;找不到 ffmpeg 时不转正像素,仍按显示方向归类。完全关闭旋转处理(也不按显示方向调宽高)可在配置里设 `organize.rotateImages=false`
## License
Apache 2.0