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.
This commit is contained in:
Leonid Nikitin 2025-06-08 22:19:28 +05:00
parent 1b1cdd5c22
commit 2909ef7cea
Signed by: kor-elf
GPG Key ID: DAB5355A11C22541
4 changed files with 251 additions and 4 deletions

View File

@ -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/convertor"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
"time"
) )
type AppContract interface { type AppContract interface {
@ -16,6 +17,8 @@ type AppContract interface {
GetItemsToConvert() convertor.ItemsToConvertContract GetItemsToConvert() convertor.ItemsToConvertContract
GetQueueService() convertor.QueueListContract GetQueueService() convertor.QueueListContract
Run() Run()
AfterClosing()
RunConvertor()
} }
type application struct { type application struct {
@ -79,3 +82,56 @@ func (a *application) GetConvertorService() convertor.ConvertorContract {
func (a *application) Run() { func (a *application) Run() {
a.fyneApp.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()
}
}

View File

@ -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/application/convertor/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
"io" "io"
"os/exec"
"strings" "strings"
) )
type ConvertorContract interface { type ConvertorContract interface {
RunConvert(setting ffmpeg.ConvertSetting, progress ffmpeg.ProgressContract) error
GetSupportFormats() (encoder.ConvertorFormatsContract, error) GetSupportFormats() (encoder.ConvertorFormatsContract, error)
GetRunningProcesses() map[int]*exec.Cmd
}
type runningProcesses struct {
items map[int]*exec.Cmd
numberOfStarts int
} }
type convertor struct { type convertor struct {
ffmpegService ffmpeg.UtilitiesContract ffmpegService ffmpeg.UtilitiesContract
runningProcesses *runningProcesses
} }
func NewConvertor( func NewConvertor(
ffmpegService ffmpeg.UtilitiesContract, ffmpegService ffmpeg.UtilitiesContract,
) ConvertorContract { ) ConvertorContract {
return &convertor{ 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) { func (c *convertor) GetSupportFormats() (encoder.ConvertorFormatsContract, error) {
var err error var err error
@ -52,3 +81,7 @@ func (c *convertor) GetSupportFormats() (encoder.ConvertorFormatsContract, error
return formats, err return formats, err
} }
func (c *convertor) GetRunningProcesses() map[int]*exec.Cmd {
return c.runningProcesses.items
}

View File

@ -1,15 +1,27 @@
package convertor package convertor
import ( import (
"bufio"
"errors"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "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 { type ProgressBarContract interface {
GetContainer() *fyne.Container GetContainer() *fyne.Container
GetProgressbar(totalDuration float64, filePath string) ffmpeg.ProgressContract
ProcessEndedWithError(errorText string)
ProcessEndedWithSuccess(file *ffmpeg.File)
} }
type progressBar struct { type progressBar struct {
@ -20,9 +32,10 @@ type progressBar struct {
messageError *canvas.Text messageError *canvas.Text
statusMessage *canvas.Text statusMessage *canvas.Text
buttonPlay *widget.Button buttonPlay *widget.Button
ffmpegService ffmpeg.UtilitiesContract
} }
func NewProgressBar() ProgressBarContract { func NewProgressBar(ffmpegService ffmpeg.UtilitiesContract) ProgressBarContract {
label := widget.NewLabel("") label := widget.NewLabel("")
progressbar := widget.NewProgressBar() progressbar := widget.NewProgressBar()
@ -55,9 +68,150 @@ func NewProgressBar() ProgressBarContract {
messageError: messageError, messageError: messageError,
statusMessage: statusMessage, statusMessage: statusMessage,
buttonPlay: buttonPlay, buttonPlay: buttonPlay,
ffmpegService: ffmpegService,
} }
} }
func (p *progressBar) GetContainer() *fyne.Container { func (p *progressBar) GetContainer() *fyne.Container {
return p.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
}

View File

@ -20,9 +20,9 @@ func main() {
} }
app.SetMetadata(appMetadata) app.SetMetadata(appMetadata)
fyneApp := app.New() fyneApp := app.New()
progressBarService := convertor.NewProgressBar()
appSetting := setting.NewSetting(fyneApp) appSetting := setting.NewSetting(fyneApp)
ffmpegService := ffmpeg.NewUtilities(appSetting) ffmpegService := ffmpeg.NewUtilities(appSetting)
progressBarService := convertor.NewProgressBar(ffmpegService)
convertorService := convertor.NewConvertor(ffmpegService) convertorService := convertor.NewConvertor(ffmpegService)
itemsToConvert := convertor.NewItemsToConvert(ffmpegService) itemsToConvert := convertor.NewItemsToConvert(ffmpegService)
queue := convertor.NewQueueList() queue := convertor.NewQueueList()
@ -37,5 +37,9 @@ func main() {
) )
mainController := controller.NewController(myApp) mainController := controller.NewController(myApp)
mainController.Start() mainController.Start()
myApp.RunConvertor()
defer myApp.AfterClosing()
myApp.Run() myApp.Run()
} }