-
Notifications
You must be signed in to change notification settings - Fork 0
/
cli.go
141 lines (113 loc) · 4.34 KB
/
cli.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/logrusorgru/aurora/v3"
"github.com/mattn/go-isatty"
)
type extensions []string
func (e *extensions) String() string {
return strings.Join(*e, ",")
}
func (e *extensions) Set(val string) error {
*e = extensions{}
for _, ext := range strings.Split(val, ",") {
*e = append(*e, ext)
}
return nil
}
var (
gSearchExt extensions
gInput string
gVerbose bool
gUseCache bool
gTidyCache bool
gExportJSON bool
gNoMergeGroups bool
GitVersion string
gShowVersion bool
gCachePath string
gThreads int
gMaxDist int
au aurora.Aurora
)
const DESCRIPTION string = `
Is a tool for finding image duplicates (or just similar images). Outputs
images grouped by similarity to stdout so you can process them as you please.
It uses https://github.com/vitali-fedulov/images (for its precise matching)
together with phash from https://github.com/corona10/goimagehash (to detect
cropped images and allow for variable similarity threshold)
`
const EXAMPLES string = `
Examples:
find duplicates in ~/Pictures
lsidups -i ~/Pictures > dups.txt
or compare just selected images
fd 'mashu' -e png --changed-within 2weeks ~/Pictures > yourlist.txt
lsidups < yourlist.txt > dups.txt
then process them in any image viewer that can read stdin (sxiv, imv...)
sxiv -io < dups.txt
or you could export json instead
lsidups -j < yourlist.txt > dups.json
if you planning to run lsidups on the same directory multiple times
- consider using cache to speed things up
lsidups -c -i ~/Pictures > dups.txt
if you want to save cache file to the custom location (directories will be created
for you if necessary)
lsidups -c -cache-path ~/where/to/store/cache.gob -i ~/Pictures > dups.txt
also it is worth noting that lsidups merges groups if some of their items are the same.
i think it makes sense from the user perspective, but the resulting group
might contain images that are not all actually similar with each other: let's
say we have 3 images: [1.png 2.png 3.png], 1 and 2 hashes are similar enough
to be considered related images, and 2 and 3 also similar enough, but 1 and 3
are far apart enough to be considered different. If you want to get 2
groups: [1.png 2.png] and [2.png 3.png] - pass flag -g
`
func usage() {
name := filepath.Base(os.Args[0])
fmt.Fprint(flag.CommandLine.Output(), name+DESCRIPTION)
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %[1]s:\n", name)
flag.PrintDefaults()
fmt.Fprint(flag.CommandLine.Output(), EXAMPLES)
}
func cacheDir() string {
base := "."
if d := os.Getenv("XDG_CACHE_HOME"); d != "" {
base = d
base = filepath.Join(d, "lsidups")
} else if d := os.Getenv("HOME"); d != "" {
base = filepath.Join(d, ".cache", "lsidups")
} else if d := os.Getenv("APPDATA"); d != "" {
base = filepath.Join(d, "lsidups")
} else {
base, _ = os.Getwd()
}
return base
}
func init() {
gSearchExt = extensions{".jpg", ".jpeg", ".png", ".gif", ".webp"}
gCachePath = filepath.Join(cacheDir(), "cachemap.gob")
flag.BoolVar(&gShowVersion, "version", false, "show version and exit")
flag.Var(&gSearchExt, "e", "image extensions (with dots) to look for")
flag.StringVar(&gInput, "i", "-",
"directory to search (recursively) for duplicates, when set to - can take list of images\n"+
"to compare from stdin")
flag.BoolVar(&gVerbose, "v", false, "show time it took to complete key parts of the search")
flag.BoolVar(&gExportJSON, "j", false, "output duplicates as json instead of standard flat list")
flag.BoolVar(&gUseCache, "c", false, "use caching (works per file path, honors mtime)")
flag.BoolVar(&gTidyCache, "ct", false, "remove missing/changed (on drive) files from cache and exit")
flag.BoolVar(&gNoMergeGroups, "g", false, "do not merge groups if some of the items are the same (default will merge)")
flag.StringVar(&gCachePath, "cache-path", gCachePath, "where cache file will be stored")
flag.IntVar(&gThreads, "T", runtime.NumCPU(), "number of processing threads")
flag.IntVar(&gMaxDist, "d", 8, "phash threshold distance (less = more precise match, but more false negatives)")
if gThreads < 1 {
gThreads = runtime.NumCPU()
}
// color output only when stderr is terminal
au = aurora.NewAurora(isatty.IsTerminal(os.Stderr.Fd()))
flag.Usage = usage
}