Leonid Nikitin 306383449a
Add FFplay support to the application
Integrated FFplay functionality across the application. This includes support for setting up the FFplay path and invoking FFplay for media playback.
2025-05-19 22:49:09 +05:00

572 lines
14 KiB
Go

package kernel
import (
"bufio"
"errors"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/nicksnyder/go-i18n/v2/i18n"
"image/color"
"io"
"regexp"
"strconv"
"strings"
)
type LayoutContract interface {
SetContent(content fyne.CanvasObject) *fyne.Container
NewProgressbar(queueId int, totalDuration float64) ProgressContract
ChangeQueueStatus(queueId int, queue *Queue)
}
type Layout struct {
layout *fyne.Container
queueLayoutObject QueueLayoutObjectContract
localizerService LocalizerContract
}
func NewLayout(queueLayoutObject QueueLayoutObjectContract, localizerService LocalizerContract) *Layout {
layout := container.NewAdaptiveGrid(2, widget.NewLabel(""), container.NewVScroll(queueLayoutObject.GetCanvasObject()))
return &Layout{
layout: layout,
queueLayoutObject: queueLayoutObject,
localizerService: localizerService,
}
}
func (l Layout) SetContent(content fyne.CanvasObject) *fyne.Container {
l.layout.Objects[0] = content
return l.layout
}
func (l Layout) NewProgressbar(queueId int, totalDuration float64) ProgressContract {
progressbar := l.queueLayoutObject.GetProgressbar(queueId)
return NewProgress(totalDuration, progressbar, l.localizerService)
}
func (l Layout) ChangeQueueStatus(queueId int, queue *Queue) {
l.queueLayoutObject.ChangeQueueStatus(queueId, queue)
}
type QueueLayoutObjectContract interface {
GetCanvasObject() fyne.CanvasObject
GetProgressbar(queueId int) *widget.ProgressBar
ChangeQueueStatus(queueId int, queue *Queue)
}
type QueueLayoutObject struct {
QueueListContract QueueListContract
queue QueueListContract
container *fyne.Container
items map[int]QueueLayoutItem
localizerService LocalizerContract
queueStatisticsFormat *queueStatisticsFormat
ffplayService FFplayContract
}
type QueueLayoutItem struct {
CanvasObject fyne.CanvasObject
ProgressBar *widget.ProgressBar
StatusMessage *canvas.Text
MessageError *canvas.Text
buttonPlay *widget.Button
status *StatusContract
}
func NewQueueLayoutObject(queue QueueListContract, localizerService LocalizerContract, ffplayService FFplayContract) *QueueLayoutObject {
title := widget.NewLabel(localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "queue"}))
title.TextStyle.Bold = true
localizerService.AddChangeCallback("queue", func(text string) {
title.Text = text
title.Refresh()
})
items := map[int]QueueLayoutItem{}
queueStatisticsFormat := newQueueStatisticsFormat(localizerService, &items)
queueLayoutObject := &QueueLayoutObject{
queue: queue,
container: container.NewVBox(
container.NewHBox(title, queueStatisticsFormat.completed.widget, queueStatisticsFormat.error.widget),
container.NewHBox(queueStatisticsFormat.inProgress.widget, queueStatisticsFormat.waiting.widget, queueStatisticsFormat.total.widget),
),
items: items,
localizerService: localizerService,
queueStatisticsFormat: queueStatisticsFormat,
ffplayService: ffplayService,
}
queue.AddListener(queueLayoutObject)
return queueLayoutObject
}
func (o QueueLayoutObject) GetCanvasObject() fyne.CanvasObject {
return o.container
}
func (o QueueLayoutObject) GetProgressbar(queueId int) *widget.ProgressBar {
if item, ok := o.items[queueId]; ok {
return item.ProgressBar
}
return widget.NewProgressBar()
}
func (o QueueLayoutObject) Add(id int, queue *Queue) {
progressBar := widget.NewProgressBar()
statusMessage := canvas.NewText(o.getStatusTitle(queue.Status), theme.Color(theme.ColorNamePrimary))
messageError := canvas.NewText("", theme.Color(theme.ColorNameError))
buttonPlay := widget.NewButtonWithIcon("", theme.Icon(theme.IconNameMediaPlay), func() {
})
buttonPlay.Hide()
content := container.NewVBox(
container.NewHScroll(widget.NewLabel(queue.Setting.VideoFileInput.Name)),
progressBar,
container.NewHScroll(container.NewHBox(
buttonPlay,
statusMessage,
)),
container.NewHScroll(messageError),
canvas.NewLine(theme.Color(theme.ColorNameFocus)),
container.NewPadded(),
)
o.queueStatisticsFormat.addQueue()
if o.queueStatisticsFormat.isChecked(queue.Status) == false {
content.Hide()
}
o.items[id] = QueueLayoutItem{
CanvasObject: content,
ProgressBar: progressBar,
StatusMessage: statusMessage,
MessageError: messageError,
buttonPlay: buttonPlay,
status: &queue.Status,
}
o.container.Add(content)
}
func (o QueueLayoutObject) Remove(id int) {
if item, ok := o.items[id]; ok {
o.container.Remove(item.CanvasObject)
o.queueStatisticsFormat.removeQueue(*item.status)
o.items[id] = QueueLayoutItem{}
}
}
func (o QueueLayoutObject) ChangeQueueStatus(queueId int, queue *Queue) {
if item, ok := o.items[queueId]; ok {
statusColor := o.getStatusColor(queue.Status)
item.StatusMessage.Text = o.getStatusTitle(queue.Status)
item.StatusMessage.Color = statusColor
fyne.Do(func() {
item.StatusMessage.Refresh()
})
if queue.Error != nil {
item.MessageError.Text = queue.Error.Error()
item.MessageError.Color = statusColor
fyne.Do(func() {
item.MessageError.Refresh()
})
}
if queue.Status == StatusType(Completed) {
item.buttonPlay.Show()
item.buttonPlay.OnTapped = func() {
item.buttonPlay.Disable()
go func() {
_ = o.ffplayService.Run(FFplaySetting{
PathToFile: queue.Setting.VideoFileOut.Path,
})
fyne.Do(func() {
item.buttonPlay.Enable()
})
}()
}
}
if o.queueStatisticsFormat.isChecked(queue.Status) == false && item.CanvasObject.Visible() == true {
item.CanvasObject.Hide()
} else if item.CanvasObject.Visible() == false {
item.CanvasObject.Show()
}
o.queueStatisticsFormat.changeQueue(queue.Status)
}
}
func (o QueueLayoutObject) getStatusColor(status StatusContract) color.Color {
if status == StatusType(Error) {
return theme.Color(theme.ColorNameError)
}
if status == StatusType(Completed) {
return color.RGBA{R: 49, G: 127, B: 114, A: 255}
}
return theme.Color(theme.ColorNamePrimary)
}
func (o QueueLayoutObject) getStatusTitle(status StatusContract) string {
return o.localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: status.Name() + "Queue"})
}
type Progress struct {
totalDuration float64
progressbar *widget.ProgressBar
protocol string
localizerService LocalizerContract
}
func NewProgress(totalDuration float64, progressbar *widget.ProgressBar, localizerService LocalizerContract) Progress {
return Progress{
totalDuration: totalDuration,
progressbar: progressbar,
protocol: "pipe:",
localizerService: localizerService,
}
}
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 = p.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "errorConverter",
})
}
return errors.New(errorText)
}
return nil
}
type queueStatistics struct {
widget *widget.Check
title string
count *int64
}
type queueStatisticsFormat struct {
waiting *queueStatistics
inProgress *queueStatistics
completed *queueStatistics
error *queueStatistics
total *queueStatistics
}
func newQueueStatisticsFormat(localizerService LocalizerContract, queueItems *map[int]QueueLayoutItem) *queueStatisticsFormat {
checkWaiting := newQueueStatistics("waitingQueue", localizerService)
checkInProgress := newQueueStatistics("inProgressQueue", localizerService)
checkCompleted := newQueueStatistics("completedQueue", localizerService)
checkError := newQueueStatistics("errorQueue", localizerService)
checkTotal := newQueueStatistics("total", localizerService)
queueStatisticsFormat := &queueStatisticsFormat{
waiting: checkWaiting,
inProgress: checkInProgress,
completed: checkCompleted,
error: checkError,
total: checkTotal,
}
checkTotal.widget.OnChanged = func(b bool) {
if b == true {
queueStatisticsFormat.allCheckboxChecked()
} else {
queueStatisticsFormat.allUnCheckboxChecked()
}
queueStatisticsFormat.redrawingQueueItems(queueItems)
}
queueStatisticsFormat.waiting.widget.OnChanged = func(b bool) {
if b == true {
queueStatisticsFormat.checkboxChecked()
} else {
queueStatisticsFormat.unCheckboxChecked()
}
queueStatisticsFormat.redrawingQueueItems(queueItems)
}
queueStatisticsFormat.inProgress.widget.OnChanged = func(b bool) {
if b == true {
queueStatisticsFormat.checkboxChecked()
} else {
queueStatisticsFormat.unCheckboxChecked()
}
queueStatisticsFormat.redrawingQueueItems(queueItems)
}
queueStatisticsFormat.completed.widget.OnChanged = func(b bool) {
if b == true {
queueStatisticsFormat.checkboxChecked()
} else {
queueStatisticsFormat.unCheckboxChecked()
}
queueStatisticsFormat.redrawingQueueItems(queueItems)
}
queueStatisticsFormat.error.widget.OnChanged = func(b bool) {
if b == true {
queueStatisticsFormat.checkboxChecked()
} else {
queueStatisticsFormat.unCheckboxChecked()
}
queueStatisticsFormat.redrawingQueueItems(queueItems)
}
return queueStatisticsFormat
}
func (f queueStatisticsFormat) redrawingQueueItems(queueItems *map[int]QueueLayoutItem) {
for _, item := range *queueItems {
if f.isChecked(*item.status) == true && item.CanvasObject.Visible() == false {
item.CanvasObject.Show()
continue
}
if f.isChecked(*item.status) == false && item.CanvasObject.Visible() == true {
item.CanvasObject.Hide()
}
}
}
func (f queueStatisticsFormat) isChecked(status StatusContract) bool {
if status == StatusType(InProgress) {
return f.inProgress.widget.Checked
}
if status == StatusType(Completed) {
return f.completed.widget.Checked
}
if status == StatusType(Error) {
return f.error.widget.Checked
}
if status == StatusType(Waiting) {
return f.waiting.widget.Checked
}
return true
}
func (f queueStatisticsFormat) addQueue() {
f.waiting.add()
f.total.add()
}
func (f queueStatisticsFormat) changeQueue(status StatusContract) {
if status == StatusType(InProgress) {
f.waiting.remove()
f.inProgress.add()
return
}
if status == StatusType(Completed) {
f.inProgress.remove()
f.completed.add()
return
}
if status == StatusType(Error) {
f.inProgress.remove()
f.error.add()
return
}
}
func (f queueStatisticsFormat) removeQueue(status StatusContract) {
f.total.remove()
if status == StatusType(Completed) {
f.completed.remove()
return
}
if status == StatusType(Error) {
f.error.remove()
return
}
if status == StatusType(InProgress) {
f.inProgress.remove()
return
}
if status == StatusType(Waiting) {
f.waiting.remove()
return
}
}
func (f queueStatisticsFormat) checkboxChecked() {
if f.total.widget.Checked == true {
return
}
if f.waiting.widget.Checked == false {
return
}
if f.inProgress.widget.Checked == false {
return
}
if f.completed.widget.Checked == false {
return
}
if f.error.widget.Checked == false {
return
}
f.total.widget.Checked = true
f.total.widget.Refresh()
}
func (f queueStatisticsFormat) unCheckboxChecked() {
if f.total.widget.Checked == false {
return
}
f.total.widget.Checked = false
f.total.widget.Refresh()
}
func (f queueStatisticsFormat) allCheckboxChecked() {
f.waiting.widget.Checked = true
f.waiting.widget.Refresh()
f.inProgress.widget.Checked = true
f.inProgress.widget.Refresh()
f.completed.widget.Checked = true
f.completed.widget.Refresh()
f.error.widget.Checked = true
f.error.widget.Refresh()
}
func (f queueStatisticsFormat) allUnCheckboxChecked() {
f.waiting.widget.Checked = false
f.waiting.widget.Refresh()
f.inProgress.widget.Checked = false
f.inProgress.widget.Refresh()
f.completed.widget.Checked = false
f.completed.widget.Refresh()
f.error.widget.Checked = false
f.error.widget.Refresh()
}
func newQueueStatistics(messaigeID string, localizerService LocalizerContract) *queueStatistics {
checkbox := widget.NewCheck("", nil)
checkbox.Checked = true
count := int64(0)
title := localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: messaigeID})
queueStatistics := &queueStatistics{
widget: checkbox,
title: strings.ToLower(title),
count: &count,
}
queueStatistics.formatText(false)
localizerService.AddChangeCallback(messaigeID, func(text string) {
queueStatistics.title = strings.ToLower(text)
queueStatistics.formatText(true)
queueStatistics.widget.Refresh()
})
return queueStatistics
}
func (s queueStatistics) add() {
*s.count += 1
s.formatText(true)
}
func (s queueStatistics) remove() {
if *s.count == 0 {
return
}
*s.count -= 1
s.formatText(true)
}
func (s queueStatistics) formatText(refresh bool) {
s.widget.Text = s.title + ": " + strconv.FormatInt(*s.count, 10)
if refresh == true {
fyne.Do(func() {
s.widget.Refresh()
})
}
}