-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
172 lines (149 loc) · 4.79 KB
/
main.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
// Note that this assumes that iphone photos have been imported into Pictures via Image Capture
package main
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
const (
defaultInputDir = "Pictures"
defaultOutputDir = "Desktop/organizer"
)
type FileInfo struct {
Name string `json:"name"`
Extension string `json:"extension"`
Size int64 `json:"size"`
InputPath string `json:"input_path"`
ModTime time.Time `json:"modified"`
CapturedTime time.Time `json:"captured"`
ExifData map[string]ExifDataEntry `json:"exif,omitempty"`
}
func main() {
// Set default input and output directories
inputDir := filepath.Join(os.Getenv("HOME"), defaultInputDir)
outputDir := filepath.Join(os.Getenv("HOME"), defaultOutputDir)
outputDirRecognized := filepath.Join(outputDir, "output")
outputDirError := filepath.Join(outputDir, "error")
// Override input/output directories with command-line arguments, if provided
if len(os.Args) == 3 {
inputDir = os.Args[1]
outputDir = os.Args[2]
outputDirRecognized = filepath.Join(outputDir, "output")
outputDirError = filepath.Join(outputDir, "error")
}
// Create the output and error directories
for _, dir := range []string{outputDirRecognized, outputDirError} {
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
log.Fatalf("Error creating directory '%s': %v", dir, err)
}
}
// Enumerate files in the input directory
fileInfos := []FileInfo{}
err := filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip directories
if info.IsDir() {
return nil
}
// Extract file metadata
fileExt := strings.ToLower(strings.TrimPrefix(filepath.Ext(info.Name()), "."))
fileInfo := FileInfo{
Name: info.Name(),
Extension: fileExt,
Size: info.Size(),
InputPath: strings.TrimPrefix(strings.TrimPrefix(path, inputDir), string(os.PathSeparator)),
ModTime: info.ModTime(),
CapturedTime: info.ModTime(),
}
// Extract EXIF dataif possible
if fileExt == "jpg" || fileExt == "jpeg" || fileExt == "heic" || fileExt == "png" {
fileInfo.ExifData = exifExtract(path)
entry, found := fileInfo.ExifData["DateTimeOriginal"]
if !found {
entry, found = fileInfo.ExifData["DateTime"]
}
if !found {
entry, found = fileInfo.ExifData["DateTimeDigitized"]
}
if found {
if dateStr, ok := entry.Value.(string); ok {
if capturedTime, err := time.Parse("2006:01:02 15:04:05", dateStr); err == nil {
fileInfo.CapturedTime = capturedTime
if false {
fmt.Printf("%s %v\n", fileInfo.Name, fileInfo.CapturedTime)
}
}
}
}
}
// Append file information
fileInfos = append(fileInfos, fileInfo)
return nil
})
if err != nil {
log.Fatalf("Error enumerating input directory: %v", err)
}
// Sort the FileInfo array by InputPath
sort.Slice(fileInfos, func(i, j int) bool {
return fileInfos[i].InputPath < fileInfos[j].InputPath
})
// Process and copy files
for _, fileInfo := range fileInfos {
srcPath := filepath.Join(inputDir, fileInfo.InputPath)
var destDir string
var destFileName string
// Check file type and decide destination directory
switch strings.ToLower(fileInfo.Extension) {
case "jpg", "jpeg", "heic", "mov", "mp4", "png", "gif":
// Generate year-month subdirectory name based on CapturedTime
yearMonth := fileInfo.CapturedTime.Format("2006-01")
destDir = filepath.Join(outputDirRecognized, yearMonth)
// Generate new filename in the desired format
destFileName = fmt.Sprintf(
"snap-%s-%s",
fileInfo.CapturedTime.Format("2006-01-02-15-04-05"),
strings.ToLower(strings.ReplaceAll(fileInfo.Name, "_", "")),
)
case "aae":
continue
default:
destDir = outputDirError
destFileName = fileInfo.Name // Keep original filename for unrecognized files
}
// Ensure the destination directory exists
if err := os.MkdirAll(destDir, os.ModePerm); err != nil {
log.Printf("Error creating directory '%s': %v", destDir, err)
continue
}
// Copy the file
destPath := filepath.Join(destDir, destFileName)
if err := copyFile(srcPath, destPath); err != nil {
log.Printf("Error copying file '%s' to '%s': %v", srcPath, destPath, err)
}
}
}
// copyFile copies a file from src to dst
func copyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("unable to open source file: %v", err)
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("unable to create destination file: %v", err)
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return fmt.Errorf("error during file copy: %v", err)
}
return nil
}