exif-dates/main.go
main.goRaw
1package main
2
3import (
4 "errors"
5 "flag"
6 "fmt"
7 "io"
8 "log"
9 "os"
10 "path"
11 "time"
12
13 "github.com/evanoberholster/imagemeta"
14)
15
16const version = "v0.0.3_2024-02-13"
17const layout = "2006-01-02T15:04:05.000Z"
18
19func main() {
20 fmt.Printf("version: %s\n (get latest version at https://github.com/arnaucube/exif-dates/releases )\n\n", version)
21
22 var versionFlag bool
23 var inputDirFlag, outputDirFlag string
24 flag.BoolVar(&versionFlag, "version", false, "print current version")
25 flag.BoolVar(&versionFlag, "v", false, "print current version")
26 flag.StringVar(&inputDirFlag, "input", "./", "input directory")
27 flag.StringVar(&inputDirFlag, "i", "./", "input directory")
28 flag.StringVar(&outputDirFlag, "output", "output", "output directory")
29 flag.StringVar(&outputDirFlag, "o", "output", "output directory")
30 flag.Parse()
31
32 if versionFlag {
33 os.Exit(0)
34 }
35
36 if err := os.Mkdir(outputDirFlag, os.ModePerm); err != nil {
37 log.Fatal("output directory already exists")
38 }
39
40 entries, err := os.ReadDir(inputDirFlag)
41 if err != nil {
42 log.Fatal(err)
43 }
44
45 nImgsDetected := 0
46 nImgsConverted := 0
47 var unconvertedFileNames []string
48 for _, e := range entries {
49 // skip subdirectories
50 if e.IsDir() {
51 continue
52 }
53 nImgsDetected++
54
55 fileName := inputDirFlag + "/" + e.Name()
56 fileExtension := path.Ext(fileName)
57 // get file
58 fmt.Printf("--> FileName: %s,", fileName)
59 f, err := os.Open(fileName)
60 if err != nil {
61 fmt.Println("\ne", err)
62 }
63 defer f.Close()
64
65 // get camera date
66 date, err := getExifDate(f)
67 if err != nil {
68 fmt.Printf(" Error: %s\n\n", err)
69 unconvertedFileNames = append(unconvertedFileNames, fileName)
70 continue
71 }
72 fmt.Println("\n DATE", date)
73 dateString := date.String()
74 if date.IsZero() {
75 // if date is not set (=0), do not use the zero date as
76 // name, and reuse the original name of the file
77 fn := e.Name()
78 dateString = fn[0 : len(fn)-len(fileExtension)]
79 }
80
81 newFileName := getValidFileName(outputDirFlag+"/"+dateString, fileExtension)
82 newFileName = newFileName + fileExtension
83
84 fmt.Printf(" storing the new img as %s\n\n", newFileName)
85
86 // duplicate the original file into newFileName
87 f, err = os.Open(fileName)
88 if err != nil {
89 fmt.Printf("\nerror (os.Open): %s\n\n", err)
90 }
91 defer f.Close()
92 fo, err := os.Create(newFileName)
93 if err != nil {
94 panic(err)
95 }
96 defer fo.Close()
97 buf := make([]byte, 1024)
98 for {
99 n, err := f.Read(buf)
100 if err != nil && err != io.EOF {
101 panic(err)
102 }
103 if n == 0 {
104 break
105 }
106
107 if _, err := fo.Write(buf[:n]); err != nil {
108 panic(err)
109 }
110 }
111 // set the img date into the file date (file's access and modification times)
112 // Notice that when exif.time=0, this will be the date being
113 // set here. In a future version might change it to just reuse
114 // the original file date.
115 os.Chtimes(newFileName, *date, *date)
116 nImgsConverted++
117 }
118 fmt.Printf("converted %d images out of %d\n", nImgsConverted, nImgsDetected)
119 fmt.Println("Unconverted images:")
120 for i := 0; i < len(unconvertedFileNames); i++ {
121 fmt.Println(" ", unconvertedFileNames[i])
122 }
123}
124
125func getExifDate(f *os.File) (*time.Time, error) {
126 x, err := imagemeta.Decode(f)
127 if err != nil {
128 return nil, err
129 }
130 t := x.DateTimeOriginal()
131 return &t, nil
132}
133
134func getValidFileName(fileName, fileExtension string) string {
135 _, err := os.Open(fileName + fileExtension)
136 if !errors.Is(err, os.ErrNotExist) {
137 return getValidFileName(fileName+"_1", fileExtension)
138 }
139 return fileName
140}