From 67c71e20703542f0c542c8179bd583fb2cba3987 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Tue, 23 Dec 2025 22:29:42 -0500 Subject: [PATCH] Restore main.go + upscale_module.go preparation - main.go restored from corruption (13,664 lines) - upscale_module.go created with AI helper functions - Ready for safer incremental extraction approach --- upscale_module.go | 173 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 upscale_module.go diff --git a/upscale_module.go b/upscale_module.go new file mode 100644 index 0000000..2883f82 --- /dev/null +++ b/upscale_module.go @@ -0,0 +1,173 @@ +package main + +import ( + "context" + "fmt" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" + + "git.leaktechnologies.dev/stu/VideoTools/internal/logging" + "git.leaktechnologies.dev/stu/VideoTools/internal/queue" + "git.leaktechnologies.dev/stu/VideoTools/internal/ui" + "git.leaktechnologies.dev/stu/VideoTools/internal/utils" +) + +// AI Helper Functions (smaller, manageable functions) + +// detectAIUpscaleBackend returns the available Real-ESRGAN backend ("ncnn", "python", or ""). +func detectAIUpscaleBackend() string { + if _, err := exec.LookPath("realesrgan-ncnn-vulkan"); err == nil { + return "ncnn" + } + + cmd := exec.Command("python3", "-c", "import realesrgan") + utils.ApplyNoWindow(cmd) + if err := cmd.Run(); err == nil { + return "python" + } + + cmd = exec.Command("python", "-c", "import realesrgan") + utils.ApplyNoWindow(cmd) + if err := cmd.Run(); err == nil { + return "python" + } + + return "" +} + +// checkAIFaceEnhanceAvailable verifies whether face enhancement tooling is available. +func checkAIFaceEnhanceAvailable(backend string) bool { + if backend != "python" { + return false + } + cmd := exec.Command("python3", "-c", "import realesrgan, gfpgan") + utils.ApplyNoWindow(cmd) + if err := cmd.Run(); err == nil { + return true + } + cmd = exec.Command("python", "-c", "import realesrgan, gfpgan") + utils.ApplyNoWindow(cmd) + return cmd.Run() == nil +} + +func aiUpscaleModelOptions() []string { + return []string{ + "General (RealESRGAN_x4plus)", + "Anime/Illustration (RealESRGAN_x4plus_anime_6B)", + "Anime Video (realesr-animevideov3)", + "General Tiny (realesr-general-x4v3)", + "2x General (RealESRGAN_x2plus)", + "Clean Restore (realesrnet-x4plus)", + } +} + +func aiUpscaleModelID(label string) string { + switch label { + case "Anime/Illustration (RealESRGAN_x4plus_anime_6B)": + return "realesrgan-x4plus-anime" + case "Anime Video (realesr-animevideov3)": + return "realesr-animevideov3" + case "General Tiny (realesr-general-x4v3)": + return "realesr-general-x4v3" + case "2x General (RealESRGAN_x2plus)": + return "realesrgan-x2plus" + case "Clean Restore (realesrnet-x4plus)": + return "realesrnet-x4plus" + default: + return "realesrgan-x4plus" + } +} + +func aiUpscaleModelLabel(modelID string) string { + switch modelID { + case "realesrgan-x4plus-anime": + return "Anime/Illustration (RealESRGAN_x4plus_anime_6B)" + case "realesr-animevideov3": + return "Anime Video (realesr-animevideov3)" + case "realesr-general-x4v3": + return "General Tiny (realesr-general-x4v3)" + case "realesrgan-x2plus": + return "2x General (RealESRGAN_x2plus)" + case "realesrnet-x4plus": + return "Clean Restore (realesrnet-x4plus)" + case "realesrgan-x4plus": + return "General (RealESRGAN_x4plus)" + default: + return "" + } +} + +// parseResolutionPreset parses resolution preset strings and returns target dimensions and whether to preserve aspect. +// Special presets like "Match Source" and relative (2X/4X) use source dimensions to preserve AR. +func parseResolutionPreset(preset string, srcW, srcH int) (width, height int, preserveAspect bool, err error) { + // Default: preserve aspect + preserveAspect = true + + // Sanitize source + if srcW < 1 || srcH < 1 { + srcW, srcH = 1920, 1080 // fallback to avoid zero division + } + + switch preset { + case "", "Match Source": + return srcW, srcH, true, nil + case "2X (relative)": + return srcW * 2, srcH * 2, true, nil + case "4X (relative)": + return srcW * 4, srcH * 4, true, nil + } + + presetMap := map[string][2]int{ + "720p (1280x720)": {1280, 720}, + "1080p (1920x1080)": {1920, 1080}, + "1440p (2560x1440)": {2560, 1440}, + "4K (3840x2160)": {3840, 2160}, + "8K (7680x4320)": {7680, 4320}, + "720p": {1280, 720}, + "1080p": {1920, 1080}, + "1440p": {2560, 1440}, + "4K": {3840, 2160}, + "8K": {7680, 4320}, + } + + if dims, ok := presetMap[preset]; ok { + // Keep aspect by default: use target height and let FFmpeg derive width + return dims[0], dims[1], true, nil + } + + return 0, 0, true, fmt.Errorf("unknown resolution preset: %s", preset) +} + +// buildUpscaleFilter builds FFmpeg scale filter string with selected method +func buildUpscaleFilter(targetWidth, targetHeight int, method string, preserveAspect bool) string { + // Ensure even dimensions for encoders + makeEven := func(v int) int { + if v%2 != 0 { + return v + 1 + } + return v + } + + h := makeEven(targetHeight) + w := targetWidth + if preserveAspect || w <= 0 { + w = -2 // FFmpeg will derive width from height while preserving AR + } + return fmt.Sprintf("scale=%d:%d:flags=%s", w, h, method) +} + +// sanitizeForPath creates a simple slug for filenames from user-visible labels +func sanitizeForPath(label string) string { + r := strings.NewReplacer(" ", "", "(", "", ")", "", "×", "x", "/", "-", "\\", "-", ":", "-", ",", "", ".", "", "_", "") + return strings.ToLower(r.Replace(label)) +} + +// Main module functions will be added here incrementally...