feat: Get claude AI to generate and debug FPS measurement functionality
This commit is contained in:
parent
f4d7a1a04c
commit
4699841f1e
2 changed files with 212 additions and 0 deletions
78
CLAUDE.md
Normal file
78
CLAUDE.md
Normal file
|
@ -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 <command> [args]
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
|
||||
### Module Management
|
||||
```bash
|
||||
go mod tidy
|
||||
go mod download
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
Available commands:
|
||||
- `count-frames <video>` - Count frames in a video
|
||||
- `compare-frames <frame1> <frame2>` - Compare two image frames
|
||||
- `count-frames-differing-pixels <frame1> <frame2>` - Count pixel differences between frames
|
||||
- `count-unique-video-frames <video1> <video2>` - Compare corresponding frames between two videos
|
||||
- `analyze-frame-persistence [--tolerance float] <video>` - **Main feature**: Analyze frame persistence and unique frames per second
|
||||
|
||||
### Frame Persistence Analysis
|
||||
|
||||
The main feature provides:
|
||||
- Real-time FPS detection from video metadata
|
||||
- Frame-by-frame comparison with previous frame
|
||||
- Detection of consecutive duplicate frame sequences (3+ identical frames)
|
||||
- Per-second unique frame counting
|
||||
- Persistence duration calculation in milliseconds
|
||||
- Configurable pixel difference tolerance (0-255)
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The application processes video frames in memory using RGBA format
|
||||
- Pixel comparison uses squared difference for tolerance-based matching
|
||||
- Video processing is done frame-by-frame to handle large files efficiently
|
||||
- Frame persistence detection only reports sequences of 3+ consecutive identical frames
|
||||
- All image formats supported by Go's image package can be used for frame comparison
|
||||
- The `analyze-frame-persistence` command is the primary tool for video quality analysis
|
134
main.go
134
main.go
|
@ -83,6 +83,26 @@ func main() {
|
|||
return countUniqueVideoFrames(cmd.StringArg("video1"), cmd.StringArg("video2"), 1, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "analyze-frame-persistence",
|
||||
Usage: "Analyze frame persistence in a single video",
|
||||
Arguments: []cli.Argument{
|
||||
&cli.StringArg{
|
||||
Name: "video",
|
||||
},
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&cli.Float64Flag{
|
||||
Name: "tolerance",
|
||||
Usage: "Pixel difference tolerance (0-255)",
|
||||
Value: 0,
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
tolerance := uint64(cmd.Float64("tolerance"))
|
||||
return analyzeFramePersistence(cmd.StringArg("video"), tolerance)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -219,3 +239,117 @@ func getImageFromFilePath(filePath string) (image.Image, error) {
|
|||
image, _, err := image.Decode(f)
|
||||
return image, err
|
||||
}
|
||||
|
||||
func analyzeFramePersistence(videoPath string, tolerance uint64) error {
|
||||
video, err := vidio.NewVideo(videoPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer video.Close()
|
||||
|
||||
fps := video.FPS()
|
||||
frameTimeMs := 1000.0 / fps
|
||||
|
||||
log.Default().Printf("Video FPS: %.2f, Frame time: %.2f ms", fps, frameTimeMs)
|
||||
|
||||
currentFrame := image.NewRGBA(image.Rect(0, 0, video.Width(), video.Height()))
|
||||
previousFrame := image.NewRGBA(image.Rect(0, 0, video.Width(), video.Height()))
|
||||
video.SetFrameBuffer(currentFrame.Pix)
|
||||
|
||||
var frameNumber int
|
||||
var uniqueFramesPerSecond []int
|
||||
var framePersistenceDurations []float64
|
||||
|
||||
currentSecond := 0
|
||||
uniqueFramesInCurrentSecond := 0
|
||||
consecutiveDuplicateCount := 0
|
||||
|
||||
hasFirstFrame := false
|
||||
|
||||
for video.Read() {
|
||||
frameNumber++
|
||||
|
||||
if !hasFirstFrame {
|
||||
copy(previousFrame.Pix, currentFrame.Pix)
|
||||
hasFirstFrame = true
|
||||
uniqueFramesInCurrentSecond = 1
|
||||
continue
|
||||
}
|
||||
|
||||
isFrameDifferent := false
|
||||
pixelDifferences := uint64(0)
|
||||
|
||||
for i := 0; i < len(currentFrame.Pix); i++ {
|
||||
if isDiffUInt8WithTolerance(currentFrame.Pix[i], previousFrame.Pix[i], tolerance) {
|
||||
pixelDifferences++
|
||||
}
|
||||
}
|
||||
|
||||
if pixelDifferences > 0 {
|
||||
isFrameDifferent = true
|
||||
}
|
||||
|
||||
if !isFrameDifferent {
|
||||
consecutiveDuplicateCount++
|
||||
} else {
|
||||
if consecutiveDuplicateCount > 1 {
|
||||
persistenceMs := float64(consecutiveDuplicateCount+1) * frameTimeMs
|
||||
framePersistenceDurations = append(framePersistenceDurations, persistenceMs)
|
||||
log.Default().Printf("Frame persisted for %.2f ms (%d consecutive duplicates)", persistenceMs, consecutiveDuplicateCount)
|
||||
}
|
||||
consecutiveDuplicateCount = 0
|
||||
|
||||
uniqueFramesInCurrentSecond++
|
||||
copy(previousFrame.Pix, currentFrame.Pix)
|
||||
}
|
||||
|
||||
newSecond := int(float64(frameNumber-1) / fps)
|
||||
if newSecond > currentSecond {
|
||||
uniqueFramesPerSecond = append(uniqueFramesPerSecond, uniqueFramesInCurrentSecond)
|
||||
log.Default().Printf("Second %d: %d unique frames", currentSecond+1, uniqueFramesInCurrentSecond)
|
||||
currentSecond = newSecond
|
||||
uniqueFramesInCurrentSecond = 0
|
||||
}
|
||||
}
|
||||
|
||||
if consecutiveDuplicateCount > 1 {
|
||||
persistenceMs := float64(consecutiveDuplicateCount+1) * frameTimeMs
|
||||
framePersistenceDurations = append(framePersistenceDurations, persistenceMs)
|
||||
log.Default().Printf("Final frame persisted for %.2f ms (%d consecutive duplicates)", persistenceMs, consecutiveDuplicateCount)
|
||||
}
|
||||
|
||||
if uniqueFramesInCurrentSecond > 0 {
|
||||
uniqueFramesPerSecond = append(uniqueFramesPerSecond, uniqueFramesInCurrentSecond)
|
||||
log.Default().Printf("Second %d: %d unique frames", currentSecond+1, uniqueFramesInCurrentSecond)
|
||||
}
|
||||
|
||||
log.Default().Printf("\n=== SUMMARY ===")
|
||||
log.Default().Printf("Total frames analyzed: %d", frameNumber)
|
||||
log.Default().Printf("Video duration: %.2f seconds", float64(frameNumber)/fps)
|
||||
|
||||
totalUniqueFrames := 0
|
||||
for i, count := range uniqueFramesPerSecond {
|
||||
totalUniqueFrames += count
|
||||
log.Default().Printf("Second %d: %d unique frames", i+1, count)
|
||||
}
|
||||
|
||||
log.Default().Printf("Total unique frames: %d", totalUniqueFrames)
|
||||
if len(uniqueFramesPerSecond) > 0 {
|
||||
log.Default().Printf("Average unique frames per second: %.2f", float64(totalUniqueFrames)/float64(len(uniqueFramesPerSecond)))
|
||||
}
|
||||
|
||||
if len(framePersistenceDurations) > 0 {
|
||||
log.Default().Printf("\nFrame persistence durations:")
|
||||
totalPersistence := 0.0
|
||||
for _, duration := range framePersistenceDurations {
|
||||
totalPersistence += duration
|
||||
}
|
||||
avgPersistence := totalPersistence / float64(len(framePersistenceDurations))
|
||||
log.Default().Printf("Average frame persistence: %.2f ms", avgPersistence)
|
||||
log.Default().Printf("Number of persistence events: %d", len(framePersistenceDurations))
|
||||
} else {
|
||||
log.Default().Printf("No frame persistence detected (all frames are unique)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue