This commit is contained in:
wenyifan 2022-08-03 10:43:12 +08:00
commit 1f009c4e05
3 changed files with 454 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.exe
*.idea
phototransfer

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module phototransfer
go 1.16

448
main.go Normal file
View File

@ -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(&copyExts, "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
}