From 5084fad903d4853cc28a4456887705bf1d070618 Mon Sep 17 00:00:00 2001 From: aria Date: Sat, 14 Jun 2025 00:35:15 +1000 Subject: [PATCH 1/4] feat: allow selecting diff checker type and min diff --- main.go | 111 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/main.go b/main.go index 1cc0cde..5d7bdd1 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ func main() { 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()) }, }, @@ -36,7 +36,7 @@ func main() { }, 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")) @@ -58,7 +58,7 @@ func main() { }, 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")) @@ -80,7 +80,7 @@ func main() { }, Action: func(ctx context.Context, cmd *cli.Command) error { - return countUniqueVideoFrames(cmd.StringArg("video1"), cmd.StringArg("video2"), 1) + return countUniqueVideoFrames(cmd.StringArg("video1"), cmd.StringArg("video2"), 1, false) }, }, }, @@ -111,32 +111,26 @@ func count_video_frames(video string) error { } 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])) + + if isDiffUInt8WithTolerance(frame1.Pix[i], frame2.Pix[i], 0) { // Set tolerance to 0 + accumError++ + } } - - log.Default().Println("Total image error: " + strconv.FormatInt(accumError, 10)) - + log.Default().Println("Total differing pixels: " + 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 } @@ -148,7 +142,6 @@ func sqDiffUInt8(x, y uint8) uint64 { func isDiffUInt8(x, y uint8) bool { d := uint64(x) - uint64(y) sq := d * d - if sq > 0 { return true } else { @@ -156,6 +149,53 @@ func isDiffUInt8(x, y uint8) bool { } } +func isDiffUInt8WithTolerance(x, y uint8, tolerance uint64) bool { + d := uint64(x) - uint64(y) + sq := d * d + if sq > tolerance { + return true + } else { + return false + } +} + +func countUniqueVideoFrames(video_path1 string, video_path2 string, min_diff uint64, use_sq_diff bool) 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 := uint64(0) + for i := 0; i < len(video1_frame.Pix); i++ { + if use_sq_diff { + if isDiffUInt8WithTolerance(video1_frame.Pix[i], video2_frame.Pix[i], min_diff) { + accumError++ + } + } else { + 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 +} + func imageToRGBA(src image.Image) *image.RGBA { // No conversion needed if image is an *image.RGBA. @@ -179,42 +219,3 @@ func getImageFromFilePath(filePath string) (image.Image, error) { 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 -} From f4d7a1a04c76162b9f88d15ce7c29576e6e77c05 Mon Sep 17 00:00:00 2001 From: aria Date: Sat, 14 Jun 2025 01:09:17 +1000 Subject: [PATCH 2/4] fix(gitignore): add compiled binary to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c2fbb0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +fps-go-brr From 4699841f1e2f6306a54fa2f45fc419b5a9d85d40 Mon Sep 17 00:00:00 2001 From: aria Date: Sat, 14 Jun 2025 01:10:00 +1000 Subject: [PATCH 3/4] feat: Get claude AI to generate and debug FPS measurement functionality --- CLAUDE.md | 78 +++++++++++++++++++++++++++++++ main.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..74307ef --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Go CLI application for video frame analysis and comparison. The project provides tools to count frames in videos, compare individual frames, analyze differences between two videos, and perform frame persistence analysis for single videos. + +## Core Architecture + +The application is built using: +- **CLI Framework**: urfave/cli/v3 for command-line interface +- **Video Processing**: AlexEidt/Vidio library for video file handling with FPS detection +- **Image Processing**: Standard Go image libraries for frame comparison + +### Main Components + +- **CLI Commands**: Five main commands for comprehensive frame analysis operations +- **Frame Comparison**: Pixel-level comparison with configurable tolerance using squared difference +- **Video Processing**: Frame-by-frame video analysis with streaming support and memory-efficient processing +- **Frame Persistence Analysis**: Detects consecutive duplicate frames and calculates persistence duration + +### Key Functions + +- `count_video_frames()`: Counts total frames in a video file +- `compare_frames()`: Compares two frames with tolerance-based difference detection +- `compare_frames_alt()`: Alternative frame comparison using exact pixel matching +- `countUniqueVideoFrames()`: Analyzes differences between corresponding frames in two videos +- `analyzeFramePersistence()`: **Main feature** - Analyzes frame persistence in single video with per-second statistics +- `isDiffUInt8WithTolerance()`: Pixel comparison with configurable tolerance threshold +- `imageToRGBA()`: Converts images to RGBA format for consistent processing + +## Development Commands + +### Build and Run +```bash +go build -o fps-go-brr . +./fps-go-brr [args] +``` + +### Testing +```bash +go test ./... +``` + +### Module Management +```bash +go mod tidy +go mod download +``` + +## CLI Usage + +Available commands: +- `count-frames