# 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 单独归类 - **媒体信息入库**:自动把文件 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 相同但路径不同视为不同文件,照常整理。 ### 色情图片识别(可选) 基于本机 **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)已随仓库入库并内嵌 ### 前端开发模式(无需重复构建) ```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