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 }