feature: 算法更新

This commit is contained in:
2024-06-08 21:13:51 +08:00
parent 9c39c2c76b
commit 9f1225b3a8
6 changed files with 471 additions and 517 deletions
+319 -488
View File
@@ -1,6 +1,7 @@
package main
import (
"flag"
"fmt"
"image"
"image/color"
@@ -13,13 +14,16 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/tiff"
_ "golang.org/x/image/webp"
)
// 支持的图片格式
// supportedExts 支持的图片格式
var supportedExts = map[string]bool{
".jpg": true, ".jpeg": true, ".png": true,
".bmp": true, ".webp": true, ".gif": true,
@@ -38,37 +42,142 @@ type WallpaperInfo struct {
// Config 配置结构
type Config struct {
SourceDir string // 源目录,如果是当前目录,设置为 "."
TargetDir string // 移动或复制到的目标目录,不建议设置为 ".",因为那不是脱了裤子放屁吗
DarkThreshold float64 // 黑暗程度评分(0-1,越高越暗)
MinWidth int // 图片最小宽度
MinHeight int // 图片最大宽度
MinSizeKB int64 // 文件大小最小值
MaxSizeMB int64 // 文件大小最大值
CopyMode bool // true=复制, false=移动
Parallel int // 并发数
Verbose bool // 是否打印日志
SourceDir string // 源目录
TargetDir string // 目标目录基名(自动按黑暗等级创建子目录)
MinWidth int // 图片最小宽度
MinHeight int // 图片最小高度
MinSizeKB int64 // 文件大小最小值(KB)
MaxSizeMB int64 // 文件大小最大值(MB)
CopyMode bool // true=复制, false=移动
Parallel int // 并发数
Verbose bool // 是否打印详细日志
SleepSeconds int // 执行前等待秒数(0=不等待)
}
// 默认配置
// DefaultConfig 默认配置
func DefaultConfig() Config {
return Config{
SourceDir: ".",
TargetDir: "./dark_wallpapers",
DarkThreshold: 0.75,
MinWidth: 1920,
MinHeight: 1080,
MinSizeKB: 100, // 最小100KB
MaxSizeMB: 100, // 最大100MB
CopyMode: false, // 默认移动
Parallel: 24, // i5-12490f配置
Verbose: true,
SourceDir: ".",
MinWidth: 1920,
MinHeight: 1080,
MinSizeKB: 100,
MaxSizeMB: 100,
CopyMode: false,
Parallel: 24,
Verbose: true,
SleepSeconds: 5,
}
}
// 处理单个图片文件
func processImageFile(path string, config Config) (*WallpaperInfo, error) {
// 检查文件大小
func main() {
config := DefaultConfig()
// 命令行参数
flag.StringVar(&config.SourceDir, "source", config.SourceDir, "源目录路径")
flag.StringVar(&config.TargetDir, "target", "", "目标目录基名(留空则自动用源目录名)")
flag.IntVar(&config.MinWidth, "min-width", config.MinWidth, "图片最小宽度(像素)")
flag.IntVar(&config.MinHeight, "min-height", config.MinHeight, "图片最小高度(像素)")
flag.Int64Var(&config.MinSizeKB, "min-size", config.MinSizeKB, "最小文件大小(KB)")
flag.Int64Var(&config.MaxSizeMB, "max-size", config.MaxSizeMB, "最大文件大小(MB)")
flag.BoolVar(&config.CopyMode, "copy", config.CopyMode, "复制模式(默认移动)")
flag.IntVar(&config.Parallel, "parallel", config.Parallel, "并发数")
flag.BoolVar(&config.Verbose, "verbose", config.Verbose, "详细输出")
flag.IntVar(&config.SleepSeconds, "sleep", config.SleepSeconds, "执行前等待秒数(0=不等待)")
flag.Parse()
// 检查源目录
if _, err := os.Stat(config.SourceDir); os.IsNotExist(err) {
log.Fatalf("源目录不存在: %s", config.SourceDir)
}
absSource, _ := filepath.Abs(config.SourceDir)
baseDirName := filepath.Base(absSource)
// 如果目标目录为空,自动生成
if config.TargetDir == "" {
config.TargetDir = filepath.Join(absSource, baseDirName+"_dark_wallpapers")
} else {
if !filepath.IsAbs(config.TargetDir) {
config.TargetDir = filepath.Join(absSource, config.TargetDir)
}
}
fmt.Println("🖤 黑色壁纸查找与移动工具")
fmt.Println(strings.Repeat("=", 50))
fmt.Printf(" 源目录: %s\n", config.SourceDir)
fmt.Printf(" 目标目录: %s(按黑暗等级分子目录)\n", config.TargetDir)
fmt.Printf(" 最小分辨率: %dx%d\n", config.MinWidth, config.MinHeight)
fmt.Printf(" 文件大小: %dKB ~ %dMB\n", config.MinSizeKB, config.MaxSizeMB)
fmt.Printf(" 模式: %s\n", map[bool]string{true: "复制", false: "移动"}[config.CopyMode])
fmt.Println(strings.Repeat("=", 50))
if config.SleepSeconds > 0 {
fmt.Printf("\n⏳ 程序将在 %d 秒后开始执行(Ctrl+C 取消)...\n", config.SleepSeconds)
time.Sleep(time.Duration(config.SleepSeconds) * time.Second)
}
// 黑暗阈值等级(从高到低)
thresholds := []int{95, 85, 75, 65, 55}
// 第一步:一次性扫描所有图片并分析
fmt.Println("\n🔍 开始扫描图片...")
allWallpapers, err := scanAllImages(config)
if err != nil {
log.Fatalf("扫描失败: %v", err)
}
fmt.Printf("\n✅ 扫描完成!共 %d 张图片满足分辨率和大小要求\n", len(allWallpapers))
if len(allWallpapers) == 0 {
fmt.Println("❌ 未找到符合条件的图片")
return
}
// 第二步:按黑暗等级分类并移动到不同子目录(从高到低,每张图片只归入一个等级)
for _, threshold := range thresholds {
minScore := float64(threshold) / 100.0
targetDir := filepath.Join(config.TargetDir, fmt.Sprintf("dark_%d", threshold))
fmt.Printf("\n%s\n", strings.Repeat("=", 60))
fmt.Printf("📁 处理黑暗等级 ≥ %d%% 的图片\n", threshold)
fmt.Printf(" 目标目录: %s\n", targetDir)
fmt.Println(strings.Repeat("=", 60))
filtered := filterByDarkness(allWallpapers, minScore)
if len(filtered) == 0 {
fmt.Printf(" ⚠️ 没有符合黑暗等级 ≥ %d%% 的图片\n", threshold)
continue
}
fmt.Printf(" 找到 %d 张符合条件的图片\n", len(filtered))
movedCount, moveErr := moveWallpapers(filtered, targetDir, config.CopyMode)
if moveErr != nil {
log.Printf("⚠️ 移动部分文件时出错: %v", moveErr)
}
fmt.Printf(" ✓ 成功移动/复制 %d 张图片\n", movedCount)
// 从待处理列表中移除本轮已处理的图片,避免后续低阈值重复处理
processed := make(map[string]struct{}, len(filtered))
for _, wp := range filtered {
processed[wp.Path] = struct{}{}
}
remaining := make([]WallpaperInfo, 0, len(allWallpapers))
for _, wp := range allWallpapers {
if _, ok := processed[wp.Path]; !ok {
remaining = append(remaining, wp)
}
}
allWallpapers = remaining
}
fmt.Printf("\n🎉 所有操作完成!文件已整理到: %s/\n", config.TargetDir)
}
// analyzeImageFile 分析单个图片的黑暗程度(不做阈值过滤)
func analyzeImageFile(path string, config Config) (*WallpaperInfo, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return nil, err
@@ -83,32 +192,23 @@ func processImageFile(path string, config Config) (*WallpaperInfo, error) {
return nil, fmt.Errorf("文件太大: %dMB > %dMB", fileInfo.Size()/1024/1024, config.MaxSizeMB)
}
// 打开图片文件
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
// 解码图片
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
// 分析图片
darkScore, avgColor, width, height := analyzeImage(img)
// 检查分辨率
if width < config.MinWidth || height < config.MinHeight {
return nil, fmt.Errorf("分辨率不足: %dx%d < %dx%d", width, height, config.MinWidth, config.MinHeight)
}
// 检查黑暗程度
if darkScore < config.DarkThreshold {
return nil, fmt.Errorf("不够黑暗: %.2f < %.2f", darkScore, config.DarkThreshold)
}
return &WallpaperInfo{
Path: path,
DarkScore: darkScore,
@@ -119,70 +219,209 @@ func processImageFile(path string, config Config) (*WallpaperInfo, error) {
}, nil
}
// 移动或复制文件到目标目录
func moveOrCopyFile(wp *WallpaperInfo, config Config) error {
// 创建目标目录
err := os.MkdirAll(config.TargetDir, 0755)
if err != nil {
// analyzeImage 分析图片的黑暗程度和尺寸
// 使用暗色像素占比(亮度 < 80 的像素比例)作为评分,比平均亮度更能准确反映暗色背景壁纸
func analyzeImage(img image.Image) (darkScore float64, avgColor color.RGBA, width, height int) {
bounds := img.Bounds()
width = bounds.Dx()
height = bounds.Dy()
totalPixels := width * height
if totalPixels == 0 {
return 0, color.RGBA{0, 0, 0, 255}, width, height
}
var sumR, sumG, sumB uint64
darkCount := 0
sampleCount := 0
// 大图采样以提高性能
sampleStep := 1
if totalPixels > 1_000_000 {
sampleStep = int(math.Sqrt(float64(totalPixels) / 100_000))
}
for y := bounds.Min.Y; y < bounds.Max.Y; y += sampleStep {
for x := bounds.Min.X; x < bounds.Max.X; x += sampleStep {
r, g, b, _ := img.At(x, y).RGBA()
r8 := uint8(r >> 8)
g8 := uint8(g >> 8)
b8 := uint8(b >> 8)
sumR += uint64(r8)
sumG += uint64(g8)
sumB += uint64(b8)
// 亮度计算(ITU-R BT.601),判断是否为暗色像素
brightness := 0.299*float64(r8) + 0.587*float64(g8) + 0.114*float64(b8)
if brightness < 80 {
darkCount++
}
sampleCount++
}
}
if sampleCount == 0 {
return 0, color.RGBA{0, 0, 0, 255}, width, height
}
// 暗色像素占比作为黑暗评分(0-1),比平均亮度更准确:
// 一张 80% 黑色背景 + 20% 明亮主体的壁纸,评分 0.80,正确反映"暗色背景"特性
darkScore = float64(darkCount) / float64(sampleCount)
avgR := uint8(sumR / uint64(sampleCount))
avgG := uint8(sumG / uint64(sampleCount))
avgB := uint8(sumB / uint64(sampleCount))
return darkScore, color.RGBA{avgR, avgG, avgB, 255}, width, height
}
// scanAllImages 一次性扫描所有图片并分析黑暗程度
func scanAllImages(config Config) ([]WallpaperInfo, error) {
var allWallpapers []WallpaperInfo
var mu sync.Mutex
jobs := make(chan string, 100)
results := make(chan *WallpaperInfo, 100)
// 预计算目标目录的绝对路径,遍历时跳过
absTarget, _ := filepath.Abs(config.TargetDir)
// 启动 worker 池
var workerWg sync.WaitGroup
for i := 0; i < config.Parallel; i++ {
workerWg.Add(1)
go func(workerID int) {
defer workerWg.Done()
for path := range jobs {
if config.Verbose {
fmt.Printf("Worker %d 处理: %s\n", workerID, filepath.Base(path))
}
wp, err := analyzeImageFile(path, config)
if err != nil {
if config.Verbose {
fmt.Printf("跳过 %s: %v\n", filepath.Base(path), err)
}
continue
}
results <- wp
}
}(i)
}
// 结果收集 goroutine(用 WaitGroup 保证完成)
var resultWg sync.WaitGroup
resultWg.Add(1)
go func() {
defer resultWg.Done()
for wp := range results {
mu.Lock()
allWallpapers = append(allWallpapers, *wp)
mu.Unlock()
}
}()
// 遍历源目录,发送任务
var processedCount int
err := filepath.WalkDir(config.SourceDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() {
// 跳过目标目录(避免重复处理之前的结果)
if absTarget != "" {
absPath, _ := filepath.Abs(path)
if absPath == absTarget || strings.HasPrefix(absPath, absTarget+string(os.PathSeparator)) {
return filepath.SkipDir
}
}
return nil
}
ext := strings.ToLower(filepath.Ext(path))
if supportedExts[ext] {
processedCount++
jobs <- path
}
return nil
})
close(jobs)
workerWg.Wait()
close(results)
resultWg.Wait() // 等待结果收集完成后再排序
// 按黑暗程度排序(从高到低)
sort.Slice(allWallpapers, func(i, j int) bool {
return allWallpapers[i].DarkScore > allWallpapers[j].DarkScore
})
fmt.Printf(" 扫描文件总数: %d\n", processedCount)
fmt.Printf(" 符合分辨率/大小要求: %d\n", len(allWallpapers))
return allWallpapers, err
}
// filterByDarkness 根据黑暗阈值筛选图片
func filterByDarkness(wallpapers []WallpaperInfo, minDarkScore float64) []WallpaperInfo {
var filtered []WallpaperInfo
for _, wp := range wallpapers {
if wp.DarkScore >= minDarkScore {
filtered = append(filtered, wp)
}
}
return filtered
}
// cleanFileName 清理文件名中的特殊字符
func cleanFileName(name string) string {
replacements := map[string]string{
" ": "_", "/": "_", "\\": "_", ":": "_",
"*": "", "?": "", "\"": "", "<": "", ">": "", "|": "",
}
result := name
for old, n := range replacements {
result = strings.ReplaceAll(result, old, n)
}
// 按 rune 截断,避免截断多字节 UTF-8 字符
runes := []rune(result)
if len(runes) > 100 {
result = string(runes[:100])
}
return result
}
// moveOrCopyFile 移动或复制文件到目标目录
func moveOrCopyFile(wp *WallpaperInfo, targetDir string, copyMode bool) error {
if err := os.MkdirAll(targetDir, 0755); err != nil {
return err
}
// 生成目标文件名(保留原文件名,添加黑暗评分)
ext := filepath.Ext(wp.Path)
baseName := strings.TrimSuffix(filepath.Base(wp.Path), ext)
// 清理文件名中的特殊字符
cleanName := cleanFileName(baseName)
// 添加分辨率信息
newFilename := fmt.Sprintf("%s_[%dx%d]_dark%.0f%s",
cleanName, wp.Width, wp.Height, wp.DarkScore*100, ext)
dstPath := filepath.Join(config.TargetDir, newFilename)
dstPath := filepath.Join(targetDir, newFilename)
// 检查目标文件是否已存在
// 文件名冲突:追加序号
if _, err := os.Stat(dstPath); err == nil {
// 文件已存在,添加序号
for i := 1; i < 100; i++ {
newFilename = fmt.Sprintf("%s_[%dx%d]_dark%.0f_(%d)%s",
cleanName, wp.Width, wp.Height, wp.DarkScore*100, i, ext)
dstPath = filepath.Join(config.TargetDir, newFilename)
dstPath = filepath.Join(targetDir, newFilename)
if _, err := os.Stat(dstPath); os.IsNotExist(err) {
break
}
}
}
if config.CopyMode {
// 复制文件
if copyMode {
return copyFile(wp.Path, dstPath)
} else {
// 移动文件
return moveFile(wp.Path, dstPath)
}
return moveFile(wp.Path, dstPath)
}
// 清理文件名
func cleanFileName(name string) string {
// 替换或移除特殊字符
replacements := map[string]string{
" ": "_", "/": "_", "\\": "_", ":": "_",
"*": "", "?": "", "\"": "", "<": "", ">": "", "|": "",
}
result := name
for old, n := range replacements {
result = strings.ReplaceAll(result, old, n)
}
// 限制长度
if len(result) > 100 {
result = result[:100]
}
return result
}
// 复制文件
// copyFile 复制文件
func copyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
@@ -200,432 +439,24 @@ func copyFile(src, dst string) error {
return err
}
// 移动文件
// moveFile 移动文件(优先 rename,跨分区时复制后删除)
func moveFile(src, dst string) error {
// 先尝试重命名(如果在同一分区)
err := os.Rename(src, dst)
if err == nil {
if err := os.Rename(src, dst); err == nil {
return nil
}
// 如果重命名失败,则复制后删除
if err := copyFile(src, dst); err != nil {
return err
}
// 删除原文件
return os.Remove(src)
}
// 显示结果摘要
func printSummary(wallpapers []WallpaperInfo, config Config) {
if len(wallpapers) == 0 {
fmt.Println("❌ 未找到符合条件的黑色壁纸")
return
}
fmt.Println("\n🎯 找到的黑色调壁纸:")
fmt.Println(strings.Repeat("=", 60))
for i, wp := range wallpapers {
if i >= 20 { // 最多显示20个
fmt.Printf("... 还有 %d 个未显示\n", len(wallpapers)-20)
break
}
action := "移动到"
if config.CopyMode {
action = "复制到"
}
fmt.Printf("%2d. %s\n", i+1, filepath.Base(wp.Path))
fmt.Printf(" 原路径: %s\n", wp.Path)
fmt.Printf(" %s: %s\n", action, config.TargetDir)
fmt.Printf(" 黑暗程度: %.1f%%\n", wp.DarkScore*100)
fmt.Printf(" 平均颜色: RGB(%3d, %3d, %3d)\n", wp.AvgColor.R, wp.AvgColor.G, wp.AvgColor.B)
fmt.Printf(" 分辨率: %d × %d\n", wp.Width, wp.Height)
fmt.Printf(" 文件大小: %.1f MB\n", float64(wp.Size)/1024/1024)
// 黑暗程度可视化
bars := int(wp.DarkScore * 20)
fmt.Printf(" [")
for j := 0; j < 20; j++ {
if j < bars {
fmt.Printf("█")
} else {
fmt.Printf(" ")
}
}
fmt.Printf("]\n\n")
}
}
// 保存结果到日志文件
func saveResultsToLog(wallpapers []WallpaperInfo, config Config) {
logFile := filepath.Join(config.TargetDir, "dark_wallpapers_log.txt")
file, err := os.Create(logFile)
if err != nil {
log.Printf("无法创建日志文件: %v", err)
return
}
defer file.Close()
fmt.Fprintf(file, "黑色壁纸处理日志 - %s\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprintf(file, "源目录: %s\n", config.SourceDir)
fmt.Fprintf(file, "目标目录: %s\n", config.TargetDir)
fmt.Fprintf(file, "黑暗阈值: %.2f\n", config.DarkThreshold)
fmt.Fprintf(file, "找到壁纸数量: %d\n\n", len(wallpapers))
for i, wp := range wallpapers {
fmt.Fprintf(file, "%d. %s\n", i+1, filepath.Base(wp.Path))
fmt.Fprintf(file, " 黑暗程度: %.1f%%\n", wp.DarkScore*100)
fmt.Fprintf(file, " 分辨率: %dx%d\n", wp.Width, wp.Height)
fmt.Fprintf(file, " 大小: %.2f MB\n", float64(wp.Size)/1024/1024)
fmt.Fprintf(file, " 原路径: %s\n", wp.Path)
fmt.Fprintf(file, " 目标: %s/%s\n\n", config.TargetDir, filepath.Base(wp.Path))
}
fmt.Printf("📝 详细日志已保存到: %s\n", logFile)
}
// 分析图片的黑暗程度和尺寸
func analyzeImage(img image.Image) (darkScore float64, avgColor color.RGBA, width, height int) {
bounds := img.Bounds()
width = bounds.Dx()
height = bounds.Dy()
totalPixels := width * height
if totalPixels == 0 {
return 0, color.RGBA{0, 0, 0, 255}, width, height
}
var sumR, sumG, sumB uint64
// 采样分析(对于大图片进行采样以提高性能)
sampleStep := 1
if totalPixels > 1000000 { // 超过100万像素则采样
sampleStep = int(math.Sqrt(float64(totalPixels) / 100000))
}
sampleCount := 0
for y := bounds.Min.Y; y < bounds.Max.Y; y += sampleStep {
for x := bounds.Min.X; x < bounds.Max.X; x += sampleStep {
r, g, b, _ := img.At(x, y).RGBA()
sumR += uint64(r >> 8)
sumG += uint64(g >> 8)
sumB += uint64(b >> 8)
sampleCount++
}
}
if sampleCount == 0 {
return 0, color.RGBA{0, 0, 0, 255}, width, height
}
avgR := uint8(sumR / uint64(sampleCount))
avgG := uint8(sumG / uint64(sampleCount))
avgB := uint8(sumB / uint64(sampleCount))
// 计算黑暗程度评分
brightness := 0.299*float64(avgR) + 0.587*float64(avgG) + 0.114*float64(avgB)
darkScore = 1.0 - (brightness / 255.0)
// 增强黑色权重
darkScore = math.Pow(darkScore, 0.6)
// 如果平均颜色值很低,进一步提高评分
if avgR < 40 && avgG < 40 && avgB < 40 {
darkScore = math.Min(1.0, darkScore*1.3)
}
// 惩罚太鲜艳的颜色
if avgR > 200 || avgG > 200 || avgB > 200 {
darkScore *= 0.7
}
return darkScore, color.RGBA{avgR, avgG, avgB, 255}, width, height
}
// findAndMoveDarkWallpapers 查找并移动黑色壁纸(单次扫描版本)
func findAndMoveDarkWallpapers(config Config) ([]WallpaperInfo, error) {
var darkWallpapers []WallpaperInfo
var mu sync.Mutex
var wg sync.WaitGroup
// 创建worker池
jobs := make(chan string, 100)
results := make(chan *WallpaperInfo, 100)
errors := make(chan error, 100)
// 启动worker
for i := 0; i < config.Parallel; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for path := range jobs {
if config.Verbose {
fmt.Printf("Worker %d 处理: %s\n", workerID, filepath.Base(path))
}
wp, err := processImageFile(path, config)
if err != nil {
if config.Verbose {
fmt.Printf("跳过 %s: %v\n", filepath.Base(path), err)
}
continue
}
results <- wp
}
}(i)
}
// 收集结果并移动文件
var processedCount int
var movedCount int
go func() {
for wp := range results {
mu.Lock()
darkWallpapers = append(darkWallpapers, *wp)
mu.Unlock()
// 移动文件
if err := moveOrCopyFile(wp, config); err != nil {
errors <- fmt.Errorf("移动文件失败 %s: %v", wp.Path, err)
continue
}
movedCount++
if config.Verbose {
fmt.Printf("✓ 已处理: %s (黑暗度: %.1f%%)\n",
filepath.Base(wp.Path), wp.DarkScore*100)
}
}
}()
// 收集错误
var processErrors []error
go func() {
for err := range errors {
processErrors = append(processErrors, err)
}
}()
// 遍历源目录,发送任务
err := filepath.WalkDir(config.SourceDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() {
// 跳过目标目录
if filepath.Clean(path) == filepath.Clean(config.TargetDir) {
return filepath.SkipDir
}
return nil
}
ext := strings.ToLower(filepath.Ext(path))
if supportedExts[ext] {
processedCount++
jobs <- path
}
return nil
})
close(jobs)
wg.Wait()
close(results)
close(errors)
// 按黑暗程度排序
sort.Slice(darkWallpapers, func(i, j int) bool {
return darkWallpapers[i].DarkScore > darkWallpapers[j].DarkScore
})
// 打印统计信息
fmt.Printf("\n📊 处理完成统计:\n")
fmt.Printf(" 扫描文件总数: %d\n", processedCount)
fmt.Printf(" 找到黑色壁纸: %d\n", len(darkWallpapers))
fmt.Printf(" 成功移动/复制: %d\n", movedCount)
if len(processErrors) > 0 {
fmt.Printf(" 错误数量: %d\n", len(processErrors))
}
if len(processErrors) > 0 && config.Verbose {
fmt.Println("\n⚠️ 错误列表:")
for _, err := range processErrors {
fmt.Printf(" %v\n", err)
}
}
return darkWallpapers, err
}
func main() {
// 配置
config := DefaultConfig()
config.SourceDir = "."
// 检查源目录是否存在
if _, err := os.Stat(config.SourceDir); os.IsNotExist(err) {
log.Fatalf("源目录不存在: %s", config.SourceDir)
return
}
rootDir, _ := os.Getwd()
baseDirName := filepath.Base(rootDir)
fmt.Println("🖤 黑色壁纸查找与移动工具")
fmt.Println(strings.Repeat("=", 40))
fmt.Println("整理完成的文件在上一级目录中,请勿整理磁盘根目录文件,若无疑问,程序将在 5s 后执行")
fmt.Println(strings.Repeat("=", 40))
time.Sleep(time.Second * 5)
// 定义多个黑暗阈值等级
targetDarkThresholds := []int{95, 85, 75, 65, 55}
// 第一步:一次性扫描所有图片,分析黑暗程度
fmt.Println("\n🔍 开始扫描图片...")
allWallpapers, err := scanAllImages(config)
if err != nil {
log.Fatalf("扫描失败: %v", err)
}
fmt.Printf("\n✅ 扫描完成!共找到 %d 张符合条件的图片\n", len(allWallpapers))
// 第二步:根据黑暗等级分类并移动到不同目录
for _, threshold := range targetDarkThresholds {
darkThreshold := float64(threshold) / 100.0 // 转换为小数形式(如 0.95)
targetDir := "../" + baseDirName + "_dark_wallpapers_" + strconv.Itoa(threshold)
fmt.Printf("\n" + strings.Repeat("=", 60) + "\n")
fmt.Printf("📁 处理黑暗等级 ≥ %d%% 的图片\n", threshold)
fmt.Printf(" 目标目录: %s\n", targetDir)
fmt.Println(strings.Repeat("=", 60))
// 筛选出符合当前黑暗阈值的图片
filteredWallpapers := filterWallpapersByDarkness(allWallpapers, darkThreshold)
if len(filteredWallpapers) == 0 {
fmt.Printf(" ⚠️ 没有符合黑暗等级 ≥ %d%% 的图片\n", threshold)
continue
}
fmt.Printf(" 找到 %d 张符合条件的图片\n", len(filteredWallpapers))
// 移动这些图片到对应目录
movedCount, err := moveWallpapersToTarget(filteredWallpapers, targetDir, config.CopyMode)
if err != nil {
log.Printf("⚠️ 移动部分文件时出错: %v", err)
}
fmt.Printf(" ✓ 成功移动/复制 %d 张图片\n", movedCount)
}
fmt.Println("\n🎉 所有操作完成!")
}
// scanAllImages 一次性扫描所有图片并分析黑暗程度
func scanAllImages(config Config) ([]WallpaperInfo, error) {
var allWallpapers []WallpaperInfo
var mu sync.Mutex
var wg sync.WaitGroup
// 创建worker池
jobs := make(chan string, 100)
results := make(chan *WallpaperInfo, 100)
// 启动worker
for i := 0; i < config.Parallel; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for path := range jobs {
if config.Verbose {
fmt.Printf("Worker %d 处理: %s\n", workerID, filepath.Base(path))
}
wp, err := processImageFile(path, config)
if err != nil {
// 静默跳过不符合条件的图片
continue
}
results <- wp
}
}(i)
}
// 收集结果
go func() {
for wp := range results {
mu.Lock()
allWallpapers = append(allWallpapers, *wp)
mu.Unlock()
}
}()
// 遍历源目录,发送任务
var processedCount int
err := filepath.WalkDir(config.SourceDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() {
return nil
}
ext := strings.ToLower(filepath.Ext(path))
if supportedExts[ext] {
processedCount++
jobs <- path
}
return nil
})
close(jobs)
wg.Wait()
close(results)
// 按黑暗程度排序(从高到低)
sort.Slice(allWallpapers, func(i, j int) bool {
return allWallpapers[i].DarkScore > allWallpapers[j].DarkScore
})
fmt.Printf(" 扫描文件总数: %d\n", processedCount)
fmt.Printf(" 符合条件的图片: %d\n", len(allWallpapers))
return allWallpapers, err
}
// filterWallpapersByDarkness 根据黑暗阈值筛选图片
func filterWallpapersByDarkness(wallpapers []WallpaperInfo, minDarkScore float64) []WallpaperInfo {
var filtered []WallpaperInfo
for _, wp := range wallpapers {
if wp.DarkScore >= minDarkScore {
filtered = append(filtered, wp)
}
}
return filtered
}
// moveWallpapersToTarget 将图片移动到指定目标目录
func moveWallpapersToTarget(wallpapers []WallpaperInfo, targetDir string, copyMode bool) (int, error) {
// moveWallpapers 将图片列表移动到指定目标目录
func moveWallpapers(wallpapers []WallpaperInfo, targetDir string, copyMode bool) (int, error) {
movedCount := 0
var firstErr error
for _, wp := range wallpapers {
// 创建临时配置用于移动文件
tempConfig := Config{
TargetDir: targetDir,
CopyMode: copyMode,
}
if err := moveOrCopyFile(&wp, tempConfig); err != nil {
if err := moveOrCopyFile(&wp, targetDir, copyMode); err != nil {
if firstErr == nil {
firstErr = err
}