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.
This commit is contained in:
Stu Leak 2025-12-17 13:11:34 -05:00
parent ccd75af936
commit 09de435839

87
main.go
View File

@ -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),