From 6dcf46c5970ee3e272bd9a7de115961c2ccc3abc Mon Sep 17 00:00:00 2001 From: aria Date: Sat, 7 Jun 2025 02:48:43 +1000 Subject: [PATCH] feat: first wave of tests I can: count how many frames in a video, compare 2 frames for differences and compare 2 videos to see if their frames match up --- .cz.toml | 7 ++ go.mod | 10 +++ go.sum | 8 ++ main.go | 220 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 .cz.toml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.cz.toml b/.cz.toml new file mode 100644 index 0000000..b09874d --- /dev/null +++ b/.cz.toml @@ -0,0 +1,7 @@ +[tool.commitizen] +name = "cz_conventional_commits" +tag_format = "$version" +version_scheme = "semver2" +version = "0.0.1" +update_changelog_on_bump = true +major_version_zero = true diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..65f4038 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module fps-go-brr + +go 1.24.3 + +require ( + github.com/AlexEidt/Vidio v1.5.1 // indirect + github.com/urfave/cli/v3 v3.3.3 // indirect + github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a // indirect + golang.org/x/tools v0.0.0-20181207195948-8634b1ecd393 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..46ec524 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/AlexEidt/Vidio v1.5.1 h1:tovwvtgQagUz1vifiL9OeWkg1fP/XUzFazFKh7tFtaE= +github.com/AlexEidt/Vidio v1.5.1/go.mod h1:djhIMnWMqPrC3X6nB6ymGX6uWWlgw+VayYGKE1bNwmI= +github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= +github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= +github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a h1:00UFliGZl2UciXe8o/2iuEsRQ9u7z0rzDTVzuj6EYY0= +github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a/go.mod h1:ofmGw6LrMypycsiWcyug6516EXpIxSbZ+uI9ppGypfY= +golang.org/x/tools v0.0.0-20181207195948-8634b1ecd393 h1:0P8IF6+RwCumULxvjp9EtJryUs46MgLIgeHbCt7NU4Q= +golang.org/x/tools v0.0.0-20181207195948-8634b1ecd393/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1cc0cde --- /dev/null +++ b/main.go @@ -0,0 +1,220 @@ +package main + +import ( + "context" + "image" + "image/draw" + "log" + "os" + "strconv" + + vidio "github.com/AlexEidt/Vidio" + "github.com/urfave/cli/v3" +) + +func main() { + cmd := &cli.Command{ + Commands: []*cli.Command{ + { + Name: "count-frames", + Usage: "Count frames", + Action: func(ctx context.Context, cmd *cli.Command) error { + // fmt.Println("added task: ", cmd.Args().First()) + return count_video_frames(cmd.Args().First()) + }, + }, + { + Name: "compare-frames", + Usage: "Are two frames different?", + Arguments: []cli.Argument{ + &cli.StringArg{ + Name: "frame1", + }, + &cli.StringArg{ + Name: "frame2", + }, + }, + + Action: func(ctx context.Context, cmd *cli.Command) error { + // fmt.Println("added task: ", cmd.Args().First()) + first_frame, _ := getImageFromFilePath(cmd.StringArg("frame1")) + second_frame, _ := getImageFromFilePath(cmd.StringArg("frame2")) + + first_rgba := imageToRGBA(first_frame) + second_rgba := imageToRGBA(second_frame) + return compare_frames(first_rgba, second_rgba) + }, + }, + { + Name: "count-frames-differing-pixels", + Usage: "Are two frames different?", + Arguments: []cli.Argument{ + &cli.StringArg{ + Name: "frame1", + }, + &cli.StringArg{ + Name: "frame2", + }, + }, + + Action: func(ctx context.Context, cmd *cli.Command) error { + // fmt.Println("added task: ", cmd.Args().First()) + first_frame, _ := getImageFromFilePath(cmd.StringArg("frame1")) + second_frame, _ := getImageFromFilePath(cmd.StringArg("frame2")) + + first_rgba := imageToRGBA(first_frame) + second_rgba := imageToRGBA(second_frame) + return compare_frames_alt(first_rgba, second_rgba) + }, + }, + { + Name: "count-unique-video-frames", + Usage: "Are two frames different?", + Arguments: []cli.Argument{ + &cli.StringArg{ + Name: "video1", + }, + &cli.StringArg{ + Name: "video2", + }, + }, + + Action: func(ctx context.Context, cmd *cli.Command) error { + return countUniqueVideoFrames(cmd.StringArg("video1"), cmd.StringArg("video2"), 1) + }, + }, + }, + } + + if err := cmd.Run(context.Background(), os.Args); err != nil { + log.Fatal(err) + } +} + +// count_video_frames +// Prints out the total ammount of frames within `video` +// +// Parameters: +// - video string - path to video file +// +// Returns: +// - error +func count_video_frames(video string) error { + log.Default().Print("Trying to open video at: " + video) + video_file, _ := vidio.NewVideo(video) + count := 0 + for video_file.Read() { + count++ + } + log.Default().Println("Video total frames: " + strconv.Itoa(count)) + return nil +} + +func compare_frames(frame1 *image.RGBA, frame2 *image.RGBA) error { + // diff_frame := image.NewRGBA(frame1.Rect) + + accumError := int64(0) + + for i := 0; i < len(frame1.Pix); i++ { + accumError += int64(sqDiffUInt8(frame1.Pix[i], frame2.Pix[i])) + } + + log.Default().Println("Total image error: " + strconv.FormatInt(accumError, 10)) + + return nil +} + +func compare_frames_alt(frame1 *image.RGBA, frame2 *image.RGBA) error { + // diff_frame := image.NewRGBA(frame1.Rect) + + accumError := int64(0) + + for i := 0; i < len(frame1.Pix); i++ { + if isDiffUInt8(frame1.Pix[i], frame2.Pix[i]) { + accumError++ + } + } + + log.Default().Println("Total differing pixels: " + strconv.FormatInt(accumError, 10)) + + return nil +} + +func sqDiffUInt8(x, y uint8) uint64 { + d := uint64(x) - uint64(y) + return d * d +} + +func isDiffUInt8(x, y uint8) bool { + d := uint64(x) - uint64(y) + sq := d * d + + if sq > 0 { + return true + } else { + return false + } +} + +func imageToRGBA(src image.Image) *image.RGBA { + + // No conversion needed if image is an *image.RGBA. + if dst, ok := src.(*image.RGBA); ok { + return dst + } + + // Use the image/draw package to convert to *image.RGBA. + b := src.Bounds() + dst := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy())) + draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src) + return dst +} + +func getImageFromFilePath(filePath string) (image.Image, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer f.Close() + image, _, err := image.Decode(f) + return image, err +} + +func countUniqueVideoFrames(video_path1 string, video_path2 string, min_diff int64) error { + video1, _ := vidio.NewVideo(video_path1) + video2, _ := vidio.NewVideo(video_path2) + + video1_frame := image.NewRGBA(image.Rect(0, 0, video1.Width(), video1.Height())) + video2_frame := image.NewRGBA(image.Rect(0, 0, video2.Width(), video2.Height())) + video1.SetFrameBuffer(video1_frame.Pix) + video2.SetFrameBuffer(video2_frame.Pix) + + total_frames := 0 + unique_frames := 0 + for video1.Read() { + total_frames++ + video2.Read() + + accumError := int64(0) + + for i := 0; i < len(video1_frame.Pix); i++ { + if isDiffUInt8(video1_frame.Pix[i], video2_frame.Pix[i]) { + accumError++ + } + } + + if min_diff <= accumError { + unique_frames++ + log.Default().Println("[" + strconv.Itoa(total_frames) + "]Unique frame") + } else { + log.Default().Println("[" + strconv.Itoa(total_frames) + "]Non-unique frame") + } + } + + video1.Close() + video2.Close() + + log.Default().Println(strconv.Itoa(unique_frames) + "/" + strconv.Itoa(total_frames) + " are unique!") + + return nil +}