gui-for-ffmpeg/convertor/view/conversion.go
Leonid Nikitin 5ab11922b9
Select "Added Files" tab after dragging and dropping files.
Ensure the "Added Files" tab is automatically selected after users drag and drop files for conversion. This improves user experience by guiding them to the newly added content.
2025-05-25 21:46:19 +05:00

540 lines
15 KiB
Go

package view
import (
"errors"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/convertor/view/form_items"
encoder2 "git.kor-elf.net/kor-elf/gui-for-ffmpeg/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/setting"
"github.com/nicksnyder/go-i18n/v2/i18n"
"image/color"
"os"
"path/filepath"
)
type ConversionContract interface {
GetContent() fyne.CanvasObject
AfterViewContent()
}
type Conversion struct {
app kernel.AppContract
form *form
conversionMessage *canvas.Text
fileForConversion *fileForConversion
directoryForSaving *directoryForSaving
overwriteOutputFiles *overwriteOutputFiles
selectEncoder *selectEncoder
runConvert func(setting HandleConvertSetting)
itemsToConvertService kernel.ItemsToConvertContract
}
type HandleConvertSetting struct {
DirectoryForSave string
OverwriteOutputFiles bool
Format string
Encoder encoder2.EncoderContract
}
func NewConversion(app kernel.AppContract, formats encoder.ConvertorFormatsContract, runConvert func(setting HandleConvertSetting), settingDirectoryForSaving setting.DirectoryForSavingContract, itemsToConvertService kernel.ItemsToConvertContract) *Conversion {
conversionMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255})
conversionMessage.TextSize = 16
conversionMessage.TextStyle = fyne.TextStyle{Bold: true}
fileForConversion := newFileForConversion(app, itemsToConvertService)
directoryForSaving := newDirectoryForSaving(app, settingDirectoryForSaving)
overwriteOutputFiles := newOverwriteOutputFiles(app)
selectEncoder := newSelectEncoder(app, formats)
items := []*widget.FormItem{
{
Text: app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{MessageID: "fileForConversionTitle"}),
Widget: fileForConversion.button,
},
{
Widget: container.NewHScroll(fileForConversion.message),
},
{
Text: app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{MessageID: "buttonForSelectedDirTitle"}),
Widget: directoryForSaving.button,
},
{
Widget: container.NewHScroll(directoryForSaving.message),
},
{
Widget: overwriteOutputFiles.checkbox,
},
{
Widget: selectEncoder.SelectFileType,
},
{
Text: app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{MessageID: "selectFormat"}),
Widget: selectEncoder.SelectFormat,
},
{
Text: app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{MessageID: "selectEncoder"}),
Widget: selectEncoder.SelectEncoder,
},
}
form := newForm(app, items)
return &Conversion{
app: app,
form: form,
conversionMessage: conversionMessage,
fileForConversion: fileForConversion,
directoryForSaving: directoryForSaving,
overwriteOutputFiles: overwriteOutputFiles,
selectEncoder: selectEncoder,
runConvert: runConvert,
itemsToConvertService: itemsToConvertService,
}
}
func (c Conversion) GetContent() fyne.CanvasObject {
c.form.form.OnSubmit = c.submit
c.fileForConversion.AddChangeCallback(c.selectFileForConversion)
c.selectEncoder.AddChangeCallback(c.changeEncoder)
if c.selectEncoder.Encoder != nil {
c.selectEncoder.SelectEncoder.SetSelectedIndex(c.selectEncoder.SelectEncoder.SelectedIndex())
}
return container.NewVBox(
c.form.form,
c.conversionMessage,
)
}
func (c Conversion) changeEncoder(encoder encoder2.EncoderContract) {
items := []*widget.FormItem{}
if form_items.Views[encoder.GetName()] != nil {
items = form_items.Views[encoder.GetName()](encoder, c.app)
}
c.form.ChangeItems(items)
}
func (c Conversion) AfterViewContent() {
if len(c.itemsToConvertService.GetItems()) == 0 {
c.form.form.Disable()
}
}
func (c Conversion) selectFileForConversion(err error) {
c.conversionMessage.Text = ""
if len(c.itemsToConvertService.GetItems()) == 0 {
if err != nil {
c.form.form.Disable()
return
}
}
c.form.form.Enable()
}
func (c Conversion) submit() {
if len(c.itemsToConvertService.GetItems()) == 0 {
showConversionMessage(c.conversionMessage, errors.New(c.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "errorNoFilesAddedForConversion",
})))
c.enableFormConversion()
return
}
if len(c.directoryForSaving.path) == 0 {
showConversionMessage(c.conversionMessage, errors.New(c.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "errorSelectedFolderSave",
})))
c.enableFormConversion()
return
}
if len(c.selectEncoder.SelectFormat.Selected) == 0 {
showConversionMessage(c.conversionMessage, errors.New(c.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "errorSelectedFormat",
})))
return
}
if c.selectEncoder.Encoder == nil {
showConversionMessage(c.conversionMessage, errors.New(c.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "errorSelectedEncoder",
})))
return
}
c.conversionMessage.Text = ""
c.fileForConversion.button.Disable()
c.directoryForSaving.button.Disable()
c.form.form.Disable()
c.runConvert(HandleConvertSetting{
DirectoryForSave: c.directoryForSaving.path,
OverwriteOutputFiles: c.overwriteOutputFiles.IsChecked(),
Format: c.selectEncoder.SelectFormat.Selected,
Encoder: c.selectEncoder.Encoder,
})
c.enableFormConversion()
if len(c.itemsToConvertService.GetItems()) == 0 {
c.form.form.Disable()
}
}
func (c Conversion) enableFormConversion() {
c.fileForConversion.button.Enable()
c.directoryForSaving.button.Enable()
c.form.form.Enable()
}
type fileForConversion struct {
button *widget.Button
message *canvas.Text
file *kernel.File
changeCallbacks map[int]func(err error)
}
func newFileForConversion(app kernel.AppContract, itemsToConvertService kernel.ItemsToConvertContract) *fileForConversion {
message := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255})
fileForConversion := &fileForConversion{
message: message,
changeCallbacks: map[int]func(err error){},
}
buttonTitle := app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "choose",
}) + "\n" + app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "or",
}) + "\n" + app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "dragAndDropFiles",
})
var locationURI fyne.ListableURI
fileForConversion.button = widget.NewButton(buttonTitle, func() {
app.GetWindow().NewFileOpen(func(r fyne.URIReadCloser, err error) {
fyne.Do(func() {
fileForConversion.message.Text = ""
fileForConversion.message.Refresh()
})
if err != nil {
fyne.Do(func() {
fileForConversion.message.Text = err.Error()
fileForConversion.message.Refresh()
})
fileForConversion.eventSelectFile(err)
return
}
if r == nil {
return
}
app.GetWindow().GetLayout().GetRightTabs().SelectAddedFilesTab()
itemsToConvertService.Add(&kernel.File{
Path: r.URI().Path(),
Name: r.URI().Name(),
Ext: r.URI().Extension(),
})
fileForConversion.eventSelectFile(nil)
listableURI := storage.NewFileURI(filepath.Dir(r.URI().Path()))
locationURI, _ = storage.ListerForURI(listableURI)
}, locationURI)
})
app.GetWindow().SetOnDropped(func(position fyne.Position, uris []fyne.URI) {
if len(uris) == 0 {
return
}
isError := false
for _, uri := range uris {
info, err := os.Stat(uri.Path())
if err != nil {
isError = true
continue
}
if info.IsDir() {
isError = true
continue
}
itemsToConvertService.Add(&kernel.File{
Path: uri.Path(),
Name: uri.Name(),
Ext: uri.Extension(),
})
fileForConversion.eventSelectFile(nil)
listableURI := storage.NewFileURI(filepath.Dir(uri.Path()))
locationURI, _ = storage.ListerForURI(listableURI)
}
app.GetWindow().GetLayout().GetRightTabs().SelectAddedFilesTab()
if isError {
fileForConversion.message.Text = app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "errorDragAndDropFile",
})
setStringErrorStyle(fileForConversion.message)
fileForConversion.eventSelectFile(errors.New(fileForConversion.message.Text))
} else {
fyne.Do(func() {
fileForConversion.message.Text = ""
fileForConversion.message.Refresh()
})
}
})
return fileForConversion
}
func (c fileForConversion) AddChangeCallback(callback func(err error)) {
c.changeCallbacks[len(c.changeCallbacks)] = callback
}
func (c fileForConversion) eventSelectFile(err error) {
for _, changeCallback := range c.changeCallbacks {
changeCallback(err)
}
}
type directoryForSaving struct {
button *widget.Button
message *canvas.Text
path string
}
func newDirectoryForSaving(app kernel.AppContract, settingDirectoryForSaving setting.DirectoryForSavingContract) *directoryForSaving {
directoryForSaving := &directoryForSaving{
path: "",
}
directoryForSaving.message = canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255})
directoryForSaving.message.TextSize = 16
directoryForSaving.message.TextStyle = fyne.TextStyle{Bold: true}
buttonTitle := app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "choose",
})
var locationURI fyne.ListableURI
location, err := getDirectoryForSaving(settingDirectoryForSaving)
if err == nil {
directoryForSaving.path = location.Path()
directoryForSaving.message.Text = location.Path()
setStringSuccessStyle(directoryForSaving.message)
}
directoryForSaving.button = widget.NewButton(buttonTitle, func() {
app.GetWindow().NewFolderOpen(func(r fyne.ListableURI, err error) {
if err != nil {
directoryForSaving.message.Text = err.Error()
setStringErrorStyle(directoryForSaving.message)
return
}
if r == nil {
return
}
directoryForSaving.path = r.Path()
directoryForSaving.message.Text = r.Path()
setStringSuccessStyle(directoryForSaving.message)
locationURI, err = storage.ListerForURI(r)
if err == nil {
_, _ = settingDirectoryForSaving.SaveDirectoryForSaving(locationURI.Path())
}
}, locationURI)
})
return directoryForSaving
}
func getDirectoryForSaving(settingDirectoryForSaving setting.DirectoryForSavingContract) (fyne.ListableURI, error) {
path, err := settingDirectoryForSaving.GetDirectoryForSaving()
if err != nil {
return nil, err
}
if len(path) > 0 {
path = "file://" + path
}
uri, err := storage.ParseURI(path)
if err != nil {
return nil, err
}
return storage.ListerForURI(uri)
}
type overwriteOutputFiles struct {
checkbox *widget.Check
isChecked bool
}
func newOverwriteOutputFiles(app kernel.AppContract) *overwriteOutputFiles {
overwriteOutputFiles := &overwriteOutputFiles{
isChecked: false,
}
checkboxOverwriteOutputFilesTitle := app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "checkboxOverwriteOutputFilesTitle",
})
overwriteOutputFiles.checkbox = widget.NewCheck(checkboxOverwriteOutputFilesTitle, func(b bool) {
overwriteOutputFiles.isChecked = b
})
return overwriteOutputFiles
}
func (receiver overwriteOutputFiles) IsChecked() bool {
return receiver.isChecked
}
type selectEncoder struct {
SelectFileType *widget.RadioGroup
SelectFormat *widget.Select
SelectEncoder *widget.Select
Encoder encoder2.EncoderContract
changeCallbacks map[int]func(encoder encoder2.EncoderContract)
}
func newSelectEncoder(app kernel.AppContract, formats encoder.ConvertorFormatsContract) *selectEncoder {
selectEncoder := &selectEncoder{
changeCallbacks: map[int]func(encoder encoder2.EncoderContract){},
}
encoders := map[int]encoder2.EncoderDataContract{}
selectEncoder.SelectEncoder = widget.NewSelect([]string{}, func(s string) {
if encoders[selectEncoder.SelectEncoder.SelectedIndex()] == nil {
return
}
selectEncoderData := encoders[selectEncoder.SelectEncoder.SelectedIndex()]
selectEncoder.ChangeEncoder(selectEncoderData.NewEncoder())
})
formatSelected := ""
selectEncoder.SelectFormat = widget.NewSelect([]string{}, func(s string) {
if formatSelected == s {
return
}
formatSelected = s
format, err := formats.GetFormat(s)
if err != nil {
return
}
encoderOptions := []string{}
encoders = map[int]encoder2.EncoderDataContract{}
for _, e := range format.GetEncoders() {
encoders[len(encoders)] = e
encoderOptions = append(encoderOptions, app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{MessageID: "encoder_" + e.GetTitle()}))
}
selectEncoder.SelectEncoder.SetOptions(encoderOptions)
selectEncoder.SelectEncoder.SetSelectedIndex(0)
})
fileTypeOptions := []string{}
for _, fileType := range encoder2.GetListFileType() {
fileTypeOptions = append(fileTypeOptions, fileType.Name())
}
encoderGroupVideo := app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{MessageID: "encoderGroupVideo"})
encoderGroupAudio := app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{MessageID: "encoderGroupAudio"})
encoderGroupImage := app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{MessageID: "encoderGroupImage"})
encoderGroup := map[string]string{
encoderGroupVideo: "video",
encoderGroupAudio: "audio",
encoderGroupImage: "image",
}
selectEncoder.SelectFileType = widget.NewRadioGroup([]string{encoderGroupVideo, encoderGroupAudio, encoderGroupImage}, func(s string) {
groupCode := encoderGroup[s]
formatOptions := []string{}
for _, f := range formats.GetFormats() {
if groupCode != f.GetFileType().Name() {
continue
}
formatOptions = append(formatOptions, f.GetTitle())
}
selectEncoder.SelectFormat.SetOptions(formatOptions)
if groupCode == encoder2.FileType(encoder2.Video).Name() {
selectEncoder.SelectFormat.SetSelected("mp4")
} else {
selectEncoder.SelectFormat.SetSelectedIndex(0)
}
})
selectEncoder.SelectFileType.Horizontal = true
selectEncoder.SelectFileType.Required = true
selectEncoder.SelectFileType.SetSelected(encoderGroupVideo)
return selectEncoder
}
func (e *selectEncoder) ChangeEncoder(encoder encoder2.EncoderContract) {
e.Encoder = encoder
e.eventSelectEncoder(e.Encoder)
}
func (e *selectEncoder) AddChangeCallback(callback func(encoder encoder2.EncoderContract)) {
e.changeCallbacks[len(e.changeCallbacks)] = callback
}
func (e *selectEncoder) eventSelectEncoder(encoder encoder2.EncoderContract) {
for _, changeCallback := range e.changeCallbacks {
changeCallback(encoder)
}
}
func setStringErrorStyle(text *canvas.Text) {
text.Color = color.RGBA{R: 255, G: 0, B: 0, A: 255}
text.Refresh()
}
func setStringSuccessStyle(text *canvas.Text) {
text.Color = color.RGBA{R: 49, G: 127, B: 114, A: 255}
text.Refresh()
}
func showConversionMessage(conversionMessage *canvas.Text, err error) {
conversionMessage.Text = err.Error()
setStringErrorStyle(conversionMessage)
}
type form struct {
form *widget.Form
items []*widget.FormItem
}
func newForm(app kernel.AppContract, items []*widget.FormItem) *form {
f := widget.NewForm()
f.SubmitText = app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "converterVideoFilesSubmitTitle",
})
f.Items = items
return &form{
form: f,
items: items,
}
}
func (f form) ChangeItems(items []*widget.FormItem) {
f.form.Items = f.items
f.form.Refresh()
f.form.Items = append(f.form.Items, items...)
f.form.Refresh()
}