feat(ui): Combine Letterbox/Pillarbox into single smart option + bump to dev21

Aspect Ratio Handling Improvements:
- Combine "Letterbox" and "Pillarbox" into single "Letterbox/Pillarbox" option
- System auto-detects direction based on aspect ratio change
  - 4:3 → 16:9 = adds pillarbox (vertical bars)
  - 16:9 → 4:3 = adds letterbox (horizontal bars)
- Update hint text for clarity: "Crop removes edges, Letterbox/Pillarbox adds black bars to fit"
- Backwards compatibility: legacy "Letterbox"/"Pillarbox" options still work in aspectFilters()

Inspired by Topaz's clear UX for aspect ratio handling.

Version Updates:
- Bump version to v0.1.0-dev21
- Increment build number to 20
- Updates both main.go and FyneApp.toml

Options now available:
- Auto (crops to fit)
- Crop (explicitly crop to target aspect)
- Letterbox/Pillarbox (adds black bars, auto-detects direction)
- Blur Fill (blurred background with original centered)
- Stretch (distorts to fit)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Stu Leak 2026-01-01 19:51:24 -05:00
parent c7ac82f306
commit 168aab1ec8
2 changed files with 18 additions and 8 deletions

View File

@ -2,5 +2,5 @@
Icon = "assets/logo/VT_Icon.png"
Name = "VideoTools"
ID = "com.leaktechnologies.videotools"
Version = "0.1.0-dev20"
Build = 19
Version = "0.1.0-dev21"
Build = 20

22
main.go
View File

@ -71,7 +71,7 @@ var (
logsDirOnce sync.Once
logsDirPath string
feedbackBundler = utils.NewFeedbackBundler()
appVersion = "v0.1.0-dev20"
appVersion = "v0.1.0-dev21"
hwAccelProbeOnce sync.Once
hwAccelSupported atomic.Value // map[string]bool
@ -6932,17 +6932,20 @@ func buildConvertView(state *appState, src *videoSource) fyne.CanvasObject {
// Wrap hint in padded container to ensure proper text wrapping in narrow windows
targetAspectHintContainer := container.NewPadded(targetAspectHint)
aspectOptions := widget.NewRadioGroup([]string{"Auto", "Crop", "Letterbox", "Pillarbox", "Blur Fill", "Stretch"}, func(value string) {
aspectOptions := widget.NewRadioGroup([]string{"Auto", "Crop", "Letterbox/Pillarbox", "Blur Fill", "Stretch"}, func(value string) {
logging.Debug(logging.CatUI, "aspect handling set to %s", value)
state.convert.AspectHandling = value
})
aspectOptions.Horizontal = false
aspectOptions.Required = true
// Map old separate options to new combined option for backwards compatibility
if state.convert.AspectHandling == "Letterbox" || state.convert.AspectHandling == "Pillarbox" {
state.convert.AspectHandling = "Letterbox/Pillarbox"
}
aspectOptions.SetSelected(state.convert.AspectHandling)
aspectOptions.SetSelected(state.convert.AspectHandling)
backgroundHint := widget.NewLabel("Shown when aspect differs; choose padding/fill style.")
backgroundHint := widget.NewLabel("Crop removes edges, Letterbox/Pillarbox adds black bars to fit.")
aspectBox := container.NewVBox(
widget.NewLabelWithStyle("Aspect Handling", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
aspectOptions,
@ -12430,7 +12433,14 @@ func aspectFilters(target float64, mode string) []string {
return []string{filterStr, "setsar=1"}
}
// Letterbox/Pillarbox: keep source resolution, just pad to target aspect with black bars
// Letterbox/Pillarbox: pad with black bars (auto-detects direction based on aspect ratio change)
// Also handles legacy "Letterbox" and "Pillarbox" options for backwards compatibility
if strings.EqualFold(mode, "Letterbox/Pillarbox") || strings.EqualFold(mode, "Letterbox") || strings.EqualFold(mode, "Pillarbox") {
pad := fmt.Sprintf("pad=w='trunc(max(iw,ih*%[1]s)/2)*2':h='trunc(max(ih,iw/%[1]s)/2)*2':x='(ow-iw)/2':y='(oh-ih)/2':color=black", ar)
return []string{pad, "setsar=1"}
}
// Default fallback: same as Letterbox/Pillarbox
pad := fmt.Sprintf("pad=w='trunc(max(iw,ih*%[1]s)/2)*2':h='trunc(max(ih,iw/%[1]s)/2)*2':x='(ow-iw)/2':y='(oh-ih)/2':color=black", ar)
return []string{pad, "setsar=1"}
}