243 lines
16 KiB
Markdown
243 lines
16 KiB
Markdown
# 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/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/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/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 |