Compare commits

..

5 commits

Author SHA1 Message Date
c6755cb585
bump: version 0.2.0 → 0.3.0
All checks were successful
Build and Release / build (push) Successful in 1m38s
2025-06-15 03:00:23 +10:00
aec0f14c68
refactor: change variable and function names to fit with golang conventions 2025-06-15 03:00:09 +10:00
3529f7e415
refactor: remove pointless else statments 2025-06-15 02:57:54 +10:00
f9575c92a9
refactor: add package string 2025-06-15 02:57:21 +10:00
1b4cd880ef
chore(gitignore): ignore .vscode settings 2025-06-15 02:54:45 +10:00
4 changed files with 61 additions and 48 deletions

View file

@ -2,6 +2,6 @@
name = "cz_conventional_commits" name = "cz_conventional_commits"
tag_format = "$version" tag_format = "$version"
version_scheme = "semver2" version_scheme = "semver2"
version = "0.2.0" version = "0.3.0"
update_changelog_on_bump = true update_changelog_on_bump = true
major_version_zero = true major_version_zero = true

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
fps-go-brr fps-go-brr
fps-go-brr-compact fps-go-brr-compact
.vscode/settings.json

View file

@ -1,3 +1,12 @@
## 0.3.0 (2025-06-15)
### Refactor
- change variable and function names to fit with golang conventions
- remove pointless else statments
- add package string
- **gitignore**: ignore compact build binary
## 0.2.0 (2025-06-14) ## 0.2.0 (2025-06-14)
### Feat ### Feat

97
main.go
View file

@ -1,3 +1,4 @@
// A Go CLI tool for video frame analysis and comparison. Analyze frame persistence, detect dropped frames, and export data for visualization tools like those used by Digital Foundry.
package main package main
import ( import (
@ -22,7 +23,7 @@ func main() {
Usage: "Count frames", Usage: "Count frames",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
return count_video_frames(cmd.Args().First()) return countVideoFrames(cmd.Args().First())
}, },
}, },
{ {
@ -39,12 +40,12 @@ func main() {
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
first_frame, _ := getImageFromFilePath(cmd.StringArg("frame1")) firstFrame, _ := getImageFromFilePath(cmd.StringArg("frame1"))
second_frame, _ := getImageFromFilePath(cmd.StringArg("frame2")) secondFrame, _ := getImageFromFilePath(cmd.StringArg("frame2"))
first_rgba := imageToRGBA(first_frame) firstRGBA := imageToRGBA(firstFrame)
second_rgba := imageToRGBA(second_frame) secondRGBA := imageToRGBA(secondFrame)
return compare_frames(first_rgba, second_rgba) return compareFrames(firstRGBA, secondRGBA)
}, },
}, },
{ {
@ -61,12 +62,12 @@ func main() {
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
first_frame, _ := getImageFromFilePath(cmd.StringArg("frame1")) firstFrame, _ := getImageFromFilePath(cmd.StringArg("frame1"))
second_frame, _ := getImageFromFilePath(cmd.StringArg("frame2")) secondFrame, _ := getImageFromFilePath(cmd.StringArg("frame2"))
first_rgba := imageToRGBA(first_frame) firstRGBA := imageToRGBA(firstFrame)
second_rgba := imageToRGBA(second_frame) secondRGBA := imageToRGBA(secondFrame)
return compare_frames_alt(first_rgba, second_rgba) return compareFramesAlt(firstRGBA, secondRGBA)
}, },
}, },
{ {
@ -119,7 +120,7 @@ func main() {
} }
} }
// count_video_frames // countVideoFrames
// Prints out the total ammount of frames within `video` // Prints out the total ammount of frames within `video`
// //
// Parameters: // Parameters:
@ -127,18 +128,18 @@ func main() {
// //
// Returns: // Returns:
// - error // - error
func count_video_frames(video string) error { func countVideoFrames(video string) error {
log.Default().Print("Trying to open video at: " + video) log.Default().Print("Trying to open video at: " + video)
video_file, _ := vidio.NewVideo(video) videoFile, _ := vidio.NewVideo(video)
count := 0 count := 0
for video_file.Read() { for videoFile.Read() {
count++ count++
} }
log.Default().Println("Video total frames: " + strconv.Itoa(count)) log.Default().Println("Video total frames: " + strconv.Itoa(count))
return nil return nil
} }
func compare_frames(frame1 *image.RGBA, frame2 *image.RGBA) error { func compareFrames(frame1 *image.RGBA, frame2 *image.RGBA) error {
accumError := int64(0) accumError := int64(0)
for i := 0; i < len(frame1.Pix); i++ { for i := 0; i < len(frame1.Pix); i++ {
@ -150,7 +151,7 @@ func compare_frames(frame1 *image.RGBA, frame2 *image.RGBA) error {
return nil return nil
} }
func compare_frames_alt(frame1 *image.RGBA, frame2 *image.RGBA) error { func compareFramesAlt(frame1 *image.RGBA, frame2 *image.RGBA) error {
// diff_frame := image.NewRGBA(frame1.Rect) // diff_frame := image.NewRGBA(frame1.Rect)
accumError := int64(0) accumError := int64(0)
for i := 0; i < len(frame1.Pix); i++ { for i := 0; i < len(frame1.Pix); i++ {
@ -172,9 +173,10 @@ func isDiffUInt8(x, y uint8) bool {
sq := d * d sq := d * d
if sq > 0 { if sq > 0 {
return true return true
} else {
return false
} }
return false
} }
func isDiffUInt8WithTolerance(x, y uint8, tolerance uint64) bool { func isDiffUInt8WithTolerance(x, y uint8, tolerance uint64) bool {
@ -182,45 +184,46 @@ func isDiffUInt8WithTolerance(x, y uint8, tolerance uint64) bool {
sq := d * d sq := d * d
if sq > tolerance { if sq > tolerance {
return true return true
} else {
return false
} }
return false
} }
func countUniqueVideoFrames(video_path1 string, video_path2 string, min_diff uint64, use_sq_diff bool) error { func countUniqueVideoFrames(videoPath1 string, videoPath2 string, minDiff uint64, useSqDiff bool) error {
video1, _ := vidio.NewVideo(video_path1) video1, _ := vidio.NewVideo(videoPath1)
video2, _ := vidio.NewVideo(video_path2) video2, _ := vidio.NewVideo(videoPath2)
video1_frame := image.NewRGBA(image.Rect(0, 0, video1.Width(), video1.Height())) video1Frame := image.NewRGBA(image.Rect(0, 0, video1.Width(), video1.Height()))
video2_frame := image.NewRGBA(image.Rect(0, 0, video2.Width(), video2.Height())) video2Frame := image.NewRGBA(image.Rect(0, 0, video2.Width(), video2.Height()))
video1.SetFrameBuffer(video1_frame.Pix) video1.SetFrameBuffer(video1Frame.Pix)
video2.SetFrameBuffer(video2_frame.Pix) video2.SetFrameBuffer(video2Frame.Pix)
total_frames := 0 totalFrames := 0
unique_frames := 0 uniqueFrames := 0
for video1.Read() { for video1.Read() {
total_frames++ totalFrames++
video2.Read() video2.Read()
accumError := uint64(0) accumError := uint64(0)
for i := 0; i < len(video1_frame.Pix); i++ { for i := 0; i < len(video1Frame.Pix); i++ {
if use_sq_diff { if useSqDiff {
if isDiffUInt8WithTolerance(video1_frame.Pix[i], video2_frame.Pix[i], min_diff) { if isDiffUInt8WithTolerance(video1Frame.Pix[i], video2Frame.Pix[i], minDiff) {
accumError++ accumError++
} }
} else { } else {
if isDiffUInt8(video1_frame.Pix[i], video2_frame.Pix[i]) { if isDiffUInt8(video1Frame.Pix[i], video2Frame.Pix[i]) {
accumError++ accumError++
} }
} }
} }
if min_diff <= accumError { if minDiff <= accumError {
unique_frames++ uniqueFrames++
log.Default().Println("[" + strconv.Itoa(total_frames) + "]Unique frame") log.Default().Println("[" + strconv.Itoa(totalFrames) + "]Unique frame")
} else { } else {
log.Default().Println("[" + strconv.Itoa(total_frames) + "]Non-unique frame") log.Default().Println("[" + strconv.Itoa(totalFrames) + "]Non-unique frame")
} }
} }
video1.Close() video1.Close()
video2.Close() video2.Close()
log.Default().Println(strconv.Itoa(unique_frames) + "/" + strconv.Itoa(total_frames) + " are unique!") log.Default().Println(strconv.Itoa(uniqueFrames) + "/" + strconv.Itoa(totalFrames) + " are unique!")
return nil return nil
} }
@ -268,10 +271,10 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
return fmt.Errorf("failed to create CSV file: %v", err) return fmt.Errorf("failed to create CSV file: %v", err)
} }
defer csvFile.Close() defer csvFile.Close()
csvWriter = csv.NewWriter(csvFile) csvWriter = csv.NewWriter(csvFile)
defer csvWriter.Flush() defer csvWriter.Flush()
err = csvWriter.Write([]string{"frame", "average_fps", "frame_time", "unique_frame_count", "real_frame_time"}) err = csvWriter.Write([]string{"frame", "average_fps", "frame_time", "unique_frame_count", "real_frame_time"})
if err != nil { if err != nil {
return fmt.Errorf("failed to write CSV header: %v", err) return fmt.Errorf("failed to write CSV header: %v", err)
@ -286,10 +289,10 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
currentFrameTime float64 currentFrameTime float64
realFrameTime float64 realFrameTime float64
} }
var frameAnalysisData []FrameData var frameAnalysisData []FrameData
var uniqueFrameDurations []int // Duration of each unique frame var uniqueFrameDurations []int // Duration of each unique frame
currentFrame := image.NewRGBA(image.Rect(0, 0, video.Width(), video.Height())) currentFrame := image.NewRGBA(image.Rect(0, 0, video.Width(), video.Height()))
previousFrame := 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) video.SetFrameBuffer(currentFrame.Pix)
@ -316,7 +319,7 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
uniqueFramesInCurrentSecond = 1 uniqueFramesInCurrentSecond = 1
totalUniqueFrames = 1 totalUniqueFrames = 1
currentUniqueFrameDuration = 1 currentUniqueFrameDuration = 1
// Store data for first frame // Store data for first frame
currentTime := float64(frameNumber) / fps currentTime := float64(frameNumber) / fps
effectiveFPS := float64(totalUniqueFrames) / currentTime effectiveFPS := float64(totalUniqueFrames) / currentTime
@ -356,7 +359,7 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
uniqueFrameDurations[totalUniqueFrames-1] = currentUniqueFrameDuration uniqueFrameDurations[totalUniqueFrames-1] = currentUniqueFrameDuration
} }
} }
if consecutiveDuplicateCount > 1 { if consecutiveDuplicateCount > 1 {
persistenceMs := float64(consecutiveDuplicateCount+1) * frameTimeMs persistenceMs := float64(consecutiveDuplicateCount+1) * frameTimeMs
framePersistenceDurations = append(framePersistenceDurations, persistenceMs) framePersistenceDurations = append(framePersistenceDurations, persistenceMs)
@ -367,7 +370,7 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
uniqueFramesInCurrentSecond++ uniqueFramesInCurrentSecond++
totalUniqueFrames++ totalUniqueFrames++
copy(previousFrame.Pix, currentFrame.Pix) copy(previousFrame.Pix, currentFrame.Pix)
// Start tracking new unique frame // Start tracking new unique frame
currentUniqueFrameDuration = 1 currentUniqueFrameDuration = 1
} }