From 2909ef7cea2cc4cd5602ec14b468dd72437dd8e1 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 8 Jun 2025 22:19:28 +0500 Subject: [PATCH] Introduce progress bar updates and queue processing logic Implemented progress bar integration with `ProgressBarContract` for real-time conversion tracking and status updates. Added queue management functionality to process files sequentially with error and completion handling. Extended `ConvertorContract` and `FFmpegContract` to support tracking of running processes and conversion progress. --- internal/application/app.go | 56 +++++++ internal/application/convertor/convertor.go | 37 ++++- internal/application/convertor/progressbar.go | 156 +++++++++++++++++- main.go | 6 +- 4 files changed, 251 insertions(+), 4 deletions(-) diff --git a/internal/application/app.go b/internal/application/app.go index 619a1e4..dc26bf3 100644 --- a/internal/application/app.go +++ b/internal/application/app.go @@ -5,6 +5,7 @@ import ( "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg" + "time" ) type AppContract interface { @@ -16,6 +17,8 @@ type AppContract interface { GetItemsToConvert() convertor.ItemsToConvertContract GetQueueService() convertor.QueueListContract Run() + AfterClosing() + RunConvertor() } type application struct { @@ -79,3 +82,56 @@ func (a *application) GetConvertorService() convertor.ConvertorContract { func (a *application) Run() { a.fyneApp.Run() } + +func (a *application) RunConvertor() { + go func() { + for { + time.Sleep(time.Millisecond * 3000) + queueId, queue := a.queueService.Next() + if queue == nil { + continue + } + + queue.Status = convertor.StatusType(convertor.InProgress) + a.queueService.EventChangeQueue(queueId, queue) + + if a.progressBarService.GetContainer().Hidden { + a.progressBarService.GetContainer().Show() + } + + totalDuration := float64(0) + ffprobe, err := a.ffmpegService.GetFFprobe() + if err == nil { + totalDuration, err = ffprobe.GetTotalDuration(&queue.Setting.FileInput) + if err != nil { + totalDuration = float64(0) + } + } + + progress := a.progressBarService.GetProgressbar( + totalDuration, + queue.Setting.FileInput.Path, + ) + + err = a.convertorService.RunConvert(*queue.Setting, progress) + if err != nil { + queue.Status = convertor.StatusType(convertor.Error) + queue.Error = err + a.queueService.EventChangeQueue(queueId, queue) + a.progressBarService.ProcessEndedWithError(err.Error()) + + continue + } + + queue.Status = convertor.StatusType(convertor.Completed) + a.queueService.EventChangeQueue(queueId, queue) + a.progressBarService.ProcessEndedWithSuccess(&queue.Setting.FileOut) + } + }() +} + +func (a *application) AfterClosing() { + for _, cmd := range a.convertorService.GetRunningProcesses() { + _ = cmd.Process.Kill() + } +} diff --git a/internal/application/convertor/convertor.go b/internal/application/convertor/convertor.go index c36dc56..2cce78b 100644 --- a/internal/application/convertor/convertor.go +++ b/internal/application/convertor/convertor.go @@ -5,25 +5,54 @@ import ( "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor/encoder" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg" "io" + "os/exec" "strings" ) type ConvertorContract interface { + RunConvert(setting ffmpeg.ConvertSetting, progress ffmpeg.ProgressContract) error GetSupportFormats() (encoder.ConvertorFormatsContract, error) + GetRunningProcesses() map[int]*exec.Cmd +} + +type runningProcesses struct { + items map[int]*exec.Cmd + numberOfStarts int } type convertor struct { - ffmpegService ffmpeg.UtilitiesContract + ffmpegService ffmpeg.UtilitiesContract + runningProcesses *runningProcesses } func NewConvertor( ffmpegService ffmpeg.UtilitiesContract, ) ConvertorContract { return &convertor{ - ffmpegService: ffmpegService, + ffmpegService: ffmpegService, + runningProcesses: &runningProcesses{items: map[int]*exec.Cmd{}, numberOfStarts: 0}, } } +func (c *convertor) RunConvert(setting ffmpeg.ConvertSetting, progress ffmpeg.ProgressContract) error { + ffmpegService, err := c.ffmpegService.GetFFmpeg() + if err != nil { + return err + } + + index := c.runningProcesses.numberOfStarts + beforeWait := func(cmd *exec.Cmd) { + c.runningProcesses.numberOfStarts++ + c.runningProcesses.items[index] = cmd + } + + afterWait := func(cmd *exec.Cmd) { + delete(c.runningProcesses.items, index) + } + + return ffmpegService.RunConvert(setting, progress, beforeWait, afterWait) +} + func (c *convertor) GetSupportFormats() (encoder.ConvertorFormatsContract, error) { var err error @@ -52,3 +81,7 @@ func (c *convertor) GetSupportFormats() (encoder.ConvertorFormatsContract, error return formats, err } + +func (c *convertor) GetRunningProcesses() map[int]*exec.Cmd { + return c.runningProcesses.items +} diff --git a/internal/application/convertor/progressbar.go b/internal/application/convertor/progressbar.go index 173e994..30c8155 100644 --- a/internal/application/convertor/progressbar.go +++ b/internal/application/convertor/progressbar.go @@ -1,15 +1,27 @@ package convertor import ( + "bufio" + "errors" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg" + "image/color" + "io" + "regexp" + "strconv" + "strings" ) type ProgressBarContract interface { GetContainer() *fyne.Container + GetProgressbar(totalDuration float64, filePath string) ffmpeg.ProgressContract + ProcessEndedWithError(errorText string) + ProcessEndedWithSuccess(file *ffmpeg.File) } type progressBar struct { @@ -20,9 +32,10 @@ type progressBar struct { messageError *canvas.Text statusMessage *canvas.Text buttonPlay *widget.Button + ffmpegService ffmpeg.UtilitiesContract } -func NewProgressBar() ProgressBarContract { +func NewProgressBar(ffmpegService ffmpeg.UtilitiesContract) ProgressBarContract { label := widget.NewLabel("") progressbar := widget.NewProgressBar() @@ -55,9 +68,150 @@ func NewProgressBar() ProgressBarContract { messageError: messageError, statusMessage: statusMessage, buttonPlay: buttonPlay, + ffmpegService: ffmpegService, } } func (p *progressBar) GetContainer() *fyne.Container { return p.container } + +func (p *progressBar) GetProgressbar(totalDuration float64, filePath string) ffmpeg.ProgressContract { + p.label.Text = filePath + p.statusMessage.Color = theme.Color(theme.ColorNamePrimary) + p.statusMessage.Text = lang.L("inProgressQueue") + p.messageError.Text = "" + fyne.Do(func() { + p.buttonPlay.Hide() + if p.errorBlock.Visible() { + p.errorBlock.Hide() + } + p.statusMessage.Refresh() + p.container.Refresh() + p.errorBlock.Refresh() + }) + + p.progressbar.Value = 0 + return NewProgress(totalDuration, p.progressbar) +} + +func (p *progressBar) ProcessEndedWithError(errorText string) { + fyne.Do(func() { + p.statusMessage.Color = theme.Color(theme.ColorNameError) + p.statusMessage.Text = lang.L("errorQueue") + p.messageError.Text = errorText + p.errorBlock.Show() + }) +} + +func (p *progressBar) ProcessEndedWithSuccess(file *ffmpeg.File) { + fyne.Do(func() { + p.statusMessage.Color = color.RGBA{R: 49, G: 127, B: 114, A: 255} + p.statusMessage.Text = lang.L("completedQueue") + p.buttonPlay.Show() + p.buttonPlay.OnTapped = func() { + p.buttonPlay.Disable() + go func() { + ffplay, err := p.ffmpegService.GetFFplay() + if err == nil { + _ = ffplay.Play(file) + } + + fyne.Do(func() { + p.buttonPlay.Enable() + }) + }() + } + }) +} + +type Progress struct { + totalDuration float64 + progressbar *widget.ProgressBar + protocol string +} + +func NewProgress(totalDuration float64, progressbar *widget.ProgressBar) ffmpeg.ProgressContract { + return &Progress{ + totalDuration: totalDuration, + progressbar: progressbar, + protocol: "pipe:", + } +} + +func (p *Progress) GetProtocole() string { + return p.protocol +} + +func (p *Progress) Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error { + isProcessCompleted := false + var errorText string + + p.progressbar.Value = 0 + p.progressbar.Max = p.totalDuration + fyne.Do(func() { + p.progressbar.Refresh() + }) + progress := 0.0 + + go func() { + scannerErr := bufio.NewReader(stdErr) + for { + line, _, err := scannerErr.ReadLine() + if err != nil { + if err == io.EOF { + break + } + continue + } + data := strings.TrimSpace(string(line)) + errorText = data + } + }() + + scannerOut := bufio.NewReader(stdOut) + for { + line, _, err := scannerOut.ReadLine() + if err != nil { + if err == io.EOF { + break + } + continue + } + data := strings.TrimSpace(string(line)) + if strings.Contains(data, "progress=end") { + p.progressbar.Value = p.totalDuration + fyne.Do(func() { + p.progressbar.Refresh() + }) + isProcessCompleted = true + break + } + + re := regexp.MustCompile(`frame=(\d+)`) + a := re.FindAllStringSubmatch(data, -1) + + if len(a) > 0 && len(a[len(a)-1]) > 0 { + c, err := strconv.Atoi(a[len(a)-1][len(a[len(a)-1])-1]) + if err != nil { + continue + } + progress = float64(c) + } + if p.progressbar.Value != progress { + p.progressbar.Value = progress + fyne.Do(func() { + p.progressbar.Refresh() + }) + } + } + + if isProcessCompleted == false { + if len(errorText) == 0 { + errorText = lang.L("errorConverter") + } + return errors.New(errorText) + } + + return nil +} diff --git a/main.go b/main.go index 9250ddb..4455f47 100644 --- a/main.go +++ b/main.go @@ -20,9 +20,9 @@ func main() { } app.SetMetadata(appMetadata) fyneApp := app.New() - progressBarService := convertor.NewProgressBar() appSetting := setting.NewSetting(fyneApp) ffmpegService := ffmpeg.NewUtilities(appSetting) + progressBarService := convertor.NewProgressBar(ffmpegService) convertorService := convertor.NewConvertor(ffmpegService) itemsToConvert := convertor.NewItemsToConvert(ffmpegService) queue := convertor.NewQueueList() @@ -37,5 +37,9 @@ func main() { ) mainController := controller.NewController(myApp) mainController.Start() + + myApp.RunConvertor() + defer myApp.AfterClosing() + myApp.Run() }