From ba95e927fed6de327b70fd136a73721a778267e3 Mon Sep 17 00:00:00 2001 From: Gregory Eremin Date: Sat, 8 Jun 2019 14:15:12 +0200 Subject: [PATCH] Initial commit --- main.go | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++ main_test.go | 17 ++++++ 2 files changed, 175 insertions(+) create mode 100644 main.go create mode 100644 main_test.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..23c87e7 --- /dev/null +++ b/main.go @@ -0,0 +1,158 @@ +package main + +import ( + "flag" + "fmt" + "io" + "log" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/rwcarlsen/goexif/exif" +) + +type processFn func(path, format string) error + +var counters = make(map[string]int) + +func main() { + srcDir := flag.String("in", ".", "Source directory") + format := flag.String("out", "dist/%Y/%m/%d-%H%M%S", + "Output format (use strftime symbols %Y, %y, %m, %d, %H, %M, %S)", + ) + dryRun := flag.Bool("dryrun", false, "Dry run") + flag.Parse() + + formatNorm := toTimeFormat(*format) + var fn processFn + n := -1 + if *dryRun { + fmt.Println("Dry run. Examples of moved files:") + fn = previewFile + n = 10 + } else { + fn = processFile + } + if err := processDir(*srcDir, formatNorm, n, fn); err != nil { + log.Println(err) + } +} + +func processDir(dir, format string, max int, fn processFn) error { + var i int + return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if i < 0 || i > max { + return filepath.SkipDir + } + if !info.IsDir() { + i++ + return fn(path, format) + } + return nil + }) +} + +func processFile(path, format string) error { + if ext := strings.ToLower(filepath.Ext(path)); !isSupportedExt(ext) { + return nil + } + + src, err := os.Open(path) + if err != nil { + return err + } + defer src.Close() + + ts, err := getDate(src) + if err != nil { + return err + } + + newPath := ts.Format(format) + "-" + strconv.Itoa(nextIndex(ts)) + ".jpg" + dir := filepath.Dir(newPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + dest, err := os.Create(newPath) + if err != nil { + return err + } + defer dest.Close() + + if _, err := io.Copy(dest, src); err != nil { + return err + } + + return nil +} + +func previewFile(path, format string) error { + if ext := strings.ToLower(filepath.Ext(path)); !isSupportedExt(ext) { + return nil + } + + src, err := os.Open(path) + if err != nil { + return err + } + defer src.Close() + + ts, err := getDate(src) + if err != nil { + return err + } + + newPath := ts.Format(format) + "-" + strconv.Itoa(nextIndex(ts)) + ".jpg" + fmt.Printf("%s -> %s\n", path, newPath) + + return nil +} + +func isSupportedExt(ext string) bool { + switch ext { + case ".jpg": + return true + default: + return false + } +} + +func getDate(fd io.ReadSeeker) (time.Time, error) { + ef, err := exif.Decode(fd) + if err != nil { + return time.Time{}, err + } + + _, err = fd.Seek(0, 0) + if err != nil { + return time.Time{}, err + } + + return ef.DateTime() +} + +func nextIndex(ts time.Time) int { + key := ts.Format("20060102150405") + counters[key]++ + return counters[key] +} + +func toTimeFormat(f string) string { + f = strings.Replace(f, "%y", "2006", -1) + f = strings.Replace(f, "%Y", "06", -1) + f = strings.Replace(f, "%m", "01", -1) + f = strings.Replace(f, "%d", "02", -1) + + f = strings.Replace(f, "%H", "15", -1) + f = strings.Replace(f, "%M", "04", -1) + f = strings.Replace(f, "%S", "05", -1) + + return f +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..a916df3 --- /dev/null +++ b/main_test.go @@ -0,0 +1,17 @@ +package main + +import ( + "testing" +) + +func TestToTimeFormat(t *testing.T) { + assertEqualStr(t, "dist/photos/06/01/02-150405", toTimeFormat("dist/photos/%Y/%m/%d-%H%M%S")) + assertEqualStr(t, "dist/2006-01-02/150405", toTimeFormat("dist/%y-%m-%d/%H%M%S")) +} + +func assertEqualStr(t *testing.T, exp, out string) { + t.Helper() + if exp != out { + t.Errorf("Expected %q, got %q", exp, out) + } +} \ No newline at end of file