# 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