Compare commits

..

No commits in common. "2761d35ed628e11500798b5eead9b2ea90060157" and "19f2922366aa7ef940fb59a63a379932784b7d27" have entirely different histories.

7 changed files with 44 additions and 127 deletions

View File

@ -768,10 +768,6 @@ This file tracks completed features, fixes, and milestones.
- ✅ Ranked benchmark results by score and added cancel confirmation
- ✅ Added estimated audio bitrate fallback when metadata is missing
- ✅ Made target file size input unit-selectable with numeric-only entry
- ✅ Prevented snippet runaway bitrates when using Match Source Format
- ✅ History sidebar refreshes when jobs complete (snippet entries now appear)
- ✅ Benchmark errors now show non-blocking notifications instead of OK popups
- ✅ Fixed stats bar updates to run on the UI thread to avoid Fyne warnings
- ✅ Stabilized video seeking and embedded rendering
- ✅ Improved player window positioning
- ✅ Fixed clear video functionality

View File

@ -1,6 +0,0 @@
[Details]
Icon = "assets/logo/VT_Icon.png"
Name = "VideoTools"
ID = "com.leaktechnologies.videotools"
Version = "0.1.0-dev19"
Build = 19

View File

@ -43,9 +43,6 @@ This file tracks upcoming features, improvements, and known issues.
- Lossless + Target Size mode support
- Audio bitrate estimation when metadata is missing
- Target size unit selector and numeric entry
- Snippet history updates in sidebar
- Non-blocking benchmark error notifications
- Stats bar updates run on the UI thread
## Priority Features for dev20+

View File

@ -1,10 +0,0 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=VideoTools
Comment=Video conversion and processing tool
Exec=/home/stu/Projects/VideoTools/VideoTools
Icon=/home/stu/Projects/VideoTools/assets/logo/VT_Icon.png
Terminal=false
Categories=AudioVideo;Video;
StartupWMClass=VideoTools

View File

@ -468,44 +468,29 @@ func NewConversionStatsBar(onTapped func()) *ConversionStatsBar {
// UpdateStats updates the stats display
func (c *ConversionStatsBar) UpdateStats(running, pending, completed, failed, cancelled int, progress float64, jobTitle string) {
c.updateStats(func() {
c.running = running
c.pending = pending
c.completed = completed
c.failed = failed
c.cancelled = cancelled
c.progress = progress
c.jobTitle = jobTitle
})
c.running = running
c.pending = pending
c.completed = completed
c.failed = failed
c.cancelled = cancelled
c.progress = progress
c.jobTitle = jobTitle
c.Refresh()
}
// UpdateStatsWithDetails updates the stats display with detailed conversion info
func (c *ConversionStatsBar) UpdateStatsWithDetails(running, pending, completed, failed, cancelled int, progress, fps, speed float64, eta, jobTitle string) {
c.updateStats(func() {
c.running = running
c.pending = pending
c.completed = completed
c.failed = failed
c.cancelled = cancelled
c.progress = progress
c.fps = fps
c.speed = speed
c.eta = eta
c.jobTitle = jobTitle
})
}
func (c *ConversionStatsBar) updateStats(update func()) {
app := fyne.CurrentApp()
if app == nil || app.Driver() == nil {
update()
c.Refresh()
return
}
app.Driver().DoFromGoroutine(func() {
update()
c.Refresh()
}, false)
c.running = running
c.pending = pending
c.completed = completed
c.failed = failed
c.cancelled = cancelled
c.progress = progress
c.fps = fps
c.speed = speed
c.eta = eta
c.jobTitle = jobTitle
c.Refresh()
}
// CreateRenderer creates the renderer for the stats bar

View File

@ -240,23 +240,13 @@ func MakeIconButton(symbol, tooltip string, tapped func()) *widget.Button {
// LoadAppIcon loads the application icon from standard locations
func LoadAppIcon() fyne.Resource {
// Try PNG first (better compatibility), then SVG
iconFiles := []string{"VT_Icon.png", "VT_Icon.svg"}
var search []string
// Search in current directory first
for _, iconFile := range iconFiles {
search = append(search, filepath.Join("assets", "logo", iconFile))
search := []string{
filepath.Join("assets", "logo", "VT_Icon.svg"),
}
// Then search relative to executable
if exe, err := os.Executable(); err == nil {
dir := filepath.Dir(exe)
for _, iconFile := range iconFiles {
search = append(search, filepath.Join(dir, "assets", "logo", iconFile))
}
search = append(search, filepath.Join(dir, "assets", "logo", "VT_Icon.svg"))
}
for _, p := range search {
if _, err := os.Stat(p); err == nil {
res, err := fyne.LoadResourceFromPath(p)
@ -264,10 +254,8 @@ func LoadAppIcon() fyne.Resource {
logging.Debug(logging.CatUI, "failed to load icon %s: %v", p, err)
continue
}
logging.Debug(logging.CatUI, "loaded app icon from %s", p)
return res
}
}
logging.Debug(logging.CatUI, "no app icon found in search paths")
return nil
}

77
main.go
View File

@ -1789,10 +1789,7 @@ func (s *appState) showBenchmark() {
return
}
logging.Debug(logging.CatSystem, "failed to generate test video: %v", err)
fyne.CurrentApp().SendNotification(&fyne.Notification{
Title: "Benchmark Error",
Content: fmt.Sprintf("Failed to generate test video: %v", err),
})
dialog.ShowError(fmt.Errorf("failed to generate test video: %w", err), s.window)
s.showMainMenu()
return
}
@ -1815,10 +1812,7 @@ func (s *appState) showBenchmark() {
return
}
logging.Debug(logging.CatSystem, "benchmark failed: %v", err)
fyne.CurrentApp().SendNotification(&fyne.Notification{
Title: "Benchmark Error",
Content: fmt.Sprintf("Benchmark failed: %v", err),
})
dialog.ShowError(fmt.Errorf("benchmark failed: %w", err), s.window)
s.showMainMenu()
return
}
@ -4095,48 +4089,27 @@ func (s *appState) executeSnippetJob(ctx context.Context, job *queue.Job, progre
args = append(args, "-b:a", "192k")
}
} else {
// For non-WMV: match source codec where possible, but cap bitrate for snippets
videoCodec := strings.ToLower(strings.TrimSpace(src.VideoCodec))
switch {
case strings.Contains(videoCodec, "264"):
videoCodec = "libx264"
case strings.Contains(videoCodec, "265"), strings.Contains(videoCodec, "hevc"):
videoCodec = "libx265"
case strings.Contains(videoCodec, "vp9"):
videoCodec = "libvpx-vp9"
case strings.Contains(videoCodec, "av1"):
videoCodec = "libsvtav1"
default:
// For non-WMV: use source codec or fallback to H.264
videoCodec := src.VideoCodec
if videoCodec == "" || strings.Contains(strings.ToLower(videoCodec), "wmv") {
videoCodec = "libx264"
}
args = append(args, "-c:v", videoCodec)
preset := conv.EncoderPreset
if preset == "" {
preset = "slow"
}
crfVal := conv.CRF
if crfVal == "" {
crfVal = "18"
}
if strings.TrimSpace(crfVal) == "0" {
crfVal = "18"
}
targetBitrate := clampSnippetBitrate(strings.TrimSpace(conv.VideoBitrate), src.Width)
if targetBitrate == "" {
targetBitrate = clampSnippetBitrate(defaultBitrate(conv.VideoCodec, src.Width, src.Bitrate), src.Width)
}
if targetBitrate == "" {
targetBitrate = clampSnippetBitrate("3500k", src.Width)
}
if strings.Contains(videoCodec, "x264") || strings.Contains(videoCodec, "x265") {
args = append(args, "-preset", preset, "-crf", crfVal, "-maxrate", targetBitrate, "-bufsize", targetBitrate)
} else if strings.Contains(videoCodec, "vp9") || strings.Contains(videoCodec, "av1") {
args = append(args, "-crf", crfVal, "-maxrate", targetBitrate, "-bufsize", targetBitrate)
// Apply encoder preset if supported codec
if strings.Contains(strings.ToLower(videoCodec), "264") ||
strings.Contains(strings.ToLower(videoCodec), "265") {
if conv.EncoderPreset != "" {
args = append(args, "-preset", conv.EncoderPreset)
} else {
args = append(args, "-preset", "slow")
}
if conv.CRF != "" {
args = append(args, "-crf", conv.CRF)
} else {
args = append(args, "-crf", "18")
}
}
// Audio codec
@ -4844,12 +4817,12 @@ func runGUI() {
// Adaptive window sizing for professional cross-resolution support
w.SetFixedSize(false) // Allow manual resizing and maximizing
// Use compact default size (800x600) that fits on any screen
// Window can be resized or maximized by user using window manager controls
w.Resize(fyne.NewSize(800, 600))
// Use conservative default size that fits on small laptop screens (1280x768)
// Window can be maximized by user using window manager controls
w.Resize(fyne.NewSize(1200, 700))
w.CenterOnScreen()
logging.Debug(logging.CatUI, "window initialized at 800x600 (compact default), manual resizing enabled")
logging.Debug(logging.CatUI, "window initialized at 1200x700 (fits 1280x768+ screens), manual resizing enabled")
state := &appState{
window: w,
@ -4934,7 +4907,6 @@ func runGUI() {
return
}
app.Driver().DoFromGoroutine(func() {
historyCount := len(state.historyEntries)
// Add completed jobs to history
jobs := state.jobQueue.List()
for _, job := range jobs {
@ -4950,11 +4922,6 @@ func runGUI() {
if state.active == "queue" {
state.refreshQueueView()
}
if state.active == "mainmenu" && state.sidebarVisible && len(state.historyEntries) != historyCount {
state.navigationHistorySuppress = true
state.showMainMenu()
state.navigationHistorySuppress = false
}
}, false)
})