From 09de435839689886fe783107565c6d60d1ad4ca0 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Wed, 17 Dec 2025 13:11:34 -0500 Subject: [PATCH] Add frame rate control to upscale module - Add upscaleFrameRate and upscaleMotionInterpolation fields to appState - Add Frame Rate section to upscale UI with dropdown and motion interpolation checkbox - Pass frame rate settings through upscale job config - Implement frame rate conversion in executeUpscaleJob using minterpolate or fps filter - Frame rate section appears after resolution selection in upscale settings Frame rate control is now available in both convert and upscale modules, allowing users to standardize content from different regions with optional motion interpolation for smooth conversion. --- main.go | 87 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/main.go b/main.go index bc21d5e..d42e796 100644 --- a/main.go +++ b/main.go @@ -667,16 +667,18 @@ type appState struct { filterActiveChain []string // Active filter chain // Upscale module state - upscaleFile *videoSource - upscaleMethod string // lanczos, bicubic, spline, bilinear - upscaleTargetRes string // 720p, 1080p, 1440p, 4K, 8K, Custom - upscaleCustomWidth int // For custom resolution - upscaleCustomHeight int // For custom resolution - upscaleAIEnabled bool // Use AI upscaling if available - upscaleAIModel string // realesrgan, realesrgan-anime, none - upscaleAIAvailable bool // Runtime detection - upscaleApplyFilters bool // Apply filters from Filters module - upscaleFilterChain []string // Transferred filters from Filters module + upscaleFile *videoSource + upscaleMethod string // lanczos, bicubic, spline, bilinear + upscaleTargetRes string // 720p, 1080p, 1440p, 4K, 8K, Custom + upscaleCustomWidth int // For custom resolution + upscaleCustomHeight int // For custom resolution + upscaleAIEnabled bool // Use AI upscaling if available + upscaleAIModel string // realesrgan, realesrgan-anime, none + upscaleAIAvailable bool // Runtime detection + upscaleApplyFilters bool // Apply filters from Filters module + upscaleFilterChain []string // Transferred filters from Filters module + upscaleFrameRate string // Source, 24, 30, 60, or custom + upscaleMotionInterpolation bool // Use motion interpolation for frame rate changes // Snippet settings snippetLength int // Length of snippet in seconds (default: 20) @@ -3728,6 +3730,8 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre } // useAI := cfg["useAI"].(bool) // TODO: Implement AI upscaling in future applyFilters := cfg["applyFilters"].(bool) + frameRate, _ := cfg["frameRate"].(string) + useMotionInterp, _ := cfg["useMotionInterpolation"].(bool) if progressCallback != nil { progressCallback(0) @@ -3751,6 +3755,17 @@ func (s *appState) executeUpscaleJob(ctx context.Context, job *queue.Job, progre scaleFilter := buildUpscaleFilter(targetWidth, targetHeight, method, preserveAR) filters = append(filters, scaleFilter) + // Add frame rate conversion if requested + if frameRate != "" && frameRate != "Source" { + if useMotionInterp { + // Use motion interpolation for smooth frame rate changes + filters = append(filters, fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1", frameRate)) + } else { + // Simple frame rate change (duplicates/drops frames) + filters = append(filters, "fps="+frameRate) + } + } + // Combine filters var vfilter string if len(filters) > 0 { @@ -11022,6 +11037,9 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { if state.upscaleAIModel == "" { state.upscaleAIModel = "realesrgan" // General purpose AI model } + if state.upscaleFrameRate == "" { + state.upscaleFrameRate = "Source" + } // Check AI availability on first load if !state.upscaleAIAvailable { @@ -11136,6 +11154,30 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { sourceResLabel, )) + // 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) { + state.upscaleFrameRate = s + frameRateLabel.SetText(fmt.Sprintf("Frame Rate: %s", s)) + }) + frameRateSelect.SetSelected(state.upscaleFrameRate) + + motionInterpCheck := widget.NewCheck("Use Motion Interpolation (slower, smoother)", func(checked bool) { + state.upscaleMotionInterpolation = checked + }) + motionInterpCheck.SetChecked(state.upscaleMotionInterpolation) + + frameRateSection := widget.NewCard("Frame Rate", "", container.NewVBox( + widget.NewLabel("Convert frame rate (optional)"), + container.NewGridWithColumns(2, + widget.NewLabel("Target FPS:"), + frameRateSelect, + ), + frameRateLabel, + motionInterpCheck, + widget.NewLabel("Motion interpolation creates smooth in-between frames"), + )) + // AI Upscaling Section var aiSection *widget.Card if state.upscaleAIAvailable { @@ -11225,17 +11267,19 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { Description: desc, OutputFile: outputPath, Config: map[string]interface{}{ - "inputPath": state.upscaleFile.Path, - "outputPath": outputPath, - "method": state.upscaleMethod, - "targetWidth": float64(targetWidth), - "targetHeight": float64(targetHeight), - "preserveAR": preserveAspect, - "useAI": state.upscaleAIEnabled && state.upscaleAIAvailable, - "aiModel": state.upscaleAIModel, - "applyFilters": state.upscaleApplyFilters, - "filterChain": state.upscaleFilterChain, - "duration": state.upscaleFile.Duration, + "inputPath": state.upscaleFile.Path, + "outputPath": outputPath, + "method": state.upscaleMethod, + "targetWidth": float64(targetWidth), + "targetHeight": float64(targetHeight), + "preserveAR": preserveAspect, + "useAI": state.upscaleAIEnabled && state.upscaleAIAvailable, + "aiModel": state.upscaleAIModel, + "applyFilters": state.upscaleApplyFilters, + "filterChain": state.upscaleFilterChain, + "duration": state.upscaleFile.Duration, + "frameRate": state.upscaleFrameRate, + "useMotionInterpolation": state.upscaleMotionInterpolation, }, }, nil } @@ -11284,6 +11328,7 @@ func buildUpscaleView(state *appState) fyne.CanvasObject { settingsPanel := container.NewVBox( traditionalSection, resolutionSection, + frameRateSection, aiSection, filterIntegrationSection, container.NewGridWithColumns(2, applyBtn, addQueueBtn),