master
parent
84f9896ff4
commit
122c2d09ec
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module ffmpeg.d
|
||||
|
||||
go 1.22.3
|
||||
Loading…
Reference in New Issue