From 122c2d09ecec4007bfa25c26ca45d48ba81248fc Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 18 Sep 2024 00:45:35 -0400 Subject: [PATCH] k --- testdata/ffmpeg.d/cmd/prune/main.go | 155 ++++++++++++++++++++++++++++ testdata/ffmpeg.d/go.mod | 3 + 2 files changed, 158 insertions(+) create mode 100644 testdata/ffmpeg.d/cmd/prune/main.go create mode 100644 testdata/ffmpeg.d/go.mod diff --git a/testdata/ffmpeg.d/cmd/prune/main.go b/testdata/ffmpeg.d/cmd/prune/main.go new file mode 100644 index 0000000..8f8065c --- /dev/null +++ b/testdata/ffmpeg.d/cmd/prune/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "context" + "fmt" + "io" + "math/big" + "os" + "os/exec" + "os/signal" + "path" + "slices" + "sort" + "strconv" + "strings" + "syscall" + "time" +) + +func main() { + ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) + defer can() + + if err := Run(ctx, os.Args[1:]); err != nil { + panic(err) + } +} + +func Run(ctx context.Context, args []string) error { + cams, err := lsd(args[0]) + if err != nil { + return err + } + + for _, cam := range cams { + files, err := lsf(cam) + if err != nil { + return err + } else if len(files) < 1 { + continue + } + + lastChunk := strings.Split(path.Base(files[len(files)-1]), ".")[0] + files = slices.DeleteFunc(files, func(f string) bool { + return strings.Contains(f, lastChunk) + }) + if len(files) == 0 { + continue + } + + lastMovementAt := time.Unix(0, 0) + prevF := files[0] + for _, f := range files[1:] { + prev, err := os.Stat(prevF) + if err != nil { + return err + } + + if ok, err := hasMovement(ctx, prevF, f); err != nil { + return err + } else if ok { + lastMovementAt = prev.ModTime() + } + + target := "trash" + if prev.ModTime().Before(lastMovementAt.Add(3 * time.Minute)) { + target = "movement" + } + if err := func() error { + gName := strings.ReplaceAll(prevF, "record", target) + if gName == prevF { + return fmt.Errorf("would overwrite original %s", prevF) + } + os.MkdirAll(path.Dir(gName), os.ModePerm) + + f, err := os.Open(prevF) + if err != nil { + return err + } + defer f.Close() + + g, err := os.Create(gName) + if err != nil { + return err + } + defer g.Close() + + if _, err := io.Copy(g, f); err != nil { + return err + } + + return os.Remove(prevF) + }(); err != nil { + return err + } + + prevF = f + } + } + return nil +} + +func hasMovement(ctx context.Context, a, b string) (bool, error) { + sizeOfCmd := exec.CommandContext(ctx, "identify", a) + sizeOfOutput, err := sizeOfCmd.CombinedOutput() + if err != nil { + return false, fmt.Errorf("failed to identify %s: (%w) %s", a, err, sizeOfOutput) + } + hw := strings.Fields(string(sizeOfOutput))[2] + h, err := strconv.ParseInt(strings.Split(hw, "x")[0], 10, 16) + if err != nil { + return false, err + } + w, err := strconv.ParseInt(strings.Split(hw, "x")[1], 10, 16) + if err != nil { + return false, err + } + total := h * w + + compareCmd := exec.CommandContext(ctx, "compare", "-metric", "AE", "-fuzz", "15%", a, b, "/dev/null") + compareOutput, _ := compareCmd.CombinedOutput() + + f, _, err := big.ParseFloat(string(compareOutput), 10, 0, big.ToNearestEven) + if err != nil { + return false, err + } + i := new(big.Int) + f.Int(i) + delta := i.Int64() + + percentPixelsChanged := int(100.0 * float64(delta) / float64(total)) + return percentPixelsChanged > 10, nil +} + +func lsd(d string) ([]string, error) { + return ls(d, true) +} + +func lsf(d string) ([]string, error) { + return ls(d, false) +} + +func ls(d string, dirs bool) ([]string, error) { + entries, err := os.ReadDir(d) + + results := make([]string, 0, len(entries)) + for i := range entries { + if dirs == entries[i].IsDir() { + results = append(results, path.Join(d, entries[i].Name())) + } + } + sort.Strings(results) + + return results, err +} diff --git a/testdata/ffmpeg.d/go.mod b/testdata/ffmpeg.d/go.mod new file mode 100644 index 0000000..638ad89 --- /dev/null +++ b/testdata/ffmpeg.d/go.mod @@ -0,0 +1,3 @@ +module ffmpeg.d + +go 1.22.3