upd
This commit is contained in:
@@ -66,26 +66,26 @@ const scanLogFlushInterval = 200
|
||||
//
|
||||
// 这样设计是为了避免"边分类边搬"导致目录反复开关、文件搬来搬去,效率更高。
|
||||
type Organizer struct {
|
||||
pickerDir string // 整理后的文件都放到这个根目录下
|
||||
ffprobePath string // ffprobe 程序路径;为空表示没有可用解码器
|
||||
ffmpegPath string // ffmpeg 程序路径;为空表示没有转正 EXIF 旋转图片的能力
|
||||
store *store.Store // 数据库;为 nil 表示不记录媒体信息
|
||||
mover *mover.Mover // 负责实际搬文件的家伙
|
||||
ctx context.Context // 中止信号;ctx 被取消时探测/归类立即停下
|
||||
nsfw *nsfw.Detector // 色情检测器;为 nil 表示没启用,图片不检测
|
||||
animeNsfw *anime_nsfw.Detector // 动漫色情检测器;为 nil 表示没启用
|
||||
rotateImages bool // 是否处理 EXIF 旋转(转正/按显示方向归类);false 则完全不动,按存储方向归类
|
||||
buckets map[string][]string // 目标目录 → 里面该放哪些文件(只记源路径)
|
||||
order []string // 目标目录的先后顺序(保证输出稳定、不来回跳)
|
||||
onProgress func(phase string, done, total int)
|
||||
pending []*store.MediaInfo // 待写入数据库的媒体记录(攒一批再批量写)
|
||||
resume map[string]store.ScanLog // 续传表:源路径 → 上次归类结果(重扫跳过重新探测)
|
||||
resumeSkip int32 // 本次续传跳过的文件数(并行累计)
|
||||
pendingScan []store.ScanLog // 待写入续传表的记录(攒一批再批量写)
|
||||
nsfwChecked int32 // 色情识别已检查的图片数(并行累计,只涨不跌)
|
||||
nsfwHits int32 // 色情识别命中的图片数(并行累计)
|
||||
animeNsfwChecked int32 // 动漫色情识别已检查的图片数
|
||||
animeNsfwHits int32 // 动漫色情识别命中的图片数
|
||||
pickerDir string // 整理后的文件都放到这个根目录下
|
||||
ffprobePath string // ffprobe 程序路径;为空表示没有可用解码器
|
||||
ffmpegPath string // ffmpeg 程序路径;为空表示没有转正 EXIF 旋转图片的能力
|
||||
store *store.Store // 数据库;为 nil 表示不记录媒体信息
|
||||
mover *mover.Mover // 负责实际搬文件的家伙
|
||||
ctx context.Context // 中止信号;ctx 被取消时探测/归类立即停下
|
||||
nsfw *nsfw.Detector // 色情检测器;为 nil 表示没启用,图片不检测
|
||||
animeNsfw *anime_nsfw.Detector // 动漫色情检测器;为 nil 表示没启用
|
||||
rotateImages bool // 是否处理 EXIF 旋转(转正/按显示方向归类);false 则完全不动,按存储方向归类
|
||||
buckets map[string][]string // 目标目录 → 里面该放哪些文件(只记源路径)
|
||||
order []string // 目标目录的先后顺序(保证输出稳定、不来回跳)
|
||||
onProgress func(phase string, done, total int)
|
||||
pending []*store.MediaInfo // 待写入数据库的媒体记录(攒一批再批量写)
|
||||
resume map[string]store.ScanLog // 续传表:源路径 → 上次归类结果(重扫跳过重新探测)
|
||||
resumeSkip int32 // 本次续传跳过的文件数(并行累计)
|
||||
pendingScan []store.ScanLog // 待写入续传表的记录(攒一批再批量写)
|
||||
nsfwChecked int32 // 色情识别已检查的图片数(并行累计,只涨不跌)
|
||||
nsfwHits int32 // 色情识别命中的图片数(并行累计)
|
||||
animeNsfwChecked int32 // 动漫色情识别已检查的图片数
|
||||
animeNsfwHits int32 // 动漫色情识别命中的图片数
|
||||
// 阶段耗时统计(并行 worker 累计,单位纳秒,只用于日志诊断瓶颈)
|
||||
tImageProbe int64 // 图片尺寸探测总耗时
|
||||
tImgMd5 int64 // 图片 MD5/指纹计算总耗时
|
||||
@@ -392,22 +392,23 @@ func (o *Organizer) flushRecords() {
|
||||
// 后面的归类(判断重复、分目录)在串行阶段根据这个结论来定。
|
||||
type imageProbeResult struct {
|
||||
// 下面几个是"体检结论",最多只有一个为 true:
|
||||
psd bool // 是 PSD 设计稿,不做处理原样收走
|
||||
unsupported bool // 不支持的格式,归入"已忽略"
|
||||
probeErr bool // 体检失败(打不开/读不出),归入"读取异常"
|
||||
resume bool // 续传命中:上次已归类过,跳过全部体检,直接复用目标目录
|
||||
resumeDir string // 续传命中的目标目录
|
||||
res *probe.ProbeResult // 体检出的尺寸等信息(体检成功时才有)
|
||||
md5Str string // 文件 MD5
|
||||
headMd5 string // 文件头采样 MD5(大文件指纹)
|
||||
tailMd5 string // 文件尾采样 MD5
|
||||
size int64 // 文件大小
|
||||
darkScore float64 // 暗色像素占比(用于亮度分类)
|
||||
lightScore float64 // 亮色像素占比
|
||||
nsfwScore float64 // 色情检测最高分(未检测为 0)
|
||||
nsfw bool // 是否判定为色情(检测可用时才可能为 true)
|
||||
animeNsfwScore float64 // 动漫色情检测最高分(未检测为 0)
|
||||
animeNsfw bool // 是否判定为动漫色情
|
||||
psd bool // 是 PSD 设计稿,不做处理原样收走
|
||||
unsupported bool // 不支持的格式,归入"已忽略"
|
||||
probeErr bool // 体检失败(打不开/读不出),归入"读取异常"
|
||||
resume bool // 续传命中:上次已归类过,跳过全部体检,直接复用目标目录
|
||||
resumeDir string // 续传命中的目标目录
|
||||
res *probe.ProbeResult // 体检出的尺寸等信息(体检成功时才有)
|
||||
md5Str string // 文件 MD5
|
||||
headMd5 string // 文件头采样 MD5(大文件指纹)
|
||||
tailMd5 string // 文件尾采样 MD5
|
||||
size int64 // 文件大小
|
||||
darkScore float64 // 暗色像素占比(用于亮度分类)
|
||||
lightScore float64 // 亮色像素占比
|
||||
nsfwScore float64 // 色情检测最高分(未检测为 0)
|
||||
nsfw bool // 是否判定为色情(检测可用时才可能为 true)
|
||||
nsfwDetections []nsfw.NsfwDetection // NudeNet 检测详情
|
||||
animeNsfwScore float64 // 动漫色情检测最高分(未检测为 0)
|
||||
animeNsfw bool // 是否判定为动漫色情
|
||||
animeNsfwRatings anime_nsfw.AnimeRatings // WD Tagger 四个 rating 的原始分数
|
||||
}
|
||||
|
||||
@@ -464,7 +465,7 @@ func (o *Organizer) probeImage(f scanner.File) imageProbeResult {
|
||||
// 色情识别(GIF 不做;没启用检测器就直接跳过)
|
||||
if o.nsfw != nil && !strings.EqualFold(f.Ext, ".gif") {
|
||||
nsfwStart := time.Now()
|
||||
score, hit, derr := o.nsfw.Detect(f.Path)
|
||||
score, hit, detections, derr := o.nsfw.Detect(f.Path)
|
||||
atomic.AddInt64(&o.tNsfw, int64(time.Since(nsfwStart)))
|
||||
if derr != nil {
|
||||
// 检测失败按"未命中"处理,不影响整理流程
|
||||
@@ -472,6 +473,7 @@ func (o *Organizer) probeImage(f scanner.File) imageProbeResult {
|
||||
} else {
|
||||
r.nsfwScore = score
|
||||
r.nsfw = hit
|
||||
r.nsfwDetections = detections
|
||||
checked := atomic.AddInt32(&o.nsfwChecked, 1)
|
||||
if hit {
|
||||
atomic.AddInt32(&o.nsfwHits, 1)
|
||||
@@ -601,6 +603,11 @@ func (o *Organizer) ProcessImages(images []scanner.File) {
|
||||
if info != nil && r.nsfwScore > 0 {
|
||||
info.NsfwScore = r.nsfwScore
|
||||
info.NsfwThreshold = o.nsfw.Threshold()
|
||||
if len(r.nsfwDetections) > 0 {
|
||||
if detJSON, err := json.Marshal(r.nsfwDetections); err == nil {
|
||||
info.NsfwDetections = string(detJSON)
|
||||
}
|
||||
}
|
||||
}
|
||||
if info != nil && r.animeNsfwScore > 0 {
|
||||
info.AnimeNsfwScore = r.animeNsfwScore
|
||||
|
||||
Reference in New Issue
Block a user