package main import ( "errors" "flag" "fmt" "io" "log" "os" "path" "time" "github.com/evanoberholster/imagemeta" ) const version = "v0.0.3_2024-02-13" const layout = "2006-01-02T15:04:05.000Z" func main() { fmt.Printf("version: %s\n (get latest version at https://github.com/arnaucube/exif-dates/releases )\n\n", version) var versionFlag bool var inputDirFlag, outputDirFlag string flag.BoolVar(&versionFlag, "version", false, "print current version") flag.BoolVar(&versionFlag, "v", false, "print current version") flag.StringVar(&inputDirFlag, "input", "./", "input directory") flag.StringVar(&inputDirFlag, "i", "./", "input directory") flag.StringVar(&outputDirFlag, "output", "output", "output directory") flag.StringVar(&outputDirFlag, "o", "output", "output directory") flag.Parse() if versionFlag { os.Exit(0) } if err := os.Mkdir(outputDirFlag, os.ModePerm); err != nil { log.Fatal("output directory already exists") } entries, err := os.ReadDir(inputDirFlag) if err != nil { log.Fatal(err) } nImgsDetected := 0 nImgsConverted := 0 var unconvertedFileNames []string for _, e := range entries { // skip subdirectories if e.IsDir() { continue } nImgsDetected++ fileName := inputDirFlag + "/" + e.Name() fileExtension := path.Ext(fileName) // get file fmt.Printf("--> FileName: %s,", fileName) f, err := os.Open(fileName) if err != nil { fmt.Println("\ne", err) } defer f.Close() // get camera date date, err := getExifDate(f) if err != nil { fmt.Printf(" Error: %s\n\n", err) unconvertedFileNames = append(unconvertedFileNames, fileName) continue } fmt.Println("\n DATE", date) dateString := date.String() if date.IsZero() { // if date is not set (=0), do not use the zero date as // name, and reuse the original name of the file fn := e.Name() dateString = fn[0 : len(fn)-len(fileExtension)] } newFileName := getValidFileName(outputDirFlag+"/"+dateString, fileExtension) newFileName = newFileName + fileExtension fmt.Printf(" storing the new img as %s\n\n", newFileName) // duplicate the original file into newFileName f, err = os.Open(fileName) if err != nil { fmt.Printf("\nerror (os.Open): %s\n\n", err) } defer f.Close() fo, err := os.Create(newFileName) if err != nil { panic(err) } defer fo.Close() buf := make([]byte, 1024) for { n, err := f.Read(buf) if err != nil && err != io.EOF { panic(err) } if n == 0 { break } if _, err := fo.Write(buf[:n]); err != nil { panic(err) } } // set the img date into the file date (file's access and modification times) // Notice that when exif.time=0, this will be the date being // set here. In a future version might change it to just reuse // the original file date. os.Chtimes(newFileName, *date, *date) nImgsConverted++ } fmt.Printf("converted %d images out of %d\n", nImgsConverted, nImgsDetected) fmt.Println("Unconverted images:") for i := 0; i < len(unconvertedFileNames); i++ { fmt.Println(" ", unconvertedFileNames[i]) } } func getExifDate(f *os.File) (*time.Time, error) { x, err := imagemeta.Decode(f) if err != nil { return nil, err } t := x.DateTimeOriginal() return &t, nil } func getValidFileName(fileName, fileExtension string) string { _, err := os.Open(fileName + fileExtension) if !errors.Is(err, os.ErrNotExist) { return getValidFileName(fileName+"_1", fileExtension) } return fileName }