From 970a2328a9d9d4aea6fef48433d4e76a1afda7e7 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Tue, 6 Jan 2026 17:25:39 -0500 Subject: [PATCH] Revert "feat(upscale): redesign layout and add encoding controls" This reverts commit ed5be79f4c6b5ddccebfa3cc2de78715db652a5c. --- diagnostic_tool.go | 159 ---------------- main.go | 454 +++++++++------------------------------------ 2 files changed, 90 insertions(+), 523 deletions(-) delete mode 100644 diagnostic_tool.go diff --git a/diagnostic_tool.go b/diagnostic_tool.go deleted file mode 100644 index 349398e..0000000 --- a/diagnostic_tool.go +++ /dev/null @@ -1,159 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "time" - - "fyne.io/fyne/v2/app" - "git.leaktechnologies.dev/stu/VideoTools/internal/logging" - "git.leaktechnologies.dev/stu/VideoTools/internal/player" - "git.leaktechnologies.dev/stu/VideoTools/internal/utils" -) - -type CrashInfo struct { - Timestamp time.Time - Error error - StackTrace string - VideoPath string - OSInfo string - MemStats runtime.MemStats - Goroutines int -} - -var crashLog []CrashInfo - -func main() { - fmt.Println("VideoTools Crash Diagnostic Tool") - - if len(os.Args) < 2 { - fmt.Println("Usage: ./diagnostic_tool ") - return - } - - videoPath := os.Args[1] - if videoPath == "" { - fmt.Println("Error: video path required") - return - } - - if _, err := os.Stat(videoPath); os.IsNotExist(err) { - fmt.Printf("Error: video file not found: %v\n", err) - return - } - - // Test with unified player - testUnifiedPlayerStability(videoPath) - - // Test with dual-process player (for comparison) - testDualProcessStability(videoPath) - - // Generate crash report - generateCrashReport() -} - -func testUnifiedPlayerStability(videoPath string) { - fmt.Printf("Testing unified player with: %s\n", videoPath) - - config := &player.Config{ - Backend: player.BackendAuto, - WindowX: 100, - WindowY: 100, - WindowWidth: 800, - WindowHeight: 600, - Volume: 100, - Muted: false, - HardwareAccel: true, - } - - p := player.NewUnifiedPlayer(config) - if p == nil { - fmt.Printf("ERROR: Failed to create unified player: %v\n", fmt.Errorf("unified player creation failed")) - return - } - - if err := p.Load(videoPath, 0); err != nil { - fmt.Printf("ERROR: Failed to load video: %v\n", err) - return - } - - if err := p.Play(); err != nil { - fmt.Printf("ERROR: Failed to start playback: %v\n", err) - return - } - - fmt.Println("Unified player test: PLAYING...") - - // Test seeking - if err := p.SeekToTime(10 * time.Second); err != nil { - fmt.Printf("ERROR: Seek failed: %v\n", err) - return - } - - fmt.Printf("Unified player test: SEEKING TO 10s - SUCCESS\n") - - // Test video info - info := p.GetVideoInfo() - if info != nil { - fmt.Printf("Video info: %dx%d @ %.2ffps %v duration %v\n", - info.Width, info.Height, info.FrameRate, info.Duration) - } else { - fmt.Println("ERROR: Failed to get video info\n") - } - - fmt.Println("Unified player test: COMPLETED SUCCESSFULLY") - - p.Close() -} - -func testDualProcessStability(videoPath string) { - fmt.Printf("Testing dual-process player with: %s\n", videoPath) - - // Simulate dual-process behavior for comparison - fmt.Println("Dual-process test: Would stutter, have A/V desync, no frame-accurate seeking") -} - -func generateCrashReport() { - fmt.Println("=== CRASH REPORT ===") - if len(crashLog) > 0 { - fmt.Printf("Total crashes: %d\n", len(crashLog)) - for i, crash := range crashLog { - fmt.Printf("Crash %d at %v: %v\n", i+1, crash.Timestamp, crash.Error) - fmt.Printf(" Path: %s\n", crash.VideoPath) - fmt.Printf(" Error: %v\n", crash.Error) - if crash.StackTrace != "" { - fmt.Printf(" Stack: %s\n", crash.StackTrace) - } - } - } - - // Save detailed crash log - logPath := filepath.Join(getLogsDir(), "crash_diagnostics.log") - file, err := os.Create(logPath) - if err != nil { - fmt.Printf("ERROR: Failed to create crash log: %v\n", err) - return - } - - defer file.Close() - - // Write crash information - for _, crash := range crashLog { - file.WriteString(fmt.Sprintf("[%s] CRASH #%d\n", i+1)) - file.WriteString(fmt.Sprintf("Time: %v\n", crash.Timestamp.Format(time.RFC3339))) - file.WriteString(fmt.Sprintf("Video: %s\n", crash.VideoPath)) - file.WriteString(fmt.Sprintf("Error: %v\n", crash.Error)) - if crash.StackTrace != "" { - file.WriteString(fmt.Sprintf("Stack: %s\n", crash.StackTrace)) - } - file.WriteString(fmt.Sprintf("OS: %s\n", crash.OSInfo)) - file.WriteString(fmt.Sprintf("Memory: %v\n", crash.MemStats)) - file.WriteString(fmt.Sprintf("Goroutines: %v\n", crash.Goroutines)) - file.WriteString("---\n") - } - - file.WriteString(fmt.Sprintf("Crashes in session: %d\n", len(crashLog))) - fmt.Printf("Crash report saved to: %s\n", logPath) -} diff --git a/main.go b/main.go index 1fa7d9b..53cc95e 100644 --- a/main.go +++ b/main.go @@ -1098,10 +1098,6 @@ type appState struct { upscaleMotionInterpolation bool // Use motion interpolation for frame rate changes upscaleBlurEnabled bool // Apply blur in upscale pipeline upscaleBlurSigma float64 // Blur strength (sigma) - upscaleEncoderPreset string // libx264 preset for upscale output - upscaleBitrateMode string // CRF, CBR, VBR - upscaleBitratePreset string // preset label for bitrate modes - upscaleManualBitrate string // manual bitrate value (e.g., 2500k) // Snippet settings snippetLength int // Length of snippet in seconds (default: 20) @@ -5562,10 +5558,6 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre useMotionInterp, _ := cfg["useMotionInterpolation"].(bool) sourceFrameRate := toFloat(cfg["sourceFrameRate"]) qualityPreset, _ := cfg["qualityPreset"].(string) - encoderPreset, _ := cfg["encoderPreset"].(string) - bitrateMode, _ := cfg["bitrateMode"].(string) - bitratePreset, _ := cfg["bitratePreset"].(string) - manualBitrate, _ := cfg["manualBitrate"].(string) blurEnabled, _ := cfg["blurEnabled"].(bool) blurSigma := toFloat(cfg["blurSigma"]) @@ -5598,91 +5590,6 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre crfValue = 16 } - resolveBitrate := func() string { - if strings.TrimSpace(manualBitrate) != "" { - return manualBitrate - } - switch bitratePreset { - case "0.5 Mbps - Ultra Low": - return "500k" - case "1.0 Mbps - Very Low": - return "1000k" - case "1.5 Mbps - Low": - return "1500k" - case "2.0 Mbps - Medium-Low": - return "2000k" - case "2.5 Mbps - Medium": - return "2500k" - case "4.0 Mbps - Good": - return "4000k" - case "6.0 Mbps - High": - return "6000k" - case "8.0 Mbps - Very High": - return "8000k" - default: - return "2500k" - } - } - - parseBitrateKbps := func(val string) int { - v := strings.TrimSpace(strings.ToLower(val)) - if v == "" { - return 0 - } - mult := 1.0 - if strings.HasSuffix(v, "k") { - v = strings.TrimSuffix(v, "k") - mult = 1 - } else if strings.HasSuffix(v, "m") { - v = strings.TrimSuffix(v, "m") - mult = 1000 - } - num, err := strconv.ParseFloat(v, 64) - if err != nil { - return 0 - } - return int(num * mult) - } - - normalizeBitrateMode := func(mode string) string { - switch { - case strings.HasPrefix(strings.ToUpper(mode), "CBR"): - return "CBR" - case strings.HasPrefix(strings.ToUpper(mode), "VBR"): - return "VBR" - default: - return "CRF" - } - } - - appendEncodingArgs := func(args []string) []string { - preset := encoderPreset - if strings.TrimSpace(preset) == "" { - preset = "slow" - } - mode := normalizeBitrateMode(bitrateMode) - args = append(args, "-preset", preset) - switch mode { - case "CBR", "VBR": - bitrateVal := resolveBitrate() - args = append(args, "-b:v", bitrateVal) - kbps := parseBitrateKbps(bitrateVal) - if kbps > 0 { - maxrate := kbps - bufsize := kbps * 2 - if mode == "VBR" { - maxrate = kbps * 2 - bufsize = kbps * 4 - } - args = append(args, "-maxrate", fmt.Sprintf("%dk", maxrate)) - args = append(args, "-bufsize", fmt.Sprintf("%dk", bufsize)) - } - default: - args = append(args, "-crf", strconv.Itoa(crfValue)) - } - return args - } - // Build filter chain var baseFilters []string @@ -5942,9 +5849,10 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre reassembleArgs = append(reassembleArgs, "-vf", finalScale) } - reassembleArgs = append(reassembleArgs, "-c:v", "libx264") - reassembleArgs = appendEncodingArgs(reassembleArgs) reassembleArgs = append(reassembleArgs, + "-c:v", "libx264", + "-preset", "slow", + "-crf", strconv.Itoa(crfValue), "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", @@ -6000,9 +5908,10 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre } // Use lossless MKV by default for upscales; copy audio - args = append(args, "-c:v", "libx264") - args = appendEncodingArgs(args) args = append(args, + "-c:v", "libx264", + "-preset", "slow", + "-crf", strconv.Itoa(crfValue), "-pix_fmt", "yuv420p", "-c:a", "copy", "-progress", "pipe:1", @@ -11358,22 +11267,8 @@ func (p *playSession) runAudio(offset float64) { p.audioActive.Store(false) return } - // Use new UnifiedPlayer with proper A/V synchronization - unifiedPlayer := player.NewUnifiedPlayer(pr) - if player == nil { - logging.Error(logging.CatPlayer, "audio player creation failed (video-only playback)") - return - } - defer pr.Close() - defer pw.Close() - - player.Play() - p.audioActive.Store(true) // Mark audio as active - localPlayer := unifiedPlayer - if localPlayer != nil { - s.window.Canvas().SetContent(localPlayer) - } - return localPlayer, nil + pr, pw := io.Pipe() + player := ctx.NewPlayer(pr) if player == nil { logging.Debug(logging.CatFFMPEG, "audio player creation failed (video-only playback)") p.audioActive.Store(false) @@ -14830,11 +14725,11 @@ func buildCompareView(state *appState) fyne.CanvasObject { // Scrollable metadata area for file 1 - use smaller minimum file1InfoScroll := container.NewVScroll(file1Info) - // Avoid rigid min sizes so window snapping works across modules. +// Avoid rigid min sizes so window snapping works across modules. // Scrollable metadata area for file 2 - use smaller minimum file2InfoScroll := container.NewVScroll(file2Info) - // Avoid rigid min sizes so window snapping works across modules. +// Avoid rigid min sizes so window snapping works across modules. // File 1 column: header, video player, metadata (using Border to make metadata expand) file1Column := container.NewBorder( @@ -15034,18 +14929,6 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { if state.upscaleQualityPreset == "" { state.upscaleQualityPreset = "Near-lossless (CRF 16)" } - if state.upscaleEncoderPreset == "" { - state.upscaleEncoderPreset = "slow" - } - if state.upscaleBitrateMode == "" { - state.upscaleBitrateMode = "CRF" - } - if state.upscaleBitratePreset == "" { - state.upscaleBitratePreset = "2.5 Mbps - Medium" - } - if state.upscaleManualBitrate == "" { - state.upscaleManualBitrate = "2500k" - } if state.upscaleAIPreset == "" { state.upscaleAIPreset = "Balanced" state.upscaleAIScale = 4.0 @@ -15141,6 +15024,62 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { methodInfo.TextStyle = fyne.TextStyle{Italic: true} methodInfo.Wrapping = fyne.TextWrapWord + traditionalSection := widget.NewCard("Traditional Scaling (FFmpeg)", "", container.NewVBox( + widget.NewLabel("Classic upscaling methods - always available"), + container.NewGridWithColumns(2, + widget.NewLabel("Scaling Algorithm:"), + methodSelect, + ), + methodLabel, + widget.NewSeparator(), + methodInfo, + )) + + // Resolution Selection Section + resLabel := widget.NewLabel(fmt.Sprintf("Target: %s", state.upscaleTargetRes)) + resSelect := widget.NewSelect([]string{ + "Match Source", + "2X (relative)", + "4X (relative)", + "720p (1280x720)", + "1080p (1920x1080)", + "1440p (2560x1440)", + "4K (3840x2160)", + "8K (7680x4320)", + "Custom", + }, func(s string) { + state.upscaleTargetRes = s + resLabel.SetText(fmt.Sprintf("Target: %s", s)) + }) + resSelect.SetSelected(state.upscaleTargetRes) + + resolutionSection := widget.NewCard("Target Resolution", "", container.NewVBox( + widget.NewLabel("Select output resolution"), + container.NewGridWithColumns(2, + widget.NewLabel("Resolution:"), + resSelect, + ), + resLabel, + sourceResLabel, + )) + + qualitySelect := widget.NewSelect([]string{ + "Lossless (CRF 0)", + "Near-lossless (CRF 16)", + "High (CRF 18)", + }, func(s string) { + state.upscaleQualityPreset = s + }) + qualitySelect.SetSelected(state.upscaleQualityPreset) + + qualitySection := widget.NewCard("Output Quality", "", container.NewVBox( + container.NewGridWithColumns(2, + widget.NewLabel("Quality:"), + qualitySelect, + ), + widget.NewLabel("Lower CRF = higher quality/larger files"), + )) + blurLabel := widget.NewLabel(fmt.Sprintf("Blur Strength: %.2f", state.upscaleBlurSigma)) blurSlider := widget.NewSlider(0.0, 8.0) blurSlider.Step = 0.1 @@ -15165,178 +15104,10 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { blurSlider.Disable() } - mediumBlue := utils.MustHex("#13182B") - navyBlue := utils.MustHex("#191F35") - - buildUpscaleBox := func(title string, content fyne.CanvasObject) fyne.CanvasObject { - bg := canvas.NewRectangle(navyBlue) - bg.CornerRadius = 10 - bg.StrokeColor = gridColor - bg.StrokeWidth = 1 - body := container.NewVBox( - widget.NewLabelWithStyle(title, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - widget.NewSeparator(), - content, - ) - return container.NewMax(bg, container.NewPadded(body)) - } - - // Resolution Selection Section - resLabel := widget.NewLabel(fmt.Sprintf("Target: %s", state.upscaleTargetRes)) - resSelect := widget.NewSelect([]string{ - "Match Source", - "2X (relative)", - "4X (relative)", - "720p (1280x720)", - "1080p (1920x1080)", - "1440p (2560x1440)", - "4K (3840x2160)", - "8K (7680x4320)", - "Custom", - }, func(s string) { - state.upscaleTargetRes = s - resLabel.SetText(fmt.Sprintf("Target: %s", s)) - }) - resSelect.SetSelected(state.upscaleTargetRes) - - resolutionSection := buildUpscaleBox("Target Resolution", container.NewVBox( - widget.NewLabel("Select output resolution"), - container.NewGridWithColumns(2, - widget.NewLabel("Resolution:"), - resSelect, - ), - resLabel, - sourceResLabel, - )) - - qualitySelect := widget.NewSelect([]string{ - "Lossless (CRF 0)", - "Near-lossless (CRF 16)", - "High (CRF 18)", - }, func(s string) { - state.upscaleQualityPreset = s - }) - qualitySelect.SetSelected(state.upscaleQualityPreset) - - encoderPresetSelect := widget.NewSelect([]string{ - "ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow", - }, func(s string) { - state.upscaleEncoderPreset = s - }) - encoderPresetSelect.SetSelected(state.upscaleEncoderPreset) - - var updateEncodingVisibility func() - - bitrateModeSelect := widget.NewSelect([]string{ - "CRF (Constant Rate Factor)", - "CBR (Constant Bitrate)", - "VBR (Variable Bitrate)", - }, func(s string) { - switch { - case strings.HasPrefix(s, "CRF"): - state.upscaleBitrateMode = "CRF" - case strings.HasPrefix(s, "CBR"): - state.upscaleBitrateMode = "CBR" - case strings.HasPrefix(s, "VBR"): - state.upscaleBitrateMode = "VBR" - default: - state.upscaleBitrateMode = s - } - if updateEncodingVisibility != nil { - updateEncodingVisibility() - } - }) - switch state.upscaleBitrateMode { - case "CBR": - bitrateModeSelect.SetSelected("CBR (Constant Bitrate)") - case "VBR": - bitrateModeSelect.SetSelected("VBR (Variable Bitrate)") - default: - bitrateModeSelect.SetSelected("CRF (Constant Rate Factor)") - } - - type bitratePreset struct { - Label string - Bitrate string - } - presets := []bitratePreset{ - {Label: "0.5 Mbps - Ultra Low", Bitrate: "500k"}, - {Label: "1.0 Mbps - Very Low", Bitrate: "1000k"}, - {Label: "1.5 Mbps - Low", Bitrate: "1500k"}, - {Label: "2.0 Mbps - Medium-Low", Bitrate: "2000k"}, - {Label: "2.5 Mbps - Medium", Bitrate: "2500k"}, - {Label: "4.0 Mbps - Good", Bitrate: "4000k"}, - {Label: "6.0 Mbps - High", Bitrate: "6000k"}, - {Label: "8.0 Mbps - Very High", Bitrate: "8000k"}, - {Label: "Manual", Bitrate: ""}, - } - bitratePresetLookup := make(map[string]bitratePreset) - var bitratePresetLabels []string - for _, p := range presets { - bitratePresetLookup[p.Label] = p - bitratePresetLabels = append(bitratePresetLabels, p.Label) - } - - manualBitrateEntry := widget.NewEntry() - manualBitrateEntry.SetPlaceHolder("e.g., 2500k") - manualBitrateEntry.SetText(state.upscaleManualBitrate) - manualBitrateEntry.OnChanged = func(val string) { - state.upscaleManualBitrate = val - } - - bitratePresetSelect := widget.NewSelect(bitratePresetLabels, func(s string) { - state.upscaleBitratePreset = s - preset := bitratePresetLookup[s] - if preset.Bitrate == "" { - manualBitrateEntry.Show() - } else { - state.upscaleManualBitrate = preset.Bitrate - manualBitrateEntry.SetText(preset.Bitrate) - manualBitrateEntry.Hide() - } - }) - bitratePresetSelect.SetSelected(state.upscaleBitratePreset) - if bitratePresetLookup[state.upscaleBitratePreset].Bitrate == "" { - manualBitrateEntry.Show() - } else { - manualBitrateEntry.Hide() - } - - updateEncodingVisibility = func() { - mode := state.upscaleBitrateMode - if mode == "" || mode == "CRF" { - qualitySelect.Enable() - bitratePresetSelect.Hide() - manualBitrateEntry.Hide() - } else { - qualitySelect.Disable() - bitratePresetSelect.Show() - if bitratePresetLookup[state.upscaleBitratePreset].Bitrate == "" { - manualBitrateEntry.Show() - } - } - } - updateEncodingVisibility() - - encodingSection := buildUpscaleBox("Video Encoding", container.NewVBox( - container.NewGridWithColumns(2, - widget.NewLabel("Encoder Preset:"), - encoderPresetSelect, - ), - container.NewGridWithColumns(2, - widget.NewLabel("Quality Preset:"), - qualitySelect, - ), - container.NewGridWithColumns(2, - widget.NewLabel("Bitrate Mode:"), - bitrateModeSelect, - ), - container.NewGridWithColumns(2, - widget.NewLabel("Bitrate Preset:"), - bitratePresetSelect, - ), - manualBitrateEntry, - widget.NewLabel("CRF mode controls quality; bitrate modes control size."), + blurSection := widget.NewCard("Blur (Optional)", "", container.NewVBox( + widget.NewLabel("Apply a soft blur during upscale processing"), + blurCheck, + container.NewVBox(blurLabel, blurSlider), )) // Frame Rate Section @@ -15352,7 +15123,7 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { }) motionInterpCheck.SetChecked(state.upscaleMotionInterpolation) - frameRateSection := buildUpscaleBox("Frame Rate", container.NewVBox( + frameRateSection := widget.NewCard("Frame Rate", "", container.NewVBox( widget.NewLabel("Convert frame rate (optional)"), container.NewGridWithColumns(2, widget.NewLabel("Target FPS:"), @@ -15369,8 +15140,8 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { aiModelLabel = aiModelOptions[0] } - // AI Upscaling Section (nested under Scaling) - var aiContent fyne.CanvasObject + // AI Upscaling Section + var aiSection *widget.Card if state.upscaleAIAvailable { var aiTileSelect *widget.Select var aiTTACheck *widget.Check @@ -15579,7 +15350,7 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { denoiseHint.TextStyle = fyne.TextStyle{Italic: true} updateDenoiseAvailability(state.upscaleAIModel) - aiContent = container.NewVBox( + aiSection = widget.NewCard("AI Upscaling", "✓ Available", container.NewVBox( widget.NewLabel("Real-ESRGAN detected - enhanced quality available"), aiEnabledCheck, container.NewGridWithColumns(2, @@ -15617,49 +15388,26 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { container.NewGridWithColumns(3, aiThreadsLoad, aiThreadsProc, aiThreadsSave), ), widget.NewLabel("Note: AI upscaling is slower but produces higher quality results"), - ) + )) } else { backendNote := "Real-ESRGAN not detected. Install for enhanced quality:" if state.upscaleAIBackend == "python" { backendNote = "Python Real-ESRGAN detected, but the ncnn backend is required for now." } - aiContent = container.NewVBox( + aiSection = widget.NewCard("AI Upscaling", "Not Available", container.NewVBox( widget.NewLabel(backendNote), widget.NewLabel("https://github.com/xinntao/Real-ESRGAN"), widget.NewLabel("Traditional scaling methods will be used."), - ) + )) } - aiSection := container.NewVBox( - widget.NewLabelWithStyle("AI Upscaling", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - widget.NewSeparator(), - aiContent, - ) - - traditionalSection := buildUpscaleBox("Scaling", container.NewVBox( - widget.NewLabel("Classic upscaling methods - always available"), - container.NewGridWithColumns(2, - widget.NewLabel("Scaling Algorithm:"), - methodSelect, - ), - methodLabel, - widget.NewSeparator(), - methodInfo, - widget.NewSeparator(), - widget.NewLabel("Optional blur"), - blurCheck, - container.NewVBox(blurLabel, blurSlider), - widget.NewSeparator(), - aiSection, - )) - // Filter Integration Section applyFiltersCheck := widget.NewCheck("Apply filters before upscaling", func(checked bool) { state.upscaleApplyFilters = checked }) applyFiltersCheck.SetChecked(state.upscaleApplyFilters) - filterIntegrationSection := buildUpscaleBox("Filter Integration", container.NewVBox( + filterIntegrationSection := widget.NewCard("Filter Integration", "", container.NewVBox( widget.NewLabel("Apply color correction and filters from Filters module"), applyFiltersCheck, widget.NewLabel("Filters will be applied before upscaling for best quality"), @@ -15704,10 +15452,6 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { "inputPath": state.upscaleFile.Path, "outputPath": outputPath, "method": state.upscaleMethod, - "encoderPreset": state.upscaleEncoderPreset, - "bitrateMode": state.upscaleBitrateMode, - "bitratePreset": state.upscaleBitratePreset, - "manualBitrate": state.upscaleManualBitrate, "targetWidth": float64(targetWidth), "targetHeight": float64(targetHeight), "targetPreset": state.upscaleTargetRes, @@ -15777,38 +15521,22 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { addQueueBtn.Importance = widget.MediumImportance // Main content - spacing := func() fyne.CanvasObject { - spacer := canvas.NewRectangle(color.Transparent) - spacer.SetMinSize(fyne.NewSize(0, 10)) - return spacer - } - - metaPanel, _ := buildMetadataPanel(state, state.upscaleFile, fyne.NewSize(0, 200)) - leftPanel := container.NewVBox( instructions, - spacing(), - buildUpscaleBox("Video", container.NewVBox( - fileLabel, - loadBtn, - filtersNavBtn, - videoContainer, - )), - spacing(), - metaPanel, + widget.NewSeparator(), + fileLabel, + loadBtn, + filtersNavBtn, ) settingsPanel := container.NewVBox( traditionalSection, - spacing(), resolutionSection, - spacing(), - encodingSection, - spacing(), + qualitySection, + blurSection, frameRateSection, - spacing(), + aiSection, filterIntegrationSection, - spacing(), container.NewGridWithColumns(2, applyBtn, addQueueBtn), ) @@ -15816,15 +15544,13 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { // Adaptive height for small screens // Avoid rigid min sizes so window snapping works across modules. - split := container.NewHSplit(leftPanel, settingsScroll) - split.Offset = 0.58 - mainContent := split - - content := container.NewMax( - canvas.NewRectangle(mediumBlue), - container.NewPadded(mainContent), + mainContent := container.New(&fixedHSplitLayout{ratio: 0.6}, + container.NewVBox(leftPanel, videoContainer), + settingsScroll, ) + content := container.NewPadded(mainContent) + return container.NewBorder(topBar, bottomBar, nil, nil, content) }