Simplify merge format dropdown with user-friendly options

Added clearer format descriptions:
- "Fast Merge (No Re-encoding)" instead of "MKV (Copy streams)"
- "Lossless MKV (Best Quality)" - new option with slow preset, CRF 18, FLAC audio
- "High Quality MP4 (H.264/H.265)" instead of technical codec names
- "DVD Format" with conditional region/aspect selectors
- "Blu-ray Format" instead of "Blu-ray (H.264)"

DVD Format improvements:
- When "DVD Format" is selected, shows Region (NTSC/PAL) and Aspect (16:9/4:3) options
- Options hidden for other formats
- Stored in state and passed to merge job config
- Updated execution to use DVD region/aspect settings

Maintains backward compatibility with legacy DVD format codes.
This commit is contained in:
Stu Leak 2025-12-17 00:52:26 -05:00
parent f6fe8bb61b
commit f653b2a4ce

114
main.go
View File

@ -617,6 +617,8 @@ type appState struct {
mergeKeepAll bool mergeKeepAll bool
mergeCodecMode string mergeCodecMode string
mergeChapters bool mergeChapters bool
mergeDVDRegion string // "NTSC" or "PAL"
mergeDVDAspect string // "16:9" or "4:3"
// Thumbnail module state // Thumbnail module state
thumbFile *videoSource thumbFile *videoSource
@ -2010,6 +2012,12 @@ func (s *appState) showMergeView() {
if s.mergeFormat == "" { if s.mergeFormat == "" {
s.mergeFormat = "mkv-copy" s.mergeFormat = "mkv-copy"
} }
if s.mergeDVDRegion == "" {
s.mergeDVDRegion = "NTSC"
}
if s.mergeDVDAspect == "" {
s.mergeDVDAspect = "16:9"
}
backBtn := widget.NewButton("< MERGE", func() { backBtn := widget.NewButton("< MERGE", func() {
s.showMainMenu() s.showMainMenu()
@ -2150,22 +2158,22 @@ func (s *appState) showMergeView() {
} }
formatMap := map[string]string{ formatMap := map[string]string{
"MKV (Copy streams)": "mkv-copy", "Fast Merge (No Re-encoding)": "mkv-copy",
"MKV (H.264)": "mkv-h264", "Lossless MKV (Best Quality)": "mkv-lossless",
"MKV (H.265)": "mkv-h265", "High Quality MP4 (H.264)": "mp4-h264",
"MP4 (H.264)": "mp4-h264", "High Quality MP4 (H.265)": "mp4-h265",
"MP4 (H.265)": "mp4-h265", "DVD Format": "dvd",
"DVD NTSC 16:9": "dvd-ntsc-169", "Blu-ray Format": "bd-h264",
"DVD NTSC 4:3": "dvd-ntsc-43",
"DVD PAL 16:9": "dvd-pal-169",
"DVD PAL 4:3": "dvd-pal-43",
"Blu-ray (H.264)": "bd-h264",
} }
var formatKeys []string // Maintain order for dropdown
for k := range formatMap { formatKeys := []string{
formatKeys = append(formatKeys, k) "Fast Merge (No Re-encoding)",
"Lossless MKV (Best Quality)",
"High Quality MP4 (H.264)",
"High Quality MP4 (H.265)",
"DVD Format",
"Blu-ray Format",
} }
slices.Sort(formatKeys)
keepAllCheck := widget.NewCheck("Keep all audio/subtitle tracks", func(v bool) { keepAllCheck := widget.NewCheck("Keep all audio/subtitle tracks", func(v bool) {
s.mergeKeepAll = v s.mergeKeepAll = v
@ -2198,19 +2206,49 @@ func (s *appState) showMergeView() {
} }
} }
// DVD-specific options
dvdRegionSelect := widget.NewSelect([]string{"NTSC", "PAL"}, func(val string) {
s.mergeDVDRegion = val
})
dvdRegionSelect.SetSelected(s.mergeDVDRegion)
dvdAspectSelect := widget.NewSelect([]string{"16:9", "4:3"}, func(val string) {
s.mergeDVDAspect = val
})
dvdAspectSelect.SetSelected(s.mergeDVDAspect)
dvdOptionsRow := container.NewHBox(
widget.NewLabel("Region:"),
dvdRegionSelect,
widget.NewLabel("Aspect:"),
dvdAspectSelect,
)
// Container for DVD options (can be shown/hidden)
dvdOptionsContainer := container.NewVBox(dvdOptionsRow)
// Create format selector (after outputEntry and updateOutputExt are defined) // Create format selector (after outputEntry and updateOutputExt are defined)
formatSelect := widget.NewSelect(formatKeys, func(val string) { formatSelect := widget.NewSelect(formatKeys, func(val string) {
s.mergeFormat = formatMap[val] s.mergeFormat = formatMap[val]
// Show/hide DVD options based on selection
if s.mergeFormat == "dvd" {
dvdOptionsContainer.Show()
} else {
dvdOptionsContainer.Hide()
}
// Set default output path if not set // Set default output path if not set
if s.mergeOutput == "" && len(s.mergeClips) > 0 { if s.mergeOutput == "" && len(s.mergeClips) > 0 {
dir := filepath.Dir(s.mergeClips[0].Path) dir := filepath.Dir(s.mergeClips[0].Path)
ext := getExtForFormat(s.mergeFormat) ext := getExtForFormat(s.mergeFormat)
basename := "merged" basename := "merged"
if strings.HasPrefix(s.mergeFormat, "dvd") { if strings.HasPrefix(s.mergeFormat, "dvd") || s.mergeFormat == "dvd" {
basename = "merged-dvd" basename = "merged-dvd"
} else if strings.HasPrefix(s.mergeFormat, "bd") { } else if strings.HasPrefix(s.mergeFormat, "bd") {
basename = "merged-bd" basename = "merged-bd"
} else if s.mergeFormat == "mkv-lossless" {
basename = "merged-lossless"
} }
s.mergeOutput = filepath.Join(dir, basename+ext) s.mergeOutput = filepath.Join(dir, basename+ext)
outputEntry.SetText(s.mergeOutput) outputEntry.SetText(s.mergeOutput)
@ -2226,6 +2264,13 @@ func (s *appState) showMergeView() {
} }
} }
// Initialize DVD options visibility
if s.mergeFormat == "dvd" {
dvdOptionsContainer.Show()
} else {
dvdOptionsContainer.Hide()
}
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 {
@ -2292,6 +2337,7 @@ func (s *appState) showMergeView() {
widget.NewLabelWithStyle("Output Options", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), widget.NewLabelWithStyle("Output Options", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
widget.NewLabel("Format"), widget.NewLabel("Format"),
formatSelect, formatSelect,
dvdOptionsContainer,
keepAllCheck, keepAllCheck,
chapterCheck, chapterCheck,
widget.NewSeparator(), widget.NewSeparator(),
@ -2358,6 +2404,8 @@ func (s *appState) addMergeToQueue(startNow bool) error {
"chapters": s.mergeChapters, "chapters": s.mergeChapters,
"codecMode": s.mergeCodecMode, "codecMode": s.mergeCodecMode,
"outputPath": s.mergeOutput, "outputPath": s.mergeOutput,
"dvdRegion": s.mergeDVDRegion,
"dvdAspect": s.mergeDVDAspect,
} }
job := &queue.Job{ job := &queue.Job{
@ -2503,7 +2551,35 @@ func (s *appState) executeMergeJob(ctx context.Context, job *queue.Job, progress
// Output profile // Output profile
switch format { switch format {
case "dvd":
// Get DVD-specific settings from config
dvdRegion, _ := cfg["dvdRegion"].(string)
dvdAspect, _ := cfg["dvdAspect"].(string)
if dvdRegion == "" {
dvdRegion = "NTSC"
}
if dvdAspect == "" {
dvdAspect = "16:9"
}
// Force MPEG-2 / AC-3
args = append(args,
"-c:v", "mpeg2video",
"-c:a", "ac3",
"-b:a", "192k",
"-max_muxing_queue_size", "1024",
)
if dvdRegion == "NTSC" {
args = append(args, "-vf", "scale=720:480,setsar=1", "-r", "30000/1001", "-pix_fmt", "yuv420p", "-aspect", dvdAspect)
args = append(args, "-target", "ntsc-dvd")
} else {
args = append(args, "-vf", "scale=720:576,setsar=1", "-r", "25", "-pix_fmt", "yuv420p", "-aspect", dvdAspect)
args = append(args, "-target", "pal-dvd")
}
case "dvd-ntsc-169", "dvd-ntsc-43", "dvd-pal-169", "dvd-pal-43": case "dvd-ntsc-169", "dvd-ntsc-43", "dvd-pal-169", "dvd-pal-43":
// Legacy DVD formats for backward compatibility
// Force MPEG-2 / AC-3 // Force MPEG-2 / AC-3
args = append(args, args = append(args,
"-c:v", "mpeg2video", "-c:v", "mpeg2video",
@ -2549,6 +2625,14 @@ func (s *appState) executeMergeJob(ctx context.Context, job *queue.Job, progress
"-crf", "28", "-crf", "28",
"-c:a", "copy", "-c:a", "copy",
) )
case "mkv-lossless":
// Lossless MKV with best quality settings
args = append(args,
"-c:v", "libx264",
"-preset", "slow",
"-crf", "18",
"-c:a", "flac",
)
case "mp4-h264": case "mp4-h264":
args = append(args, args = append(args,
"-c:v", "libx264", "-c:v", "libx264",