Extract filters module from main.go
- Create filters_module.go (257 lines) - Move showFiltersView() and buildFiltersView() - Reduce main.go by ~257 lines (from 14245 to 13988) - All syntax checks pass, module ready for testing
This commit is contained in:
parent
a42b353aea
commit
c98c1aa924
257
filters_module.go
Normal file
257
filters_module.go
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"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/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *appState) showFiltersView() {
|
||||||
|
s.stopPreview()
|
||||||
|
s.lastModule = s.active
|
||||||
|
s.active = "filters"
|
||||||
|
s.setContent(buildFiltersView(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFiltersView(state *appState) fyne.CanvasObject {
|
||||||
|
filtersColor := moduleColor("filters")
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
backBtn := widget.NewButton("< FILTERS", func() {
|
||||||
|
state.showMainMenu()
|
||||||
|
})
|
||||||
|
backBtn.Importance = widget.LowImportance
|
||||||
|
|
||||||
|
// Queue button
|
||||||
|
queueBtn := widget.NewButton("View Queue", func() {
|
||||||
|
state.showQueue()
|
||||||
|
})
|
||||||
|
state.queueBtn = queueBtn
|
||||||
|
state.updateQueueButtonLabel()
|
||||||
|
|
||||||
|
// Top bar with module color
|
||||||
|
topBar := ui.TintedBar(filtersColor, container.NewHBox(backBtn, layout.NewSpacer(), queueBtn))
|
||||||
|
bottomBar := moduleFooter(filtersColor, layout.NewSpacer(), state.statsBar)
|
||||||
|
|
||||||
|
// Instructions
|
||||||
|
instructions := widget.NewLabel("Apply filters and color corrections to your video. Preview changes in real-time.")
|
||||||
|
instructions.Wrapping = fyne.TextWrapWord
|
||||||
|
instructions.Alignment = fyne.TextAlignCenter
|
||||||
|
|
||||||
|
// Initialize state defaults
|
||||||
|
if state.filterBrightness == 0 && state.filterContrast == 0 && state.filterSaturation == 0 {
|
||||||
|
state.filterBrightness = 0.0 // -1.0 to 1.0
|
||||||
|
state.filterContrast = 1.0 // 0.0 to 3.0
|
||||||
|
state.filterSaturation = 1.0 // 0.0 to 3.0
|
||||||
|
state.filterSharpness = 0.0 // 0.0 to 5.0
|
||||||
|
state.filterDenoise = 0.0 // 0.0 to 10.0
|
||||||
|
}
|
||||||
|
if state.filterInterpPreset == "" {
|
||||||
|
state.filterInterpPreset = "Balanced"
|
||||||
|
}
|
||||||
|
if state.filterInterpFPS == "" {
|
||||||
|
state.filterInterpFPS = "60"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFilterChain := func() {
|
||||||
|
var chain []string
|
||||||
|
if state.filterInterpEnabled {
|
||||||
|
fps := state.filterInterpFPS
|
||||||
|
if fps == "" {
|
||||||
|
fps = "60"
|
||||||
|
}
|
||||||
|
var filter string
|
||||||
|
switch state.filterInterpPreset {
|
||||||
|
case "Ultra Fast":
|
||||||
|
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=blend", fps)
|
||||||
|
case "Fast":
|
||||||
|
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=duplicate", fps)
|
||||||
|
case "High Quality":
|
||||||
|
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1:search_param=32", fps)
|
||||||
|
case "Maximum Quality":
|
||||||
|
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1:search_param=64", fps)
|
||||||
|
default: // Balanced
|
||||||
|
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=obmc:me_mode=bidir:me=epzs:search_param=16:vsbmc=0", fps)
|
||||||
|
}
|
||||||
|
chain = append(chain, filter)
|
||||||
|
}
|
||||||
|
state.filterActiveChain = chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// File label
|
||||||
|
fileLabel := widget.NewLabel("No file loaded")
|
||||||
|
fileLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||||||
|
|
||||||
|
var videoContainer fyne.CanvasObject
|
||||||
|
if state.filtersFile != nil {
|
||||||
|
fileLabel.SetText(fmt.Sprintf("File: %s", filepath.Base(state.filtersFile.Path)))
|
||||||
|
videoContainer = buildVideoPane(state, fyne.NewSize(480, 270), state.filtersFile, nil)
|
||||||
|
} else {
|
||||||
|
videoContainer = container.NewCenter(widget.NewLabel("No video loaded"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load button
|
||||||
|
loadBtn := widget.NewButton("Load Video", func() {
|
||||||
|
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
|
||||||
|
if err != nil || reader == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
path := reader.URI().Path()
|
||||||
|
go func() {
|
||||||
|
src, err := probeVideo(path)
|
||||||
|
if err != nil {
|
||||||
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
||||||
|
dialog.ShowError(err, state.window)
|
||||||
|
}, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
||||||
|
state.filtersFile = src
|
||||||
|
state.showFiltersView()
|
||||||
|
}, false)
|
||||||
|
}()
|
||||||
|
}, state.window)
|
||||||
|
})
|
||||||
|
loadBtn.Importance = widget.HighImportance
|
||||||
|
|
||||||
|
// Navigation to Upscale module
|
||||||
|
upscaleNavBtn := widget.NewButton("Send to Upscale →", func() {
|
||||||
|
if state.filtersFile != nil {
|
||||||
|
state.upscaleFile = state.filtersFile
|
||||||
|
buildFilterChain()
|
||||||
|
state.upscaleFilterChain = append([]string{}, state.filterActiveChain...)
|
||||||
|
}
|
||||||
|
state.showUpscaleView()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Color Correction Section
|
||||||
|
colorSection := widget.NewCard("Color Correction", "", container.NewVBox(
|
||||||
|
widget.NewLabel("Adjust brightness, contrast, and saturation"),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Brightness:"),
|
||||||
|
widget.NewSlider(-1.0, 1.0),
|
||||||
|
widget.NewLabel("Contrast:"),
|
||||||
|
widget.NewSlider(0.0, 3.0),
|
||||||
|
widget.NewLabel("Saturation:"),
|
||||||
|
widget.NewSlider(0.0, 3.0),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Enhancement Section
|
||||||
|
enhanceSection := widget.NewCard("Enhancement", "", container.NewVBox(
|
||||||
|
widget.NewLabel("Sharpen, blur, and denoise"),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Sharpness:"),
|
||||||
|
widget.NewSlider(0.0, 5.0),
|
||||||
|
widget.NewLabel("Denoise:"),
|
||||||
|
widget.NewSlider(0.0, 10.0),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Transform Section
|
||||||
|
transformSection := widget.NewCard("Transform", "", container.NewVBox(
|
||||||
|
widget.NewLabel("Rotate and flip video"),
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Rotation:"),
|
||||||
|
widget.NewSelect([]string{"0°", "90°", "180°", "270°"}, func(s string) {}),
|
||||||
|
widget.NewLabel("Flip Horizontal:"),
|
||||||
|
widget.NewCheck("", func(b bool) { state.filterFlipH = b }),
|
||||||
|
widget.NewLabel("Flip Vertical:"),
|
||||||
|
widget.NewCheck("", func(b bool) { state.filterFlipV = b }),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Creative Effects Section
|
||||||
|
creativeSection := widget.NewCard("Creative Effects", "", container.NewVBox(
|
||||||
|
widget.NewLabel("Apply artistic effects"),
|
||||||
|
widget.NewCheck("Grayscale", func(b bool) { state.filterGrayscale = b }),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Frame Interpolation Section
|
||||||
|
interpEnabledCheck := widget.NewCheck("Enable Frame Interpolation", func(checked bool) {
|
||||||
|
state.filterInterpEnabled = checked
|
||||||
|
buildFilterChain()
|
||||||
|
})
|
||||||
|
interpEnabledCheck.SetChecked(state.filterInterpEnabled)
|
||||||
|
|
||||||
|
interpPresetSelect := widget.NewSelect([]string{"Ultra Fast", "Fast", "Balanced", "High Quality", "Maximum Quality"}, func(val string) {
|
||||||
|
state.filterInterpPreset = val
|
||||||
|
buildFilterChain()
|
||||||
|
})
|
||||||
|
interpPresetSelect.SetSelected(state.filterInterpPreset)
|
||||||
|
|
||||||
|
interpFPSSelect := widget.NewSelect([]string{"24", "30", "50", "59.94", "60"}, func(val string) {
|
||||||
|
state.filterInterpFPS = val
|
||||||
|
buildFilterChain()
|
||||||
|
})
|
||||||
|
interpFPSSelect.SetSelected(state.filterInterpFPS)
|
||||||
|
|
||||||
|
interpHint := widget.NewLabel("Balanced preset is recommended; higher presets are CPU-intensive.")
|
||||||
|
interpHint.TextStyle = fyne.TextStyle{Italic: true}
|
||||||
|
interpHint.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
|
interpSection := widget.NewCard("Frame Interpolation (Minterpolate)", "", container.NewVBox(
|
||||||
|
widget.NewLabel("Generate smoother motion by interpolating new frames"),
|
||||||
|
interpEnabledCheck,
|
||||||
|
container.NewGridWithColumns(2,
|
||||||
|
widget.NewLabel("Preset:"),
|
||||||
|
interpPresetSelect,
|
||||||
|
widget.NewLabel("Target FPS:"),
|
||||||
|
interpFPSSelect,
|
||||||
|
),
|
||||||
|
interpHint,
|
||||||
|
))
|
||||||
|
buildFilterChain()
|
||||||
|
|
||||||
|
// Apply button
|
||||||
|
applyBtn := widget.NewButton("Apply Filters", func() {
|
||||||
|
if state.filtersFile == nil {
|
||||||
|
dialog.ShowInformation("No Video", "Please load a video first.", state.window)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buildFilterChain()
|
||||||
|
dialog.ShowInformation("Filters", "Filters are now configured and will be applied when sent to Upscale.", state.window)
|
||||||
|
})
|
||||||
|
applyBtn.Importance = widget.HighImportance
|
||||||
|
|
||||||
|
// Main content
|
||||||
|
leftPanel := container.NewVBox(
|
||||||
|
instructions,
|
||||||
|
widget.NewSeparator(),
|
||||||
|
fileLabel,
|
||||||
|
loadBtn,
|
||||||
|
upscaleNavBtn,
|
||||||
|
)
|
||||||
|
|
||||||
|
settingsPanel := container.NewVBox(
|
||||||
|
colorSection,
|
||||||
|
enhanceSection,
|
||||||
|
transformSection,
|
||||||
|
interpSection,
|
||||||
|
creativeSection,
|
||||||
|
applyBtn,
|
||||||
|
)
|
||||||
|
|
||||||
|
settingsScroll := container.NewVScroll(settingsPanel)
|
||||||
|
// Adaptive height for small screens - allow content to flow
|
||||||
|
settingsScroll.SetMinSize(fyne.NewSize(350, 400))
|
||||||
|
|
||||||
|
mainContent := container.New(&fixedHSplitLayout{ratio: 0.6},
|
||||||
|
container.NewVBox(leftPanel, container.NewCenter(videoContainer)),
|
||||||
|
settingsScroll,
|
||||||
|
)
|
||||||
|
|
||||||
|
content := container.NewPadded(mainContent)
|
||||||
|
|
||||||
|
return container.NewBorder(topBar, bottomBar, nil, nil, content)
|
||||||
|
}
|
||||||
292
main.go
292
main.go
|
|
@ -2741,13 +2741,6 @@ func (s *appState) showPlayerView() {
|
||||||
s.setContent(buildPlayerView(s))
|
s.setContent(buildPlayerView(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appState) showFiltersView() {
|
|
||||||
s.stopPreview()
|
|
||||||
s.lastModule = s.active
|
|
||||||
s.active = "filters"
|
|
||||||
s.setContent(buildFiltersView(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appState) showUpscaleView() {
|
func (s *appState) showUpscaleView() {
|
||||||
s.stopPreview()
|
s.stopPreview()
|
||||||
s.lastModule = s.active
|
s.lastModule = s.active
|
||||||
|
|
@ -8955,26 +8948,30 @@ func buildVideoPane(state *appState, min fyne.Size, src *videoSource, onCover fu
|
||||||
}
|
}
|
||||||
|
|
||||||
type playSession struct {
|
type playSession struct {
|
||||||
path string
|
path string
|
||||||
fps float64
|
fps float64
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
targetW int
|
targetW int
|
||||||
targetH int
|
targetH int
|
||||||
volume float64
|
volume float64
|
||||||
muted bool
|
muted bool
|
||||||
paused bool
|
paused bool
|
||||||
current float64
|
current float64
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
prog func(float64)
|
prog func(float64)
|
||||||
frameFunc func(int) // Callback for frame number updates
|
frameFunc func(int) // Callback for frame number updates
|
||||||
img *canvas.Image
|
img *canvas.Image
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
videoCmd *exec.Cmd
|
videoCmd *exec.Cmd
|
||||||
audioCmd *exec.Cmd
|
audioCmd *exec.Cmd
|
||||||
frameN int
|
frameN int
|
||||||
duration float64 // Total duration in seconds
|
duration float64 // Total duration in seconds
|
||||||
|
startTime time.Time
|
||||||
|
audioTime atomic.Value // float64 - Audio master clock time
|
||||||
|
videoTime float64 // Last video frame time
|
||||||
|
syncOffset float64 // A/V sync offset for adjustment
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioCtxGlobal struct {
|
var audioCtxGlobal struct {
|
||||||
|
|
@ -9196,6 +9193,10 @@ func (p *playSession) startLocked(offset float64) {
|
||||||
p.paused = false
|
p.paused = false
|
||||||
p.current = offset
|
p.current = offset
|
||||||
p.frameN = 0
|
p.frameN = 0
|
||||||
|
p.startTime = time.Now()
|
||||||
|
p.audioTime.Store(offset)
|
||||||
|
p.videoTime = offset
|
||||||
|
p.syncOffset = 0
|
||||||
logging.Debug(logging.CatFFMPEG, "playSession start path=%s offset=%.3f fps=%.3f target=%dx%d", p.path, offset, p.fps, p.targetW, p.targetH)
|
logging.Debug(logging.CatFFMPEG, "playSession start path=%s offset=%.3f fps=%.3f target=%dx%d", p.path, offset, p.fps, p.targetW, p.targetH)
|
||||||
p.runVideo(offset)
|
p.runVideo(offset)
|
||||||
p.runAudio(offset)
|
p.runAudio(offset)
|
||||||
|
|
@ -13094,243 +13095,6 @@ func buildPlayerView(state *appState) fyne.CanvasObject {
|
||||||
return container.NewBorder(topBar, bottomBar, nil, nil, content)
|
return container.NewBorder(topBar, bottomBar, nil, nil, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildFiltersView creates the Filters module UI
|
|
||||||
func buildFiltersView(state *appState) fyne.CanvasObject {
|
|
||||||
filtersColor := moduleColor("filters")
|
|
||||||
|
|
||||||
// Back button
|
|
||||||
backBtn := widget.NewButton("< FILTERS", func() {
|
|
||||||
state.showMainMenu()
|
|
||||||
})
|
|
||||||
backBtn.Importance = widget.LowImportance
|
|
||||||
|
|
||||||
// Queue button
|
|
||||||
queueBtn := widget.NewButton("View Queue", func() {
|
|
||||||
state.showQueue()
|
|
||||||
})
|
|
||||||
state.queueBtn = queueBtn
|
|
||||||
state.updateQueueButtonLabel()
|
|
||||||
|
|
||||||
// Top bar with module color
|
|
||||||
topBar := ui.TintedBar(filtersColor, container.NewHBox(backBtn, layout.NewSpacer(), queueBtn))
|
|
||||||
bottomBar := moduleFooter(filtersColor, layout.NewSpacer(), state.statsBar)
|
|
||||||
|
|
||||||
// Instructions
|
|
||||||
instructions := widget.NewLabel("Apply filters and color corrections to your video. Preview changes in real-time.")
|
|
||||||
instructions.Wrapping = fyne.TextWrapWord
|
|
||||||
instructions.Alignment = fyne.TextAlignCenter
|
|
||||||
|
|
||||||
// Initialize state defaults
|
|
||||||
if state.filterBrightness == 0 && state.filterContrast == 0 && state.filterSaturation == 0 {
|
|
||||||
state.filterBrightness = 0.0 // -1.0 to 1.0
|
|
||||||
state.filterContrast = 1.0 // 0.0 to 3.0
|
|
||||||
state.filterSaturation = 1.0 // 0.0 to 3.0
|
|
||||||
state.filterSharpness = 0.0 // 0.0 to 5.0
|
|
||||||
state.filterDenoise = 0.0 // 0.0 to 10.0
|
|
||||||
}
|
|
||||||
if state.filterInterpPreset == "" {
|
|
||||||
state.filterInterpPreset = "Balanced"
|
|
||||||
}
|
|
||||||
if state.filterInterpFPS == "" {
|
|
||||||
state.filterInterpFPS = "60"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFilterChain := func() {
|
|
||||||
var chain []string
|
|
||||||
if state.filterInterpEnabled {
|
|
||||||
fps := state.filterInterpFPS
|
|
||||||
if fps == "" {
|
|
||||||
fps = "60"
|
|
||||||
}
|
|
||||||
var filter string
|
|
||||||
switch state.filterInterpPreset {
|
|
||||||
case "Ultra Fast":
|
|
||||||
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=blend", fps)
|
|
||||||
case "Fast":
|
|
||||||
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=duplicate", fps)
|
|
||||||
case "High Quality":
|
|
||||||
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1:search_param=32", fps)
|
|
||||||
case "Maximum Quality":
|
|
||||||
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1:search_param=64", fps)
|
|
||||||
default: // Balanced
|
|
||||||
filter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=obmc:me_mode=bidir:me=epzs:search_param=16:vsbmc=0", fps)
|
|
||||||
}
|
|
||||||
chain = append(chain, filter)
|
|
||||||
}
|
|
||||||
state.filterActiveChain = chain
|
|
||||||
}
|
|
||||||
|
|
||||||
// File label
|
|
||||||
fileLabel := widget.NewLabel("No file loaded")
|
|
||||||
fileLabel.TextStyle = fyne.TextStyle{Bold: true}
|
|
||||||
|
|
||||||
var videoContainer fyne.CanvasObject
|
|
||||||
if state.filtersFile != nil {
|
|
||||||
fileLabel.SetText(fmt.Sprintf("File: %s", filepath.Base(state.filtersFile.Path)))
|
|
||||||
videoContainer = buildVideoPane(state, fyne.NewSize(480, 270), state.filtersFile, nil)
|
|
||||||
} else {
|
|
||||||
videoContainer = container.NewCenter(widget.NewLabel("No video loaded"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load button
|
|
||||||
loadBtn := widget.NewButton("Load Video", func() {
|
|
||||||
dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) {
|
|
||||||
if err != nil || reader == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
path := reader.URI().Path()
|
|
||||||
go func() {
|
|
||||||
src, err := probeVideo(path)
|
|
||||||
if err != nil {
|
|
||||||
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
|
||||||
dialog.ShowError(err, state.window)
|
|
||||||
}, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
|
||||||
state.filtersFile = src
|
|
||||||
state.showFiltersView()
|
|
||||||
}, false)
|
|
||||||
}()
|
|
||||||
}, state.window)
|
|
||||||
})
|
|
||||||
loadBtn.Importance = widget.HighImportance
|
|
||||||
|
|
||||||
// Navigation to Upscale module
|
|
||||||
upscaleNavBtn := widget.NewButton("Send to Upscale →", func() {
|
|
||||||
if state.filtersFile != nil {
|
|
||||||
state.upscaleFile = state.filtersFile
|
|
||||||
buildFilterChain()
|
|
||||||
state.upscaleFilterChain = append([]string{}, state.filterActiveChain...)
|
|
||||||
}
|
|
||||||
state.showUpscaleView()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Color Correction Section
|
|
||||||
colorSection := widget.NewCard("Color Correction", "", container.NewVBox(
|
|
||||||
widget.NewLabel("Adjust brightness, contrast, and saturation"),
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Brightness:"),
|
|
||||||
widget.NewSlider(-1.0, 1.0),
|
|
||||||
widget.NewLabel("Contrast:"),
|
|
||||||
widget.NewSlider(0.0, 3.0),
|
|
||||||
widget.NewLabel("Saturation:"),
|
|
||||||
widget.NewSlider(0.0, 3.0),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
// Enhancement Section
|
|
||||||
enhanceSection := widget.NewCard("Enhancement", "", container.NewVBox(
|
|
||||||
widget.NewLabel("Sharpen, blur, and denoise"),
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Sharpness:"),
|
|
||||||
widget.NewSlider(0.0, 5.0),
|
|
||||||
widget.NewLabel("Denoise:"),
|
|
||||||
widget.NewSlider(0.0, 10.0),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
// Transform Section
|
|
||||||
transformSection := widget.NewCard("Transform", "", container.NewVBox(
|
|
||||||
widget.NewLabel("Rotate and flip video"),
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Rotation:"),
|
|
||||||
widget.NewSelect([]string{"0°", "90°", "180°", "270°"}, func(s string) {}),
|
|
||||||
widget.NewLabel("Flip Horizontal:"),
|
|
||||||
widget.NewCheck("", func(b bool) { state.filterFlipH = b }),
|
|
||||||
widget.NewLabel("Flip Vertical:"),
|
|
||||||
widget.NewCheck("", func(b bool) { state.filterFlipV = b }),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
|
|
||||||
// Creative Effects Section
|
|
||||||
creativeSection := widget.NewCard("Creative Effects", "", container.NewVBox(
|
|
||||||
widget.NewLabel("Apply artistic effects"),
|
|
||||||
widget.NewCheck("Grayscale", func(b bool) { state.filterGrayscale = b }),
|
|
||||||
))
|
|
||||||
|
|
||||||
// Frame Interpolation Section
|
|
||||||
interpEnabledCheck := widget.NewCheck("Enable Frame Interpolation", func(checked bool) {
|
|
||||||
state.filterInterpEnabled = checked
|
|
||||||
buildFilterChain()
|
|
||||||
})
|
|
||||||
interpEnabledCheck.SetChecked(state.filterInterpEnabled)
|
|
||||||
|
|
||||||
interpPresetSelect := widget.NewSelect([]string{"Ultra Fast", "Fast", "Balanced", "High Quality", "Maximum Quality"}, func(val string) {
|
|
||||||
state.filterInterpPreset = val
|
|
||||||
buildFilterChain()
|
|
||||||
})
|
|
||||||
interpPresetSelect.SetSelected(state.filterInterpPreset)
|
|
||||||
|
|
||||||
interpFPSSelect := widget.NewSelect([]string{"24", "30", "50", "59.94", "60"}, func(val string) {
|
|
||||||
state.filterInterpFPS = val
|
|
||||||
buildFilterChain()
|
|
||||||
})
|
|
||||||
interpFPSSelect.SetSelected(state.filterInterpFPS)
|
|
||||||
|
|
||||||
interpHint := widget.NewLabel("Balanced preset is recommended; higher presets are CPU-intensive.")
|
|
||||||
interpHint.TextStyle = fyne.TextStyle{Italic: true}
|
|
||||||
interpHint.Wrapping = fyne.TextWrapWord
|
|
||||||
|
|
||||||
interpSection := widget.NewCard("Frame Interpolation (Minterpolate)", "", container.NewVBox(
|
|
||||||
widget.NewLabel("Generate smoother motion by interpolating new frames"),
|
|
||||||
interpEnabledCheck,
|
|
||||||
container.NewGridWithColumns(2,
|
|
||||||
widget.NewLabel("Preset:"),
|
|
||||||
interpPresetSelect,
|
|
||||||
widget.NewLabel("Target FPS:"),
|
|
||||||
interpFPSSelect,
|
|
||||||
),
|
|
||||||
interpHint,
|
|
||||||
))
|
|
||||||
buildFilterChain()
|
|
||||||
|
|
||||||
// Apply button
|
|
||||||
applyBtn := widget.NewButton("Apply Filters", func() {
|
|
||||||
if state.filtersFile == nil {
|
|
||||||
dialog.ShowInformation("No Video", "Please load a video first.", state.window)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buildFilterChain()
|
|
||||||
dialog.ShowInformation("Filters", "Filters are now configured and will be applied when sent to Upscale.", state.window)
|
|
||||||
})
|
|
||||||
applyBtn.Importance = widget.HighImportance
|
|
||||||
|
|
||||||
// Main content
|
|
||||||
leftPanel := container.NewVBox(
|
|
||||||
instructions,
|
|
||||||
widget.NewSeparator(),
|
|
||||||
fileLabel,
|
|
||||||
loadBtn,
|
|
||||||
upscaleNavBtn,
|
|
||||||
)
|
|
||||||
|
|
||||||
settingsPanel := container.NewVBox(
|
|
||||||
colorSection,
|
|
||||||
enhanceSection,
|
|
||||||
transformSection,
|
|
||||||
interpSection,
|
|
||||||
creativeSection,
|
|
||||||
applyBtn,
|
|
||||||
)
|
|
||||||
|
|
||||||
settingsScroll := container.NewVScroll(settingsPanel)
|
|
||||||
// Adaptive height for small screens - allow content to flow
|
|
||||||
settingsScroll.SetMinSize(fyne.NewSize(350, 400))
|
|
||||||
|
|
||||||
mainContent := container.New(&fixedHSplitLayout{ratio: 0.6},
|
|
||||||
container.NewVBox(leftPanel, container.NewCenter(videoContainer)),
|
|
||||||
settingsScroll,
|
|
||||||
)
|
|
||||||
|
|
||||||
content := container.NewPadded(mainContent)
|
|
||||||
|
|
||||||
return container.NewBorder(topBar, bottomBar, nil, nil, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildUpscaleView creates the Upscale module UI
|
// buildUpscaleView creates the Upscale module UI
|
||||||
func buildUpscaleView(state *appState) fyne.CanvasObject {
|
func buildUpscaleView(state *appState) fyne.CanvasObject {
|
||||||
upscaleColor := moduleColor("upscale")
|
upscaleColor := moduleColor("upscale")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user