commit 1f009c4e05f3932d5324667dbf2701e4eb50a0d0 Author: wenyifan Date: Wed Aug 3 10:43:12 2022 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3392656 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.exe +*.idea +phototransfer \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c5060ac --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module phototransfer + +go 1.16 diff --git a/main.go b/main.go new file mode 100644 index 0000000..39ee275 --- /dev/null +++ b/main.go @@ -0,0 +1,448 @@ +package main + +import ( + "flag" + "fmt" + "io" + "io/fs" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + "unsafe" +) + +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + proc = modkernel32.NewProc("GetFileTime") + allExts []string +) + +type Context struct { + Stop bool + ImgSizeThreshold int64 + VideoSizeThreshold int64 + CopyUseLink bool +} + +func main() { + context := &Context{ + Stop: false, + } + var rootPath string + var targetPath string + var exts string + var videoExts string + var copyExts string + var threadCount int + var videoThreadCount int + flag.StringVar(&rootPath, "root", "", "source root dir") + flag.StringVar(&targetPath, "target", "", "target dir") + flag.StringVar(&exts, "ext", "jpg|bmp|gif", "image ext") + flag.StringVar(&videoExts, "video-ext", "", "video file ext") + flag.StringVar(©Exts, "copy-ext", "mkv|mp4|mov|m4v|mpeg", "copy file ext") + flag.Int64Var(&context.ImgSizeThreshold, "img-size", 1024, "image size larger than will be processed (KiB)") + flag.Int64Var(&context.VideoSizeThreshold, "video-size", 2048000, "video size larger than will be processed (KiB)") + flag.IntVar(&threadCount, "thread", 10, "thread count") + flag.IntVar(&videoThreadCount, "video-thread", 1, "video thread count") + flag.BoolVar(&context.CopyUseLink, "link", true, "copy use link instead") + flag.Parse() + + _, errSt := os.Stat(rootPath) + if errSt != nil { + fmt.Println("root path not exist") + return + } + + if targetPath == "" { + fmt.Println("target path not set") + return + } + + if rootPath[len(rootPath)-1:] == "\\" && rootPath[len(rootPath)-1:] == "/" { + rootPath = rootPath[:len(rootPath)-1] + } + if targetPath[len(targetPath)-1:] == "\\" && targetPath[len(targetPath)-1:] == "/" { + targetPath = targetPath[:len(targetPath)-1] + } + + _, errTarget := os.Stat(targetPath) + if errTarget != nil { + os.MkdirAll(targetPath, os.ModePerm) + } + + ext := strings.Split(strings.ToLower(exts), "|") + videoExt := strings.Split(strings.ToLower(videoExts), "|") + copyExt := strings.Split(strings.ToLower(copyExts), "|") + + allExts = append(allExts, ext...) + allExts = append(allExts, videoExt...) + allExts = append(allExts, copyExt...) + + var paths []string + var videoPaths []string + var copyPaths []string + var dirs []string + err := filepath.Walk(rootPath, func(path string, f os.FileInfo, err error) error { + if f == nil { + return err + } + if f.IsDir() { + dirs = append(dirs, path) + return nil + } + if len(path) > 3 { + e := path[len(path)-3:] + if checkIsInList(strings.ToLower(e), copyExt) { + copyPaths = append(copyPaths, path) + return nil + } + if checkIsInList(strings.ToLower(e), ext) { + paths = append(paths, path) + return nil + } + if checkIsInList(strings.ToLower(e), videoExt) { + videoPaths = append(videoPaths, path) + return nil + } + } + return nil + }) + if err != nil { + fmt.Printf("filepath.Walk() returned %v\n\n", err) + } + for _, dir := range dirs { + processDir(dir, rootPath, targetPath) + } + //copy + if len(copyExt) > 0 { + runWorker(copyPaths, threadCount, rootPath, targetPath, "copy", context) + } + //photo + if len(ext) > 0 { + runWorker(paths, threadCount, rootPath, targetPath, "image", context) + } + //video + if len(videoExt) > 0 { + runWorker(videoPaths, videoThreadCount, rootPath, targetPath, "video", context) + } +} + +func runWorker(paths []string, threadCount int, rootPath string, targetPath string, processType string, context *Context) { + cmd := make(chan string, len(paths)) + resultCh := make(chan string, len(paths)) + for i := 0; i < threadCount; i++ { + go worker(cmd, resultCh, rootPath, targetPath, processType, context) + } + + for _, path := range paths { + cmd <- path + } + total := len(paths) + for i := range paths { + s := <-resultCh + fmt.Println(processType + ": " + strconv.Itoa(i+1) + "/" + strconv.Itoa(total) + " | " + s) + } +} + +func worker(path <-chan string, result chan<- string, rootPath string, targetPath string, processType string, context *Context) { + for { + if context.Stop { + return + } + if str, ok := <-path; ok { + if str != "" { + switch processType { + case "image": + stat, err := os.Stat(str) + if err != nil { + continue + } + if stat.Size() > context.ImgSizeThreshold*1024 { + process(str, rootPath, targetPath) + } else { + if context.CopyUseLink { + processLink(str, rootPath, targetPath) + } else { + processCopy(str, rootPath, targetPath) + } + } + case "video": + stat, err := os.Stat(str) + if err != nil { + continue + } + if stat.Size() > context.VideoSizeThreshold*1024 { + processVideo(str, rootPath, targetPath) + } else { + if context.CopyUseLink { + processLink(str, rootPath, targetPath) + } else { + processCopy(str, rootPath, targetPath) + } + } + case "copy": + if context.CopyUseLink { + processLink(str, rootPath, targetPath) + } else { + processCopy(str, rootPath, targetPath) + } + + } + } + result <- str + } else { + return + } + } +} + +func processDir(path string, rootPath string, targetPath string) { + rawDir := path[len(rootPath):] + + _, errStat := os.Stat(targetPath + rawDir) + + var ctime, atime, mtime time.Time + var errGetFileTime error + flag := false + filepath.Walk(path, func(p string, info fs.FileInfo, err error) error { + if flag { + return nil + } + if len(p) > 3 { + e := p[len(p)-3:] + if checkIsInList(strings.ToLower(e), allExts) { + ctime, atime, mtime, errGetFileTime = GetFileTime(p) + flag = true + } + } + return nil + }) + if errGetFileTime != nil || !flag { + ctime, atime, mtime, _ = GetFileTime(path) + } + if os.IsNotExist(errStat) { + os.MkdirAll(targetPath+rawDir, os.ModePerm) + SetFileTime(targetPath+rawDir, ctime, atime, mtime) + fmt.Println("mkdir: " + targetPath + rawDir) + } else { + SetFileTime(targetPath+rawDir, ctime, atime, mtime) + fmt.Println("Exist: " + targetPath + rawDir) + } +} + +func process(path string, rootPath string, targetPath string) { + rawPath := path[len(rootPath):] + + _, err := os.Stat(targetPath + rawPath) + if err == nil { + return + } + + command := exec.Command("magick.exe", "convert", "-resize", "1920x1920>", "-quality", "90", path, targetPath+rawPath) + stdout, _ := command.StdoutPipe() + + defer stdout.Close() + + err2 := command.Start() + if err2 != nil { + fmt.Println("command Start Error:" + err2.Error()) + } + ioutil.ReadAll(stdout) + + ctime, atime, mtime, errGetFileTime := GetFileTime(rootPath + rawPath) + if errGetFileTime != nil { + fmt.Println("GetFileTime Error:" + errGetFileTime.Error()) + return + } + + err3 := SetFileTime(targetPath+rawPath, ctime, atime, mtime) + if err3 != nil { + fmt.Println("SetFileTime Error:" + err3.Error()) + } + +} + +func processCopy(path string, rootPath string, targetPath string) { + rawPath := path[len(rootPath):] + + _, err := os.Stat(targetPath + rawPath) + if err == nil { + return + } + + _, errCopy := copy(path, targetPath+rawPath) + if errCopy != nil { + return + } + + ctime, atime, mtime, errGetFileTime := GetFileTime(path) + if errGetFileTime != nil { + fmt.Println("GetFileTime Error:" + errGetFileTime.Error()) + return + } + + err3 := SetFileTime(targetPath+rawPath, ctime, atime, mtime) + if err3 != nil { + fmt.Println("SetFileTime Error:" + err3.Error()) + } +} + +func processLink(path string, rootPath string, targetPath string) { + rawPath := path[len(rootPath):] + + _, err := os.Stat(targetPath + rawPath) + if err == nil { + return + } + + errLink := os.Link(path, targetPath+rawPath) + if errLink == nil { + return + } + + _, errCopy := copy(path, targetPath+rawPath) + if errCopy != nil { + return + } + + ctime, atime, mtime, errGetFileTime := GetFileTime(path) + if errGetFileTime != nil { + fmt.Println("GetFileTime Error:" + errGetFileTime.Error()) + return + } + + err3 := SetFileTime(targetPath+rawPath, ctime, atime, mtime) + if err3 != nil { + fmt.Println("SetFileTime Error:" + err3.Error()) + } +} + +func processVideo(path string, rootPath string, targetPath string) { + rawPath := path[len(rootPath):] + + _, err := os.Stat(targetPath + rawPath) + if err == nil { + return + } + + index := strings.LastIndex(rawPath, ".") + litePath := rawPath[:index] + + out := targetPath + litePath + ".mp4" + + _, errOut := os.Stat(out) + if errOut == nil { + return + } + + _, errTmp := os.Stat(out + ".tmp") + if errTmp == nil { + os.Remove(out + ".tmp") + } + + command := exec.Command("ffmpeg.exe", "-i", path, "-pix_fmt", "yuv420p", "-acodec", "copy", "-vcodec", "libx264", "-crf", "21", "-f", "mp4", out+".tmp") + stdout, _ := command.StdoutPipe() + + defer stdout.Close() + + err2 := command.Start() + if err2 != nil { + fmt.Println("command Start Error:" + err2.Error()) + } + ioutil.ReadAll(stdout) + + ctime, atime, mtime, errGetFileTime := GetFileTime(rootPath + rawPath) + if errGetFileTime != nil { + fmt.Println("GetFileTime Error:" + errGetFileTime.Error()) + return + } + + os.Rename(out+".tmp", out) + err3 := SetFileTime(out, ctime, atime, mtime) + if err3 != nil { + fmt.Println("SetFileTime Error:" + err3.Error()) + } +} + +func checkIsInList(str string, list []string) bool { + for _, s := range list { + if s == str { + return true + } + } + return false +} + +func SetFileTime(path string, ctime, atime, mtime time.Time) (err error) { + path, err = syscall.FullPath(path) + if err != nil { + return + } + pathPtr, err := syscall.UTF16PtrFromString(path) + if err != nil { + return + } + handle, err := syscall.CreateFile(pathPtr, syscall.FILE_WRITE_ATTRIBUTES, syscall.FILE_SHARE_WRITE, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) + if err != nil { + return + } + + defer syscall.Close(handle) + a := syscall.NsecToFiletime(syscall.TimespecToNsec(syscall.NsecToTimespec(atime.UnixNano()))) + c := syscall.NsecToFiletime(syscall.TimespecToNsec(syscall.NsecToTimespec(ctime.UnixNano()))) + m := syscall.NsecToFiletime(syscall.TimespecToNsec(syscall.NsecToTimespec(mtime.UnixNano()))) + return syscall.SetFileTime(handle, &c, &a, &m) +} + +func GetFileTime(path string) (ctime time.Time, atime time.Time, mtime time.Time, err error) { + path, err = syscall.FullPath(path) + if err != nil { + return + } + pathPtr, err := syscall.UTF16PtrFromString(path) + if err != nil { + return + } + handle, err := syscall.CreateFile(pathPtr, syscall.FILE_WRITE_ATTRIBUTES, syscall.FILE_SHARE_WRITE, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) + if err != nil { + return + } + + var a syscall.Filetime + var c syscall.Filetime + var m syscall.Filetime + + syscall.Syscall6(proc.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(&c)), uintptr(unsafe.Pointer(&a)), uintptr(unsafe.Pointer(&m)), 0, 0) + return time.Unix(c.Nanoseconds()/1e9, 0), time.Unix(c.Nanoseconds()/1e9, 0), time.Unix(m.Nanoseconds()/1e9, 0), err +} + +func copy(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + + defer destination.Close() + nBytes, err := io.Copy(destination, source) + return nBytes, err +}