diff --git a/.cz.toml b/.cz.toml index f93ec1e..f13b3f9 100644 --- a/.cz.toml +++ b/.cz.toml @@ -2,6 +2,6 @@ name = "cz_conventional_commits" tag_format = "$version" version_scheme = "semver2" -version = "0.5.0" +version = "0.4.0" update_changelog_on_bump = true major_version_zero = true diff --git a/CHANGELOG.md b/CHANGELOG.md index ca5ca3e..56c5d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,3 @@ -## 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) diff --git a/CLAUDE.md b/CLAUDE.md index cdc3578..79722b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,10 +43,6 @@ go build -o fps-go-brr . # Optimized compact build (requires UPX) ./build-compact.sh ./fps-go-brr-compact [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 @@ -61,12 +57,10 @@ go mod download ``` ### Release Builds -- Forgejo Actions automatically build and release cross-platform binaries on tag pushes +- Forgejo Actions automatically build and release both normal and compact binaries on tag pushes - Uses custom runner: `9950x` -- 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 +- Normal and compact builds are uploaded as separate artifacts +- UPX compression applied to compact builds for size optimization ## Repository Information @@ -86,7 +80,6 @@ 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 diff --git a/README.md b/README.md index eab8096..bd74021 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ 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 @@ -107,7 +106,6 @@ 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 @@ -139,7 +137,6 @@ 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 diff --git a/go.mod b/go.mod index c27d3ff..65f4038 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,7 @@ 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 ) diff --git a/go.sum b/go.sum index b1bef7b..46ec524 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,8 @@ 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= diff --git a/main.go b/main.go index 50fbce3..7896318 100644 --- a/main.go +++ b/main.go @@ -2,21 +2,16 @@ package main import ( - "cmp" "context" "encoding/csv" "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" ) @@ -110,22 +105,11 @@ 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, - }, }, Action: func(ctx context.Context, cmd *cli.Command) error { tolerance := uint64(cmd.Float64("tolerance")) csvOutput := cmd.String("csv-output") - return analyzeFramePersistence(cmd.StringArg("video"), tolerance, csvOutput, cmd.Bool("resdet"), cmd.Bool("verbose")) + return analyzeFramePersistence(cmd.StringArg("video"), tolerance, csvOutput) }, }, }, @@ -267,7 +251,7 @@ func getImageFromFilePath(filePath string) (image.Image, error) { return image, err } -func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput string, toggleResdet bool, verbose bool) error { +func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput string) error { video, err := vidio.NewVideo(videoPath) if err != nil { return err @@ -304,8 +288,6 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin effectiveFPS float64 currentFrameTime float64 realFrameTime float64 - frameWidth int - frameHeight int } var frameAnalysisData []FrameData @@ -319,8 +301,6 @@ 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 @@ -330,44 +310,9 @@ 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 { - 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) - } - - frameWidth = frameWidthOut - frameHeight = frameHeightOut - } - - frameWidthMeasurements = append(frameWidthMeasurements, frameWidth) - frameHeightMeasurements = append(frameHeightMeasurements, frameHeight) - if !hasFirstFrame { copy(previousFrame.Pix, currentFrame.Pix) hasFirstFrame = true @@ -385,8 +330,6 @@ 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 } @@ -442,25 +385,17 @@ func analyzeFramePersistence(videoPath string, tolerance uint64, csvOutput strin effectiveFPS: effectiveFPS, currentFrameTime: actualFrameTimeMs, realFrameTime: 0, // Will be calculated in second pass - frameWidth: frameWidth, - frameHeight: frameHeight, }) - 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 - } + 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 } - - bar.Increment() } - bar.Finish() - // Record the final unique frame duration if totalUniqueFrames > 0 { if len(uniqueFrameDurations) < totalUniqueFrames { @@ -480,8 +415,6 @@ 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) @@ -528,26 +461,5 @@ 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 }