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 读取媒体流判定。
快速开始
运行
运行编译出的 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),防止重名覆盖。
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。
明暗评分算法
brightness = 0.299×R + 0.587×G + 0.114×B (ITU-R BT.601)
darkScore = 亮度 < 80 的像素占比
lightScore = 亮度 > 175 的像素占比
- 判定规则:
lightScore高于darkScore15 个百分点以上归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)
构建编译
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)可执行程序
# 方式一: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 后,还要注意:
- ffprobe(视频/音频探测):Linux 构建不内嵌 ffprobe(仓库只带了 Windows 版),程序会回退用系统 PATH 里的
ffprobe。CentOS 上先装:没装 ffprobe 时视频/音频整理会跳过,图片照常处理。# CentOS/RHEL 需要先装 EPEL 再装 ffmpeg(自带 ffprobe) sudo yum install -y epel-release sudo yum install -y ffmpeg # 验证:ffprobe -version - 色情识别(可选):Linux 上同样要装 Python 与 nudenet,并在
application.properties里配nsfw.python(如/usr/bin/python3):模型文件sudo yum install -y python3 pip3 install nudenetlib/nwfs/320n.onnx需与二进制放在同一目录(没有会自动下载)。 - 二次元识别(可选):Linux 上同样要装 Python 与依赖,并在
application.properties里配hentai.python(如/usr/bin/python3):模型文件首次启动会自动从 HuggingFace 下载到pip3 install onnxruntime Pillow numpylib/wd-tagger/(约 1.5GB,需联网)。 - 其余步骤与 Windows 一致:
application.properties、media-info.db随运行目录自动创建。
前端开发模式(无需重复构建)
# 终端 1:后端源码运行(端口 8080)
go run .
# 终端 2:前端开发服务器(端口 3000,/api 代理到 8080,热更新)
cd frontend
npm install && npm run dev
浏览器访问 http://localhost:3000 联调。只有发布时才需要 npm run build 重新构建前端。
目录结构
├── 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