-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfmdupes.go
258 lines (227 loc) · 5.35 KB
/
fmdupes.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
package main
// TODO: sort by count = len(val)
// TODO: save data for know time, size and bitrate
// TODO: process flags
// TODO: fix error when press 'q'
// TODO: move delete file code to function
// TODO: comment and refactor all code
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/cheggaaa/pb"
tag "github.com/wtolson/go-taglib"
gcfg "gopkg.in/gcfg.v1"
)
// Types
type Config struct {
InputDir string
CountDuplicates int `gcfg:"count"`
}
type configFile struct {
Fmdupes Config
}
type Mp3Song struct {
Filename string
Artist string
Title string
Genre string
Path string
Bitrate int
Size int64
}
type SongType struct {
Artist string
Title string
}
// Global
var mp3List []Mp3Song
var xxx map[SongType][]string
var cntMP3Files int
var bar *pb.ProgressBar
func GetMp3Data(filename string) (Mp3Song, error) {
mp3File, err := tag.Read(filename)
if err != nil {
fmt.Println("Open: unable to open file: ", err)
return Mp3Song{}, err
}
defer mp3File.Close()
//fmt.Printf("f: %s, artist: %s, title: %s\n",
// filename, mp3File.Artist(), mp3File.Title())
// fmt.Printf("file: %s, bitrate: %v\n",
// filename, mp3File.Bitrate())
return Mp3Song{
Filename: filename,
Artist: mp3File.Artist(),
Title: mp3File.Title(),
Genre: mp3File.Genre(),
Bitrate: mp3File.Bitrate(),
Size: 0,
}, nil
}
func CountDirWalk(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
if filepath.Ext(path) != ".mp3" {
return nil
}
cntMP3Files++
return nil
}
func DirWalk(path string, fi os.FileInfo, err error) error {
//fmt.Println("walk path: ", path)
if fi.IsDir() {
//fmt.Println("Search in ", path)
//fmt.Printf("Process %d files\n", len(mp3List))
return nil
}
if filepath.Ext(path) != ".mp3" {
return nil
}
data, err := GetMp3Data(path)
if err == nil {
data.Size = fi.Size()
data.Path = path
mp3List = append(mp3List, data)
xxx[SongType{Artist: data.Artist, Title: data.Title}] =
append(xxx[SongType{Artist: data.Artist, Title: data.Title}], path)
bar.Increment()
}
return nil
}
func loadConfig(cfgFile string) Config {
var cfg configFile
err := gcfg.ReadFileInto(&cfg, cfgFile)
if err != nil {
fmt.Errorf("Error reading config file: %s", err)
}
if cfg.Fmdupes.CountDuplicates == 0 {
cfg.Fmdupes.CountDuplicates = 2
}
return cfg.Fmdupes
}
func main() {
xxx = make(map[SongType][]string)
// for future
// delete := flag.Bool("d", false, "prompt user for files to preserve and delete all")
// size := flag.Bool("S", false, "show size of duplicate files")
// flag.Usage = func() {
// fmt.Fprintf(os.Stderr, "Usage: %s OPTIONS dirs\n", os.Args[0])
// flag.PrintDefaults()
// os.Exit(2)
// }
// flag.Parse()
cfg := loadConfig("fmdupes.conf")
// Walk in dirs
//
dirs := strings.Split(cfg.InputDir, ",")
// count all music files
for _, dir := range dirs {
err := filepath.Walk(dir, CountDirWalk)
if err != nil {
fmt.Errorf("DirWalk error: %v", err)
}
}
fmt.Printf("Found %d music files in directory %s\n", cntMP3Files, cfg.InputDir)
bar = pb.New(cntMP3Files)
bar.Start()
for _, dir := range dirs {
err := filepath.Walk(dir, DirWalk)
if err != nil {
fmt.Errorf("DirWalk error: %v", err)
}
}
bar.FinishPrint("Result:")
fmt.Println("-------")
cntDuplicates := 0
for _, val := range xxx {
if len(val) > 1 {
cntDuplicates++
}
}
fmt.Printf("Found %d duplicates\n", cntDuplicates)
exit := false
cntCons := 0
var deleteAllSize int64
var cntDeletedFiles int
for key, val := range xxx {
if exit {
break
}
count := len(val)
if count > 1 {
cntCons++
}
if count >= cfg.CountDuplicates {
fmt.Printf("%d. %v, %d duplicates\n", cntCons, key, count)
for id, path := range val {
i := id + 1
fmt.Printf("[%d] %s\n", i, path)
}
fmt.Printf("Set [1-%d, all] to delete: ", len(val))
in := ""
fmt.Scanln(&in)
switch in {
case "0":
break
case "":
break
case "q":
exit = true
break
case "all":
fmt.Println("Need to test")
for _, filepath := range val {
f, _ := os.Open(filepath)
fi, _ := f.Stat()
size := fi.Size()
f.Close()
var kilobytes float64
kilobytes = (float64)(size / 1024)
var megabytes float64
megabytes = (float64)(kilobytes / 1024)
fmt.Printf("Delete %s, size: %.3f MB\n", filepath, megabytes)
err := os.Remove(filepath)
if err != nil {
fmt.Printf("Error delete file: %s\n", err)
} else {
deleteAllSize += size
cntDeletedFiles++
}
}
break
default:
ids := strings.Split(in, ",")
for _, is := range ids {
ii, err := strconv.Atoi(is)
if err == nil && ii <= len(val) {
filepath := val[ii-1]
f, _ := os.Open(filepath)
fi, _ := f.Stat()
size := fi.Size()
f.Close()
var kilobytes float64
kilobytes = (float64)(size / 1024)
var megabytes float64
megabytes = (float64)(kilobytes / 1024)
fmt.Printf("Delete %s, size: %.3f MB\n", filepath, megabytes)
err := os.Remove(filepath)
if err != nil {
fmt.Printf("Error delete file: %s\n", err)
} else {
deleteAllSize += size
cntDeletedFiles++
}
}
}
}
fmt.Println()
} // if
} // for
fmt.Printf("Delete %d files, all size: %.3f MB\n",
cntDeletedFiles,
((float64)(deleteAllSize) / 1024 / 1024))
}