commit 9a89c04b4cb67208a9bf2e0bf820c486d66f9162 Author: PhatPhuckDave Date: Fri May 23 13:08:45 2025 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e90cf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.jpg +*.jpeg diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ed9616f --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module imhash + +go 1.24.3 + +require ( + git.site.quack-lab.dev/dave/cylogger v1.2.2 + github.com/corona10/goimagehash v1.1.0 +) + +require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a1985a8 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +git.site.quack-lab.dev/dave/cylogger v1.2.2 h1:4xUXASEBlG9NiGxh7f57xHh9imW4unHzakIEpQoKC5E= +git.site.quack-lab.dev/dave/cylogger v1.2.2/go.mod h1:VS9MI4Y/cwjCBZgel7dSfCQlwtAgHmfvixOoBgBhtKg= +github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI= +github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a4c3a5a --- /dev/null +++ b/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "flag" + "image/jpeg" + "os" + "sync" + + logger "git.site.quack-lab.dev/dave/cylogger" + "github.com/corona10/goimagehash" +) + +func main() { + thresh := flag.Int("thresh", 10, "Threshold for distance") + flag.Parse() + logger.InitFlag() + hashes := &sync.Map{} + logger.Info("Starting") + logger.Info("Threshold: %v", *thresh) + logger.Info("Files: %d", len(flag.Args())) + + wg := sync.WaitGroup{} + for _, file := range flag.Args() { + wg.Add(1) + go func(file string) { + defer wg.Done() + log := logger.Default.WithPrefix(file) + imgfile, err := os.Open(file) + if err != nil { + log.Error("Failed to open file: %v", err) + return + } + defer imgfile.Close() + img, err := jpeg.Decode(imgfile) + if err != nil { + log.Error("Failed to decode image: %v", err) + return + } + hash, err := goimagehash.ExtPerceptionHash(img, 8, 8) + if err != nil { + log.Error("Failed to calculate hash: %v", err) + return + } + log.Debug("Hashed: %v", hash) + hashes.Store(file, hash) + }(file) + } + + groupedImages := make(map[string][]string) + wg.Wait() + hashes.Range(func(key, value interface{}) bool { + filea := key.(string) + hasha := value.(*goimagehash.ExtImageHash) + hashes.Range(func(key, value interface{}) bool { + fileb := key.(string) + hashb := value.(*goimagehash.ExtImageHash) + if filea == fileb { + return true + } + distance, err := hasha.Distance(hashb) + if err != nil { + logger.Error("Failed to calculate distance: %v", err) + return true + } + logger.Debug("Distance between %v and %v: %v", filea, fileb, distance) + if distance <= *thresh { + groupedImages[filea] = append(groupedImages[filea], fileb) + } + return true + }) + return true + }) + + for file, files := range groupedImages { + logger.Info("Grouped %v with %v", file, files) + } + logger.Info("Done") +}