- 新增 anime_nsfw 模块:WD Tagger v3 模型检测动漫/二次元图片 - 修复误判:当 general 评分最高时判定为安全,避免非动漫图误判 - 数据库存储四个 rating 原始分数(JSON),无需重扫即可查看 - 媒体库新增动漫色情检测列、文件路径列(支持一键复制) - 修复预览图加载占位符显示两个点的问题 - 移动文件按钮不再限制扫描完成后才显示
109 lines
4.1 KiB
Go
109 lines
4.1 KiB
Go
// Package config 负责读程序的配置文件(application.properties)。
|
||
//
|
||
// 大白话理解:程序启动时先看看配置文件里写了啥,没写就用默认值。
|
||
// 配置文件就在程序旁边,叫 application.properties,用记事本就能改。
|
||
package config
|
||
|
||
import (
|
||
"runtime"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/magiconair/properties"
|
||
)
|
||
|
||
// Config 程序配置:
|
||
// - Port:网页服务用哪个端口(默认 8080)
|
||
// - Dsn:数据库文件叫什么(默认 media-info.db,放在程序目录下)
|
||
// - AutoOpen:启动后要不要自动打开浏览器
|
||
// - FFmpegPath:ffmpeg 程序路径(用于把带 EXIF 旋转标记的图片转正);留空自动在 PATH 里找
|
||
// - RotateImages:是否处理图片 EXIF 旋转(默认 true);false 则完全不处理,按存储方向/尺寸归类
|
||
// - Workers:探测/MIME/移动阶段的并发 worker 数(默认 4;设 0 表示用满全部 CPU 核数)
|
||
// - NSFWEnabled:是否开启色情图片识别(本地 python + NudeNet,默认 false)
|
||
// - NSFWPython:python 可执行文件路径;留空自动在 PATH 里找
|
||
// - NSFWThreshold:判定阈值 0~1(默认 0.5)
|
||
// - AnimeNSFWEnabled:是否开启动漫色情图片识别(本地 python + WD Tagger v3,默认 false)
|
||
// - AnimeNSFWThreshold:动漫判定阈值 0~1(默认 0.5)
|
||
type Config struct {
|
||
Port int
|
||
Dsn string
|
||
AutoOpen bool
|
||
FFmpegPath string
|
||
RotateImages bool
|
||
Workers int
|
||
NSFWEnabled bool
|
||
NSFWPython string
|
||
NSFWThreshold float64
|
||
AnimeNSFWEnabled bool
|
||
AnimeNSFWThreshold float64
|
||
}
|
||
|
||
// appConfig 当前生效的配置(程序运行期间一直不变)。
|
||
var appConfig Config
|
||
|
||
// GetAppConfig 返回当前生效的配置。
|
||
func GetAppConfig() Config {
|
||
return appConfig
|
||
}
|
||
|
||
// LoadAppConfig 从 application.properties 读配置;文件不存在就用默认值。
|
||
func LoadAppConfig() {
|
||
// 先给一套默认值
|
||
appConfig = Config{Port: 8080, Dsn: "media-info.db", AutoOpen: true, RotateImages: true, Workers: 4, NSFWThreshold: 0.5, AnimeNSFWThreshold: 0.5}
|
||
|
||
p, err := properties.LoadFile("application.properties", properties.UTF8)
|
||
if err != nil {
|
||
return // 没有配置文件,就用默认值
|
||
}
|
||
// 配置文件里写了就覆盖默认值
|
||
if port := p.GetInt("server.port", 0); port > 0 {
|
||
appConfig.Port = port
|
||
}
|
||
if v, ok := p.Get("database.dsn"); ok && v != "" {
|
||
appConfig.Dsn = v
|
||
}
|
||
appConfig.AutoOpen = p.GetBool("server.autoOpen", true)
|
||
// ffmpeg 路径:留空表示自动在 PATH 里找
|
||
if v, ok := p.Get("ffmpeg.path"); ok && v != "" {
|
||
appConfig.FFmpegPath = v
|
||
}
|
||
appConfig.RotateImages = p.GetBool("organize.rotateImages", true)
|
||
// 并发 worker 数:只认非负整数,填了别的就保持默认
|
||
if v, ok := p.Get("organize.workers"); ok && strings.TrimSpace(v) != "" {
|
||
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil && n >= 0 {
|
||
appConfig.Workers = n
|
||
}
|
||
}
|
||
// 色情识别:默认关;python 路径留空自动在 PATH 找;阈值只认 0~1 之间的小数
|
||
appConfig.NSFWEnabled = p.GetBool("nsfw.enabled", false)
|
||
if v, ok := p.Get("nsfw.python"); ok && v != "" {
|
||
appConfig.NSFWPython = v
|
||
}
|
||
if v, ok := p.Get("nsfw.threshold"); ok && strings.TrimSpace(v) != "" {
|
||
if t, err := strconv.ParseFloat(strings.TrimSpace(v), 64); err == nil && t > 0 && t < 1 {
|
||
appConfig.NSFWThreshold = t
|
||
}
|
||
}
|
||
// 动漫色情识别:默认关;阈值只认 0~1 之间的小数
|
||
appConfig.AnimeNSFWEnabled = p.GetBool("anime_nsfw.enabled", false)
|
||
if v, ok := p.Get("anime_nsfw.threshold"); ok && strings.TrimSpace(v) != "" {
|
||
if t, err := strconv.ParseFloat(strings.TrimSpace(v), 64); err == nil && t > 0 && t < 1 {
|
||
appConfig.AnimeNSFWThreshold = t
|
||
}
|
||
}
|
||
}
|
||
|
||
// WorkerCount 返回探测/MIME/移动阶段使用的并发 worker 数:
|
||
// - 配置里 organize.workers > 0 时用配置值(默认 4);
|
||
// - 配置为 0 时用满全部 CPU 核数。
|
||
func WorkerCount() int {
|
||
if appConfig.Workers > 0 {
|
||
return appConfig.Workers
|
||
}
|
||
n := runtime.NumCPU()
|
||
if n < 1 {
|
||
return 1
|
||
}
|
||
return n
|
||
}
|