diff --git a/BUGS.md b/BUGS.md index a68a568..d4a1284 100644 --- a/BUGS.md +++ b/BUGS.md @@ -93,12 +93,12 @@ None currently open. ## 🧭 Feature Requests (Planned) ### FEAT-003: Enhancement module blur control -- **Status**: 🧭 PLANNED +- **Status**: 🧭 IN PROGRESS - **Reporter**: Jake (2026-01-06) - **Module**: Enhancement - **Description**: Enhancement panel should include a blur control in addition to sharpen/denoise. - **Impact**: Medium - expected control in enhancement workflow -- **Notes**: Add to enhancement UI alongside existing sliders. +- **Notes**: Implemented blur controls in Upscale first; Enhancement UI still pending. ### FEAT-004: Upscale output quality should use Bitrate Mode controls - **Status**: 🧭 PLANNED diff --git a/internal/state/manager.go b/internal/state/manager.go new file mode 100644 index 0000000..3a47451 --- /dev/null +++ b/internal/state/manager.go @@ -0,0 +1,34 @@ +package state + +import ( + "fyne.io/fyne/v2/widget" + "sync" +) + +type StateManager struct { + mu sync.RWMutex + + // Current mode settings + crfMode CRFMode + vbrMode VBRMode + currentQuality string + currentBitrate string + currentCRFValue int64 + currentVBRValue int64 + + // Registered widgets for synchronization + qualityWidgets []*widget.Select + bitrateWidgets []*widget.Select +} + +type CRFMode string +type VBRMode string + +const ( + CRFManual CRFMode = "manual" + CRFQuality CRFMode = "quality" + CRFBitrate CRFMode = "bitrate" + VBRStandard VBRMode = "standard" + VBRHQ VBRMode = "hq" + VBRConstrained VBRMode = "constrained" +) diff --git a/main.go b/main.go index ea2ec5f..53cc95e 100644 --- a/main.go +++ b/main.go @@ -1096,6 +1096,8 @@ type appState struct { upscaleFilterChain []string // Transferred filters from Filters module upscaleFrameRate string // Source, 24, 30, 60, or custom upscaleMotionInterpolation bool // Use motion interpolation for frame rate changes + upscaleBlurEnabled bool // Apply blur in upscale pipeline + upscaleBlurSigma float64 // Blur strength (sigma) // Snippet settings snippetLength int // Length of snippet in seconds (default: 20) @@ -5556,6 +5558,8 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre useMotionInterp, _ := cfg["useMotionInterpolation"].(bool) sourceFrameRate := toFloat(cfg["sourceFrameRate"]) qualityPreset, _ := cfg["qualityPreset"].(string) + blurEnabled, _ := cfg["blurEnabled"].(bool) + blurSigma := toFloat(cfg["blurSigma"]) if progressCallback != nil { progressCallback(0) @@ -5613,6 +5617,10 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre } } + if blurEnabled && blurSigma > 0 { + baseFilters = append(baseFilters, fmt.Sprintf("gblur=sigma=%.2f", blurSigma)) + } + if useAI { if aiBackend != "ncnn" { return fmt.Errorf("AI upscaling backend not available") @@ -14934,6 +14942,9 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { state.upscaleAIThreadsProc = 2 state.upscaleAIThreadsSave = 2 } + if state.upscaleBlurSigma <= 0 { + state.upscaleBlurSigma = 1.5 + } // Check AI availability on first load if state.upscaleAIBackend == "" { @@ -15069,6 +15080,36 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { 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 + blurSlider.Value = state.upscaleBlurSigma + blurSlider.OnChanged = func(v float64) { + state.upscaleBlurSigma = v + blurLabel.SetText(fmt.Sprintf("Blur Strength: %.2f", v)) + } + + blurCheck := widget.NewCheck("Enable Blur", func(checked bool) { + state.upscaleBlurEnabled = checked + if checked { + blurSlider.Enable() + } else { + blurSlider.Disable() + } + }) + blurCheck.SetChecked(state.upscaleBlurEnabled) + if state.upscaleBlurEnabled { + blurSlider.Enable() + } else { + blurSlider.Disable() + } + + blurSection := widget.NewCard("Blur (Optional)", "", container.NewVBox( + widget.NewLabel("Apply a soft blur during upscale processing"), + blurCheck, + container.NewVBox(blurLabel, blurSlider), + )) + // Frame Rate Section frameRateLabel := widget.NewLabel(fmt.Sprintf("Frame Rate: %s", state.upscaleFrameRate)) frameRateSelect := widget.NewSelect([]string{"Source", "23.976", "24", "25", "29.97", "30", "50", "59.94", "60"}, func(s string) { @@ -15437,6 +15478,8 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { "aiOutputFormat": state.upscaleAIOutputFormat, "applyFilters": state.upscaleApplyFilters, "filterChain": state.upscaleFilterChain, + "blurEnabled": state.upscaleBlurEnabled, + "blurSigma": state.upscaleBlurSigma, "duration": state.upscaleFile.Duration, "sourceFrameRate": state.upscaleFile.FrameRate, "frameRate": state.upscaleFrameRate, @@ -15490,6 +15533,7 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { traditionalSection, resolutionSection, qualitySection, + blurSection, frameRateSection, aiSection, filterIntegrationSection,