159 lines
2.8 KiB
Go
159 lines
2.8 KiB
Go
|
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
|
||
|
}
|