Files
kenaito-media-picker/README.md
T
2025-08-26 21:07:42 +08:00

243 lines
16 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 端可逐张预览复核
- **二次元识别(可选)**:本机 Python + wd-tagger 识别二次元/动漫风格图片,输出 safe/questionable/explicit 三级分级,命中的图片移入 `图片_二次元_*` 目录并记录分级标签与 top tags,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 |
| 媒体库 | 按类型 / 方向 / 分辨率 / 编码 / 格式 / 文件名筛选,分页 + 列排序 |
| 色情识别 | 展示最近一次开启识别扫描命中的图片:检测分 + 完整路径 + 缩略图预览,便于人工复核 |
| 二次元识别 | 展示最近一次开启识别扫描命中的二次元图片:分级(safe/questionable/explicit)+ 检测分 + top 标签 + 完整路径 + 缩略图预览,便于人工复核 |
## 配置(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),敏感部位检测分达到即判为色情 |
| `hentai.enabled` | `false` | 是否开启二次元图片识别 |
| `hentai.python` | 空 | python 可执行文件路径;留空自动在 PATH 找(可与 nsfw.python 相同) |
| `hentai.threshold` | `0.5` | 判定阈值(0~1),questionable 或 explicit 分数达到即判为二次元敏感 |
## 整理规则
整理结果统一放进输出目录下的 **`KMP已整理`** 目录(默认在扫描目录下创建,也可在页面指定存放目录)。文件名会加一长串随机前缀(如 `a1b2..._照片.jpg`),防止重名覆盖。
```text
KMP已整理/
├── 图片_横屏_1080P/
│ ├── bright_20/ ← 偏亮
│ ├── mid/ ← 明暗均衡
│ └── dark_40/ ← 偏暗
├── 图片_竖屏_2K/
├── 图片_等比_普通/ ← 非标分辨率:普通 / 1K~19K / 原图
├── 图片_横屏_UltraWide3KP/
├── 图片_GIF/
├── 图片_PSD/ ← PSD 设计稿原样收走
├── 图片_读取异常/ 图片_已忽略/
├── 图片_敏感_横屏_1080P/bright_20/ ← 判为色情的图片(明暗子目录照常)
├── 图片_二次元_横屏_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/human_nwfs/320n.onnx`(本仓库已附带,约 11.6MB,离线可用);本地缺失时自动下载到该位置,下载失败再回退用 nudenet 包自带模型
- **可调**:`nsfw.python`(python 路径)、`nsfw.threshold`(判定阈值)
- **敏感类**:`FEMALE_GENITALIA_EXPOSED` / `MALE_GENITALIA_EXPOSED` / `ANUS_EXPOSED` / `FEMALE_BREAST_EXPOSED`,任一分数达标即判定
- **降级**:未装 python / nudenet 时打印警告并跳过识别,图片按普通流程整理,不影响其它功能
### 二次元图片识别(可选)
基于本机 **Python + wd-tagger**(SmilingWolf/wd-vit-large-tagger-v3)的二次元图片分级识别,专门针对动漫/二次元风格训练,对 R15/R18 擦边图识别效果优于 NudeNet。判为敏感(questionable/explicit)的图片移入 `图片_二次元_*` 分类目录(明暗子目录照常保留),并可在 Web「二次元识别」页面查看每张图的分级、检测分、top 标签、路径与预览。
- **开启**:`application.properties` 设 `hentai.enabled=true`
- **依赖**:本机安装 Python 并 `pip install onnxruntime Pillow numpy`
- **模型**:首次启动自动从 HuggingFace 下载到 `lib/wd-tagger/`(约 1.5GB,需联网);下载完成后离线可用
- **可调**:`hentai.python`(python 路径)、`hentai.threshold`(判定阈值)
- **分级输出**:`rating:safe` / `rating:questionable` / `rating:explicit`,questionable 或 explicit 分数达到阈值即判定为敏感
- **标签输出**:同时输出 top 10 高置信度标签(如 `1girl`, `solo`, `long_hair` 等),便于人工复核
- **降级**:未装 python / 依赖或模型下载失败时打印警告并跳过识别,图片按普通流程整理,不影响其它功能
- **与色情识别的关系**:两者独立运行,可同时启用;同一张图优先走色情识别(nsfw > hentai > normal)
## 构建编译
```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/human_nwfs/320n.onnx` 需与二进制放在同一目录(没有会自动下载)。
3. **二次元识别(可选)**:Linux 上同样要装 Python 与依赖,并在 `application.properties` 里配 `hentai.python`(如 `/usr/bin/python3`):
```shell
pip3 install onnxruntime Pillow numpy
```
模型文件首次启动会自动从 HuggingFace 下载到 `lib/wd-tagger/`(约 1.5GB,需联网)。
4. 其余步骤与 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/human_nwfs/320n.onnx # 色情识别模型(随仓库分发,离线可用)
├── lib/wd-tagger/ # 二次元识别模型(首次启动自动下载,约 1.5GB)
├── build.sh # 编译脚本
├── internal/
│ ├── scanner/ # 文件扫描、MIME 读取、候选过滤
│ ├── probe/ # 媒体探测:图片尺寸 / ffprobe / EXIF / 明暗分析(含内嵌 ffprobe)
│ ├── classify/ # 纯函数归类规则(方向 + 标准分辨率 + K档 + 明暗等级)
│ ├── organize/ # 整理编排:探测 → 归类 → 收集 → 移动 → 入库
│ ├── mover/ # 文件移动(rename / 跨盘 copy+delete)
│ ├── store/ # SQLite 媒体信息 / 色情记录 / 二次元记录持久化 + 查询/统计
│ ├── service/ # 后台任务编排(扫描整理任务 + 状态/日志/WebSocket)
│ ├── nsfw/ # 色情图片识别(内嵌 python 服务 + NudeNet 本地 HTTP)
│ ├── hentai/ # 二次元图片识别(内嵌 python 服务 + wd-tagger 本地 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