From 46d210d6d55f8e41c8a8f938cf5a3a0d72fdaf02 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 18 May 2025 19:28:16 +0500 Subject: [PATCH 01/12] Added return after kernel.PanicErrorLang(err, appMetadata) to avoid unpredictable results during an error. --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index 2294a2b..d6b6b83 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ func init() { localizerService, err := kernel.NewLocalizer("languages", language.Russian) if err != nil { kernel.PanicErrorLang(err, appMetadata) + return } ffPathUtilities = &kernel.FFPathUtilities{FFmpeg: "", FFprobe: ""} -- 2.47.2 From 9d46db43c261db76a7e935db9bcfe51bcba4eeb5 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 18 May 2025 19:31:59 +0500 Subject: [PATCH 02/12] Default language I made it so that if the OS language matches the language into which there is a translation, it would be used by default. And if not, then I would suggest choosing which language to use. --- handler/main.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/handler/main.go b/handler/main.go index e2e6398..0e97c6f 100644 --- a/handler/main.go +++ b/handler/main.go @@ -1,6 +1,7 @@ package handler import ( + "fyne.io/fyne/v2/lang" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/localizer" ) @@ -29,8 +30,11 @@ func NewMainHandler( func (h MainHandler) Start() { language, err := h.localizerRepository.GetCode() if err != nil { - h.menuHandler.LanguageSelection() - return + err = h.app.GetLocalizerService().SetCurrentLanguageByCode(lang.SystemLocale().LanguageString()) + if err != nil { + h.menuHandler.LanguageSelection() + return + } } _ = h.app.GetLocalizerService().SetCurrentLanguageByCode(language) -- 2.47.2 From a831d56d93306695f1d7ee4b6e425702c3d9a235 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 18 May 2025 19:32:57 +0500 Subject: [PATCH 03/12] Add localized error handling for database timeout Introduced a new localized error message "errorDatabaseTimeout" in multiple languages (English, Kazakh, Russian) and updated the `PanicError` method to handle database timeout errors more gracefully. This improves user feedback by providing context-specific error messages. --- error/view.go | 28 +++++++++++++++++++++++++--- languages/active.en.toml | 4 ++++ languages/active.kk.toml | 4 ++++ languages/active.ru.toml | 1 + 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/error/view.go b/error/view.go index 4845ab0..2d68856 100644 --- a/error/view.go +++ b/error/view.go @@ -1,11 +1,14 @@ package error import ( + "errors" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/widget" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/localizer" "github.com/nicksnyder/go-i18n/v2/i18n" + "go.etcd.io/bbolt" ) type ViewContract interface { @@ -13,24 +16,38 @@ type ViewContract interface { } type View struct { - app kernel.AppContract + app kernel.AppContract + isSetLanguage bool } func NewView(app kernel.AppContract) *View { return &View{ - app: app, + app: app, + isSetLanguage: true, } } func (v View) PanicError(err error) { + if v.isSetLanguage { + v.isSetLanguage = false + _ = v.app.GetLocalizerService().SetCurrentLanguageByCode(lang.SystemLocale().LanguageString()) + } + messageHead := v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "error", }) + messagetText := err.Error() + if errors.Is(err, bbolt.ErrTimeout) { + messagetText = v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorDatabaseTimeout", + }) + } + v.app.GetWindow().SetContent(container.NewBorder( container.NewVBox( widget.NewLabel(messageHead), - widget.NewLabel(err.Error()), + widget.NewLabel(messagetText), ), nil, nil, @@ -42,6 +59,11 @@ func (v View) PanicError(err error) { } func (v View) PanicErrorWriteDirectoryData() { + if v.isSetLanguage { + v.isSetLanguage = false + _ = v.app.GetLocalizerService().SetCurrentLanguageByCode(lang.SystemLocale().LanguageString()) + } + message := v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "errorDatabase", }) diff --git a/languages/active.en.toml b/languages/active.en.toml index fc2bb35..fb2946f 100644 --- a/languages/active.en.toml +++ b/languages/active.en.toml @@ -222,6 +222,10 @@ other = "Couldn't convert video" hash = "sha1-531abc3f0d12727e542df6e5a22de91098380fc1" other = "could not create file 'database' in folder 'data'" +[errorDatabaseTimeout] +hash = "sha1-f8153516ac2442d19be4b6daccce839d204ff09f" +other = "Could not open configuration file.\nMake sure another copy of the program is not running!" + [errorDragAndDrop1File] hash = "sha1-a8edb5cbd622f3ce4ec07a2377e22ec5fad4491b" other = "You can only drag and drop 1 file." diff --git a/languages/active.kk.toml b/languages/active.kk.toml index c17d909..713378e 100644 --- a/languages/active.kk.toml +++ b/languages/active.kk.toml @@ -222,6 +222,10 @@ other = "Бейнені түрлендіру мүмкін болмады" hash = "sha1-531abc3f0d12727e542df6e5a22de91098380fc1" other = "'data' қалтасында 'database' файлын жасау мүмкін болмады" +[errorDatabaseTimeout] +hash = "sha1-f8153516ac2442d19be4b6daccce839d204ff09f" +other = "Конфигурация файлын аша алмады.\nБағдарламаның басқа көшірмесі іске қосылмағанына көз жеткізіңіз!" + [errorDragAndDrop1File] hash = "sha1-a8edb5cbd622f3ce4ec07a2377e22ec5fad4491b" other = "Тек 1 файлды сүйреп апаруға болады" diff --git a/languages/active.ru.toml b/languages/active.ru.toml index 39e1679..18e2ab2 100644 --- a/languages/active.ru.toml +++ b/languages/active.ru.toml @@ -54,6 +54,7 @@ encoder_xbm = "XBM (X BitMap) image" error = "Произошла ошибка!" errorConverter = "не смогли отконвертировать видео" errorDatabase = "не смогли создать файл 'database' в папке 'data'" +errorDatabaseTimeout = "Не смогли открыть файл конфигурации.\nУбедитесь, что другая копия программы не запущена!" errorDragAndDrop1File = "Можно перетащить только 1 файл" errorFFmpeg = "это не FFmpeg" errorFFmpegVersion = "Не смогли определить версию FFmpeg" -- 2.47.2 From 306383449a0db22b99ab0a371f0feac5a9afc3e3 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Mon, 19 May 2025 22:49:09 +0500 Subject: [PATCH 04/12] 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. --- convertor/repository.go | 10 ++++++++++ convertor/view.go | 3 ++- convertor/view_setting.go | 15 +++++++++++++-- handler/convertor.go | 32 +++++++++++++++++++++++++++++--- handler/convertor_anyos.go | 2 +- handler/convertor_linux.go | 14 ++++++++++++-- handler/convertor_windows.go | 8 ++++++-- handler/menu.go | 8 +++++++- kernel/convertor.go | 28 ++++++++++++++++++++++++++++ kernel/ffplay.go | 30 ++++++++++++++++++++++++++++++ kernel/layout.go | 29 +++++++++++++++++++++++++++-- kernel/window.go | 4 +++- languages/active.en.toml | 16 ++++++++++++++-- languages/active.kk.toml | 16 ++++++++++++++-- languages/active.ru.toml | 5 ++++- main.go | 13 +++++++++++-- menu/view.go | 35 +++++++++++++++++++++++++++++++++-- 17 files changed, 244 insertions(+), 24 deletions(-) create mode 100644 kernel/ffplay.go diff --git a/convertor/repository.go b/convertor/repository.go index e31f64a..f751607 100644 --- a/convertor/repository.go +++ b/convertor/repository.go @@ -9,6 +9,8 @@ type RepositoryContract interface { SavePathFfmpeg(code string) (setting.Setting, error) GetPathFfprobe() (string, error) SavePathFfprobe(code string) (setting.Setting, error) + GetPathFfplay() (string, error) + SavePathFfplay(code string) (setting.Setting, error) } type Repository struct { @@ -34,3 +36,11 @@ func (r Repository) GetPathFfprobe() (string, error) { func (r Repository) SavePathFfprobe(path string) (setting.Setting, error) { return r.settingRepository.CreateOrUpdate("ffprobe", path) } + +func (r Repository) GetPathFfplay() (string, error) { + return r.settingRepository.GetValue("ffplay") +} + +func (r Repository) SavePathFfplay(path string) (setting.Setting, error) { + return r.settingRepository.CreateOrUpdate("ffplay", path) +} diff --git a/convertor/view.go b/convertor/view.go index 35056fb..58e6a3f 100644 --- a/convertor/view.go +++ b/convertor/view.go @@ -18,7 +18,8 @@ type ViewContract interface { SelectFFPath( ffmpegPath string, ffprobePath string, - save func(ffmpegPath string, ffprobePath string) error, + ffplayPath string, + save func(ffmpegPath string, ffprobePath string, ffplayPath string) error, cancel func(), donwloadFFmpeg func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error, ) diff --git a/convertor/view_setting.go b/convertor/view_setting.go index b52315c..ca5e3f4 100644 --- a/convertor/view_setting.go +++ b/convertor/view_setting.go @@ -15,7 +15,8 @@ import ( func (v View) SelectFFPath( currentPathFfmpeg string, currentPathFfprobe string, - save func(ffmpegPath string, ffprobePath string) error, + currentPathFfplay string, + save func(ffmpegPath string, ffprobePath string, ffplayPath string) error, cancel func(), donwloadFFmpeg func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error, ) { @@ -25,6 +26,7 @@ func (v View) SelectFFPath( ffmpegPath, buttonFFmpeg, buttonFFmpegMessage := v.getButtonSelectFile(currentPathFfmpeg) ffprobePath, buttonFFprobe, buttonFFprobeMessage := v.getButtonSelectFile(currentPathFfprobe) + ffplayPath, buttonFFplay, buttonFFplayMessage := v.getButtonSelectFile(currentPathFfplay) link := widget.NewHyperlink("https://ffmpeg.org/download.html", &url.URL{ Scheme: "https", @@ -58,6 +60,15 @@ func (v View) SelectFFPath( { Widget: container.NewHScroll(buttonFFprobeMessage), }, + { + Text: v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "pathToFfplay", + }), + Widget: buttonFFplay, + }, + { + Widget: container.NewHScroll(buttonFFplayMessage), + }, { Widget: errorMessage, }, @@ -66,7 +77,7 @@ func (v View) SelectFFPath( MessageID: "save", }), OnSubmit: func() { - err := save(*ffmpegPath, *ffprobePath) + err := save(*ffmpegPath, *ffprobePath, *ffplayPath) if err != nil { errorMessage.Text = err.Error() } diff --git a/handler/convertor.go b/handler/convertor.go index 4f0b2ab..e67fa81 100644 --- a/handler/convertor.go +++ b/handler/convertor.go @@ -16,6 +16,7 @@ type ConvertorHandlerContract interface { FfPathSelection() GetFfmpegVersion() (string, error) GetFfprobeVersion() (string, error) + GetFfplayVersion() (string, error) } type ConvertorHandler struct { @@ -53,13 +54,14 @@ func (h ConvertorHandler) MainConvertor() { h.convertorView.Main(conversion) return } - h.convertorView.SelectFFPath("", "", h.saveSettingFFPath, nil, h.downloadFFmpeg) + h.convertorView.SelectFFPath("", "", "", h.saveSettingFFPath, nil, h.downloadFFmpeg) } func (h ConvertorHandler) FfPathSelection() { ffmpeg, _ := h.convertorRepository.GetPathFfmpeg() ffprobe, _ := h.convertorRepository.GetPathFfprobe() - h.convertorView.SelectFFPath(ffmpeg, ffprobe, h.saveSettingFFPath, h.MainConvertor, h.downloadFFmpeg) + ffplay, _ := h.convertorRepository.GetPathFfplay() + h.convertorView.SelectFFPath(ffmpeg, ffprobe, ffplay, h.saveSettingFFPath, h.MainConvertor, h.downloadFFmpeg) } func (h ConvertorHandler) GetFfmpegVersion() (string, error) { @@ -70,6 +72,10 @@ func (h ConvertorHandler) GetFfprobeVersion() (string, error) { return h.app.GetConvertorService().GetFFprobeVersion() } +func (h ConvertorHandler) GetFfplayVersion() (string, error) { + return h.app.GetConvertorService().GetFFplayVersion() +} + func (h ConvertorHandler) runConvert(setting view.HandleConvertSetting) { h.app.GetQueue().Add(&kernel.ConvertSetting{ VideoFileInput: setting.FileInput, @@ -98,15 +104,21 @@ func (h ConvertorHandler) checkingFFPathUtilities() bool { if ffprobeChecking == false { continue } + + ffplayChecking, _ := h.app.GetConvertorService().ChangeFFplayPath(item.FFplay) + if ffplayChecking == false { + continue + } _, _ = h.convertorRepository.SavePathFfmpeg(item.FFmpeg) _, _ = h.convertorRepository.SavePathFfprobe(item.FFprobe) + _, _ = h.convertorRepository.SavePathFfplay(item.FFplay) return true } return false } -func (h ConvertorHandler) saveSettingFFPath(ffmpegPath string, ffprobePath string) error { +func (h ConvertorHandler) saveSettingFFPath(ffmpegPath string, ffprobePath string, ffplayPath string) error { ffmpegChecking, _ := h.app.GetConvertorService().ChangeFFmpegPath(ffmpegPath) if ffmpegChecking == false { errorText := h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ @@ -123,8 +135,17 @@ func (h ConvertorHandler) saveSettingFFPath(ffmpegPath string, ffprobePath strin return errors.New(errorText) } + ffplayChecking, _ := h.app.GetConvertorService().ChangeFFplayPath(ffplayPath) + if ffplayChecking == false { + errorText := h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorFFplay", + }) + return errors.New(errorText) + } + _, _ = h.convertorRepository.SavePathFfmpeg(ffmpegPath) _, _ = h.convertorRepository.SavePathFfprobe(ffprobePath) + _, _ = h.convertorRepository.SavePathFfplay(ffplayPath) h.MainConvertor() @@ -142,5 +163,10 @@ func (h ConvertorHandler) checkingFFPath() bool { return false } + _, err = h.app.GetConvertorService().GetFFplayVersion() + if err != nil { + return false + } + return true } diff --git a/handler/convertor_anyos.go b/handler/convertor_anyos.go index 5143398..76daa45 100644 --- a/handler/convertor_anyos.go +++ b/handler/convertor_anyos.go @@ -10,7 +10,7 @@ import ( ) func getPathsToFF() []kernel.FFPathUtilities { - return []kernel.FFPathUtilities{{"ffmpeg/bin/ffmpeg", "ffmpeg/bin/ffprobe"}, {"ffmpeg", "ffprobe"}} + return []kernel.FFPathUtilities{{FFmpeg: "ffmpeg/bin/ffmpeg", FFprobe: "ffmpeg/bin/ffprobe", FFplay: "ffmpeg/bin/ffplay"}, {FFmpeg: "ffmpeg", FFprobe: "ffprobe", FFplay: "ffplay"}} } func (h ConvertorHandler) downloadFFmpeg(progressBar *widget.ProgressBar, progressMessage *canvas.Text) (err error) { diff --git a/handler/convertor_linux.go b/handler/convertor_linux.go index e0995f2..c1a032a 100644 --- a/handler/convertor_linux.go +++ b/handler/convertor_linux.go @@ -19,7 +19,7 @@ import ( ) func getPathsToFF() []kernel.FFPathUtilities { - return []kernel.FFPathUtilities{{"ffmpeg/bin/ffmpeg", "ffmpeg/bin/ffprobe"}, {"ffmpeg", "ffprobe"}} + return []kernel.FFPathUtilities{{FFmpeg: "ffmpeg/bin/ffmpeg", FFprobe: "ffmpeg/bin/ffprobe", FFplay: "ffmpeg/bin/ffplay"}, {FFmpeg: "ffmpeg", FFprobe: "ffprobe", FFplay: "ffplay"}} } func (h ConvertorHandler) downloadFFmpeg(progressBar *widget.ProgressBar, progressMessage *canvas.Text) (err error) { @@ -60,7 +60,11 @@ func (h ConvertorHandler) downloadFFmpeg(progressBar *widget.ProgressBar, progre progressMessage.Refresh() }) - err = h.saveSettingFFPath("ffmpeg/ffmpeg-master-latest-linux64-gpl/bin/ffmpeg", "ffmpeg/ffmpeg-master-latest-linux64-gpl/bin/ffprobe") + err = h.saveSettingFFPath( + "ffmpeg/ffmpeg-master-latest-linux64-gpl/bin/ffmpeg", + "ffmpeg/ffmpeg-master-latest-linux64-gpl/bin/ffprobe", + "ffmpeg/ffmpeg-master-latest-linux64-gpl/bin/ffplay", + ) if err != nil { return err } @@ -217,6 +221,12 @@ func unTarXz(fileTar string, directory string, progressBar *widget.ProgressBar) return err } + ffplayPath := filepath.Join(directory, "ffmpeg-master-latest-linux64-gpl", "bin", "ffplay") + err = os.Chmod(ffplayPath, 0755) + if err != nil { + return err + } + return nil } diff --git a/handler/convertor_windows.go b/handler/convertor_windows.go index 3a31e5e..fb08d87 100644 --- a/handler/convertor_windows.go +++ b/handler/convertor_windows.go @@ -19,7 +19,7 @@ import ( ) func getPathsToFF() []kernel.FFPathUtilities { - return []kernel.FFPathUtilities{{"ffmpeg\\bin\\ffmpeg.exe", "ffmpeg\\bin\\ffprobe.exe"}} + return []kernel.FFPathUtilities{{FFmpeg: "ffmpeg\\bin\\ffmpeg.exe", FFprobe: "ffmpeg\\bin\\ffprobe.exe", FFplay: "ffmpeg\\bin\\ffplay.exe"}} } func (h ConvertorHandler) downloadFFmpeg(progressBar *widget.ProgressBar, progressMessage *canvas.Text) (err error) { @@ -59,7 +59,11 @@ func (h ConvertorHandler) downloadFFmpeg(progressBar *widget.ProgressBar, progre fyne.Do(func() { progressMessage.Refresh() }) - err = h.saveSettingFFPath("ffmpeg/ffmpeg-master-latest-win64-gpl/bin/ffmpeg.exe", "ffmpeg/ffmpeg-master-latest-win64-gpl/bin/ffprobe.exe") + err = h.saveSettingFFPath( + "ffmpeg/ffmpeg-master-latest-win64-gpl/bin/ffmpeg.exe", + "ffmpeg/ffmpeg-master-latest-win64-gpl/bin/ffprobe.exe", + "ffmpeg/ffmpeg-master-latest-win64-gpl/bin/ffplay.exe", + ) if err != nil { return err } diff --git a/handler/menu.go b/handler/menu.go index 79668c9..586dafd 100644 --- a/handler/menu.go +++ b/handler/menu.go @@ -117,8 +117,14 @@ func (h MenuHandler) openAbout() { MessageID: "errorFFprobeVersion", }) } + ffplay, err := h.convertorHandler.GetFfplayVersion() + if err != nil { + ffplay = h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorFFplayVersion", + }) + } - h.menuView.About(ffmpeg, ffprobe) + h.menuView.About(ffmpeg, ffprobe, ffplay) } func (h MenuHandler) openGratitude() { diff --git a/kernel/convertor.go b/kernel/convertor.go index ac765b5..179cff3 100644 --- a/kernel/convertor.go +++ b/kernel/convertor.go @@ -32,8 +32,10 @@ type ConvertorContract interface { GetTotalDuration(file *File) (float64, error) GetFFmpegVesrion() (string, error) GetFFprobeVersion() (string, error) + GetFFplayVersion() (string, error) ChangeFFmpegPath(path string) (bool, error) ChangeFFprobePath(path string) (bool, error) + ChangeFFplayPath(path string) (bool, error) GetRunningProcesses() map[int]*exec.Cmd GetSupportFormats() (encoder.ConvertorFormatsContract, error) } @@ -46,6 +48,7 @@ type ProgressContract interface { type FFPathUtilities struct { FFmpeg string FFprobe string + FFplay string } type runningProcesses struct { @@ -177,6 +180,17 @@ func (s Convertor) GetFFprobeVersion() (string, error) { return text[0], nil } +func (s Convertor) GetFFplayVersion() (string, error) { + cmd := exec.Command(s.ffPathUtilities.FFplay, "-version") + helper.PrepareBackgroundCommand(cmd) + out, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + text := regexp.MustCompile("\r?\n").Split(strings.TrimSpace(string(out)), -1) + return text[0], nil +} + func (s Convertor) ChangeFFmpegPath(path string) (bool, error) { cmd := exec.Command(path, "-version") helper.PrepareBackgroundCommand(cmd) @@ -205,6 +219,20 @@ func (s Convertor) ChangeFFprobePath(path string) (bool, error) { return true, nil } +func (s Convertor) ChangeFFplayPath(path string) (bool, error) { + cmd := exec.Command(path, "-version") + helper.PrepareBackgroundCommand(cmd) + out, err := cmd.CombinedOutput() + if err != nil { + return false, err + } + if strings.Contains(strings.TrimSpace(string(out)), "ffplay") == false { + return false, nil + } + s.ffPathUtilities.FFplay = path + return true, nil +} + func (s Convertor) GetSupportFormats() (encoder.ConvertorFormatsContract, error) { formats := encoder.NewConvertorFormats() cmd := exec.Command(s.ffPathUtilities.FFmpeg, "-encoders") diff --git a/kernel/ffplay.go b/kernel/ffplay.go new file mode 100644 index 0000000..cc6b783 --- /dev/null +++ b/kernel/ffplay.go @@ -0,0 +1,30 @@ +package kernel + +import ( + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/helper" + "os/exec" +) + +type FFplay struct { + ffPathUtilities *FFPathUtilities +} + +type FFplaySetting struct { + PathToFile string +} + +type FFplayContract interface { + Run(setting FFplaySetting) error +} + +func NewFFplay(ffPathUtilities *FFPathUtilities) *FFplay { + return &FFplay{ffPathUtilities: ffPathUtilities} +} + +func (ffplay FFplay) Run(setting FFplaySetting) error { + args := []string{setting.PathToFile} + cmd := exec.Command(ffplay.ffPathUtilities.FFplay, args...) + helper.PrepareBackgroundCommand(cmd) + + return cmd.Start() +} diff --git a/kernel/layout.go b/kernel/layout.go index a65aeff..94ec22a 100644 --- a/kernel/layout.go +++ b/kernel/layout.go @@ -66,6 +66,7 @@ type QueueLayoutObject struct { items map[int]QueueLayoutItem localizerService LocalizerContract queueStatisticsFormat *queueStatisticsFormat + ffplayService FFplayContract } type QueueLayoutItem struct { @@ -73,11 +74,12 @@ type QueueLayoutItem struct { ProgressBar *widget.ProgressBar StatusMessage *canvas.Text MessageError *canvas.Text + buttonPlay *widget.Button status *StatusContract } -func NewQueueLayoutObject(queue QueueListContract, localizerService LocalizerContract) *QueueLayoutObject { +func NewQueueLayoutObject(queue QueueListContract, localizerService LocalizerContract, ffplayService FFplayContract) *QueueLayoutObject { title := widget.NewLabel(localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "queue"})) title.TextStyle.Bold = true @@ -98,6 +100,7 @@ func NewQueueLayoutObject(queue QueueListContract, localizerService LocalizerCon items: items, localizerService: localizerService, queueStatisticsFormat: queueStatisticsFormat, + ffplayService: ffplayService, } queue.AddListener(queueLayoutObject) @@ -121,11 +124,18 @@ func (o QueueLayoutObject) Add(id int, queue *Queue) { 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(statusMessage), + container.NewHScroll(container.NewHBox( + buttonPlay, + statusMessage, + )), container.NewHScroll(messageError), canvas.NewLine(theme.Color(theme.ColorNameFocus)), container.NewPadded(), @@ -141,6 +151,7 @@ func (o QueueLayoutObject) Add(id int, queue *Queue) { ProgressBar: progressBar, StatusMessage: statusMessage, MessageError: messageError, + buttonPlay: buttonPlay, status: &queue.Status, } o.container.Add(content) @@ -169,6 +180,20 @@ func (o QueueLayoutObject) ChangeQueueStatus(queueId int, queue *Queue) { 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 { diff --git a/kernel/window.go b/kernel/window.go index 7bc565c..cec7b5b 100644 --- a/kernel/window.go +++ b/kernel/window.go @@ -83,5 +83,7 @@ func (w Window) GetLayout() LayoutContract { } func (w Window) SetOnDropped(callback func(position fyne.Position, uris []fyne.URI)) { - w.windowFyne.SetOnDropped(callback) + fyne.Do(func() { + w.windowFyne.SetOnDropped(callback) + }) } diff --git a/languages/active.en.toml b/languages/active.en.toml index fb2946f..8c4b120 100644 --- a/languages/active.en.toml +++ b/languages/active.en.toml @@ -23,8 +23,8 @@ hash = "sha1-0ec753be8df955a117404fb634b01b45eb386e2a" other = "Cancel" [changeFFPath] -hash = "sha1-46793a2844600d0eb19fa3540fb9564ee5705491" -other = "FFmpeg and FFprobe" +hash = "sha1-1f704de0560f8135eb6924cd232ed919ca2e5af0" +other = "FFmpeg, FFprobe and FFplay" [changeLanguage] hash = "sha1-8b276eaf378d485c769fb3d5dcc06dfc25b0c01b" @@ -238,6 +238,14 @@ other = "this is not FFmpeg" hash = "sha1-9a4148d42186b6b32cf83bef726e23022c53283f" other = "Could not determine FFmpeg version" +[errorFFplay] +hash = "sha1-988122112ac6002094e25518cfb5f0d606217298" +other = "this is not FFplay" + +[errorFFplayVersion] +hash = "sha1-cd60928d20d93210e103dd464306ab138bf1b184" +other = "Could not determine FFplay version" + [errorFFprobe] hash = "sha1-86d1b0b4c4ccd6a4f71e758fc67ce11aff4ba9b8" other = "this is not FFprobe" @@ -330,6 +338,10 @@ other = "Enable option" hash = "sha1-fafc50f1db0f720fe83a96cd70a9e1ad824e96b6" other = "Path to FFmpeg:" +[pathToFfplay] +hash = "sha1-5389830dd75a63aa8a5e41e8f07c5fadd8385398" +other = "Path to FFplay:" + [pathToFfprobe] hash = "sha1-b872edc9633a2e81ef678dc46fe46a7e91732024" other = "Path to FFprobe:" diff --git a/languages/active.kk.toml b/languages/active.kk.toml index 713378e..53b83d4 100644 --- a/languages/active.kk.toml +++ b/languages/active.kk.toml @@ -23,8 +23,8 @@ hash = "sha1-0ec753be8df955a117404fb634b01b45eb386e2a" other = "Болдырмау" [changeFFPath] -hash = "sha1-46793a2844600d0eb19fa3540fb9564ee5705491" -other = "FFmpeg және FFprobe" +hash = "sha1-1f704de0560f8135eb6924cd232ed919ca2e5af0" +other = "FFmpeg, FFprobe және FFplay" [changeLanguage] hash = "sha1-8b276eaf378d485c769fb3d5dcc06dfc25b0c01b" @@ -238,6 +238,14 @@ other = "бұл FFmpeg емес" hash = "sha1-9a4148d42186b6b32cf83bef726e23022c53283f" other = "FFmpeg нұсқасын анықтау мүмкін болмады" +[errorFFplay] +hash = "sha1-988122112ac6002094e25518cfb5f0d606217298" +other = "бұл FFplay емес" + +[errorFFplayVersion] +hash = "sha1-cd60928d20d93210e103dd464306ab138bf1b184" +other = "FFplay нұсқасын анықтау мүмкін болмады" + [errorFFprobe] hash = "sha1-86d1b0b4c4ccd6a4f71e758fc67ce11aff4ba9b8" other = "бұл FFprobe емес" @@ -330,6 +338,10 @@ other = "Опцияны қосу" hash = "sha1-fafc50f1db0f720fe83a96cd70a9e1ad824e96b6" other = "FFmpeg жол:" +[pathToFfplay] +hash = "sha1-5389830dd75a63aa8a5e41e8f07c5fadd8385398" +other = "FFplay жол:" + [pathToFfprobe] hash = "sha1-b872edc9633a2e81ef678dc46fe46a7e91732024" other = "FFprobe жол:" diff --git a/languages/active.ru.toml b/languages/active.ru.toml index 18e2ab2..cfafd1c 100644 --- a/languages/active.ru.toml +++ b/languages/active.ru.toml @@ -4,7 +4,7 @@ aboutText = "Простенький интерфейс для консольно buttonDownloadFFmpeg = "Скачать автоматически FFmpeg" buttonForSelectedDirTitle = "Сохранить в папку:" cancel = "Отмена" -changeFFPath = "FFmpeg и FFprobe" +changeFFPath = "FFmpeg, FFprobe и FFplay" changeLanguage = "Поменять язык" checkboxOverwriteOutputFilesTitle = "Разрешить перезаписать файл" choose = "выбрать" @@ -58,6 +58,8 @@ errorDatabaseTimeout = "Не смогли открыть файл конфигу errorDragAndDrop1File = "Можно перетащить только 1 файл" errorFFmpeg = "это не FFmpeg" errorFFmpegVersion = "Не смогли определить версию FFmpeg" +errorFFplay = "это не FFplay" +errorFFplayVersion = "Не смогли определить версию FFplay" errorFFprobe = "это не FFprobe" errorFFprobeVersion = "Не смогли определить версию FFprobe" errorIsFolder = "Можно перетаскивать только файл" @@ -81,6 +83,7 @@ licenseLinkOther = "Лицензии от других продуктов, ко or = "или" parameterCheckbox = "Включить параметр" pathToFfmpeg = "Путь к FFmpeg:" +pathToFfplay = "Путь к FFplay:" pathToFfprobe = "Путь к FFprobe:" preset_fast = "fast (медленней чем faster, но будет файл и меньше весить)" preset_faster = "faster (медленней чем veryfast, но будет файл и меньше весить)" diff --git a/main.go b/main.go index d6b6b83..b9d8ea2 100644 --- a/main.go +++ b/main.go @@ -36,15 +36,16 @@ func init() { return } - ffPathUtilities = &kernel.FFPathUtilities{FFmpeg: "", FFprobe: ""} + ffPathUtilities = &kernel.FFPathUtilities{FFmpeg: "", FFprobe: "", FFplay: ""} convertorService := kernel.NewService(ffPathUtilities) + ffplayService := kernel.NewFFplay(ffPathUtilities) queue := kernel.NewQueueList() application = kernel.NewApp( appMetadata, localizerService, queue, - kernel.NewQueueLayoutObject(queue, localizerService), + kernel.NewQueueLayoutObject(queue, localizerService, ffplayService), convertorService, ) } @@ -93,6 +94,14 @@ func main() { } ffPathUtilities.FFprobe = pathFFprobe + pathFFplay, err := convertorRepository.GetPathFfplay() + if err != nil && errors.Is(err, dberror.ErrRecordNotFound) == false { + errorView.PanicError(err) + application.GetWindow().ShowAndRun() + return + } + ffPathUtilities.FFplay = pathFFplay + application.RunConvertor() defer application.AfterClosing() diff --git a/menu/view.go b/menu/view.go index 1d4fde2..6c96a4f 100644 --- a/menu/view.go +++ b/menu/view.go @@ -12,7 +12,7 @@ import ( ) type ViewContract interface { - About(ffmpegVersion string, ffprobeVersion string) + About(ffmpegVersion string, ffprobeVersion string, ffplayVersion string) Gratitude() } @@ -60,7 +60,7 @@ func (v View) Gratitude() { view.Show() } -func (v View) About(ffmpegVersion string, ffprobeVersion string) { +func (v View) About(ffmpegVersion string, ffprobeVersion string, ffplayVersion string) { view := v.app.GetAppFyne().NewWindow(v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "about", })) @@ -134,6 +134,7 @@ func (v View) About(ffmpegVersion string, ffprobeVersion string) { )), v.getAboutFfmpeg(ffmpegVersion), v.getAboutFfprobe(ffprobeVersion), + v.getAboutFfplay(ffplayVersion), widget.NewCard(v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "AlsoUsedProgram", }), "", v.getOther()), @@ -207,6 +208,36 @@ func (v View) getAboutFfprobe(version string) *fyne.Container { ) } +func (v View) getAboutFfplay(version string) *fyne.Container { + programmName := canvas.NewText(" FFplay", colornames.Darkgreen) + programmName.TextStyle = fyne.TextStyle{Bold: true} + programmName.TextSize = 20 + + programmLink := widget.NewHyperlink(v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "programmLink", + }), &url.URL{ + Scheme: "https", + Host: "ffmpeg.org", + Path: "ffplay.html", + }) + + licenseLink := widget.NewHyperlink(v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "licenseLink", + }), &url.URL{ + Scheme: "https", + Host: "ffmpeg.org", + Path: "legal.html", + }) + + return container.NewVBox( + programmName, + widget.NewLabel(version), + widget.NewRichTextFromMarkdown("**FFmpeg** is a trademark of **[Fabrice Bellard](http://bellard.org/)**, originator of the **[FFmpeg](https://ffmpeg.org/about.html)** project."), + widget.NewRichTextFromMarkdown("This software uses libraries from the **FFmpeg** project under the **[LGPLv2.1](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**."), + container.NewHBox(programmLink, licenseLink), + ) +} + func (v View) getOther() *fyne.Container { return container.NewVBox( canvas.NewLine(colornames.Darkgreen), -- 2.47.2 From 883bf376b0c66538bcb65c15163554df1868a631 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Wed, 21 May 2025 00:22:42 +0500 Subject: [PATCH 05/12] Add FFplay help feature and keyboard shortcut guide Introduced a new "Help FFplay" section in the help menu to provide information about FFplay player keyboard shortcuts and actions. --- handler/menu.go | 15 ++-- languages/active.en.toml | 96 +++++++++++++++++++++++++ languages/active.kk.toml | 96 +++++++++++++++++++++++++ languages/active.ru.toml | 24 +++++++ menu/view.go | 147 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 372 insertions(+), 6 deletions(-) diff --git a/handler/menu.go b/handler/menu.go index 586dafd..dff0534 100644 --- a/handler/menu.go +++ b/handler/menu.go @@ -88,14 +88,21 @@ func (h MenuHandler) getMenuHelp() *fyne.Menu { gratitude := fyne.NewMenuItem(h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "gratitude", - }), h.openGratitude) + }), h.menuView.Gratitude) h.app.GetLocalizerService().AddChangeCallback("gratitude", func(text string) { gratitude.Label = text }) + helpFFplay := fyne.NewMenuItem(h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplay", + }), h.menuView.HelpFFplay) + h.app.GetLocalizerService().AddChangeCallback("helpFFplay", func(text string) { + helpFFplay.Label = text + }) + help := fyne.NewMenu(h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "help", - }), about, gratitude) + }), helpFFplay, about, gratitude) h.app.GetLocalizerService().AddChangeCallback("help", func(text string) { help.Label = text help.Refresh() @@ -127,10 +134,6 @@ func (h MenuHandler) openAbout() { h.menuView.About(ffmpeg, ffprobe, ffplay) } -func (h MenuHandler) openGratitude() { - h.menuView.Gratitude() -} - func (h MenuHandler) LanguageSelection() { h.localizerView.LanguageSelection(func(lang kernel.Lang) { _, _ = h.localizerRepository.Save(lang.Code) diff --git a/languages/active.en.toml b/languages/active.en.toml index 8c4b120..d56d354 100644 --- a/languages/active.en.toml +++ b/languages/active.en.toml @@ -306,6 +306,102 @@ other = "I sincerely thank you for your invaluable\n\r and timely assistance:" hash = "sha1-6a45cef900c668effcb2ab10da05855c1fd10f6f" other = "Help" +[helpFFplay] +hash = "sha1-ecc294b8b3d217ee1c2d63dc2f0253c3d1b3712c" +other = "FFplay Player Keys" + +[helpFFplayActivateFrameStepMode] +hash = "sha1-f47ede90932d69465f6197cb2a7cc4d1e3ab150e" +other = "Activate frame-by-frame mode." + +[helpFFplayCycleVideoFiltersOrShowModes] +hash = "sha1-83bb702c777e4768cdc326a668d541c23ab759b7" +other = "A cycle of video filters or display modes." + +[helpFFplayDecreaseVolume] +hash = "sha1-de28db96a9c22be885ec5067a13f8f17fd3954bc" +other = "Decrease the volume." + +[helpFFplayDescription] +hash = "sha1-f5441f6aee76222c4120066575e80c2d177ac3c0" +other = "Description" + +[helpFFplayDoubleClickLeftMouseButton] +hash = "sha1-2657aa576055769952dfcde570fc9b4765d594ad" +other = "double click\nleft mouse button" + +[helpFFplayIncreaseVolume] +hash = "sha1-8ba7bde2d9a80f4a7cd122cf4973975698d3bd34" +other = "Increase the volume." + +[helpFFplayKeyDown] +hash = "sha1-c5aefd2f8c6908a69b08fe4a2d235b1ae0113470" +other = "down" + +[helpFFplayKeyHoldS] +hash = "sha1-89c5dd8287c15b3f40db66e06b038c34a715f02f" +other = "hold S" + +[helpFFplayKeyLeft] +hash = "sha1-feb671890703fb0300a436744d34018bbc7ba13a" +other = "left" + +[helpFFplayKeyRight] +hash = "sha1-a4f025d4bf7f90ee5bec6c48b2710bc9c5bbb267" +other = "right" + +[helpFFplayKeySpace] +hash = "sha1-a367ad00358ec44edc1d54a96df6f9114b0f8697" +other = "SPACE" + +[helpFFplayKeyUp] +hash = "sha1-e4845aa8c0e100a80eaf65446c59085236fd2098" +other = "up" + +[helpFFplayKeys] +hash = "sha1-0ad272ade8c568f394499f1492ecfab56e701e5d" +other = "Keys" + +[helpFFplayPause] +hash = "sha1-e83e107900fde0c39295f599c2cf8fba8d8cb604" +other = "Pause or continue playing." + +[helpFFplayQuit] +hash = "sha1-70785a2fd5d5a6519b7439f0d8cfcd7d54c5771d" +other = "Close the player." + +[helpFFplaySeekBForward10Minutes] +hash = "sha1-58ed63343376240f2596e447b5245c1805f35234" +other = "Fast forward 10 minutes." + +[helpFFplaySeekBForward1Minute] +hash = "sha1-3fe46b8d5413b7fdc53ae9ed9427bcb1769ec74c" +other = "Fast forward 1 minute." + +[helpFFplaySeekBackward10Minutes] +hash = "sha1-927dffe9af72ffd40f46873b452a4c90627bccf8" +other = "Rewind 10 minutes." + +[helpFFplaySeekBackward10Seconds] +hash = "sha1-e97615ecec0f8cf5647e8802bdda38dc2b0d809f" +other = "Rewind 10 seconds." + +[helpFFplaySeekBackward1Minute] +hash = "sha1-5b19e280a0850122c8ebc80c622491bb09520e1a" +other = "Rewind 1 minute." + +[helpFFplaySeekForward10Seconds] +hash = "sha1-8d840251d4a1668edaea3515df197a8a79031ec3" +other = "Fast forward 10 seconds." + +[helpFFplayToggleFullScreen] +hash = "sha1-d32df02849258c5b02f15e5711f54ee6a8a75fd4" +other = "Switch to full screen or exit full screen." + +[helpFFplayToggleMute] +hash = "sha1-4bdbb124fe8de3a8037c1e74719e9600b21b25ab" +other = "Mute or unmute." + [inProgressQueue] hash = "sha1-eff79c40e2100ae5fadf3a7d99336025edcca8b5" other = "In Progress" diff --git a/languages/active.kk.toml b/languages/active.kk.toml index 53b83d4..729140a 100644 --- a/languages/active.kk.toml +++ b/languages/active.kk.toml @@ -306,6 +306,102 @@ other = "Сізге баға жетпес және уақтылы көмекте hash = "sha1-6a45cef900c668effcb2ab10da05855c1fd10f6f" other = "Анықтама" +[helpFFplay] +hash = "sha1-ecc294b8b3d217ee1c2d63dc2f0253c3d1b3712c" +other = "FFplay ойнатқышының пернелері" + +[helpFFplayActivateFrameStepMode] +hash = "sha1-f47ede90932d69465f6197cb2a7cc4d1e3ab150e" +other = "Уақыт аралығын іске қосыңыз." + +[helpFFplayCycleVideoFiltersOrShowModes] +hash = "sha1-83bb702c777e4768cdc326a668d541c23ab759b7" +other = "Бейне сүзгілерінің немесе дисплей режимдерінің циклі." + +[helpFFplayDecreaseVolume] +hash = "sha1-de28db96a9c22be885ec5067a13f8f17fd3954bc" +other = "Дыбыс деңгейін төмендетіңіз." + +[helpFFplayDescription] +hash = "sha1-f5441f6aee76222c4120066575e80c2d177ac3c0" +other = "Сипаттама" + +[helpFFplayDoubleClickLeftMouseButton] +hash = "sha1-2657aa576055769952dfcde570fc9b4765d594ad" +other = "тінтуірдің сол жақ\nбатырмасын екі рет басу" + +[helpFFplayIncreaseVolume] +hash = "sha1-8ba7bde2d9a80f4a7cd122cf4973975698d3bd34" +other = "Дыбыс деңгейін арттыру." + +[helpFFplayKeyDown] +hash = "sha1-c5aefd2f8c6908a69b08fe4a2d235b1ae0113470" +other = "төмен" + +[helpFFplayKeyHoldS] +hash = "sha1-89c5dd8287c15b3f40db66e06b038c34a715f02f" +other = "ұстау S" + +[helpFFplayKeyLeft] +hash = "sha1-feb671890703fb0300a436744d34018bbc7ba13a" +other = "сол" + +[helpFFplayKeyRight] +hash = "sha1-a4f025d4bf7f90ee5bec6c48b2710bc9c5bbb267" +other = "құқық" + +[helpFFplayKeySpace] +hash = "sha1-a367ad00358ec44edc1d54a96df6f9114b0f8697" +other = "SPACE (пробел)" + +[helpFFplayKeyUp] +hash = "sha1-e4845aa8c0e100a80eaf65446c59085236fd2098" +other = "жоғары" + +[helpFFplayKeys] +hash = "sha1-0ad272ade8c568f394499f1492ecfab56e701e5d" +other = "Кілттер" + +[helpFFplayPause] +hash = "sha1-e83e107900fde0c39295f599c2cf8fba8d8cb604" +other = "Кідіртіңіз немесе жоғалтуды жалғастырыңыз." + +[helpFFplayQuit] +hash = "sha1-70785a2fd5d5a6519b7439f0d8cfcd7d54c5771d" +other = "Ойнатқышты жабыңыз." + +[helpFFplaySeekBForward10Minutes] +hash = "sha1-58ed63343376240f2596e447b5245c1805f35234" +other = "10 минутқа алға айналдырыңыз." + +[helpFFplaySeekBForward1Minute] +hash = "sha1-3fe46b8d5413b7fdc53ae9ed9427bcb1769ec74c" +other = "1 минутқа алға айналдырыңыз." + +[helpFFplaySeekBackward10Minutes] +hash = "sha1-927dffe9af72ffd40f46873b452a4c90627bccf8" +other = "10 минутқа артқа айналдырыңыз." + +[helpFFplaySeekBackward10Seconds] +hash = "sha1-e97615ecec0f8cf5647e8802bdda38dc2b0d809f" +other = "10 секундқа артқа айналдырыңыз." + +[helpFFplaySeekBackward1Minute] +hash = "sha1-5b19e280a0850122c8ebc80c622491bb09520e1a" +other = "1 минутқа артқа айналдырыңыз." + +[helpFFplaySeekForward10Seconds] +hash = "sha1-8d840251d4a1668edaea3515df197a8a79031ec3" +other = "10 секунд алға айналдырыңыз." + +[helpFFplayToggleFullScreen] +hash = "sha1-d32df02849258c5b02f15e5711f54ee6a8a75fd4" +other = "Толық экранға ауысу немесе толық экраннан шығу." + +[helpFFplayToggleMute] +hash = "sha1-4bdbb124fe8de3a8037c1e74719e9600b21b25ab" +other = "Дыбысты өшіріңіз немесе дыбысты қосыңыз." + [inProgressQueue] hash = "sha1-eff79c40e2100ae5fadf3a7d99336025edcca8b5" other = "Орындалуда" diff --git a/languages/active.ru.toml b/languages/active.ru.toml index cfafd1c..949ee38 100644 --- a/languages/active.ru.toml +++ b/languages/active.ru.toml @@ -75,6 +75,30 @@ formPreset = "Предустановка" gratitude = "Благодарность" gratitudeText = "Я искренне благодарю вас за неоценимую\n\rи своевременную помощь:" help = "Справка" +helpFFplay = "Клавиши проигрывателя FFplay" +helpFFplayActivateFrameStepMode = "Активировать покадровый режим." +helpFFplayCycleVideoFiltersOrShowModes = "Цикл видеофильтров или режимов показа." +helpFFplayDecreaseVolume = "Уменьшить громкость." +helpFFplayDescription = "Описание" +helpFFplayDoubleClickLeftMouseButton = "двойной щелчок\nлевой кнопкой мыши" +helpFFplayIncreaseVolume = "Увеличить громкость." +helpFFplayKeyDown = "вниз" +helpFFplayKeyHoldS = "держать S" +helpFFplayKeyLeft = "лево" +helpFFplayKeyRight = "право" +helpFFplayKeySpace = "SPACE (пробел)" +helpFFplayKeyUp = "вверх" +helpFFplayKeys = "Клавиши" +helpFFplayPause = "Поставить на паузу или продолжить проигрывать." +helpFFplayQuit = "Закрыть проигрыватель." +helpFFplaySeekBForward10Minutes = "Перемотать вперёд на 10 минут." +helpFFplaySeekBForward1Minute = "Перемотать вперёд на 1 минуту." +helpFFplaySeekBackward10Minutes = "Перемотать назад на 10 минут." +helpFFplaySeekBackward10Seconds = "Перемотать назад на 10 секунд." +helpFFplaySeekBackward1Minute = "Перемотать назад на 1 минуту." +helpFFplaySeekForward10Seconds = "Перемотать вперёд на 10 секунд." +helpFFplayToggleFullScreen = "Переключиться на полный экран или выйти с полного экрана." +helpFFplayToggleMute = "Отключить звук или включить звук." inProgressQueue = "Выполняется" languageSelectionFormHead = "Переключить язык" languageSelectionHead = "Выберите язык" diff --git a/menu/view.go b/menu/view.go index 6c96a4f..324b012 100644 --- a/menu/view.go +++ b/menu/view.go @@ -4,6 +4,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel" "github.com/nicksnyder/go-i18n/v2/i18n" @@ -14,6 +15,7 @@ import ( type ViewContract interface { About(ffmpegVersion string, ffprobeVersion string, ffplayVersion string) Gratitude() + HelpFFplay() } type View struct { @@ -144,6 +146,151 @@ func (v View) About(ffmpegVersion string, ffprobeVersion string, ffplayVersion s view.Show() } +func (v View) HelpFFplay() { + view := v.app.GetAppFyne().NewWindow(v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplay", + })) + view.Resize(fyne.Size{Width: 800, Height: 550}) + view.SetFixedSize(true) + + data := [][]string{ + []string{ + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayKeys", + }), + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayDescription", + }), + }, + []string{ + "Q, ESC", + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayQuit", + }), + }, + []string{ + "F, " + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayDoubleClickLeftMouseButton", + }), + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayToggleFullScreen", + }), + }, + []string{ + "P, " + + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayKeySpace", + }), + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayPause", + }), + }, + []string{ + "M", + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayToggleMute", + }), + }, + []string{ + "9, /", + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayDecreaseVolume", + }), + }, + []string{ + "0, *", + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayIncreaseVolume", + }), + }, + []string{ + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayKeyLeft", + }), + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplaySeekBackward10Seconds", + }), + }, + []string{ + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayKeyRight", + }), + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplaySeekForward10Seconds", + }), + }, + []string{ + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayKeyDown", + }), + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplaySeekBackward1Minute", + }), + }, + []string{ + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayKeyUp", + }), + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplaySeekBForward1Minute", + }), + }, + []string{ + "Page Down", + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplaySeekBackward10Minutes", + }), + }, + []string{ + "Page Up", + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplaySeekBForward10Minutes", + }), + }, + []string{ + "S, " + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayKeyHoldS", + }), + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayActivateFrameStepMode", + }), + }, + []string{ + "W", + v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "helpFFplayCycleVideoFiltersOrShowModes", + }), + }, + } + + list := widget.NewTable( + func() (int, int) { + return len(data), len(data[0]) + }, + func() fyne.CanvasObject { + return widget.NewLabel("") + }, + func(i widget.TableCellID, o fyne.CanvasObject) { + if i.Row == 0 { + o.(*widget.Label).TextStyle.Bold = true + o.(*widget.Label).SizeName = theme.SizeNameSubHeadingText + } + if i.Col == 0 { + o.(*widget.Label).TextStyle.Bold = true + } + o.(*widget.Label).SetText(data[i.Row][i.Col]) + }) + list.SetRowHeight(0, 40) + list.SetColumnWidth(0, 200) + list.SetColumnWidth(1, 585) + list.SetRowHeight(2, 55) + view.SetContent( + container.NewScroll(list), + ) + view.CenterOnScreen() + view.Show() +} + func (v View) getCopyright() *widget.RichText { return widget.NewRichTextFromMarkdown("Copyright (c) 2024 **[Leonid Nikitin (kor-elf)](https://git.kor-elf.net/kor-elf/)**.") } -- 2.47.2 From 712ec2f1826bd33a4867741375e6e58158bf47eb Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Thu, 22 May 2025 21:42:45 +0500 Subject: [PATCH 06/12] Remove language selection to a new settings section. --- handler/menu.go | 34 +++++++++++++--- languages/active.en.toml | 4 ++ languages/active.kk.toml | 4 ++ languages/active.ru.toml | 1 + main.go | 3 +- menu/view_setting.go | 88 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 menu/view_setting.go diff --git a/handler/menu.go b/handler/menu.go index dff0534..0bd5d07 100644 --- a/handler/menu.go +++ b/handler/menu.go @@ -17,6 +17,7 @@ type MenuHandler struct { app kernel.AppContract convertorHandler ConvertorHandlerContract menuView menu.ViewContract + menuViewSetting menu.ViewSettingContract localizerView localizer.ViewContract localizerRepository localizer.RepositoryContract } @@ -25,6 +26,7 @@ func NewMenuHandler( app kernel.AppContract, convertorHandler ConvertorHandlerContract, menuView menu.ViewContract, + menuViewSetting menu.ViewSettingContract, localizerView localizer.ViewContract, localizerRepository localizer.RepositoryContract, ) *MenuHandler { @@ -32,6 +34,7 @@ func NewMenuHandler( app: app, convertorHandler: convertorHandler, menuView: menuView, + menuViewSetting: menuViewSetting, localizerView: localizerView, localizerRepository: localizerRepository, } @@ -53,11 +56,11 @@ func (h MenuHandler) getMenuSettings() *fyne.Menu { quit.Label = text }) - languageSelection := fyne.NewMenuItem(h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ - MessageID: "changeLanguage", - }), h.LanguageSelection) - h.app.GetLocalizerService().AddChangeCallback("changeLanguage", func(text string) { - languageSelection.Label = text + settingsSelection := fyne.NewMenuItem(h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "settings", + }), h.settingsSelection) + h.app.GetLocalizerService().AddChangeCallback("settings", func(text string) { + settingsSelection.Label = text }) ffPathSelection := fyne.NewMenuItem(h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ @@ -69,7 +72,7 @@ func (h MenuHandler) getMenuSettings() *fyne.Menu { settings := fyne.NewMenu(h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "settings", - }), languageSelection, ffPathSelection, quit) + }), settingsSelection, ffPathSelection, quit) h.app.GetLocalizerService().AddChangeCallback("settings", func(text string) { settings.Label = text settings.Refresh() @@ -140,3 +143,22 @@ func (h MenuHandler) LanguageSelection() { h.convertorHandler.MainConvertor() }) } + +func (h MenuHandler) settingsSelection() { + save := func(setting *menu.SettingForm) error { + err := h.app.GetLocalizerService().SetCurrentLanguage(setting.Language) + if err != nil { + return err + } + _, err = h.localizerRepository.Save(setting.Language.Code) + if err != nil { + return err + } + h.convertorHandler.MainConvertor() + return nil + } + cancel := func() { + h.convertorHandler.MainConvertor() + } + h.menuViewSetting.Main(save, cancel) +} diff --git a/languages/active.en.toml b/languages/active.en.toml index d56d354..491dca3 100644 --- a/languages/active.en.toml +++ b/languages/active.en.toml @@ -422,6 +422,10 @@ other = "License information" hash = "sha1-359fff328717c05104e51a2d29f05bf1875d26b7" other = "Licenses from other products used in the program" +[menuSettingsLanguage] +hash = "sha1-ed3f0e507a5b4ed0649d7c768fe0d47413d839ba" +other = "Language" + [or] hash = "sha1-30bb0333ca1583110e4ced513b5d2455b86f529b" other = "or" diff --git a/languages/active.kk.toml b/languages/active.kk.toml index 729140a..a59a702 100644 --- a/languages/active.kk.toml +++ b/languages/active.kk.toml @@ -422,6 +422,10 @@ other = "Лицензия туралы ақпарат" hash = "sha1-359fff328717c05104e51a2d29f05bf1875d26b7" other = "Бағдарламада пайдаланылатын басқа өнімдердің лицензиялары" +[menuSettingsLanguage] +hash = "sha1-ed3f0e507a5b4ed0649d7c768fe0d47413d839ba" +other = "Тіл" + [or] hash = "sha1-30bb0333ca1583110e4ced513b5d2455b86f529b" other = "немесе" diff --git a/languages/active.ru.toml b/languages/active.ru.toml index 949ee38..2827dd5 100644 --- a/languages/active.ru.toml +++ b/languages/active.ru.toml @@ -104,6 +104,7 @@ languageSelectionFormHead = "Переключить язык" languageSelectionHead = "Выберите язык" licenseLink = "Сведения о лицензии" licenseLinkOther = "Лицензии от других продуктов, которые используются в программе" +menuSettingsLanguage = "Язык" or = "или" parameterCheckbox = "Включить параметр" pathToFfmpeg = "Путь к FFmpeg:" diff --git a/main.go b/main.go index b9d8ea2..31c5879 100644 --- a/main.go +++ b/main.go @@ -111,7 +111,8 @@ func main() { localizerRepository := localizer.NewRepository(settingRepository) menuView := menu.NewView(application) - mainMenu := handler.NewMenuHandler(application, convertorHandler, menuView, localizerView, localizerRepository) + menuSettingView := menu.NewViewSetting(application) + mainMenu := handler.NewMenuHandler(application, convertorHandler, menuView, menuSettingView, localizerView, localizerRepository) mainHandler := handler.NewMainHandler(application, convertorHandler, mainMenu, localizerRepository) mainHandler.Start() diff --git a/menu/view_setting.go b/menu/view_setting.go new file mode 100644 index 0000000..d7d6433 --- /dev/null +++ b/menu/view_setting.go @@ -0,0 +1,88 @@ +package menu + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel" + "github.com/nicksnyder/go-i18n/v2/i18n" + "image/color" +) + +type ViewSettingContract interface { + Main( + save func(*SettingForm) error, + cancel func(), + ) +} + +type SettingForm struct { + Language kernel.Lang +} + +type ViewSetting struct { + app kernel.AppContract +} + +func NewViewSetting(app kernel.AppContract) *ViewSetting { + return &ViewSetting{ + app: app, + } +} + +func (v ViewSetting) Main(save func(*SettingForm) error, cancel func()) { + errorMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255}) + errorMessage.TextSize = 16 + errorMessage.TextStyle = fyne.TextStyle{Bold: true} + + viewSettingForm := &SettingForm{ + Language: v.app.GetLocalizerService().GetCurrentLanguage().Lang, + } + + languageItems := []string{} + langByTitle := map[string]kernel.Lang{} + for _, language := range v.app.GetLocalizerService().GetLanguages() { + languageItems = append(languageItems, language.Title) + langByTitle[language.Title] = language + } + selectLanguages := widget.NewSelect(languageItems, func(s string) { + if lang, ok := langByTitle[s]; ok { + viewSettingForm.Language = lang + } + }) + selectLanguages.Selected = v.app.GetLocalizerService().GetCurrentLanguage().Lang.Title + + form := &widget.Form{ + Items: []*widget.FormItem{ + { + Text: v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "menuSettingsLanguage", + }), + Widget: selectLanguages, + }, + { + Widget: errorMessage, + }, + }, + SubmitText: v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "save", + }), + OnSubmit: func() { + err := save(viewSettingForm) + if err != nil { + errorMessage.Text = err.Error() + } + }, + } + if cancel != nil { + form.OnCancel = cancel + form.CancelText = v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "cancel", + }) + } + + messageHead := v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "settings", + }) + v.app.GetWindow().SetContent(widget.NewCard(messageHead, "", form)) +} -- 2.47.2 From 82167f042f33df315b118d1e7ccb06179934e59f Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Fri, 23 May 2025 20:18:05 +0500 Subject: [PATCH 07/12] Add theme management functionality to the application Implemented a theme management system allowing users to select and persist themes (default, light, dark) in the settings menu. --- handler/menu.go | 10 +++ languages/active.en.toml | 16 ++++ languages/active.kk.toml | 16 ++++ languages/active.ru.toml | 4 + main.go | 8 +- menu/view_setting.go | 40 ++++++++-- theme/repository.go | 28 +++++++ theme/theme.go | 158 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 270 insertions(+), 10 deletions(-) create mode 100644 theme/repository.go create mode 100644 theme/theme.go diff --git a/handler/menu.go b/handler/menu.go index 0bd5d07..fa32d8b 100644 --- a/handler/menu.go +++ b/handler/menu.go @@ -5,6 +5,7 @@ import ( "git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/localizer" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/menu" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/theme" "github.com/nicksnyder/go-i18n/v2/i18n" ) @@ -20,6 +21,7 @@ type MenuHandler struct { menuViewSetting menu.ViewSettingContract localizerView localizer.ViewContract localizerRepository localizer.RepositoryContract + themeService theme.ThemeContract } func NewMenuHandler( @@ -29,6 +31,7 @@ func NewMenuHandler( menuViewSetting menu.ViewSettingContract, localizerView localizer.ViewContract, localizerRepository localizer.RepositoryContract, + themeService theme.ThemeContract, ) *MenuHandler { return &MenuHandler{ app: app, @@ -37,6 +40,7 @@ func NewMenuHandler( menuViewSetting: menuViewSetting, localizerView: localizerView, localizerRepository: localizerRepository, + themeService: themeService, } } @@ -154,6 +158,12 @@ func (h MenuHandler) settingsSelection() { if err != nil { return err } + + err = h.themeService.SetCurrentTheme(setting.ThemeInfo) + if err != nil { + return err + } + h.convertorHandler.MainConvertor() return nil } diff --git a/languages/active.en.toml b/languages/active.en.toml index 491dca3..e89a35c 100644 --- a/languages/active.en.toml +++ b/languages/active.en.toml @@ -426,6 +426,10 @@ other = "Licenses from other products used in the program" hash = "sha1-ed3f0e507a5b4ed0649d7c768fe0d47413d839ba" other = "Language" +[menuSettingsTheme] +hash = "sha1-553c45f1b84a92b08dc1f088c13f924cde95765e" +other = "Theme" + [or] hash = "sha1-30bb0333ca1583110e4ced513b5d2455b86f529b" other = "or" @@ -522,6 +526,18 @@ other = "Settings" hash = "sha1-f5b8ed88e9609963035d2235be0a79bbec619976" other = "Checking FFmpeg for serviceability..." +[themesNameDark] +hash = "sha1-bd16b234708a2515a9f2d0ca41fb11e7fe8a38a2" +other = "Dark" + +[themesNameDefault] +hash = "sha1-469631cb165dcbbfea9e747056c25fbccb28c481" +other = "Default" + +[themesNameLight] +hash = "sha1-8080010c5e7d7edf56e89a99d8a2422898417845" +other = "Light" + [titleDownloadLink] hash = "sha1-92df86371f6c3a06ca1e4754f113142776a32d49" other = "You can download it from here" diff --git a/languages/active.kk.toml b/languages/active.kk.toml index a59a702..72e3e16 100644 --- a/languages/active.kk.toml +++ b/languages/active.kk.toml @@ -426,6 +426,10 @@ other = "Бағдарламада пайдаланылатын басқа өні hash = "sha1-ed3f0e507a5b4ed0649d7c768fe0d47413d839ba" other = "Тіл" +[menuSettingsTheme] +hash = "sha1-553c45f1b84a92b08dc1f088c13f924cde95765e" +other = "Тақырып" + [or] hash = "sha1-30bb0333ca1583110e4ced513b5d2455b86f529b" other = "немесе" @@ -522,6 +526,18 @@ other = "Параметрлер" hash = "sha1-f5b8ed88e9609963035d2235be0a79bbec619976" other = "FFmpeg функционалдығы тексерілуде..." +[themesNameDark] +hash = "sha1-bd16b234708a2515a9f2d0ca41fb11e7fe8a38a2" +other = "Қараңғы тақырып" + +[themesNameDefault] +hash = "sha1-469631cb165dcbbfea9e747056c25fbccb28c481" +other = "Әдепкі бойынша" + +[themesNameLight] +hash = "sha1-8080010c5e7d7edf56e89a99d8a2422898417845" +other = "Жеңіл тақырып" + [titleDownloadLink] hash = "sha1-92df86371f6c3a06ca1e4754f113142776a32d49" other = "Сіз оны осы жерден жүктей аласыз" diff --git a/languages/active.ru.toml b/languages/active.ru.toml index 2827dd5..638bde7 100644 --- a/languages/active.ru.toml +++ b/languages/active.ru.toml @@ -105,6 +105,7 @@ languageSelectionHead = "Выберите язык" licenseLink = "Сведения о лицензии" licenseLinkOther = "Лицензии от других продуктов, которые используются в программе" menuSettingsLanguage = "Язык" +menuSettingsTheme = "Тема" or = "или" parameterCheckbox = "Включить параметр" pathToFfmpeg = "Путь к FFmpeg:" @@ -129,6 +130,9 @@ selectFFPathTitle = "Укажите путь к FFmpeg и к FFprobe" selectFormat = "Расширение файла:" settings = "Настройки" testFF = "Проверка FFmpeg на работоспособность..." +themesNameDark = "Тёмная" +themesNameDefault = "По умолчанию" +themesNameLight = "Светлая" titleDownloadLink = "Скачать можно от сюда" total = "Всего" unzipRun = "Распаковывается..." diff --git a/main.go b/main.go index 31c5879..18d96ff 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "git.kor-elf.net/kor-elf/gui-for-ffmpeg/menu" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/migration" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/setting" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/theme" "go.etcd.io/bbolt" "golang.org/x/text/language" "os" @@ -109,10 +110,13 @@ func main() { convertorView := convertor.NewView(application) convertorHandler := handler.NewConvertorHandler(application, convertorView, errorView, convertorRepository, settingDirectoryForSaving) + themeRepository := theme.NewRepository(settingRepository) + themeService := theme.NewTheme(application, themeRepository) + localizerRepository := localizer.NewRepository(settingRepository) menuView := menu.NewView(application) - menuSettingView := menu.NewViewSetting(application) - mainMenu := handler.NewMenuHandler(application, convertorHandler, menuView, menuSettingView, localizerView, localizerRepository) + menuSettingView := menu.NewViewSetting(application, themeService) + mainMenu := handler.NewMenuHandler(application, convertorHandler, menuView, menuSettingView, localizerView, localizerRepository, themeService) mainHandler := handler.NewMainHandler(application, convertorHandler, mainMenu, localizerRepository) mainHandler.Start() diff --git a/menu/view_setting.go b/menu/view_setting.go index d7d6433..045662e 100644 --- a/menu/view_setting.go +++ b/menu/view_setting.go @@ -5,6 +5,7 @@ import ( "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/widget" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/theme" "github.com/nicksnyder/go-i18n/v2/i18n" "image/color" ) @@ -17,16 +18,19 @@ type ViewSettingContract interface { } type SettingForm struct { - Language kernel.Lang + Language kernel.Lang + ThemeInfo theme.ThemeInfoContract } type ViewSetting struct { - app kernel.AppContract + app kernel.AppContract + themeService theme.ThemeContract } -func NewViewSetting(app kernel.AppContract) *ViewSetting { +func NewViewSetting(app kernel.AppContract, themeService theme.ThemeContract) *ViewSetting { return &ViewSetting{ - app: app, + app: app, + themeService: themeService, } } @@ -36,7 +40,8 @@ func (v ViewSetting) Main(save func(*SettingForm) error, cancel func()) { errorMessage.TextStyle = fyne.TextStyle{Bold: true} viewSettingForm := &SettingForm{ - Language: v.app.GetLocalizerService().GetCurrentLanguage().Lang, + Language: v.app.GetLocalizerService().GetCurrentLanguage().Lang, + ThemeInfo: v.themeService.GetCurrentThemeInfo(), } languageItems := []string{} @@ -45,12 +50,25 @@ func (v ViewSetting) Main(save func(*SettingForm) error, cancel func()) { languageItems = append(languageItems, language.Title) langByTitle[language.Title] = language } - selectLanguages := widget.NewSelect(languageItems, func(s string) { + selectLanguage := widget.NewSelect(languageItems, func(s string) { if lang, ok := langByTitle[s]; ok { viewSettingForm.Language = lang } }) - selectLanguages.Selected = v.app.GetLocalizerService().GetCurrentLanguage().Lang.Title + selectLanguage.Selected = v.app.GetLocalizerService().GetCurrentLanguage().Lang.Title + + themeItems := []string{} + themeByTitle := map[string]theme.ThemeInfoContract{} + for _, themeInfo := range v.themeService.List() { + themeItems = append(themeItems, themeInfo.GetTitle()) + themeByTitle[themeInfo.GetTitle()] = themeInfo + } + selectTheme := widget.NewSelect(themeItems, func(s string) { + if themeInfo, ok := themeByTitle[s]; ok { + viewSettingForm.ThemeInfo = themeInfo + } + }) + selectTheme.Selected = v.themeService.GetCurrentThemeInfo().GetTitle() form := &widget.Form{ Items: []*widget.FormItem{ @@ -58,7 +76,13 @@ func (v ViewSetting) Main(save func(*SettingForm) error, cancel func()) { Text: v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "menuSettingsLanguage", }), - Widget: selectLanguages, + Widget: selectLanguage, + }, + { + Text: v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "menuSettingsTheme", + }), + Widget: selectTheme, }, { Widget: errorMessage, diff --git a/theme/repository.go b/theme/repository.go new file mode 100644 index 0000000..91b2351 --- /dev/null +++ b/theme/repository.go @@ -0,0 +1,28 @@ +package theme + +import "git.kor-elf.net/kor-elf/gui-for-ffmpeg/setting" + +type RepositoryContract interface { + GetCode() string + Save(code string) (setting.Setting, error) +} + +type Repository struct { + settingRepository setting.RepositoryContract +} + +func NewRepository(settingRepository setting.RepositoryContract) *Repository { + return &Repository{settingRepository: settingRepository} +} + +func (r Repository) GetCode() string { + name, err := r.settingRepository.GetValue("theme") + if err != nil { + return "default" + } + return name +} + +func (r Repository) Save(code string) (setting.Setting, error) { + return r.settingRepository.CreateOrUpdate("theme", code) +} diff --git a/theme/theme.go b/theme/theme.go new file mode 100644 index 0000000..ba0dfa0 --- /dev/null +++ b/theme/theme.go @@ -0,0 +1,158 @@ +package theme + +import ( + "fyne.io/fyne/v2" + fyneTheme "fyne.io/fyne/v2/theme" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel" + "github.com/nicksnyder/go-i18n/v2/i18n" + "image/color" +) + +type ThemeContract interface { + List() map[string]ThemeInfoContract + GetCurrentThemeInfo() ThemeInfoContract + SetCurrentTheme(themeInfo ThemeInfoContract) error +} + +type theme struct { + app kernel.AppContract + repository RepositoryContract + list map[string]ThemeInfoContract +} + +func NewTheme(app kernel.AppContract, repository RepositoryContract) ThemeContract { + theme := &theme{ + app: app, + repository: repository, + list: getThemes(app.GetLocalizerService()), + } + + theme.init() + + return theme +} + +func (t theme) init() { + themeInfo := t.GetCurrentThemeInfo() + if themeInfo.GetName() == "default" { + t.app.GetAppFyne().Settings().SetTheme(fyneTheme.DefaultTheme()) + return + } + t.app.GetAppFyne().Settings().SetTheme(&forcedVariant{theme: fyneTheme.DefaultTheme(), variant: themeInfo.GetVariant()}) +} + +func (t theme) GetCurrentThemeInfo() ThemeInfoContract { + themes := t.List() + if themeInfo, ok := themes[t.repository.GetCode()]; ok { + return themeInfo + } + + return themes["default"] +} + +func (t theme) List() map[string]ThemeInfoContract { + return t.list +} + +func (t theme) SetCurrentTheme(themeInfo ThemeInfoContract) error { + _, err := t.repository.Save(themeInfo.GetName()) + if err != nil { + return err + } + + if themeInfo.GetName() == "default" { + t.app.GetAppFyne().Settings().SetTheme(fyneTheme.DefaultTheme()) + return nil + } + t.app.GetAppFyne().Settings().SetTheme(&forcedVariant{theme: fyneTheme.DefaultTheme(), variant: themeInfo.GetVariant()}) + + return nil +} + +type ThemeInfoContract interface { + GetName() string + GetTitle() string + GetVariant() fyne.ThemeVariant +} + +type themeInfo struct { + name string + title string + variant fyne.ThemeVariant +} + +func (inf themeInfo) GetName() string { + return inf.name +} + +func (inf themeInfo) GetTitle() string { + return inf.title +} + +func (inf themeInfo) GetVariant() fyne.ThemeVariant { + return inf.variant +} + +func getThemes(localizer kernel.LocalizerContract) map[string]ThemeInfoContract { + themesNameDefault := &themeInfo{ + name: "default", + title: localizer.GetMessage(&i18n.LocalizeConfig{ + MessageID: "themesNameDefault", + }), + } + + themesNameLight := &themeInfo{ + name: "light", + title: localizer.GetMessage(&i18n.LocalizeConfig{ + MessageID: "themesNameLight", + }), + variant: fyneTheme.VariantLight, + } + + themesNameDark := &themeInfo{ + name: "dark", + title: localizer.GetMessage(&i18n.LocalizeConfig{ + MessageID: "themesNameDark", + }), + variant: fyneTheme.VariantDark, + } + + list := map[string]ThemeInfoContract{ + "default": themesNameDefault, + "light": themesNameLight, + "dark": themesNameDark, + } + + localizer.AddChangeCallback("themesNameDefault", func(text string) { + themesNameDefault.title = text + }) + localizer.AddChangeCallback("themesNameLight", func(text string) { + themesNameLight.title = text + }) + localizer.AddChangeCallback("themesNameDark", func(text string) { + themesNameDark.title = text + }) + + return list +} + +type forcedVariant struct { + theme fyne.Theme + variant fyne.ThemeVariant +} + +func (f *forcedVariant) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color { + return f.theme.Color(name, f.variant) +} + +func (f *forcedVariant) Font(style fyne.TextStyle) fyne.Resource { + return fyneTheme.DefaultTheme().Font(style) +} + +func (f *forcedVariant) Icon(name fyne.ThemeIconName) fyne.Resource { + return fyneTheme.DefaultTheme().Icon(name) +} + +func (f *forcedVariant) Size(name fyne.ThemeSizeName) float32 { + return fyneTheme.DefaultTheme().Size(name) +} -- 2.47.2 From 84b36dd29e863893bd92d3030f8cf2736228ecfd Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 25 May 2025 01:25:40 +0500 Subject: [PATCH 08/12] Make it possible to drag and drop multiple files It is now possible to add multiple files before sending them to the processing queue. --- convertor/view/conversion.go | 169 ++++++++++++----------- handler/convertor.go | 35 +++-- kernel/app.go | 39 +++++- kernel/items_to_convert.go | 151 +++++++++++++++++++++ kernel/layout.go | 185 +++++++------------------ kernel/progressbar.go | 254 +++++++++++++++++++++++++++++++++++ kernel/right_tabs.go | 76 +++++++++++ languages/active.en.toml | 34 +++-- languages/active.kk.toml | 34 +++-- languages/active.ru.toml | 10 +- main.go | 9 +- 11 files changed, 741 insertions(+), 255 deletions(-) create mode 100644 kernel/items_to_convert.go create mode 100644 kernel/progressbar.go create mode 100644 kernel/right_tabs.go diff --git a/convertor/view/conversion.go b/convertor/view/conversion.go index 5d95e18..546f273 100644 --- a/convertor/view/conversion.go +++ b/convertor/view/conversion.go @@ -24,30 +24,30 @@ type ConversionContract interface { } type Conversion struct { - app kernel.AppContract - form *form - conversionMessage *canvas.Text - fileForConversion *fileForConversion - directoryForSaving *directoryForSaving - overwriteOutputFiles *overwriteOutputFiles - selectEncoder *selectEncoder - runConvert func(setting HandleConvertSetting) + 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 { - FileInput kernel.File DirectoryForSave string OverwriteOutputFiles bool Format string Encoder encoder2.EncoderContract } -func NewConversion(app kernel.AppContract, formats encoder.ConvertorFormatsContract, runConvert func(setting HandleConvertSetting), settingDirectoryForSaving setting.DirectoryForSavingContract) *Conversion { +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) + fileForConversion := newFileForConversion(app, itemsToConvertService) directoryForSaving := newDirectoryForSaving(app, settingDirectoryForSaving) overwriteOutputFiles := newOverwriteOutputFiles(app) selectEncoder := newSelectEncoder(app, formats) @@ -85,14 +85,15 @@ func NewConversion(app kernel.AppContract, formats encoder.ConvertorFormatsContr form := newForm(app, items) return &Conversion{ - app: app, - form: form, - conversionMessage: conversionMessage, - fileForConversion: fileForConversion, - directoryForSaving: directoryForSaving, - overwriteOutputFiles: overwriteOutputFiles, - selectEncoder: selectEncoder, - runConvert: runConvert, + app: app, + form: form, + conversionMessage: conversionMessage, + fileForConversion: fileForConversion, + directoryForSaving: directoryForSaving, + overwriteOutputFiles: overwriteOutputFiles, + selectEncoder: selectEncoder, + runConvert: runConvert, + itemsToConvertService: itemsToConvertService, } } @@ -121,20 +122,32 @@ func (c Conversion) changeEncoder(encoder encoder2.EncoderContract) { } func (c Conversion) AfterViewContent() { - c.form.form.Disable() + if len(c.itemsToConvertService.GetItems()) == 0 { + c.form.form.Disable() + } } func (c Conversion) selectFileForConversion(err error) { c.conversionMessage.Text = "" - if err != nil { - c.form.form.Disable() - return + 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", @@ -160,18 +173,17 @@ func (c Conversion) submit() { c.directoryForSaving.button.Disable() c.form.form.Disable() - setting := HandleConvertSetting{ - FileInput: *c.fileForConversion.file, + c.runConvert(HandleConvertSetting{ DirectoryForSave: c.directoryForSaving.path, OverwriteOutputFiles: c.overwriteOutputFiles.IsChecked(), Format: c.selectEncoder.SelectFormat.Selected, Encoder: c.selectEncoder.Encoder, - } - c.runConvert(setting) + }) c.enableFormConversion() - c.fileForConversion.message.Text = "" - c.form.form.Disable() + if len(c.itemsToConvertService.GetItems()) == 0 { + c.form.form.Disable() + } } func (c Conversion) enableFormConversion() { @@ -188,44 +200,49 @@ type fileForConversion struct { changeCallbacks map[int]func(err error) } -func newFileForConversion(app kernel.AppContract) *fileForConversion { +func newFileForConversion(app kernel.AppContract, itemsToConvertService kernel.ItemsToConvertContract) *fileForConversion { + message := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255}) fileForConversion := &fileForConversion{ - file: &kernel.File{}, + message: message, + changeCallbacks: map[int]func(err error){}, } buttonTitle := app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "choose", - }) + "\n\r\n\r" + app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + }) + "\n" + app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ MessageID: "or", - }) + "\n\r\n\r" + app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ - MessageID: "dragAndDrop1File", + }) + "\n" + app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "dragAndDropFiles", }) - fileForConversion.message = canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255}) - fileForConversion.message.TextSize = 16 - fileForConversion.message.TextStyle = fyne.TextStyle{Bold: true} - 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 { - fileForConversion.message.Text = err.Error() - setStringErrorStyle(fileForConversion.message) + fyne.Do(func() { + fileForConversion.message.Text = err.Error() + fileForConversion.message.Refresh() + }) fileForConversion.eventSelectFile(err) return } if r == nil { return } + app.GetWindow().GetLayout().GetRightTabs().SelectAddedFilesTab() - fileForConversion.file.Path = r.URI().Path() - fileForConversion.file.Name = r.URI().Name() - fileForConversion.file.Ext = r.URI().Extension() - - fileForConversion.message.Text = r.URI().Path() - setStringSuccessStyle(fileForConversion.message) + itemsToConvertService.Add(&kernel.File{ + Path: r.URI().Path(), + Name: r.URI().Name(), + Ext: r.URI().Extension(), + }) fileForConversion.eventSelectFile(nil) @@ -239,43 +256,41 @@ func newFileForConversion(app kernel.AppContract) *fileForConversion { return } - if len(uris) > 1 { + 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) + } + if isError { fileForConversion.message.Text = app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ - MessageID: "errorDragAndDrop1File", + MessageID: "errorDragAndDropFile", }) setStringErrorStyle(fileForConversion.message) fileForConversion.eventSelectFile(errors.New(fileForConversion.message.Text)) - return - } - - uri := uris[0] - info, err := os.Stat(uri.Path()) - if err != nil { - fileForConversion.message.Text = err.Error() - setStringErrorStyle(fileForConversion.message) - fileForConversion.eventSelectFile(err) - return - } - if info.IsDir() { - fileForConversion.message.Text = app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ - MessageID: "errorIsFolder", + } else { + fyne.Do(func() { + fileForConversion.message.Text = "" + fileForConversion.message.Refresh() }) - setStringErrorStyle(fileForConversion.message) - fileForConversion.eventSelectFile(errors.New(fileForConversion.message.Text)) - return } - - fileForConversion.file.Path = uri.Path() - fileForConversion.file.Name = uri.Name() - fileForConversion.file.Ext = uri.Extension() - - fileForConversion.message.Text = uri.Path() - setStringSuccessStyle(fileForConversion.message) - - fileForConversion.eventSelectFile(nil) - - listableURI := storage.NewFileURI(filepath.Dir(uri.Path())) - locationURI, _ = storage.ListerForURI(listableURI) }) return fileForConversion diff --git a/handler/convertor.go b/handler/convertor.go index e67fa81..a0cd29b 100644 --- a/handler/convertor.go +++ b/handler/convertor.go @@ -25,6 +25,7 @@ type ConvertorHandler struct { errorView error2.ViewContract convertorRepository convertor.RepositoryContract settingDirectoryForSaving setting.DirectoryForSavingContract + itemsToConvertService kernel.ItemsToConvertContract } func NewConvertorHandler( @@ -33,6 +34,7 @@ func NewConvertorHandler( errorView error2.ViewContract, convertorRepository convertor.RepositoryContract, settingDirectoryForSaving setting.DirectoryForSavingContract, + itemsToConvertService kernel.ItemsToConvertContract, ) *ConvertorHandler { return &ConvertorHandler{ app: app, @@ -40,6 +42,7 @@ func NewConvertorHandler( errorView: errorView, convertorRepository: convertorRepository, settingDirectoryForSaving: settingDirectoryForSaving, + itemsToConvertService: itemsToConvertService, } } @@ -50,7 +53,7 @@ func (h ConvertorHandler) MainConvertor() { h.errorView.PanicError(err) return } - conversion := view.NewConversion(h.app, formats, h.runConvert, h.settingDirectoryForSaving) + conversion := view.NewConversion(h.app, formats, h.runConvert, h.settingDirectoryForSaving, h.itemsToConvertService) h.convertorView.Main(conversion) return } @@ -77,16 +80,26 @@ func (h ConvertorHandler) GetFfplayVersion() (string, error) { } func (h ConvertorHandler) runConvert(setting view.HandleConvertSetting) { - h.app.GetQueue().Add(&kernel.ConvertSetting{ - VideoFileInput: setting.FileInput, - VideoFileOut: kernel.File{ - Path: setting.DirectoryForSave + helper.PathSeparator() + setting.FileInput.Name + "." + setting.Format, - Name: setting.FileInput.Name, - Ext: "." + setting.Format, - }, - OverwriteOutputFiles: setting.OverwriteOutputFiles, - Encoder: setting.Encoder, - }) + h.app.GetWindow().GetLayout().GetRightTabs().SelectFileQueueTab() + + for _, item := range h.itemsToConvertService.GetItems() { + file := item.GetFile() + if file == nil { + continue + } + + h.app.GetQueue().Add(&kernel.ConvertSetting{ + VideoFileInput: *file, + VideoFileOut: kernel.File{ + Path: setting.DirectoryForSave + helper.PathSeparator() + file.Name + "." + setting.Format, + Name: file.Name, + Ext: "." + setting.Format, + }, + OverwriteOutputFiles: setting.OverwriteOutputFiles, + Encoder: setting.Encoder, + }) + } + h.itemsToConvertService.AfterAddingQueue() } func (h ConvertorHandler) checkingFFPathUtilities() bool { diff --git a/kernel/app.go b/kernel/app.go index e1ec4f9..3babb14 100644 --- a/kernel/app.go +++ b/kernel/app.go @@ -12,6 +12,7 @@ type AppContract interface { GetQueue() QueueListContract GetLocalizerService() LocalizerContract GetConvertorService() ConvertorContract + GetFFplayService() FFplayContract AfterClosing() RunConvertor() } @@ -21,27 +22,36 @@ type App struct { Window WindowContract Queue QueueListContract - localizerService LocalizerContract - convertorService ConvertorContract + localizerService LocalizerContract + convertorService ConvertorContract + blockProgressbarService BlockProgressbarContract + ffplayService FFplayContract } func NewApp( metadata *fyne.AppMetadata, localizerService LocalizerContract, queue QueueListContract, - queueLayoutObject QueueLayoutObjectContract, + ffplayService FFplayContract, convertorService ConvertorContract, ) *App { app.SetMetadata(*metadata) a := app.New() + statusesText := GetBlockProgressbarStatusesText(localizerService) + blockProgressbarService := NewBlockProgressbar(statusesText, ffplayService) + rightTabsService := NewRightTabs(localizerService) + queueLayoutObject := NewQueueLayoutObject(queue, localizerService, ffplayService, rightTabsService, blockProgressbarService.GetContainer()) + return &App{ AppFyne: a, - Window: newWindow(a.NewWindow("GUI for FFmpeg"), NewLayout(queueLayoutObject, localizerService)), + Window: newWindow(a.NewWindow("GUI for FFmpeg"), NewLayout(queueLayoutObject, localizerService, rightTabsService)), Queue: queue, - localizerService: localizerService, - convertorService: convertorService, + localizerService: localizerService, + convertorService: convertorService, + blockProgressbarService: blockProgressbarService, + ffplayService: ffplayService, } } @@ -65,6 +75,10 @@ func (a App) GetConvertorService() ConvertorContract { return a.convertorService } +func (a App) GetFFplayService() FFplayContract { + return a.ffplayService +} + func (a App) AfterClosing() { for _, cmd := range a.convertorService.GetRunningProcesses() { _ = cmd.Process.Kill() @@ -81,22 +95,33 @@ func (a App) RunConvertor() { } queue.Status = StatusType(InProgress) a.Window.GetLayout().ChangeQueueStatus(queueId, queue) + if a.blockProgressbarService.GetContainer().Hidden { + a.blockProgressbarService.GetContainer().Show() + } totalDuration, err := a.convertorService.GetTotalDuration(&queue.Setting.VideoFileInput) if err != nil { totalDuration = 0 } - progress := a.Window.GetLayout().NewProgressbar(queueId, totalDuration) + + progress := a.blockProgressbarService.GetProgressbar( + totalDuration, + queue.Setting.VideoFileInput.Path, + a.localizerService, + ) err = a.convertorService.RunConvert(*queue.Setting, progress) if err != nil { queue.Status = StatusType(Error) queue.Error = err a.Window.GetLayout().ChangeQueueStatus(queueId, queue) + a.blockProgressbarService.ProcessEndedWithError(err.Error()) + continue } queue.Status = StatusType(Completed) a.Window.GetLayout().ChangeQueueStatus(queueId, queue) + a.blockProgressbarService.ProcessEndedWithSuccess(queue.Setting.VideoFileOut.Path) } }() } diff --git a/kernel/items_to_convert.go b/kernel/items_to_convert.go new file mode 100644 index 0000000..0adbc3d --- /dev/null +++ b/kernel/items_to_convert.go @@ -0,0 +1,151 @@ +package kernel + +import ( + "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" +) + +type ItemsToConvertContract interface { + Add(file *File) + GetItems() map[int]ItemToConvertContract + AfterAddingQueue() +} + +type ItemsToConvert struct { + nextId int + items map[int]ItemToConvertContract + itemsContainer *fyne.Container + ffplayService FFplayContract + isAutoRemove bool +} + +func NewItemsToConvert(itemsContainer *fyne.Container, ffplayService FFplayContract, localizerService LocalizerContract) *ItemsToConvert { + containerForItems := container.NewVBox() + ItemsToConvert := &ItemsToConvert{ + nextId: 0, + items: map[int]ItemToConvertContract{}, + itemsContainer: containerForItems, + ffplayService: ffplayService, + isAutoRemove: true, + } + + line := canvas.NewLine(theme.Color(theme.ColorNameFocus)) + line.StrokeWidth = 5 + checkboxAutoRemove := widget.NewCheck(localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "autoClearAfterAddingToQueue", + }), func(checked bool) { + ItemsToConvert.isAutoRemove = checked + }) + checkboxAutoRemove.SetChecked(ItemsToConvert.isAutoRemove) + localizerService.AddChangeCallback("autoClearAfterAddingToQueue", func(text string) { + checkboxAutoRemove.Text = text + }) + + buttonClear := widget.NewButton(localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "clearAll", + }), func() { + ItemsToConvert.clear() + }) + buttonClear.Importance = widget.DangerImportance + localizerService.AddChangeCallback("clearAll", func(text string) { + buttonClear.Text = text + }) + + itemsContainer.Add(container.NewVBox( + container.NewPadded(), + container.NewBorder(nil, nil, nil, buttonClear, container.NewHScroll(checkboxAutoRemove)), + container.NewPadded(), + line, + container.NewPadded(), + containerForItems, + )) + + return ItemsToConvert +} + +func (items *ItemsToConvert) Add(file *File) { + nextId := items.nextId + var content *fyne.Container + var buttonPlay *widget.Button + + buttonPlay = widget.NewButtonWithIcon("", theme.Icon(theme.IconNameMediaPlay), func() { + buttonPlay.Disable() + go func() { + _ = items.ffplayService.Run(FFplaySetting{ + PathToFile: file.Path, + }) + fyne.Do(func() { + buttonPlay.Enable() + }) + }() + }) + + buttonRemove := widget.NewButtonWithIcon("", theme.Icon(theme.IconNameDelete), func() { + items.itemsContainer.Remove(content) + items.itemsContainer.Refresh() + delete(items.items, nextId) + }) + buttonRemove.Importance = widget.DangerImportance + + content = container.NewVBox( + container.NewBorder( + nil, + nil, + buttonPlay, + buttonRemove, + container.NewHScroll(widget.NewLabel(file.Name)), + ), + container.NewHScroll(widget.NewLabel(file.Path)), + container.NewPadded(), + canvas.NewLine(theme.Color(theme.ColorNameFocus)), + container.NewPadded(), + ) + + items.itemsContainer.Add(content) + items.items[nextId] = NewItemToConvert(file, content) + items.nextId++ +} + +func (items *ItemsToConvert) GetItems() map[int]ItemToConvertContract { + return items.items +} + +func (items *ItemsToConvert) AfterAddingQueue() { + if items.isAutoRemove { + items.clear() + } +} + +func (items *ItemsToConvert) clear() { + items.itemsContainer.RemoveAll() + items.items = map[int]ItemToConvertContract{} +} + +type ItemToConvertContract interface { + GetFile() *File + GetContent() *fyne.Container +} + +type ItemToConvert struct { + file *File + content *fyne.Container +} + +func NewItemToConvert(file *File, content *fyne.Container) *ItemToConvert { + return &ItemToConvert{ + file: file, + content: content, + } +} + +func (item ItemToConvert) GetFile() *File { + return item.file +} + +func (item ItemToConvert) GetContent() *fyne.Container { + return item.content +} diff --git a/kernel/layout.go b/kernel/layout.go index 94ec22a..3d34179 100644 --- a/kernel/layout.go +++ b/kernel/layout.go @@ -1,8 +1,6 @@ package kernel import ( - "bufio" - "errors" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" @@ -10,31 +8,31 @@ import ( "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) + GetRightTabs() RightTabsContract } type Layout struct { layout *fyne.Container queueLayoutObject QueueLayoutObjectContract localizerService LocalizerContract + rightTabsService RightTabsContract } -func NewLayout(queueLayoutObject QueueLayoutObjectContract, localizerService LocalizerContract) *Layout { - layout := container.NewAdaptiveGrid(2, widget.NewLabel(""), container.NewVScroll(queueLayoutObject.GetCanvasObject())) +func NewLayout(queueLayoutObject QueueLayoutObjectContract, localizerService LocalizerContract, rightTabsService RightTabsContract) *Layout { + layout := container.NewAdaptiveGrid(2, widget.NewLabel(""), queueLayoutObject.GetCanvasObject()) return &Layout{ layout: layout, queueLayoutObject: queueLayoutObject, localizerService: localizerService, + rightTabsService: rightTabsService, } } @@ -43,18 +41,16 @@ func (l Layout) SetContent(content fyne.CanvasObject) *fyne.Container { 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) } +func (l Layout) GetRightTabs() RightTabsContract { + return l.rightTabsService +} + type QueueLayoutObjectContract interface { GetCanvasObject() fyne.CanvasObject - GetProgressbar(queueId int) *widget.ProgressBar ChangeQueueStatus(queueId int, queue *Queue) } @@ -63,6 +59,7 @@ type QueueLayoutObject struct { queue QueueListContract container *fyne.Container + containerItems *fyne.Container items map[int]QueueLayoutItem localizerService LocalizerContract queueStatisticsFormat *queueStatisticsFormat @@ -70,16 +67,16 @@ type QueueLayoutObject struct { } type QueueLayoutItem struct { - CanvasObject fyne.CanvasObject - ProgressBar *widget.ProgressBar - StatusMessage *canvas.Text - MessageError *canvas.Text - buttonPlay *widget.Button + CanvasObject fyne.CanvasObject + BlockMessageError *container.Scroll + StatusMessage *canvas.Text + MessageError *canvas.Text + buttonPlay *widget.Button status *StatusContract } -func NewQueueLayoutObject(queue QueueListContract, localizerService LocalizerContract, ffplayService FFplayContract) *QueueLayoutObject { +func NewQueueLayoutObject(queue QueueListContract, localizerService LocalizerContract, ffplayService FFplayContract, rightTabsService RightTabsContract, blockProgressbar *fyne.Container) *QueueLayoutObject { title := widget.NewLabel(localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "queue"})) title.TextStyle.Bold = true @@ -91,12 +88,27 @@ func NewQueueLayoutObject(queue QueueListContract, localizerService LocalizerCon items := map[int]QueueLayoutItem{} queueStatisticsFormat := newQueueStatisticsFormat(localizerService, &items) + line := canvas.NewLine(theme.Color(theme.ColorNameFocus)) + line.StrokeWidth = 5 + + rightTabsService.GetFileQueueContainer().Add(container.NewVBox( + container.NewPadded(), + container.NewHBox(title, queueStatisticsFormat.completed.widget, queueStatisticsFormat.error.widget), + container.NewHBox(queueStatisticsFormat.inProgress.widget, queueStatisticsFormat.waiting.widget, queueStatisticsFormat.total.widget), + container.NewPadded(), + line, + container.NewPadded(), + )) 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), + container: container.NewBorder( + container.NewVBox( + blockProgressbar, + widget.NewSeparator(), + ), + nil, nil, nil, container.NewVScroll(rightTabsService.GetTabs()), ), + containerItems: rightTabsService.GetFileQueueContainer(), items: items, localizerService: localizerService, queueStatisticsFormat: queueStatisticsFormat, @@ -112,31 +124,24 @@ 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() + blockMessageError := container.NewHScroll(messageError) + blockMessageError.Hide() content := container.NewVBox( container.NewHScroll(widget.NewLabel(queue.Setting.VideoFileInput.Name)), - progressBar, - container.NewHScroll(container.NewHBox( + container.NewHBox( buttonPlay, statusMessage, - )), - container.NewHScroll(messageError), + ), + blockMessageError, + container.NewPadded(), canvas.NewLine(theme.Color(theme.ColorNameFocus)), container.NewPadded(), ) @@ -147,14 +152,14 @@ func (o QueueLayoutObject) Add(id int, queue *Queue) { } o.items[id] = QueueLayoutItem{ - CanvasObject: content, - ProgressBar: progressBar, - StatusMessage: statusMessage, - MessageError: messageError, - buttonPlay: buttonPlay, - status: &queue.Status, + CanvasObject: content, + StatusMessage: statusMessage, + BlockMessageError: blockMessageError, + MessageError: messageError, + buttonPlay: buttonPlay, + status: &queue.Status, } - o.container.Add(content) + o.containerItems.Add(content) } func (o QueueLayoutObject) Remove(id int) { @@ -177,6 +182,7 @@ func (o QueueLayoutObject) ChangeQueueStatus(queueId int, queue *Queue) { item.MessageError.Text = queue.Error.Error() item.MessageError.Color = statusColor fyne.Do(func() { + item.BlockMessageError.Show() item.MessageError.Refresh() }) } @@ -219,101 +225,6 @@ 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 diff --git a/kernel/progressbar.go b/kernel/progressbar.go new file mode 100644 index 0000000..48b8485 --- /dev/null +++ b/kernel/progressbar.go @@ -0,0 +1,254 @@ +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 BlockProgressbarContract interface { + GetContainer() *fyne.Container + GetProgressbar(totalDuration float64, filePath string, localizerService LocalizerContract) Progress + ProcessEndedWithError(errorText string) + ProcessEndedWithSuccess(filePath string) +} + +type BlockProgressbar struct { + container *fyne.Container + label *widget.Label + progressbar *widget.ProgressBar + errorBlock *container.Scroll + messageError *canvas.Text + statusMessage *canvas.Text + buttonPlay *widget.Button + statusesText *BlockProgressbarStatusesText + ffplayService FFplayContract +} + +func NewBlockProgressbar(statusesText *BlockProgressbarStatusesText, ffplayService FFplayContract) *BlockProgressbar { + label := widget.NewLabel("") + progressbar := widget.NewProgressBar() + + statusMessage := canvas.NewText("", theme.Color(theme.ColorNamePrimary)) + messageError := canvas.NewText("", theme.Color(theme.ColorNameError)) + buttonPlay := widget.NewButtonWithIcon("", theme.Icon(theme.IconNameMediaPlay), func() { + + }) + buttonPlay.Hide() + + errorBlock := container.NewHScroll(messageError) + errorBlock.Hide() + + content := container.NewVBox( + container.NewHScroll(label), + progressbar, + container.NewHScroll(container.NewHBox( + buttonPlay, + statusMessage, + )), + errorBlock, + ) + content.Hide() + + return &BlockProgressbar{ + container: content, + label: label, + progressbar: progressbar, + errorBlock: errorBlock, + messageError: messageError, + statusMessage: statusMessage, + buttonPlay: buttonPlay, + statusesText: statusesText, + ffplayService: ffplayService, + } +} + +func (block BlockProgressbar) GetContainer() *fyne.Container { + return block.container +} + +func (block BlockProgressbar) GetProgressbar(totalDuration float64, filePath string, localizerService LocalizerContract) Progress { + block.label.Text = filePath + block.statusMessage.Color = theme.Color(theme.ColorNamePrimary) + block.statusMessage.Text = block.statusesText.inProgress + block.messageError.Text = "" + fyne.Do(func() { + block.buttonPlay.Hide() + if block.errorBlock.Visible() { + block.errorBlock.Hide() + } + block.statusMessage.Refresh() + block.container.Refresh() + block.errorBlock.Refresh() + }) + + block.progressbar.Value = 0 + return NewProgress(totalDuration, block.progressbar, localizerService) +} + +func (block BlockProgressbar) ProcessEndedWithError(errorText string) { + fyne.Do(func() { + block.statusMessage.Color = theme.Color(theme.ColorNameError) + block.statusMessage.Text = block.statusesText.error + block.messageError.Text = errorText + block.errorBlock.Show() + }) +} + +func (block BlockProgressbar) ProcessEndedWithSuccess(filePath string) { + fyne.Do(func() { + block.statusMessage.Color = color.RGBA{R: 49, G: 127, B: 114, A: 255} + block.statusMessage.Text = block.statusesText.completed + block.buttonPlay.Show() + block.buttonPlay.OnTapped = func() { + block.buttonPlay.Disable() + go func() { + _ = block.ffplayService.Run(FFplaySetting{ + PathToFile: filePath, + }) + fyne.Do(func() { + block.buttonPlay.Enable() + }) + }() + } + }) +} + +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 BlockProgressbarStatusesText struct { + inProgress string + completed string + error string +} + +func GetBlockProgressbarStatusesText(localizerService LocalizerContract) *BlockProgressbarStatusesText { + statusesText := &BlockProgressbarStatusesText{ + inProgress: localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "inProgressQueue", + }), + completed: localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "completedQueue", + }), + error: localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorQueue", + }), + } + + localizerService.AddChangeCallback("inProgressQueue", func(text string) { + statusesText.inProgress = text + }) + + localizerService.AddChangeCallback("completedQueue", func(text string) { + statusesText.completed = text + }) + + localizerService.AddChangeCallback("errorQueue", func(text string) { + statusesText.error = text + }) + + return statusesText +} diff --git a/kernel/right_tabs.go b/kernel/right_tabs.go new file mode 100644 index 0000000..16f8de1 --- /dev/null +++ b/kernel/right_tabs.go @@ -0,0 +1,76 @@ +package kernel + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "github.com/nicksnyder/go-i18n/v2/i18n" +) + +type RightTabsContract interface { + GetTabs() *container.AppTabs + GetAddedFilesContainer() *fyne.Container + GetFileQueueContainer() *fyne.Container + SelectFileQueueTab() + SelectAddedFilesTab() +} + +type RightTabs struct { + tabs *container.AppTabs + + addedFilesContainer *fyne.Container + addedFilesTab *container.TabItem + + fileQueueContainer *fyne.Container + fileQueueTab *container.TabItem +} + +func NewRightTabs(localizerService LocalizerContract) *RightTabs { + addedFilesContainer := container.NewVBox() + addedFilesTab := container.NewTabItem(localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "addedFilesTitle"}), addedFilesContainer) + localizerService.AddChangeCallback("addedFilesTitle", func(text string) { + addedFilesTab.Text = text + }) + + fileQueueContainer := container.NewVBox() + fileQueueTab := container.NewTabItem(localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "fileQueueTitle"}), fileQueueContainer) + localizerService.AddChangeCallback("fileQueueTitle", func(text string) { + fileQueueTab.Text = text + }) + + tabs := container.NewAppTabs( + addedFilesTab, + fileQueueTab, + ) + + return &RightTabs{ + tabs: tabs, + addedFilesContainer: addedFilesContainer, + addedFilesTab: addedFilesTab, + fileQueueContainer: fileQueueContainer, + fileQueueTab: fileQueueTab, + } +} + +func (t RightTabs) GetTabs() *container.AppTabs { + return t.tabs +} + +func (t RightTabs) GetAddedFilesContainer() *fyne.Container { + return t.addedFilesContainer +} + +func (t RightTabs) GetFileQueueContainer() *fyne.Container { + return t.fileQueueContainer +} + +func (t RightTabs) SelectFileQueueTab() { + fyne.Do(func() { + t.tabs.Select(t.fileQueueTab) + }) +} + +func (t RightTabs) SelectAddedFilesTab() { + fyne.Do(func() { + t.tabs.Select(t.addedFilesTab) + }) +} diff --git a/languages/active.en.toml b/languages/active.en.toml index e89a35c..c5c158d 100644 --- a/languages/active.en.toml +++ b/languages/active.en.toml @@ -10,6 +10,14 @@ other = "About" hash = "sha1-8bd565814118ba8b90c40eb5b62acf8d2676e7d6" other = "A simple interface for the FFmpeg console utility. \nBut I am not the author of the FFmpeg utility itself." +[addedFilesTitle] +hash = "sha1-8ba0f6e477b0d78df2cc06f1d8b41b888623b851" +other = "Added files" + +[autoClearAfterAddingToQueue] +hash = "sha1-b3781695a4c35380d2cd075bb52f27a2a6d8f19c" +other = "Auto-clear after adding to queue" + [buttonDownloadFFmpeg] hash = "sha1-c223c2e15171156192bc3146aee91e6094bb475b" other = "Download FFmpeg automatically" @@ -38,6 +46,10 @@ other = "Allow file to be overwritten" hash = "sha1-f60bb5f761024d973834b5e9d25ceebce2c85f94" other = "choose" +[clearAll] +hash = "sha1-f32702d79ac206432400ac6b041695d020f6fa77" +other = "Clear List" + [completedQueue] hash = "sha1-398c7d4f7b0d522afb930769c0fbb1a9f4b61fbe" other = "Completed" @@ -62,9 +74,9 @@ other = "Will be downloaded from the site:" hash = "sha1-55f87f114628fa2d5d8e67d1e1cda22c0e4f9271" other = "Downloading..." -[dragAndDrop1File] -hash = "sha1-7259670822df1cc92ef5f06ed3c0e9407746975a" -other = "drag and drop 1 file" +[dragAndDropFiles] +hash = "sha1-07bb747cc7590d7a51cdf96dff49a74139097766" +other = "drag and drop files" [encoderGroupAudio] hash = "sha1-24321cb5400df96be8f3e2131918bebdb3a01bba" @@ -226,9 +238,9 @@ other = "could not create file 'database' in folder 'data'" hash = "sha1-f8153516ac2442d19be4b6daccce839d204ff09f" other = "Could not open configuration file.\nMake sure another copy of the program is not running!" -[errorDragAndDrop1File] -hash = "sha1-a8edb5cbd622f3ce4ec07a2377e22ec5fad4491b" -other = "You can only drag and drop 1 file." +[errorDragAndDropFile] +hash = "sha1-863cf1ad9c820d5b0c2006ceeaa29e25f81c1714" +other = "Not all files were added" [errorFFmpeg] hash = "sha1-ccf0b95c0d1b392dc215258d917eb4e5d0b88ed0" @@ -254,9 +266,9 @@ other = "this is not FFprobe" hash = "sha1-da7b37d7df3fafbd153665b13888413d52b24c17" other = "Failed to determine FFprobe version" -[errorIsFolder] -hash = "sha1-f937d090b6e320957514d850657cdf2f911dc6aa" -other = "You can only drag and drop a file" +[errorNoFilesAddedForConversion] +hash = "sha1-5cf1f65bef15cb0382e56be98f44c6abde56a314" +other = "There are no files to convert" [errorQueue] hash = "sha1-72aecd9ad85642d84d62dbbf3cf70953c5f696c7" @@ -290,6 +302,10 @@ other = "**FFmpeg** is a trademark of **[Fabrice Bellard](http://bellard.org/)** hash = "sha1-96ac799e1086b31fd8f5f8d4c801829d6c853f08" other = "File:" +[fileQueueTitle] +hash = "sha1-aec93b16baeaf55fed871075c9494a460e4a91b8" +other = "Queue" + [formPreset] hash = "sha1-7759891ba1ef9f7adc70defc7ac18fbf149c1a68" other = "Preset" diff --git a/languages/active.kk.toml b/languages/active.kk.toml index 72e3e16..2074652 100644 --- a/languages/active.kk.toml +++ b/languages/active.kk.toml @@ -10,6 +10,14 @@ other = "Бағдарлама туралы" hash = "sha1-8bd565814118ba8b90c40eb5b62acf8d2676e7d6" other = "FFmpeg консоль утилитасы үшін қарапайым интерфейс. \nБірақ мен FFmpeg утилитасының авторы емеспін." +[addedFilesTitle] +hash = "sha1-8ba0f6e477b0d78df2cc06f1d8b41b888623b851" +other = "Қосылған файлдар" + +[autoClearAfterAddingToQueue] +hash = "sha1-b3781695a4c35380d2cd075bb52f27a2a6d8f19c" +other = "Кезекке қосқаннан кейін тазалаңыз" + [buttonDownloadFFmpeg] hash = "sha1-c223c2e15171156192bc3146aee91e6094bb475b" other = "FFmpeg автоматты түрде жүктеп алыңыз" @@ -38,6 +46,10 @@ other = "Файлды қайта жазуға рұқсат беріңіз" hash = "sha1-f60bb5f761024d973834b5e9d25ceebce2c85f94" other = "таңдау" +[clearAll] +hash = "sha1-f32702d79ac206432400ac6b041695d020f6fa77" +other = "Тізімді өшіру" + [completedQueue] hash = "sha1-398c7d4f7b0d522afb930769c0fbb1a9f4b61fbe" other = "Дайын" @@ -62,9 +74,9 @@ other = "Сайттан жүктеледі:" hash = "sha1-55f87f114628fa2d5d8e67d1e1cda22c0e4f9271" other = "Жүктеп алынуда..." -[dragAndDrop1File] -hash = "sha1-7259670822df1cc92ef5f06ed3c0e9407746975a" -other = "1 файлды сүйреңіз" +[dragAndDropFiles] +hash = "sha1-07bb747cc7590d7a51cdf96dff49a74139097766" +other = "файлдарды сүйреп апарыңыз" [encoderGroupAudio] hash = "sha1-24321cb5400df96be8f3e2131918bebdb3a01bba" @@ -226,9 +238,9 @@ other = "'data' қалтасында 'database' файлын жасау мүмк hash = "sha1-f8153516ac2442d19be4b6daccce839d204ff09f" other = "Конфигурация файлын аша алмады.\nБағдарламаның басқа көшірмесі іске қосылмағанына көз жеткізіңіз!" -[errorDragAndDrop1File] -hash = "sha1-a8edb5cbd622f3ce4ec07a2377e22ec5fad4491b" -other = "Тек 1 файлды сүйреп апаруға болады" +[errorDragAndDropFile] +hash = "sha1-863cf1ad9c820d5b0c2006ceeaa29e25f81c1714" +other = "Барлық файлдар қосылмаған" [errorFFmpeg] hash = "sha1-ccf0b95c0d1b392dc215258d917eb4e5d0b88ed0" @@ -254,9 +266,9 @@ other = "бұл FFprobe емес" hash = "sha1-da7b37d7df3fafbd153665b13888413d52b24c17" other = "FFprobe нұсқасын анықтау мүмкін болмады" -[errorIsFolder] -hash = "sha1-f937d090b6e320957514d850657cdf2f911dc6aa" -other = "Тек файлды сүйреп апаруға болады" +[errorNoFilesAddedForConversion] +hash = "sha1-5cf1f65bef15cb0382e56be98f44c6abde56a314" +other = "Түрлендіруге арналған файлдар жоқ" [errorQueue] hash = "sha1-72aecd9ad85642d84d62dbbf3cf70953c5f696c7" @@ -290,6 +302,10 @@ other = "FFmpeg — **[FFmpeg](https://ffmpeg.org/about.html)** жобасын hash = "sha1-96ac799e1086b31fd8f5f8d4c801829d6c853f08" other = "Файл:" +[fileQueueTitle] +hash = "sha1-aec93b16baeaf55fed871075c9494a460e4a91b8" +other = "Кезек" + [formPreset] hash = "sha1-7759891ba1ef9f7adc70defc7ac18fbf149c1a68" other = "Алдын ала орнатылған" diff --git a/languages/active.ru.toml b/languages/active.ru.toml index 638bde7..2383c00 100644 --- a/languages/active.ru.toml +++ b/languages/active.ru.toml @@ -1,6 +1,8 @@ AlsoUsedProgram = "Также в программе используется:" about = "О программе" aboutText = "Простенький интерфейс для консольной утилиты FFmpeg. \nНо я не являюсь автором самой утилиты FFmpeg." +addedFilesTitle = "Добавленные файлы" +autoClearAfterAddingToQueue = "Очищать после добавления в очередь" buttonDownloadFFmpeg = "Скачать автоматически FFmpeg" buttonForSelectedDirTitle = "Сохранить в папку:" cancel = "Отмена" @@ -8,13 +10,14 @@ changeFFPath = "FFmpeg, FFprobe и FFplay" changeLanguage = "Поменять язык" checkboxOverwriteOutputFilesTitle = "Разрешить перезаписать файл" choose = "выбрать" +clearAll = "Очистить список" completedQueue = "Готово" converterVideoFilesSubmitTitle = "Конвертировать" converterVideoFilesTitle = "Конвертер видео, аудио и картинок" download = "Скачать" downloadFFmpegFromSite = "Будет скачано с сайта:" downloadRun = "Скачивается..." -dragAndDrop1File = "перетащить 1 файл" +dragAndDropFiles = "перетащить файлы" encoderGroupAudio = "Аудио" encoderGroupImage = "Картинки" encoderGroupVideo = "Видео" @@ -55,14 +58,14 @@ error = "Произошла ошибка!" errorConverter = "не смогли отконвертировать видео" errorDatabase = "не смогли создать файл 'database' в папке 'data'" errorDatabaseTimeout = "Не смогли открыть файл конфигурации.\nУбедитесь, что другая копия программы не запущена!" -errorDragAndDrop1File = "Можно перетащить только 1 файл" +errorDragAndDropFile = "Не все файлы добавились" errorFFmpeg = "это не FFmpeg" errorFFmpegVersion = "Не смогли определить версию FFmpeg" errorFFplay = "это не FFplay" errorFFplayVersion = "Не смогли определить версию FFplay" errorFFprobe = "это не FFprobe" errorFFprobeVersion = "Не смогли определить версию FFprobe" -errorIsFolder = "Можно перетаскивать только файл" +errorNoFilesAddedForConversion = "Нет файлов для конвертации" errorQueue = "Ошибка" errorSelectedEncoder = "Конвертер не выбран" errorSelectedFolderSave = "Папка для сохранения не выбрана!" @@ -71,6 +74,7 @@ exit = "Выход" ffmpegLGPL = "Это программное обеспечение использует библиотеки из проекта **FFmpeg** под **[LGPLv2.1](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**." ffmpegTrademark = "**FFmpeg** — торговая марка **[Fabrice Bellard](http://bellard.org/)** , создателя проекта **[FFmpeg](https://ffmpeg.org/about.html)**." fileForConversionTitle = "Файл:" +fileQueueTitle = "Очередь" formPreset = "Предустановка" gratitude = "Благодарность" gratitudeText = "Я искренне благодарю вас за неоценимую\n\rи своевременную помощь:" diff --git a/main.go b/main.go index 18d96ff..c681288 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,7 @@ func init() { appMetadata, localizerService, queue, - kernel.NewQueueLayoutObject(queue, localizerService, ffplayService), + ffplayService, convertorService, ) } @@ -108,7 +108,12 @@ func main() { localizerView := localizer.NewView(application) convertorView := convertor.NewView(application) - convertorHandler := handler.NewConvertorHandler(application, convertorView, errorView, convertorRepository, settingDirectoryForSaving) + itemsToConvertService := kernel.NewItemsToConvert( + application.GetWindow().GetLayout().GetRightTabs().GetAddedFilesContainer(), + application.GetFFplayService(), + application.GetLocalizerService(), + ) + convertorHandler := handler.NewConvertorHandler(application, convertorView, errorView, convertorRepository, settingDirectoryForSaving, itemsToConvertService) themeRepository := theme.NewRepository(settingRepository) themeService := theme.NewTheme(application, themeRepository) -- 2.47.2 From 5b15848048d6b563590ae56b08009e093489c619 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 25 May 2025 01:29:14 +0500 Subject: [PATCH 09/12] Update version to 0.9.0 Bump the application version from 0.8.0 to 0.9.0 in both `main.go` and `FyneApp.toml`. This reflects the latest changes and prepares the app for the next release. --- FyneApp.toml | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FyneApp.toml b/FyneApp.toml index 3b204ee..1fe1f80 100644 --- a/FyneApp.toml +++ b/FyneApp.toml @@ -2,7 +2,7 @@ Icon = "icon.png" Name = "GUI for FFmpeg" ID = "net.kor-elf.projects.gui-for-ffmpeg" - Version = "0.8.0" + Version = "0.9.0" Build = 4 [Migrations] diff --git a/main.go b/main.go index c681288..5f30405 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ func init() { appMetadata := &fyne.AppMetadata{ ID: "net.kor-elf.projects.gui-for-ffmpeg", Name: "GUI for FFmpeg", - Version: "0.8.0", + Version: "0.9.0", Icon: iconResource, } -- 2.47.2 From 5f72ce8c56a6092445a89f0776feb7304e3b3fb7 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 25 May 2025 01:48:33 +0500 Subject: [PATCH 10/12] Update screenshot for GUI in FFmpeg documentation Replaced the GUI screenshot to reflect the latest interface changes. This ensures alignment with the current functionality and improves user clarity. --- images/screenshot-gui-for-ffmpeg.png | Bin 65303 -> 78653 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/screenshot-gui-for-ffmpeg.png b/images/screenshot-gui-for-ffmpeg.png index fba50e6f0c3d5970f3e6bd1dda2a00f2c0ef86c2..0fc23fd6a47d7ac03fa66a884b78e1b309cc99b7 100644 GIT binary patch literal 78653 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

E{^L`OzEE8C7HTRo(@;!IeU5+azqK2l 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 literal 65303 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 Date: Sun, 25 May 2025 21:46:19 +0500 Subject: [PATCH 11/12] 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. --- convertor/view/conversion.go | 1 + 1 file changed, 1 insertion(+) diff --git a/convertor/view/conversion.go b/convertor/view/conversion.go index 546f273..947a37c 100644 --- a/convertor/view/conversion.go +++ b/convertor/view/conversion.go @@ -279,6 +279,7 @@ func newFileForConversion(app kernel.AppContract, itemsToConvertService kernel.I 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", -- 2.47.2 From 7340f43d6e453b85a36cd10cd113a5183c7cac2c Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Sun, 25 May 2025 22:51:59 +0500 Subject: [PATCH 12/12] Remove unused helper function from ffplay.go The `PrepareBackgroundCommand` function has been removed because it prevents the program window from being displayed on Windows. --- kernel/ffplay.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/kernel/ffplay.go b/kernel/ffplay.go index cc6b783..0b5538f 100644 --- a/kernel/ffplay.go +++ b/kernel/ffplay.go @@ -1,7 +1,6 @@ package kernel import ( - "git.kor-elf.net/kor-elf/gui-for-ffmpeg/helper" "os/exec" ) @@ -24,7 +23,6 @@ func NewFFplay(ffPathUtilities *FFPathUtilities) *FFplay { func (ffplay FFplay) Run(setting FFplaySetting) error { args := []string{setting.PathToFile} cmd := exec.Command(ffplay.ffPathUtilities.FFplay, args...) - helper.PrepareBackgroundCommand(cmd) return cmd.Start() } -- 2.47.2