Compare commits
No commits in common. "c6755cb58586e6294224df2f9a23df1d0379e086" and "8ba0319c6e5e38efdfbd313a45bf629642d3824c" have entirely different histories.
c6755cb585
...
8ba0319c6e
4 changed files with 48 additions and 61 deletions
2
.cz.toml
2
.cz.toml
|
@ -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.3.0"
|
version = "0.2.0"
|
||||||
update_changelog_on_bump = true
|
update_changelog_on_bump = true
|
||||||
major_version_zero = true
|
major_version_zero = true
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
||||||
fps-go-brr
|
fps-go-brr
|
||||||
fps-go-brr-compact
|
fps-go-brr-compact
|
||||||
.vscode/settings.json
|
|
||||||
|
|
|
@ -1,12 +1,3 @@
|
||||||
## 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
97
main.go
|
@ -1,4 +1,3 @@
|
||||||
// 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 (
|
||||||
|
@ -23,7 +22,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 countVideoFrames(cmd.Args().First())
|
return count_video_frames(cmd.Args().First())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -40,12 +39,12 @@ func main() {
|
||||||
|
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
|
||||||
firstFrame, _ := getImageFromFilePath(cmd.StringArg("frame1"))
|
first_frame, _ := getImageFromFilePath(cmd.StringArg("frame1"))
|
||||||
secondFrame, _ := getImageFromFilePath(cmd.StringArg("frame2"))
|
second_frame, _ := getImageFromFilePath(cmd.StringArg("frame2"))
|
||||||
|
|
||||||
firstRGBA := imageToRGBA(firstFrame)
|
first_rgba := imageToRGBA(first_frame)
|
||||||
secondRGBA := imageToRGBA(secondFrame)
|
second_rgba := imageToRGBA(second_frame)
|
||||||
return compareFrames(firstRGBA, secondRGBA)
|
return compare_frames(first_rgba, second_rgba)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -62,12 +61,12 @@ func main() {
|
||||||
|
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
|
||||||
firstFrame, _ := getImageFromFilePath(cmd.StringArg("frame1"))
|
first_frame, _ := getImageFromFilePath(cmd.StringArg("frame1"))
|
||||||
secondFrame, _ := getImageFromFilePath(cmd.StringArg("frame2"))
|
second_frame, _ := getImageFromFilePath(cmd.StringArg("frame2"))
|
||||||
|
|
||||||
firstRGBA := imageToRGBA(firstFrame)
|
first_rgba := imageToRGBA(first_frame)
|
||||||
secondRGBA := imageToRGBA(secondFrame)
|
second_rgba := imageToRGBA(second_frame)
|
||||||
return compareFramesAlt(firstRGBA, secondRGBA)
|
return compare_frames_alt(first_rgba, second_rgba)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -120,7 +119,7 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// countVideoFrames
|
// count_video_frames
|
||||||
// Prints out the total ammount of frames within `video`
|
// Prints out the total ammount of frames within `video`
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
|
@ -128,18 +127,18 @@ func main() {
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - error
|
// - error
|
||||||
func countVideoFrames(video string) error {
|
func count_video_frames(video string) error {
|
||||||
log.Default().Print("Trying to open video at: " + video)
|
log.Default().Print("Trying to open video at: " + video)
|
||||||
videoFile, _ := vidio.NewVideo(video)
|
video_file, _ := vidio.NewVideo(video)
|
||||||
count := 0
|
count := 0
|
||||||
for videoFile.Read() {
|
for video_file.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 compareFrames(frame1 *image.RGBA, frame2 *image.RGBA) error {
|
func compare_frames(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++ {
|
||||||
|
|
||||||
|
@ -151,7 +150,7 @@ func compareFrames(frame1 *image.RGBA, frame2 *image.RGBA) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareFramesAlt(frame1 *image.RGBA, frame2 *image.RGBA) error {
|
func compare_frames_alt(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++ {
|
||||||
|
@ -173,10 +172,9 @@ 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 {
|
||||||
|
@ -184,46 +182,45 @@ 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(videoPath1 string, videoPath2 string, minDiff uint64, useSqDiff bool) error {
|
func countUniqueVideoFrames(video_path1 string, video_path2 string, min_diff uint64, use_sq_diff bool) error {
|
||||||
video1, _ := vidio.NewVideo(videoPath1)
|
video1, _ := vidio.NewVideo(video_path1)
|
||||||
video2, _ := vidio.NewVideo(videoPath2)
|
video2, _ := vidio.NewVideo(video_path2)
|
||||||
video1Frame := image.NewRGBA(image.Rect(0, 0, video1.Width(), video1.Height()))
|
video1_frame := image.NewRGBA(image.Rect(0, 0, video1.Width(), video1.Height()))
|
||||||
video2Frame := image.NewRGBA(image.Rect(0, 0, video2.Width(), video2.Height()))
|
video2_frame := image.NewRGBA(image.Rect(0, 0, video2.Width(), video2.Height()))
|
||||||
video1.SetFrameBuffer(video1Frame.Pix)
|
video1.SetFrameBuffer(video1_frame.Pix)
|
||||||
video2.SetFrameBuffer(video2Frame.Pix)
|
video2.SetFrameBuffer(video2_frame.Pix)
|
||||||
totalFrames := 0
|
total_frames := 0
|
||||||
uniqueFrames := 0
|
unique_frames := 0
|
||||||
for video1.Read() {
|
for video1.Read() {
|
||||||
totalFrames++
|
total_frames++
|
||||||
video2.Read()
|
video2.Read()
|
||||||
accumError := uint64(0)
|
accumError := uint64(0)
|
||||||
for i := 0; i < len(video1Frame.Pix); i++ {
|
for i := 0; i < len(video1_frame.Pix); i++ {
|
||||||
if useSqDiff {
|
if use_sq_diff {
|
||||||
if isDiffUInt8WithTolerance(video1Frame.Pix[i], video2Frame.Pix[i], minDiff) {
|
if isDiffUInt8WithTolerance(video1_frame.Pix[i], video2_frame.Pix[i], min_diff) {
|
||||||
accumError++
|
accumError++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if isDiffUInt8(video1Frame.Pix[i], video2Frame.Pix[i]) {
|
if isDiffUInt8(video1_frame.Pix[i], video2_frame.Pix[i]) {
|
||||||
accumError++
|
accumError++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if minDiff <= accumError {
|
if min_diff <= accumError {
|
||||||
uniqueFrames++
|
unique_frames++
|
||||||
log.Default().Println("[" + strconv.Itoa(totalFrames) + "]Unique frame")
|
log.Default().Println("[" + strconv.Itoa(total_frames) + "]Unique frame")
|
||||||
} else {
|
} else {
|
||||||
log.Default().Println("[" + strconv.Itoa(totalFrames) + "]Non-unique frame")
|
log.Default().Println("[" + strconv.Itoa(total_frames) + "]Non-unique frame")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
video1.Close()
|
video1.Close()
|
||||||
video2.Close()
|
video2.Close()
|
||||||
log.Default().Println(strconv.Itoa(uniqueFrames) + "/" + strconv.Itoa(totalFrames) + " are unique!")
|
log.Default().Println(strconv.Itoa(unique_frames) + "/" + strconv.Itoa(total_frames) + " are unique!")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,10 +268,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)
|
||||||
|
@ -289,10 +286,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)
|
||||||
|
@ -319,7 +316,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
|
||||||
|
@ -359,7 +356,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)
|
||||||
|
@ -370,7 +367,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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue