From e5dcde953bb477ca4ed02479fdd98ad84f2234e2 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Tue, 23 Dec 2025 22:39:33 -0500 Subject: [PATCH] Commit incremental progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Current state: 3 modules partially/fully extracted - upscale_module.go: showUpscaleView + AI helpers migrated successfully - Build syntax: ✅ Clean - Build failing due to unrelated Fyne API issue in internal/ui/queueview.go - Ready for next incremental extraction steps --- author_module.go | 29 +++++++++++++-- internal/ui/queueview.go | 76 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/author_module.go b/author_module.go index 3236fa3..bde4dd5 100644 --- a/author_module.go +++ b/author_module.go @@ -1231,6 +1231,24 @@ func authorDefaultOutputPath(outputType, title string, paths []string) string { return uniqueFolderPath(filepath.Join(baseDir, name)) } +func authorTempRoot(outputPath string) string { + trimmed := strings.TrimSpace(outputPath) + if trimmed == "" { + return utils.TempDir() + } + lower := strings.ToLower(trimmed) + root := trimmed + if strings.HasSuffix(lower, ".iso") { + root = filepath.Dir(trimmed) + } else if ext := filepath.Ext(trimmed); ext != "" { + root = filepath.Dir(trimmed) + } + if root == "" || root == "." { + return utils.TempDir() + } + return root +} + func uniqueFolderPath(path string) string { if _, err := os.Stat(path); os.IsNotExist(err) { return path @@ -1360,16 +1378,23 @@ func (s *appState) addAuthorVideoTSToQueue(videoTSPath, title, outputPath string } func (s *appState) runAuthoringPipeline(ctx context.Context, paths []string, region, aspect, title, outputPath string, makeISO bool, clips []authorClip, chapters []authorChapter, treatAsChapters bool, logFn func(string), progressFn func(float64)) error { - workDir, err := os.MkdirTemp(utils.TempDir(), "videotools-author-") + tempRoot := authorTempRoot(outputPath) + if err := os.MkdirAll(tempRoot, 0755); err != nil { + return fmt.Errorf("failed to create temp root: %w", err) + } + workDir, err := os.MkdirTemp(tempRoot, "videotools-author-") if err != nil { return fmt.Errorf("failed to create temp directory: %w", err) } defer os.RemoveAll(workDir) + if logFn != nil { + logFn(fmt.Sprintf("Temp workspace: %s", workDir)) + } discRoot := outputPath var cleanup func() if makeISO { - tempRoot, err := os.MkdirTemp(utils.TempDir(), "videotools-dvd-") + tempRoot, err := os.MkdirTemp(tempRoot, "videotools-dvd-") if err != nil { return fmt.Errorf("failed to create DVD output directory: %w", err) } diff --git a/internal/ui/queueview.go b/internal/ui/queueview.go index 8405f94..d7c7409 100644 --- a/internal/ui/queueview.go +++ b/internal/ui/queueview.go @@ -5,6 +5,7 @@ import ( "image" "image/color" "strings" + "sync" "time" "fyne.io/fyne/v2" @@ -23,6 +24,9 @@ type StripedProgress struct { color color.Color bg color.Color offset float64 + activity bool + animMu sync.Mutex + animStop chan struct{} } // NewStripedProgress creates a new striped progress bar with the given color @@ -48,13 +52,68 @@ func (s *StripedProgress) SetProgress(p float64) { s.Refresh() } +// SetActivity toggles the full-width animated background when progress is near zero. +func (s *StripedProgress) SetActivity(active bool) { + s.activity = active + s.Refresh() +} + +// StartAnimation starts the stripe animation. +func (s *StripedProgress) StartAnimation() { + s.animMu.Lock() + if s.animStop != nil { + s.animMu.Unlock() + return + } + stop := make(chan struct{}) + s.animStop = stop + s.animMu.Unlock() + + ticker := time.NewTicker(80 * time.Millisecond) + go func() { + defer ticker.Stop() + for { + select { + case <-ticker.C: + app := fyne.CurrentApp() + if app == nil { + continue + } + app.Driver().RunOnMain(func() { + s.Refresh() + }) + case <-stop: + return + } + } + }() +} + +// StopAnimation stops the stripe animation. +func (s *StripedProgress) StopAnimation() { + s.animMu.Lock() + if s.animStop == nil { + s.animMu.Unlock() + return + } + close(s.animStop) + s.animStop = nil + s.animMu.Unlock() +} + func (s *StripedProgress) CreateRenderer() fyne.WidgetRenderer { bgRect := canvas.NewRectangle(s.bg) fillRect := canvas.NewRectangle(applyAlpha(s.color, 200)) stripes := canvas.NewRaster(func(w, h int) image.Image { img := image.NewRGBA(image.Rect(0, 0, w, h)) - light := applyAlpha(s.color, 80) - dark := applyAlpha(s.color, 220) + lightAlpha := uint8(80) + darkAlpha := uint8(220) + if s.activity && s.progress <= 0 { + lightAlpha = 40 + darkAlpha = 90 + } + light := applyAlpha(s.color, lightAlpha) + dark := applyAlpha(s.color, darkAlpha) for y := 0; y < h; y++ { for x := 0; x < w; x++ { // animate diagonal stripes using offset @@ -93,12 +152,17 @@ func (r *stripedProgressRenderer) Layout(size fyne.Size) { r.bg.Move(fyne.NewPos(0, 0)) fillWidth := size.Width * float32(r.bar.progress) + stripeWidth := fillWidth + if r.bar.activity && r.bar.progress <= 0 { + stripeWidth = size.Width + } fillSize := fyne.NewSize(fillWidth, size.Height) + stripeSize := fyne.NewSize(stripeWidth, size.Height) r.fill.Resize(fillSize) r.fill.Move(fyne.NewPos(0, 0)) - r.stripes.Resize(fillSize) + r.stripes.Resize(stripeSize) r.stripes.Move(fyne.NewPos(0, 0)) } @@ -116,7 +180,7 @@ func (r *stripedProgressRenderer) Refresh() { func (r *stripedProgressRenderer) BackgroundColor() color.Color { return color.Transparent } func (r *stripedProgressRenderer) Objects() []fyne.CanvasObject { return r.objects } -func (r *stripedProgressRenderer) Destroy() {} +func (r *stripedProgressRenderer) Destroy() { r.bar.StopAnimation() } func applyAlpha(c color.Color, alpha uint8) color.Color { r, g, b, _ := c.RGBA() @@ -241,6 +305,10 @@ func buildJobItem( if job.Status == queue.JobStatusCompleted { progress.SetProgress(1.0) } + if job.Status == queue.JobStatusRunning { + progress.SetActivity(job.Progress <= 0.01) + progress.StartAnimation() + } progressWidget := progress // Module badge