Initial commit
This commit is contained in:
commit
ba95e927fe
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue