From ea7cfbbf6a101308bd76e7494a721baeb199a640 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Tue, 6 Jan 2026 23:42:14 -0500 Subject: [PATCH] Clamp menu logo scale and add preview --- author_menu.go | 13 ++++++-- author_module.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/author_menu.go b/author_menu.go index 1b2cd6e..919d4cd 100644 --- a/author_menu.go +++ b/author_menu.go @@ -298,7 +298,7 @@ func buildMenuBackground(ctx context.Context, outputPath, title string, buttons logoPath := resolveMenuLogoPath(logo) if logoPath != "" { posExpr := resolveMenuLogoPosition(logo, width, height) - scaleExpr := fmt.Sprintf("scale=iw*%.2f:ih*%.2f", resolveMenuLogoScale(logo), resolveMenuLogoScale(logo)) + scaleExpr := resolveMenuLogoScaleExpr(logo, width, height) args = append(args, "-i", logoPath) filterExpr = fmt.Sprintf("[0:v]%s[bg];[1:v]%s[logo];[bg][logo]overlay=%s", filterChain, scaleExpr, posExpr) } @@ -343,7 +343,7 @@ func buildDarkMenuBackground(ctx context.Context, outputPath, title string, butt logoPath := resolveMenuLogoPath(logo) if logoPath != "" { posExpr := resolveMenuLogoPosition(logo, width, height) - scaleExpr := fmt.Sprintf("scale=iw*%.2f:ih*%.2f", resolveMenuLogoScale(logo), resolveMenuLogoScale(logo)) + scaleExpr := resolveMenuLogoScaleExpr(logo, width, height) args = append(args, "-i", logoPath) filterExpr = fmt.Sprintf("[0:v]%s[bg];[1:v]%s[logo];[bg][logo]overlay=%s", filterChain, scaleExpr, posExpr) } @@ -381,7 +381,7 @@ func buildPosterMenuBackground(ctx context.Context, outputPath, title string, bu logoPath := resolveMenuLogoPath(logo) if logoPath != "" { posExpr := resolveMenuLogoPosition(logo, width, height) - scaleExpr := fmt.Sprintf("scale=iw*%.2f:ih*%.2f", resolveMenuLogoScale(logo), resolveMenuLogoScale(logo)) + scaleExpr := resolveMenuLogoScaleExpr(logo, width, height) args = append(args, "-i", logoPath) filterExpr = fmt.Sprintf("[0:v]scale=%d:%d,%s[bg];[1:v]%s[logo];[bg][logo]overlay=%s", width, height, filterChain, scaleExpr, posExpr) } @@ -573,6 +573,13 @@ func resolveMenuLogoScale(logo menuLogoOptions) float64 { return logo.Scale } +func resolveMenuLogoScaleExpr(logo menuLogoOptions, width, height int) string { + scale := resolveMenuLogoScale(logo) + maxW := float64(width) * 0.25 + maxH := float64(height) * 0.25 + return fmt.Sprintf("scale=w='min(iw*%.2f,%.0f)':h='min(ih*%.2f,%.0f)':force_original_aspect_ratio=decrease", scale, maxW, scale, maxH) +} + func resolveMenuLogoPosition(logo menuLogoOptions, width, height int) string { margin := logo.Margin if margin < 0 { diff --git a/author_module.go b/author_module.go index 5798aac..6e0e205 100644 --- a/author_module.go +++ b/author_module.go @@ -8,6 +8,7 @@ import ( "encoding/xml" "errors" "fmt" + "image" "image/color" "io" "math" @@ -1030,6 +1031,78 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { logoLabel := widget.NewLabel(state.authorMenuLogoPath) logoLabel.Wrapping = fyne.TextWrapWord + logoPreview := canvas.NewImageFromFile("") + logoPreview.FillMode = canvas.ImageFillContain + logoPreview.SetMinSize(fyne.NewSize(220, 120)) + logoPreviewLabel := widget.NewLabel("No logo selected") + logoPreviewLabel.Wrapping = fyne.TextWrapWord + logoPreviewSize := widget.NewLabel("") + logoPreviewSize.Wrapping = fyne.TextWrapWord + + menuPreviewSize := func() (int, int) { + width := 720 + height := 480 + switch strings.ToUpper(strings.TrimSpace(state.authorRegion)) { + case "PAL": + height = 576 + } + return width, height + } + + updateLogoPreview := func() { + if !state.authorMenuLogoEnabled { + logoPreview.Hide() + logoPreviewLabel.SetText("Logo preview disabled") + logoPreviewSize.SetText("") + return + } + + path := state.authorMenuLogoPath + if strings.TrimSpace(path) == "" { + path = filepath.Join("assets", "logo", "VT_Logo.png") + } + + if _, err := os.Stat(path); err != nil { + logoPreview.Hide() + logoPreviewLabel.SetText("Logo file not found") + logoPreviewSize.SetText("") + return + } + + logoPreview.Show() + logoPreviewLabel.SetText(filepath.Base(path)) + logoPreview.File = path + logoPreview.Refresh() + + file, err := os.Open(path) + if err != nil { + logoPreviewSize.SetText("") + return + } + defer file.Close() + + cfg, _, err := image.DecodeConfig(file) + if err != nil { + logoPreviewSize.SetText("") + return + } + + menuW, menuH := menuPreviewSize() + maxW := int(float64(menuW) * 0.25) + maxH := int(float64(menuH) * 0.25) + scale := state.authorMenuLogoScale + targetW := int(math.Round(float64(cfg.Width) * scale)) + targetH := int(math.Round(float64(cfg.Height) * scale)) + if targetW > maxW || targetH > maxH { + ratioW := float64(maxW) / float64(targetW) + ratioH := float64(maxH) / float64(targetH) + ratio := math.Min(ratioW, ratioH) + targetW = int(math.Round(float64(targetW) * ratio)) + targetH = int(math.Round(float64(targetH) * ratio)) + } + + logoPreviewSize.SetText(fmt.Sprintf("Logo size: %dx%d (max %dx%d)", targetW, targetH, maxW, maxH)) + } logoPickButton := widget.NewButton("Select Logo", func() { dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) { if err != nil || reader == nil { @@ -1038,6 +1111,7 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { defer reader.Close() state.authorMenuLogoPath = reader.URI().Path() logoLabel.SetText(state.authorMenuLogoPath) + updateLogoPreview() state.updateAuthorSummary() state.persistAuthorConfig() }, state.window) @@ -1082,6 +1156,7 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { logoScaleSelect := widget.NewSelect(scaleOptions, func(value string) { if scale, ok := scaleValueByLabel[value]; ok { state.authorMenuLogoScale = scale + updateLogoPreview() state.persistAuthorConfig() } }) @@ -1148,7 +1223,11 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { info := widget.NewLabel("DVD menus are generated using the VideoTools theme and IBM Plex Mono. Menu settings apply only to disc authoring.") info.Wrapping = fyne.TextWrapWord - previewBox := buildMenuBox("Preview", widget.NewLabel("Menu preview is generated during authoring.")) + previewBox := buildMenuBox("Logo Preview", container.NewVBox( + logoPreviewLabel, + logoPreview, + logoPreviewSize, + )) menuCore := buildMenuBox("Menu Core", container.NewVBox( createMenuCheck, @@ -1233,11 +1312,13 @@ func buildAuthorMenuTab(state *appState) fyne.CanvasObject { logoEnableCheck.OnChanged = func(checked bool) { state.authorMenuLogoEnabled = checked + updateLogoPreview() updateMenuControls(state.authorCreateMenu) state.updateAuthorSummary() state.persistAuthorConfig() } + updateLogoPreview() updateMenuControls(state.authorCreateMenu) return container.NewPadded(controls)