From 4bc2f2ab50d825e983608079f411629be569e6db Mon Sep 17 00:00:00 2001 From: Roman Baeriswyl Date: Mon, 27 Oct 2025 22:51:42 +0100 Subject: [PATCH] Added more stuff --- go.mod | 1 + go.sum | 2 + main.go | 9 ++++ shared/files.go | 22 +++++++++ shared/trashcan.go | 51 +++++++++++++++++++++ tasks/clean-mov-files.go | 43 ++++++++++++++++++ tasks/image-sort.go | 96 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 224 insertions(+) create mode 100644 shared/files.go create mode 100644 shared/trashcan.go create mode 100644 tasks/clean-mov-files.go create mode 100644 tasks/image-sort.go diff --git a/go.mod b/go.mod index 2c3b4e7..136711f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.25.2 require ( github.com/roemer/goext v0.8.0 github.com/roemer/gotaskr v0.7.0 + github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd ) require ( diff --git a/go.sum b/go.sum index afaab13..987ffba 100644 --- a/go.sum +++ b/go.sum @@ -108,6 +108,8 @@ github.com/roemer/gotaskr v0.7.0 h1:zP2SOVJAV4yGdItmEIYjYxgXF8gzmX+y8a6tnbXOz0o= github.com/roemer/gotaskr v0.7.0/go.mod h1:ue5tvkLn9BG8i41JogicOxS5rYqJlbHW8Hst9VXxliA= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= diff --git a/main.go b/main.go index c550c80..62ec3ce 100644 --- a/main.go +++ b/main.go @@ -29,4 +29,13 @@ func init() { } return tasks.NewBlackBorderAnalyzeTask("blackborder.log").Run(folder) }) + + // args: --folder=C:\Users\rbaer\Desktop\test + gotaskr.Task("Clean-Mov-Files", func() error { + folder, hasArg := gotaskr.GetArgument("folder") + if !hasArg { + return fmt.Errorf("folder argument is required") + } + return (&tasks.CleanMovFilesTask{}).Run(folder) + }) } diff --git a/shared/files.go b/shared/files.go new file mode 100644 index 0000000..e3930a3 --- /dev/null +++ b/shared/files.go @@ -0,0 +1,22 @@ +package shared + +import ( + "os" + "path/filepath" + "strings" +) + +func FileNameWithoutExt(fileName string) string { + return strings.TrimSuffix(fileName, filepath.Ext(fileName)) +} + +func FileExists(filePath string) (bool, error) { + info, err := os.Stat(filePath) + if err == nil { + return !info.IsDir(), nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} diff --git a/shared/trashcan.go b/shared/trashcan.go new file mode 100644 index 0000000..a7786bc --- /dev/null +++ b/shared/trashcan.go @@ -0,0 +1,51 @@ +package shared + +import ( + "fmt" + "syscall" + "unsafe" +) + +// Constants for the SHFileOperation function +const ( + FO_DELETE = 3 + FOF_ALLOWUNDO = 0x0040 // Move to Recycle Bin instead of permanently deleting + FOF_NOCONFIRMATION = 0x0010 // No confirmation dialogs +) + +// SHFILEOPSTRUCT structure (Windows API) +type SHFILEOPSTRUCT struct { + Hwnd uintptr + WFunc uint32 + PFrom *uint16 + PTo *uint16 + FFlags uint16 + FAnyOperationsAborted bool + HNameMappings uintptr + LpszProgressTitle *uint16 +} + +// MoveToTrash moves a file or folder to the Windows Recycle Bin. +func MoveToTrash(path string) error { + // The string must be double null-terminated for the Windows API + from, err := syscall.UTF16FromString(path) + if err != nil { + return err + } + + op := SHFILEOPSTRUCT{ + WFunc: FO_DELETE, + PFrom: &from[0], + FFlags: FOF_ALLOWUNDO | FOF_NOCONFIRMATION, + } + + shell32 := syscall.NewLazyDLL("shell32.dll") + shFileOperation := shell32.NewProc("SHFileOperationW") + + ret, _, _ := shFileOperation.Call(uintptr(unsafe.Pointer(&op))) + if ret != 0 { + return fmt.Errorf("failed to move to trash: error code %d", ret) + } + + return nil +} diff --git a/tasks/clean-mov-files.go b/tasks/clean-mov-files.go new file mode 100644 index 0000000..857a5b3 --- /dev/null +++ b/tasks/clean-mov-files.go @@ -0,0 +1,43 @@ +package tasks + +import ( + "fmt" + "os" + "path/filepath" + "varia-go/shared" +) + +type CleanMovFilesTask struct{} + +func (t *CleanMovFilesTask) Run(folderPath string) error { + return filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Check if the file has a .mov extension + if filepath.Ext(info.Name()) == ".mov" { + baseName := shared.FileNameWithoutExt(info.Name()) + heicPath := filepath.Join(filepath.Dir(path), baseName+".heic") + jpgPath := filepath.Join(filepath.Dir(path), baseName+".jpg") + + // Check if corresponding .heic or .jpg file exists + heicExists, err := shared.FileExists(heicPath) + if err != nil { + return err + } + jpgExists, err := shared.FileExists(jpgPath) + if err != nil { + return err + } + fmt.Println(baseName) + if heicExists || jpgExists { + fmt.Printf("Sending to Recycle Bin: %s\n", path) + if err := shared.MoveToTrash(path); err != nil { + return fmt.Errorf("failed to move to Recycle Bin: %w", err) + } + } + } + return nil + }) +} diff --git a/tasks/image-sort.go b/tasks/image-sort.go new file mode 100644 index 0000000..813fd8f --- /dev/null +++ b/tasks/image-sort.go @@ -0,0 +1,96 @@ +package tasks + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "time" + + "github.com/rwcarlsen/goexif/exif" +) + +type ImageSortTask struct{} + +func (t *ImageSortTask) Run(folderPath string) error { + return filepath.WalkDir(folderPath, func(path string, d fs.DirEntry, err error) error { + return visit(folderPath, path, d, err) + }) +} + +func visit(basePath, path string, di fs.DirEntry, err1 error) error { + fmt.Printf("Visited: %s\n", path) + + if di.IsDir() { + if path == basePath { + return nil + } + return fs.SkipDir + } + f, err := os.Open(path) + if err != nil { + return err + } + + x, err := exif.Decode(f) + if err != nil { + return err + } + + dateTag, err := x.Get(exif.DateTime) + if err != nil { + return err + } + dateVal, err := dateTag.StringVal() + if err != nil { + return err + } + if dateVal == "0000:00:00 00:00:00" { + return nil + } + date, err := time.Parse("2006:01:02 15:04:05", dateVal) + if err != nil { + return err + } + + f.Close() + + newFolderName := fmt.Sprintf("%04d.%02d", date.Year(), date.Month()) + + if err := os.MkdirAll(filepath.Join(basePath, newFolderName), os.ModePerm); err != nil { + return err + } + + if err := os.Rename(path, filepath.Join(basePath, newFolderName, filepath.Base(path))); err != nil { + return err + } + //copy(path, filepath.Join(basePath, newFolderName, filepath.Base(path))) + + return nil +} + +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 +}