Compare commits

...

12 commits
0.3.0 ... main

Author SHA1 Message Date
bdfd6cba2e
fix(msin): actually fix the issue... 2025-06-26 18:57:20 +10:00
001c747103
fix(main): index out of range [-1] 2025-06-26 01:18:12 +10:00
55236c0a24
feat(analyze-frame-persistence): add flags for defining min res
This should allow for improved results, at the very least filtering out junk data
2025-06-26 00:12:17 +10:00
fa9aa2e174
bump: version 0.5.0 → 0.5.1
All checks were successful
Build and Release / build (push) Successful in 3m44s
2025-06-17 00:40:42 +10:00
50131d949b
fix(csv output): add missing frame width and height headers 2025-06-17 00:40:24 +10:00
a31c976f38
bump: version 0.4.0 → 0.5.0
All checks were successful
Build and Release / build (push) Successful in 3m37s
2025-06-16 02:20:21 +10:00
08317bd3df
refactor(README): add resdet info 2025-06-16 02:20:04 +10:00
e8742aba21
feat(main): add a ton of features
- progress bar
- frame res measurements
- verbosity flag
2025-06-16 02:17:49 +10:00
801abef51b
refactor(README): add new lines before lists to fit markdown standard 2025-06-16 02:06:59 +10:00
b04cbf006a
feat: add gox build script
All checks were successful
Build and Release / build (push) Successful in 2m50s
2025-06-15 04:39:06 +10:00
7c14b59bb6
bump: version 0.3.0 → 0.4.0
All checks were successful
Build and Release / build (push) Successful in 3m35s
2025-06-15 03:45:36 +10:00
51144a9911
ci: cross compile automatically 2025-06-15 03:45:12 +10:00
9 changed files with 306 additions and 33 deletions

View file

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

View file

@ -21,21 +21,26 @@ jobs:
with:
go-version: '1.21'
- name: Install UPX
- name: Install gox and UPX
run: |
go install github.com/mitchellh/gox@latest
wget -O upx.tar.xz https://github.com/upx/upx/releases/download/v5.0.1/upx-5.0.1-amd64_linux.tar.xz
tar -xf upx.tar.xz
cp upx-5.0.1-amd64_linux/upx /usr/local/bin/
cp upx-5.0.1-amd64_linux/upx.1 /usr/local/share/man/man1/ || true
chmod +x /usr/local/bin/upx
- name: Build normal binary
- name: Build cross-platform binaries
run: |
go build -o fps-go-brr .
gox -os="darwin" -os="linux" -os="windows" -arch="amd64" -arch="arm64" -osarch="linux/386" -osarch="windows/386" -output="build/{{.Dir}}-{{.OS}}-{{.Arch}}"
- name: Build compact binary
- name: Compress Linux binaries with UPX
run: |
./build-compact.sh
for file in build/*linux*; do
if [ -f "$file" ]; then
upx --brute "$file"
fi
done
- name: Get version
id: version
@ -46,23 +51,56 @@ jobs:
echo "version=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
fi
- name: Create release directory
- name: Create platform bundles
run: |
mkdir -p release
cp fps-go-brr release/fps-go-brr-${{ steps.version.outputs.version }}
cp fps-go-brr-compact release/fps-go-brr-compact-${{ steps.version.outputs.version }}
- name: Upload normal binary
# Create Darwin (macOS) bundle
mkdir -p bundle-darwin
cp build/fps-go-brr-darwin-amd64 bundle-darwin/ 2>/dev/null || true
cp build/fps-go-brr-darwin-arm64 bundle-darwin/ 2>/dev/null || true
if [ "$(ls -A bundle-darwin 2>/dev/null)" ]; then
tar -czf release/fps-go-brr-darwin-${{ steps.version.outputs.version }}.tar.gz -C bundle-darwin .
fi
# Create Linux bundle
mkdir -p bundle-linux
cp build/fps-go-brr-linux-amd64 bundle-linux/ 2>/dev/null || true
cp build/fps-go-brr-linux-arm64 bundle-linux/ 2>/dev/null || true
cp build/fps-go-brr-linux-386 bundle-linux/ 2>/dev/null || true
if [ "$(ls -A bundle-linux 2>/dev/null)" ]; then
tar -czf release/fps-go-brr-linux-${{ steps.version.outputs.version }}.tar.gz -C bundle-linux .
fi
# Create Windows bundle
mkdir -p bundle-windows
cp build/fps-go-brr-windows-amd64.exe bundle-windows/ 2>/dev/null || true
cp build/fps-go-brr-windows-arm64.exe bundle-windows/ 2>/dev/null || true
cp build/fps-go-brr-windows-386.exe bundle-windows/ 2>/dev/null || true
if [ "$(ls -A bundle-windows 2>/dev/null)" ]; then
tar -czf release/fps-go-brr-windows-${{ steps.version.outputs.version }}.tar.gz -C bundle-windows .
fi
- name: Upload Darwin bundle
uses: forgejo/upload-artifact@v4
with:
name: fps-go-brr-normal-${{ steps.version.outputs.version }}
path: release/fps-go-brr-${{ steps.version.outputs.version }}
name: fps-go-brr-darwin-${{ steps.version.outputs.version }}
path: release/fps-go-brr-darwin-${{ steps.version.outputs.version }}.tar.gz
if-no-files-found: ignore
- name: Upload compact binary
- name: Upload Linux bundle
uses: forgejo/upload-artifact@v4
with:
name: fps-go-brr-compact-${{ steps.version.outputs.version }}
path: release/fps-go-brr-compact-${{ steps.version.outputs.version }}
name: fps-go-brr-linux-${{ steps.version.outputs.version }}
path: release/fps-go-brr-linux-${{ steps.version.outputs.version }}.tar.gz
if-no-files-found: ignore
- name: Upload Windows bundle
uses: forgejo/upload-artifact@v4
with:
name: fps-go-brr-windows-${{ steps.version.outputs.version }}
path: release/fps-go-brr-windows-${{ steps.version.outputs.version }}.tar.gz
if-no-files-found: ignore
- name: Create Release
if: startsWith(github.ref, 'refs/tags/')
@ -75,7 +113,8 @@ jobs:
## fps-go-brr ${{ steps.version.outputs.version }}
### Downloads
- `fps-go-brr-${{ steps.version.outputs.version }}` - Normal build
- `fps-go-brr-compact-${{ steps.version.outputs.version }}` - Compact build (optimized with UPX compression)
- `fps-go-brr-darwin-${{ steps.version.outputs.version }}.tar.gz` - macOS builds (amd64, arm64)
- `fps-go-brr-linux-${{ steps.version.outputs.version }}.tar.gz` - Linux builds (amd64, arm64, 386) - compressed with UPX
- `fps-go-brr-windows-${{ steps.version.outputs.version }}.tar.gz` - Windows builds (amd64, arm64, 386)
The compact build is smaller but may have slightly slower startup time due to decompression.
Linux binaries are compressed with UPX for smaller size but may have slightly slower startup time due to decompression.

View file

@ -1,3 +1,23 @@
## 0.5.1 (2025-06-17)
### Fix
- **csv output**: add missing frame width and height headers
## 0.5.0 (2025-06-16)
### Feat
- **main**: add a ton of features - progress bar - frame res measurements - verbosity flag
- add gox build script
### Refactor
- **README**: add resdet info
- **README**: add new lines before lists to fit markdown standard
## 0.4.0 (2025-06-15)
## 0.3.0 (2025-06-15)
### Refactor

View file

@ -43,6 +43,10 @@ go build -o fps-go-brr .
# Optimized compact build (requires UPX)
./build-compact.sh
./fps-go-brr-compact <command> [args]
# Cross-platform build (requires gox)
go install github.com/mitchellh/gox@latest
gox -os="darwin" -os="linux" -os="windows" -arch="amd64" -arch="arm64" -osarch="linux/386" -osarch="windows/386"
```
### Testing
@ -57,10 +61,12 @@ go mod download
```
### Release Builds
- Forgejo Actions automatically build and release both normal and compact binaries on tag pushes
- Forgejo Actions automatically build and release cross-platform binaries on tag pushes
- Uses custom runner: `9950x`
- Normal and compact builds are uploaded as separate artifacts
- UPX compression applied to compact builds for size optimization
- Cross-compilation via `gox` for Darwin (macOS), Linux, and Windows
- Multiple architectures: amd64, arm64, and 386 (Linux/Windows only)
- UPX compression applied to Linux builds only using `--brute` flag
- Platform-specific `.tar.gz` bundles for distribution
## Repository Information
@ -80,6 +86,7 @@ This project draws inspiration from:
## Memories
- The forgejo workflow runner is executed as root so it does not need to use root
- Updated workflow to use `gox` for cross-compilation and create platform-specific `.tar.gz` bundles
## CLI Usage

View file

@ -12,6 +12,7 @@ A Go CLI tool for video frame analysis and comparison. Analyze frame persistence
- **Configurable Tolerance**: Adjust pixel difference sensitivity for noisy videos
- **Real-time Analysis**: Stream processing for efficient memory usage
- **Two-pass Architecture**: Accurate frame timing calculations for smooth visualizations
- **Resolution Measurements**: Using the [resdet](https://github.com/0x09/resdet) cli to log dynamic resolutions (Requires resdet to be installed seperately)
## Quick Start
@ -106,6 +107,7 @@ This is an early-stage project. Contributions, bug reports, and feature requests
## Technical Details
Built with:
- **CLI Framework**: [urfave/cli/v3](https://github.com/urfave/cli)
- **Video Processing**: [AlexEidt/Vidio](https://github.com/AlexEidt/Vidio)
- **Image Processing**: Go standard library
@ -137,6 +139,7 @@ The testing phase for Claude's coding agent in this repo is finished and it shal
SPDX-License-Identifier: MIT OR Apache-2.0
This project is dual-licensed under your choice of:
- MIT License - see [LICENSE.MIT](LICENSE.MIT) file for details
- Apache License 2.0 - see [LICENSE.Apache-2.0](LICENSE.Apache-2.0) file for details

3
build-all.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
gox -os="darwin" -os="linux" -os="windows" -arch="amd64" -arch="arm64" -osarch="linux/386" -osarch="windows/386"

9
go.mod
View file

@ -4,7 +4,16 @@ go 1.24.3
require (
github.com/AlexEidt/Vidio v1.5.1 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/cheggaaa/pb v1.0.29 // indirect
github.com/cheggaaa/pb/v3 v3.1.7 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/urfave/cli/v3 v3.3.3 // indirect
github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/tools v0.0.0-20181207195948-8634b1ecd393 // indirect
)

27
go.sum
View file

@ -1,8 +1,35 @@
github.com/AlexEidt/Vidio v1.5.1 h1:tovwvtgQagUz1vifiL9OeWkg1fP/XUzFazFKh7tFtaE=
github.com/AlexEidt/Vidio v1.5.1/go.mod h1:djhIMnWMqPrC3X6nB6ymGX6uWWlgw+VayYGKE1bNwmI=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=
github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI=
github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
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/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=

189
main.go
View file

@ -2,16 +2,22 @@
package main
import (
"cmp"
"context"
"encoding/csv"
"errors"
"fmt"
"image"
"image/draw"
"image/png"
"log"
"os"
"os/exec"
"strconv"
"strings"
vidio "github.com/AlexEidt/Vidio"
"github.com/cheggaaa/pb"
"github.com/urfave/cli/v3"
)
@ -95,9 +101,9 @@ func main() {
},
},
Flags: []cli.Flag{
&cli.Float64Flag{
&cli.Uint64Flag{
Name: "tolerance",
Usage: "Pixel difference tolerance (0-255)",
Usage: "Pixel difference tolerance (0-255?)",
Value: 0,
},
&cli.StringFlag{
@ -105,11 +111,46 @@ func main() {
Usage: "Path to CSV file for frame data output",
Value: "",
},
&cli.BoolFlag{
Name: "resdet",
Usage: "use the resdet cli to measure each frame's resoltion\nWARNING: This will slow the process down by a LOT",
Value: false,
},
&cli.BoolFlag{
Name: "verbose",
Usage: "print out total unique frames for every second of measurements",
Value: false,
},
&cli.BoolFlag{
Name: "testing-log",
Usage: "make a seperate output csv in the same folder that live updates",
Value: false,
},
&cli.IntFlag{
Name: "resdet-minimum-height",
Usage: "minimum possible height of detected image",
Value: 0,
},
&cli.IntFlag{
Name: "resdet-minimum-width",
Usage: "minimum possible width of detected image",
Value: 0,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
tolerance := uint64(cmd.Float64("tolerance"))
videoPath := cmd.StringArg("video")
if videoPath == "" {
return fmt.Errorf("Must provide video path")
}
tolerance := cmd.Uint64("tolerance")
csvOutput := cmd.String("csv-output")
return analyzeFramePersistence(cmd.StringArg("video"), tolerance, csvOutput)
return analyzeFramePersistence(cmd.StringArg("video"), tolerance, csvOutput, cmd.Bool("resdet"), cmd.Bool("verbose"), cmd.Int("resdet-minimum-height"), cmd.Int("resdet-minimum-width"), cmd.Bool("testing-log"))
},
},
},
@ -251,7 +292,7 @@ func getImageFromFilePath(filePath string) (image.Image, error) {
return image, err
}
func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput string) error {
func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput string, toggleResdet bool, verbose bool, minResdetHeight int, minResdetWidth int, liveCSV bool) error {
video, err := vidio.NewVideo(videoPath)
if err != nil {
return err
@ -275,7 +316,7 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
csvWriter = csv.NewWriter(csvFile)
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", "frame_width", "frame_height"})
if err != nil {
return fmt.Errorf("failed to write CSV header: %v", err)
}
@ -288,6 +329,8 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
effectiveFPS float64
currentFrameTime float64
realFrameTime float64
frameWidth int
frameHeight int
}
var frameAnalysisData []FrameData
@ -301,6 +344,8 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
var frameNumber int
var uniqueFramesPerSecond []int
var framePersistenceDurations []float64
var frameWidthMeasurements []int
var frameHeightMeasurements []int
currentSecond := 0
uniqueFramesInCurrentSecond := 0
@ -310,9 +355,33 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
hasFirstFrame := false
bar := pb.StartNew(video.Frames())
for video.Read() {
frameNumber++
// frame colum will be full of 0s normally, not the worst compromise
frameWidth := 0
frameHeight := 0
// mesure resoltion
if toggleResdet {
var lastFrameWidth int
var lastFrameHeight int
if len(frameWidthMeasurements) != 0 {
lastFrameWidth = frameWidthMeasurements[len(frameWidthMeasurements)-1]
lastFrameHeight = frameHeightMeasurements[len(frameHeightMeasurements)-1]
} else {
lastFrameWidth = currentFrame.Bounds().Max.X
lastFrameHeight = currentFrame.Bounds().Max.X
}
frameWidth, frameHeight = resdet(verbose, currentFrame, frameWidth, frameHeight, minResdetHeight, minResdetHeight, lastFrameWidth, lastFrameHeight)
}
frameWidthMeasurements = append(frameWidthMeasurements, frameWidth)
frameHeightMeasurements = append(frameHeightMeasurements, frameHeight)
if !hasFirstFrame {
copy(previousFrame.Pix, currentFrame.Pix)
hasFirstFrame = true
@ -330,6 +399,8 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
effectiveFPS: effectiveFPS,
currentFrameTime: actualFrameTimeMs,
realFrameTime: 0, // Will be calculated in second pass
frameWidth: frameWidth,
frameHeight: frameHeight,
})
continue
}
@ -385,17 +456,52 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
effectiveFPS: effectiveFPS,
currentFrameTime: actualFrameTimeMs,
realFrameTime: 0, // Will be calculated in second pass
frameWidth: frameWidth,
frameHeight: frameHeight,
})
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 verbose {
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 liveCSV {
if _, err := os.Stat("live.csv"); errors.Is(err, os.ErrNotExist) {
f, err := os.Create("live.csv")
if err != nil {
return err
}
_, err = f.WriteString("frame, average_fps, frame_time, unique_frame_count, real_frame_time, frame_width, frame_height\n")
if err != nil {
return err
}
f.Close()
}
f, err := os.OpenFile("live.csv", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
currentFrameData := frameAnalysisData[len(frameAnalysisData)-1]
fmt.Fprintf(f, "%v, %.2f, %.2f, %v, %.2f, %v, %v\n", currentFrameData.frameNumber, currentFrameData.effectiveFPS, currentFrameData.currentFrameTime, currentFrameData.uniqueFrameCount, currentFrameData.realFrameTime, currentFrameData.frameWidth, currentFrameData.frameHeight)
f.Close()
}
bar.Increment()
}
bar.Finish()
// Record the final unique frame duration
if totalUniqueFrames > 0 {
if len(uniqueFrameDurations) < totalUniqueFrames {
@ -415,6 +521,8 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
fmt.Sprintf("%.2f", frameData.currentFrameTime),
strconv.Itoa(frameData.uniqueFrameCount),
fmt.Sprintf("%.2f", realFrameTimeMs),
strconv.Itoa(frameData.frameWidth),
strconv.Itoa(frameData.frameHeight),
})
if err != nil {
log.Default().Printf("Warning: failed to write CSV row %d: %v", i+1, err)
@ -461,5 +569,62 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin
log.Default().Printf("No frame persistence detected (all frames are unique)")
}
if len(frameWidthMeasurements) > 0 && len(frameHeightMeasurements) > 0 {
sumWidth := 0
sumHeight := 0
for _, width := range frameWidthMeasurements {
sumWidth += width
}
if sumWidth != 0 {
for _, height := range frameHeightMeasurements {
sumHeight += height
}
avgWidth := float64(sumWidth) / float64(len(frameWidthMeasurements))
avgHeight := float64(sumHeight) / float64(len(frameHeightMeasurements))
log.Default().Printf("Average Width: %.2f", avgWidth)
log.Default().Printf("Average Height: %.2f", avgHeight)
}
}
return nil
}
func resdet(verbose bool, currentFrame *image.RGBA, frameWidth int, frameHeight int, minHeight int, minWidth int, prevFrameWidth int, prevFrameHeight int) (int, int) {
frameFile, err0 := os.Create("/tmp/frame.png")
err1 := png.Encode(frameFile, currentFrame)
out, err2 := exec.Command("resdet", "-v", "1", frameFile.Name()).Output()
err3 := frameFile.Close()
err4 := os.Remove(frameFile.Name())
formattedOutput := strings.Split(string(out), " ")
frameWidthOut, err5 := strconv.Atoi(formattedOutput[0])
frameHeightOut, err6 := strconv.Atoi(strings.TrimSuffix(formattedOutput[1], "\n"))
if err := cmp.Or(err0, err1, err2, err3, err4, err5, err6); err != nil {
log.Fatal(err)
}
if frameHeightOut > minHeight {
frameHeight = frameHeightOut
} else {
frameHeight = prevFrameHeight
}
if frameWidthOut > minWidth {
frameWidth = frameWidthOut
} else {
frameWidth = prevFrameWidth
}
return frameWidth, frameHeight
}