arnaucube
arnaucube/exif-datesPublic Self-hosted repository
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}