449 lines
11 KiB
Go
449 lines
11 KiB
Go
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
|
|
}
|