Add frame rate controls to merge and convert simple mode
- Add mergeFrameRate and mergeMotionInterpolation fields to appState - Add frame rate dropdown and motion interpolation checkbox to merge UI - Pass frame rate settings through merge job config - Implement frame rate conversion in executeMergeJob (for non-DVD formats) - Add frame rate controls to convert module's simple mode Frame rate conversion with optional motion interpolation is now available in: - Convert module (simple and advanced modes) - Merge module - Upscale module All modules support both simple fps conversion (fast) and motion interpolation (slower, smoother) for professional frame rate standardization.
This commit is contained in:
parent
09de435839
commit
34e613859d
77
main.go
77
main.go
|
|
@ -632,14 +632,16 @@ type appState struct {
|
||||||
autoCompare bool // Auto-load Compare module after conversion
|
autoCompare bool // Auto-load Compare module after conversion
|
||||||
|
|
||||||
// Merge state
|
// Merge state
|
||||||
mergeClips []mergeClip
|
mergeClips []mergeClip
|
||||||
mergeFormat string
|
mergeFormat string
|
||||||
mergeOutput string
|
mergeOutput string
|
||||||
mergeKeepAll bool
|
mergeKeepAll bool
|
||||||
mergeCodecMode string
|
mergeCodecMode string
|
||||||
mergeChapters bool
|
mergeChapters bool
|
||||||
mergeDVDRegion string // "NTSC" or "PAL"
|
mergeDVDRegion string // "NTSC" or "PAL"
|
||||||
mergeDVDAspect string // "16:9" or "4:3"
|
mergeDVDAspect string // "16:9" or "4:3"
|
||||||
|
mergeFrameRate string // Source, 24, 30, 60, or custom
|
||||||
|
mergeMotionInterpolation bool // Use motion interpolation for frame rate changes
|
||||||
|
|
||||||
// Thumbnail module state
|
// Thumbnail module state
|
||||||
thumbFile *videoSource
|
thumbFile *videoSource
|
||||||
|
|
@ -2055,6 +2057,9 @@ func (s *appState) showMergeView() {
|
||||||
if s.mergeDVDAspect == "" {
|
if s.mergeDVDAspect == "" {
|
||||||
s.mergeDVDAspect = "16:9"
|
s.mergeDVDAspect = "16:9"
|
||||||
}
|
}
|
||||||
|
if s.mergeFrameRate == "" {
|
||||||
|
s.mergeFrameRate = "Source"
|
||||||
|
}
|
||||||
|
|
||||||
backBtn := widget.NewButton("< MERGE", func() {
|
backBtn := widget.NewButton("< MERGE", func() {
|
||||||
s.showMainMenu()
|
s.showMainMenu()
|
||||||
|
|
@ -2308,6 +2313,23 @@ func (s *appState) showMergeView() {
|
||||||
dvdOptionsContainer.Hide()
|
dvdOptionsContainer.Hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frame Rate controls
|
||||||
|
frameRateSelect := widget.NewSelect([]string{"Source", "23.976", "24", "25", "29.97", "30", "50", "59.94", "60"}, func(val string) {
|
||||||
|
s.mergeFrameRate = val
|
||||||
|
})
|
||||||
|
frameRateSelect.SetSelected(s.mergeFrameRate)
|
||||||
|
|
||||||
|
motionInterpCheck := widget.NewCheck("Use Motion Interpolation (slower, smoother)", func(checked bool) {
|
||||||
|
s.mergeMotionInterpolation = checked
|
||||||
|
})
|
||||||
|
motionInterpCheck.SetChecked(s.mergeMotionInterpolation)
|
||||||
|
|
||||||
|
frameRateRow := container.NewVBox(
|
||||||
|
widget.NewLabel("Frame Rate"),
|
||||||
|
frameRateSelect,
|
||||||
|
motionInterpCheck,
|
||||||
|
)
|
||||||
|
|
||||||
browseOut := widget.NewButton("Browse", func() {
|
browseOut := widget.NewButton("Browse", func() {
|
||||||
dialog.ShowFileSave(func(writer fyne.URIWriteCloser, err error) {
|
dialog.ShowFileSave(func(writer fyne.URIWriteCloser, err error) {
|
||||||
if err != nil || writer == nil {
|
if err != nil || writer == nil {
|
||||||
|
|
@ -2375,6 +2397,9 @@ func (s *appState) showMergeView() {
|
||||||
widget.NewLabel("Format"),
|
widget.NewLabel("Format"),
|
||||||
formatSelect,
|
formatSelect,
|
||||||
dvdOptionsContainer,
|
dvdOptionsContainer,
|
||||||
|
widget.NewSeparator(),
|
||||||
|
frameRateRow,
|
||||||
|
widget.NewSeparator(),
|
||||||
keepAllCheck,
|
keepAllCheck,
|
||||||
chapterCheck,
|
chapterCheck,
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
|
|
@ -2435,14 +2460,16 @@ func (s *appState) addMergeToQueue(startNow bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := map[string]interface{}{
|
config := map[string]interface{}{
|
||||||
"clips": clips,
|
"clips": clips,
|
||||||
"format": s.mergeFormat,
|
"format": s.mergeFormat,
|
||||||
"keepAllStreams": s.mergeKeepAll,
|
"keepAllStreams": s.mergeKeepAll,
|
||||||
"chapters": s.mergeChapters,
|
"chapters": s.mergeChapters,
|
||||||
"codecMode": s.mergeCodecMode,
|
"codecMode": s.mergeCodecMode,
|
||||||
"outputPath": s.mergeOutput,
|
"outputPath": s.mergeOutput,
|
||||||
"dvdRegion": s.mergeDVDRegion,
|
"dvdRegion": s.mergeDVDRegion,
|
||||||
"dvdAspect": s.mergeDVDAspect,
|
"dvdAspect": s.mergeDVDAspect,
|
||||||
|
"frameRate": s.mergeFrameRate,
|
||||||
|
"useMotionInterpolation": s.mergeMotionInterpolation,
|
||||||
}
|
}
|
||||||
|
|
||||||
job := &queue.Job{
|
job := &queue.Job{
|
||||||
|
|
@ -2724,6 +2751,21 @@ func (s *appState) executeMergeJob(ctx context.Context, job *queue.Job, progress
|
||||||
args = append(args, "-c", "copy")
|
args = append(args, "-c", "copy")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frame rate handling (for non-DVD formats that don't lock frame rate)
|
||||||
|
frameRate, _ := cfg["frameRate"].(string)
|
||||||
|
useMotionInterp, _ := cfg["useMotionInterpolation"].(bool)
|
||||||
|
if frameRate != "" && frameRate != "Source" && format != "dvd" && !strings.HasPrefix(format, "dvd-") {
|
||||||
|
// Build frame rate filter
|
||||||
|
var frFilter string
|
||||||
|
if useMotionInterp {
|
||||||
|
frFilter = fmt.Sprintf("minterpolate=fps=%s:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1", frameRate)
|
||||||
|
} else {
|
||||||
|
frFilter = "fps=" + frameRate
|
||||||
|
}
|
||||||
|
// Add as separate filter
|
||||||
|
args = append(args, "-vf", frFilter)
|
||||||
|
}
|
||||||
|
|
||||||
// Add progress output for live updates (must be before output path)
|
// Add progress output for live updates (must be before output path)
|
||||||
args = append(args, "-progress", "pipe:1", "-nostats")
|
args = append(args, "-progress", "pipe:1", "-nostats")
|
||||||
|
|
||||||
|
|
@ -5458,6 +5500,9 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
|
||||||
simpleBitrateSelect,
|
simpleBitrateSelect,
|
||||||
widget.NewLabelWithStyle("Target Resolution", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Target Resolution", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
resolutionSelectSimple,
|
resolutionSelectSimple,
|
||||||
|
widget.NewLabelWithStyle("Frame Rate", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
|
frameRateSelect,
|
||||||
|
motionInterpCheck,
|
||||||
widget.NewLabelWithStyle("Target Aspect Ratio", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
widget.NewLabelWithStyle("Target Aspect Ratio", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
targetAspectSelectSimple,
|
targetAspectSelectSimple,
|
||||||
targetAspectHint,
|
targetAspectHint,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user