Compare commits

..

No commits in common. "1a04cab1d6bf06bf248a5acddc485d0b10b4bf85" and "83ad75e04d3d6ca946952001a8ed1f80bc369f4f" have entirely different histories.

110
main.go
View File

@ -301,8 +301,7 @@ func (s *appState) openLogViewer(title, path string, live bool) {
text.Disable()
bg := canvas.NewRectangle(color.NRGBA{0x15, 0x1a, 0x24, 0xff}) // slightly lighter than app bg
scroll := container.NewVScroll(container.NewMax(bg, text))
// Adaptive min size - allows proper scaling on small screens
scroll.SetMinSize(fyne.NewSize(600, 350))
scroll.SetMinSize(fyne.NewSize(700, 450))
stop := make(chan struct{})
if live {
@ -3367,19 +3366,15 @@ func (s *appState) executeSnippetJob(ctx context.Context, job *queue.Job, progre
if useSourceFormat {
// Source format mode: Use stream copy for clean extraction
// Note: This uses keyframe cutting, so duration may not be frame-perfect
// Calculate end time for more accurate cutting
endTime := center + float64(snippetLength)
args = []string{
"-y",
"-hide_banner",
"-loglevel", "error",
"-accurate_seek", // Seek accurately to keyframes
"-ss", start,
"-i", inputPath,
"-to", fmt.Sprintf("%.2f", endTime), // Use -to instead of -t for better accuracy
"-c", "copy", // Stream copy - no re-encoding
"-map", "0", // Include all streams
"-avoid_negative_ts", "make_zero", // Fix timestamp issues
"-t", fmt.Sprintf("%d", snippetLength),
"-c", "copy", // Stream copy - no re-encoding
"-map", "0", // Include all streams
outputPath,
}
} else {
@ -3733,15 +3728,11 @@ func runGUI() {
} else {
logging.Debug(logging.CatUI, "app icon not found; continuing without custom icon")
}
// Adaptive window sizing for professional cross-resolution support
w.SetFixedSize(false) // Allow manual resizing and maximizing
// 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))
// Use a generous default window size that fits typical desktops without overflowing.
w.Resize(fyne.NewSize(1280, 800))
w.SetFixedSize(false) // Allow manual resizing
w.CenterOnScreen()
logging.Debug(logging.CatUI, "window initialized at 1200x700 (fits 1280x768+ screens), manual resizing enabled")
logging.Debug(logging.CatUI, "window initialized with manual resizing and centering enabled")
state := &appState{
window: w,
@ -6087,8 +6078,7 @@ Metadata: %s`,
previewImg := canvas.NewImageFromResource(img)
previewImg.FillMode = canvas.ImageFillContain
// Adaptive size for small screens
previewImg.SetMinSize(fyne.NewSize(640, 360))
previewImg.SetMinSize(fyne.NewSize(800, 450))
infoLabel := widget.NewLabel("Left: Original | Right: Deinterlaced")
infoLabel.Alignment = fyne.TextAlignCenter
@ -6999,11 +6989,10 @@ func (s *appState) handleDrop(pos fyne.Position, items []fyne.URI) {
return
}
// Load all videos into memory (don't auto-queue)
// This allows users to adjust settings or generate snippets before manually queuing
// If multiple videos, add all to queue
if len(videoPaths) > 1 {
logging.Debug(logging.CatUI, "multiple videos dropped in convert module; loading all into memory")
go s.loadMultipleVideos(videoPaths)
logging.Debug(logging.CatUI, "multiple videos dropped in convert module; adding all to queue")
go s.batchAddToQueue(videoPaths)
} else {
// Single video: load it
logging.Debug(logging.CatUI, "single video dropped in convert module; loading: %s", videoPaths[0])
@ -7407,65 +7396,6 @@ func (s *appState) loadVideo(path string) {
}, false)
}
// loadMultipleVideos loads multiple videos into memory without auto-queuing
func (s *appState) loadMultipleVideos(paths []string) {
logging.Debug(logging.CatModule, "loading %d videos into memory", len(paths))
var validVideos []*videoSource
var failedFiles []string
for _, path := range paths {
src, err := probeVideo(path)
if err != nil {
logging.Debug(logging.CatFFMPEG, "ffprobe failed for %s: %v", path, err)
failedFiles = append(failedFiles, filepath.Base(path))
continue
}
validVideos = append(validVideos, src)
}
if len(validVideos) == 0 {
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
msg := fmt.Sprintf("Failed to analyze %d file(s):\n%s", len(failedFiles), strings.Join(failedFiles, ", "))
s.showErrorWithCopy("Load Failed", fmt.Errorf("%s", msg))
}, false)
return
}
// Load all videos into loadedVideos array
s.loadedVideos = validVideos
s.currentIndex = 0
// Load the first video to display
firstVideo := validVideos[0]
if frames, err := capturePreviewFrames(firstVideo.Path, firstVideo.Duration); err == nil {
firstVideo.PreviewFrames = frames
if len(frames) > 0 {
s.currentFrame = frames[0]
}
}
s.applyInverseDefaults(firstVideo)
s.convert.OutputBase = s.resolveOutputBase(firstVideo, false)
if firstVideo.EmbeddedCoverArt != "" {
s.convert.CoverArtPath = firstVideo.EmbeddedCoverArt
} else {
s.convert.CoverArtPath = ""
}
s.convert.AspectHandling = "Auto"
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
msg := fmt.Sprintf("Loaded %d video(s) into memory.\nUse arrow buttons or Convert/Snippet buttons to process.", len(validVideos))
if len(failedFiles) > 0 {
msg += fmt.Sprintf("\n\n%d file(s) failed to analyze:\n%s", len(failedFiles), strings.Join(failedFiles, ", "))
}
dialog.ShowInformation("Videos Loaded", msg, s.window)
s.showConvertView(firstVideo)
}, false)
logging.Debug(logging.CatModule, "loaded %d videos into memory", len(validVideos))
}
func (s *appState) clearVideo() {
logging.Debug(logging.CatModule, "clearing loaded video")
s.stopPlayer()
@ -10333,15 +10263,11 @@ func buildThumbView(state *appState) fyne.CanvasObject {
go func() {
img := canvas.NewImageFromFile(contactSheetPath)
img.FillMode = canvas.ImageFillContain
// Adaptive size for small screens - use scrollable dialog
img.SetMinSize(fyne.NewSize(640, 480))
img.SetMinSize(fyne.NewSize(800, 600))
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
// Wrap in scroll container for large contact sheets
scroll := container.NewScroll(img)
d := dialog.NewCustom("Contact Sheet", "Close", scroll, state.window)
// Adaptive dialog size that fits on 1280x768 screens
d.Resize(fyne.NewSize(700, 600))
d := dialog.NewCustom("Contact Sheet", "Close", img, state.window)
d.Resize(fyne.NewSize(900, 700))
d.Show()
}, false)
}()
@ -10619,8 +10545,7 @@ func buildFiltersView(state *appState) fyne.CanvasObject {
)
settingsScroll := container.NewVScroll(settingsPanel)
// Adaptive height for small screens - allow content to flow
settingsScroll.SetMinSize(fyne.NewSize(350, 400))
settingsScroll.SetMinSize(fyne.NewSize(400, 600))
mainContent := container.NewHSplit(
container.NewVBox(leftPanel, videoContainer),
@ -10925,8 +10850,7 @@ func buildUpscaleView(state *appState) fyne.CanvasObject {
)
settingsScroll := container.NewVScroll(settingsPanel)
// Adaptive height for small screens
settingsScroll.SetMinSize(fyne.NewSize(400, 400))
settingsScroll.SetMinSize(fyne.NewSize(450, 600))
mainContent := container.NewHSplit(
container.NewVBox(leftPanel, videoContainer),