diff --git a/internal/application/setting/ffmpeg.go b/internal/application/setting/ffmpeg.go index 84f0789..e20bb93 100644 --- a/internal/application/setting/ffmpeg.go +++ b/internal/application/setting/ffmpeg.go @@ -1,7 +1,12 @@ package setting func (s *setting) GetFFmpegPath() string { - return s.fyneApp.Preferences().String("ffmpegPath") + path := s.fyneApp.Preferences().String("ffmpegPath") + if path == "" { + return "ffmpeg" + } + + return path } func (s *setting) SetFFmpegPath(path string) { @@ -9,7 +14,12 @@ func (s *setting) SetFFmpegPath(path string) { } func (s *setting) GetFFprobePath() string { - return s.fyneApp.Preferences().String("ffprobePath") + path := s.fyneApp.Preferences().String("ffprobePath") + if path == "" { + return "ffprobe" + } + + return path } func (s *setting) SetFFprobePath(path string) { @@ -17,7 +27,12 @@ func (s *setting) SetFFprobePath(path string) { } func (s *setting) GetFFplayPath() string { - return s.fyneApp.Preferences().String("ffplayPath") + path := s.fyneApp.Preferences().String("ffplayPath") + if path == "" { + return "ffplay" + } + + return path } func (s *setting) SetFFplayPath(path string) { diff --git a/internal/controller/convertor.go b/internal/controller/convertor.go index 1b6ccdc..eba2f76 100644 --- a/internal/controller/convertor.go +++ b/internal/controller/convertor.go @@ -1,8 +1,58 @@ package controller -import "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view" +import ( + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/download/service" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view" +) func (c *controller) convertor() { content := view.Convertor() c.window.SetContent(content) } + +func (c *controller) settingConvertor(isAllowCancellation bool) { + ffmpegPath := c.app.GetFFmpegService().GetFFmpegPath() + ffprobePath := c.app.GetFFmpegService().GetFFprobePath() + ffplayPath := c.app.GetFFmpegService().GetFFplayPath() + + var cancel func() + cancel = nil + if isAllowCancellation { + cancel = func() { + c.convertor() + } + } + + content := view.ConfiguringFFmpegUtilities( + c.window, + ffmpegPath, + ffprobePath, + ffplayPath, + c.saveSettingConvertor, + cancel, + service.DownloadFFmpeg(c.app, c.saveSettingConvertor), + ) + c.window.SetContent(content) +} + +func (c *controller) saveSettingConvertor(ffmpegPath string, ffprobePath string, ffplayPath string) error { + var err error + + err = c.app.GetFFmpegService().ChangeFFmpeg(ffmpegPath) + if err != nil { + return err + } + + c.app.GetFFmpegService().ChangeFFprobe(ffprobePath) + if err != nil { + return err + } + + c.app.GetFFmpegService().ChangeFFplay(ffplayPath) + if err != nil { + return err + } + + c.convertor() + return nil +} diff --git a/internal/controller/main.go b/internal/controller/main.go index 2e5832a..11cac23 100644 --- a/internal/controller/main.go +++ b/internal/controller/main.go @@ -13,7 +13,7 @@ type ControllerContract interface { type controller struct { app application.AppContract - window window.MainWindowContract + window window.WindowContract } func NewController(app application.AppContract) ControllerContract { @@ -26,11 +26,10 @@ func NewController(app application.AppContract) ControllerContract { } func (c *controller) Start() { - c.window.Show() - isDefault, err := c.initLanguage() if err != nil { c.startWithError(err) + c.window.Show() return } @@ -46,14 +45,21 @@ func (c *controller) Start() { c.verificareaFFmpeg() }) c.window.SetContent(content) + c.window.Show() return } c.window.InitLayout() c.verificareaFFmpeg() + c.window.Show() } func (c *controller) verificareaFFmpeg() { + if !c.app.GetFFmpegService().UtilityCheck() { + c.settingConvertor(false) + return + } + c.convertor() } diff --git a/internal/ffmpeg/download/gui/download_anyos.go b/internal/ffmpeg/download/gui/download_anyos.go new file mode 100644 index 0000000..6957f9d --- /dev/null +++ b/internal/ffmpeg/download/gui/download_anyos.go @@ -0,0 +1,14 @@ +//go:build !windows && !linux +// +build !windows,!linux + +package gui + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" +) + +func DownloadFFmpeg(donwloadFFmpeg func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error) fyne.CanvasObject { + return container.NewVBox() +} diff --git a/internal/ffmpeg/download/gui/download_linux.go b/internal/ffmpeg/download/gui/download_linux.go new file mode 100644 index 0000000..4884408 --- /dev/null +++ b/internal/ffmpeg/download/gui/download_linux.go @@ -0,0 +1,59 @@ +//go:build linux +// +build linux + +package gui + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/widget" + "golang.org/x/image/colornames" + "image/color" +) + +func DownloadFFmpeg(donwloadFFmpeg func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error) fyne.CanvasObject { + errorDownloadFFmpegMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255}) + errorDownloadFFmpegMessage.TextSize = 16 + errorDownloadFFmpegMessage.TextStyle = fyne.TextStyle{Bold: true} + + progressDownloadFFmpegMessage := canvas.NewText("", color.RGBA{R: 49, G: 127, B: 114, A: 255}) + progressDownloadFFmpegMessage.TextSize = 16 + progressDownloadFFmpegMessage.TextStyle = fyne.TextStyle{Bold: true} + + progressBar := widget.NewProgressBar() + + var buttonDownloadFFmpeg *widget.Button + + buttonDownloadFFmpeg = widget.NewButton(lang.L("download"), func() { + fyne.Do(func() { + buttonDownloadFFmpeg.Disable() + }) + go func() { + err := donwloadFFmpeg(progressBar, progressDownloadFFmpegMessage) + if err != nil { + errorDownloadFFmpegMessage.Text = err.Error() + } + fyne.Do(func() { + buttonDownloadFFmpeg.Enable() + }) + }() + + }) + + downloadFFmpegFromSiteMessage := lang.L("downloadFFmpegFromSite") + + return container.NewVBox( + canvas.NewLine(colornames.Darkgreen), + widget.NewCard(lang.L("buttonDownloadFFmpeg"), "", container.NewVBox( + widget.NewRichTextFromMarkdown( + downloadFFmpegFromSiteMessage+" [https://github.com/BtbN/FFmpeg-Builds/releases](https://github.com/BtbN/FFmpeg-Builds/releases)", + ), + buttonDownloadFFmpeg, + container.NewHScroll(errorDownloadFFmpegMessage), + progressDownloadFFmpegMessage, + progressBar, + )), + ) +} diff --git a/internal/ffmpeg/download/gui/download_windows.go b/internal/ffmpeg/download/gui/download_windows.go new file mode 100644 index 0000000..1841311 --- /dev/null +++ b/internal/ffmpeg/download/gui/download_windows.go @@ -0,0 +1,59 @@ +//go:build windows +// +build windows + +package gui + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/widget" + "golang.org/x/image/colornames" + "image/color" +) + +func DownloadFFmpeg(donwloadFFmpeg func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error) fyne.CanvasObject { + errorDownloadFFmpegMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255}) + errorDownloadFFmpegMessage.TextSize = 16 + errorDownloadFFmpegMessage.TextStyle = fyne.TextStyle{Bold: true} + + progressDownloadFFmpegMessage := canvas.NewText("", color.RGBA{R: 49, G: 127, B: 114, A: 255}) + progressDownloadFFmpegMessage.TextSize = 16 + progressDownloadFFmpegMessage.TextStyle = fyne.TextStyle{Bold: true} + + progressBar := widget.NewProgressBar() + + var buttonDownloadFFmpeg *widget.Button + + buttonDownloadFFmpeg = widget.NewButton(lang.L("download"), func() { + + go func() { + fyne.Do(func() { + buttonDownloadFFmpeg.Disable() + }) + err := donwloadFFmpeg(progressBar, progressDownloadFFmpegMessage) + if err != nil { + errorDownloadFFmpegMessage.Text = err.Error() + } + fyne.Do(func() { + buttonDownloadFFmpeg.Enable() + }) + }() + }) + + downloadFFmpegFromSiteMessage := lang.L("downloadFFmpegFromSite") + + return container.NewVBox( + canvas.NewLine(colornames.Darkgreen), + widget.NewCard(lang.L("buttonDownloadFFmpeg"), "", container.NewVBox( + widget.NewRichTextFromMarkdown( + downloadFFmpegFromSiteMessage+" [https://github.com/BtbN/FFmpeg-Builds/releases](https://github.com/BtbN/FFmpeg-Builds/releases)", + ), + buttonDownloadFFmpeg, + container.NewHScroll(errorDownloadFFmpegMessage), + progressDownloadFFmpegMessage, + progressBar, + )), + ) +} diff --git a/internal/ffmpeg/download/service/download.go b/internal/ffmpeg/download/service/download.go new file mode 100644 index 0000000..f0a8438 --- /dev/null +++ b/internal/ffmpeg/download/service/download.go @@ -0,0 +1,21 @@ +package service + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/download/gui" +) + +func DownloadFFmpeg(app application.AppContract, save func(ffmpegPath string, ffprobePath string, ffplayPath string) error) fyne.CanvasObject { + return gui.DownloadFFmpeg(func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error { + var err error + err = startDownload(app, progressBar, progressMessage, save) + if err != nil { + return err + } + + return nil + }) +} diff --git a/internal/ffmpeg/download/service/download_anyos.go b/internal/ffmpeg/download/service/download_anyos.go new file mode 100644 index 0000000..edee036 --- /dev/null +++ b/internal/ffmpeg/download/service/download_anyos.go @@ -0,0 +1,15 @@ +//go:build !windows && !linux +// +build !windows,!linux + +package service + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application" +) + +func startDownload(app application.AppContract, progressBar *widget.ProgressBar, progressMessage *canvas.Text, save func(ffmpegPath string, ffprobePath string, ffplayPath string) error) error { + return nil +} diff --git a/internal/ffmpeg/download/service/download_linux.go b/internal/ffmpeg/download/service/download_linux.go new file mode 100644 index 0000000..32d0af0 --- /dev/null +++ b/internal/ffmpeg/download/service/download_linux.go @@ -0,0 +1,236 @@ +//go:build linux +// +build linux + +package service + +import ( + "archive/tar" + "errors" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application" + "github.com/ulikunitz/xz" + "io" + "net/http" + "os" + "path/filepath" +) + +func startDownload(app application.AppContract, progressBar *widget.ProgressBar, progressMessage *canvas.Text, save func(ffmpegPath string, ffprobePath string, ffplayPath string) error) error { + var err error + + dir, err := localSharePath() + if err != nil { + return err + } + dir = filepath.Join(dir, "fyne", app.FyneApp().UniqueID()) + err = os.MkdirAll(dir, 0755) + if err != nil { + return err + } + + fyne.Do(func() { + progressMessage.Text = lang.L("downloadRun") + progressMessage.Refresh() + }) + err = downloadFile(dir+"/ffmpeg.tar.xz", "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz", progressBar) + if err != nil { + return err + } + + fyne.Do(func() { + progressMessage.Text = lang.L("unzipRun") + progressMessage.Refresh() + }) + err = unTarXz(dir+"/ffmpeg.tar.xz", dir, progressBar) + if err != nil { + return err + } + _ = os.Remove(dir + "/ffmpeg.tar.xz") + + fyne.Do(func() { + progressMessage.Text = lang.L("testFF") + progressMessage.Refresh() + }) + + err = save( + dir+"/ffmpeg-master-latest-linux64-gpl/bin/ffmpeg", + dir+"/ffmpeg-master-latest-linux64-gpl/bin/ffprobe", + dir+"/ffmpeg-master-latest-linux64-gpl/bin/ffplay", + ) + if err != nil { + return err + } + + fyne.Do(func() { + progressMessage.Text = lang.L("completedQueue") + progressMessage.Refresh() + }) + + return nil +} + +func localSharePath() (string, error) { + xdgDataHome := os.Getenv("XDG_DATA_HOME") + if xdgDataHome != "" { + return xdgDataHome, nil + } + homeDir, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(homeDir, ".local", "share"), nil +} + +func downloadFile(filepath string, url string, progressBar *widget.ProgressBar) (err error) { + progressBar.Value = 0 + progressBar.Max = 100 + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + buf := make([]byte, 32*1024) + var downloaded int64 + for { + n, err := resp.Body.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + if n > 0 { + f.Write(buf[:n]) + downloaded += int64(n) + progressBar.Value = float64(downloaded) / float64(resp.ContentLength) * 100 + fyne.Do(func() { + progressBar.Refresh() + }) + } + } + return nil +} + +func unTarXz(fileTar string, directory string, progressBar *widget.ProgressBar) error { + progressBar.Value = 0 + progressBar.Max = 100 + + fyne.Do(func() { + progressBar.Refresh() + }) + + f, err := os.Open(fileTar) + if err != nil { + return err + } + defer f.Close() + + xzReader, err := xz.NewReader(f) + if err != nil { + return err + } + + tarReader := tar.NewReader(xzReader) + + totalFiles := 0 + for { + _, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + totalFiles++ + } + + // Rewind back to the beginning of the file to re-process + _, err = f.Seek(0, 0) + if err != nil { + return err + } + + xzReader, err = xz.NewReader(f) + if err != nil { + return err + } + + tarReader = tar.NewReader(xzReader) + + // We count the number of files already unpacked + unpackedFiles := 0 + + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + targetPath := filepath.Join(directory, header.Name) + switch header.Typeflag { + case tar.TypeDir: + err := os.MkdirAll(targetPath, 0755) + if err != nil { + return err + } + case tar.TypeReg: + outFile, err := os.Create(targetPath) + if err != nil { + return err + } + defer outFile.Close() + + _, err = io.Copy(outFile, tarReader) + + if err != nil { + return err + } + default: + return errors.New("unsupported file type") + } + + unpackedFiles++ + progressBar.Value = float64(unpackedFiles) / float64(totalFiles) * 100 + fyne.Do(func() { + progressBar.Refresh() + }) + } + + ffmpegPath := filepath.Join(directory, "ffmpeg-master-latest-linux64-gpl", "bin", "ffmpeg") + err = os.Chmod(ffmpegPath, 0755) + if err != nil { + return err + } + + ffprobePath := filepath.Join(directory, "ffmpeg-master-latest-linux64-gpl", "bin", "ffprobe") + err = os.Chmod(ffprobePath, 0755) + if err != nil { + 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/internal/ffmpeg/download/service/download_windows.go b/internal/ffmpeg/download/service/download_windows.go new file mode 100644 index 0000000..f3bc33f --- /dev/null +++ b/internal/ffmpeg/download/service/download_windows.go @@ -0,0 +1,175 @@ +//go:build windows +// +build windows + +package service + +import ( + "archive/zip" + "errors" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application" + "io" + "net/http" + "os" + "path/filepath" + "strings" +) + +func startDownload(app application.AppContract, progressBar *widget.ProgressBar, progressMessage *canvas.Text, save func(ffmpegPath string, ffprobePath string, ffplayPath string) error) error { + var err error + + dir := os.Getenv("APPDATA") + dir = filepath.Join(dir, "fyne", app.FyneApp().UniqueID()) + err = os.MkdirAll(dir, 0755) + if err != nil { + return err + } + + fyne.Do(func() { + progressMessage.Text = lang.L("downloadRun") + progressMessage.Refresh() + }) + err = downloadFile(dir+"/ffmpeg.zip", "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip", progressBar) + if err != nil { + return err + } + + fyne.Do(func() { + progressMessage.Text = lang.L("unzipRun") + progressMessage.Refresh() + }) + err = unZip(dir+"/ffmpeg.zip", dir, progressBar) + if err != nil { + return err + } + _ = os.Remove(dir + "/ffmpeg.zip") + + fyne.Do(func() { + progressMessage.Text = lang.L("testFF") + progressMessage.Refresh() + }) + err = save( + dir+"/ffmpeg-master-latest-win64-gpl/bin/ffmpeg.exe", + dir+"/ffmpeg-master-latest-win64-gpl/bin/ffprobe.exe", + dir+"/ffmpeg-master-latest-win64-gpl/bin/ffplay.exe", + ) + if err != nil { + return err + } + + fyne.Do(func() { + progressMessage.Text = lang.L("completedQueue") + progressMessage.Refresh() + }) + + return nil +} + +func downloadFile(filepath string, url string, progressBar *widget.ProgressBar) (err error) { + progressBar.Value = 0 + progressBar.Max = 100 + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + buf := make([]byte, 32*1024) + var downloaded int64 + for { + n, err := resp.Body.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + if n > 0 { + f.Write(buf[:n]) + downloaded += int64(n) + progressBar.Value = float64(downloaded) / float64(resp.ContentLength) * 100 + fyne.Do(func() { + progressBar.Refresh() + }) + } + } + return nil +} + +func unZip(fileZip string, directory string, progressBar *widget.ProgressBar) error { + progressBar.Value = 0 + progressBar.Max = 100 + + fyne.Do(func() { + progressBar.Refresh() + }) + + archive, err := zip.OpenReader(fileZip) + if err != nil { + return err + } + defer archive.Close() + + totalBytes := int64(0) + for _, f := range archive.File { + totalBytes += int64(f.UncompressedSize64) + } + + unpackedBytes := int64(0) + + for _, f := range archive.File { + filePath := filepath.Join(directory, f.Name) + + if !strings.HasPrefix(filePath, filepath.Clean(directory)+string(os.PathSeparator)) { + return errors.New("invalid file path") + } + if f.FileInfo().IsDir() { + os.MkdirAll(filePath, os.ModePerm) + continue + } + + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + return err + } + + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + + fileInArchive, err := f.Open() + if err != nil { + return err + } + + bytesRead, err := io.Copy(dstFile, fileInArchive) + if err != nil { + return err + } + + unpackedBytes += bytesRead + progressBar.Value = float64(unpackedBytes) / float64(totalBytes) * 100 + fyne.Do(func() { + progressBar.Refresh() + }) + + dstFile.Close() + fileInArchive.Close() + } + + return nil +} diff --git a/internal/ffmpeg/ffmpeg.go b/internal/ffmpeg/ffmpeg.go index c7e3019..ccbb577 100644 --- a/internal/ffmpeg/ffmpeg.go +++ b/internal/ffmpeg/ffmpeg.go @@ -1,19 +1,52 @@ package ffmpeg +import ( + "errors" + "fyne.io/fyne/v2/lang" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils" + "os/exec" + "strings" +) + type FFmpegContract interface { - SetPath(path string) + GetPath() string } type ffmpeg struct { path string } -func newFFmpeg(path string) FFmpegContract { +func newFFmpeg(path string) (FFmpegContract, error) { + if path == "" { + return nil, errors.New(lang.L("errorFFmpeg")) + } + + isCheck, err := checkFFmpegPath(path) + if err != nil { + return nil, err + } + if isCheck == false { + return nil, errors.New(lang.L("errorFFmpeg")) + } + return &ffmpeg{ path: path, - } + }, nil } -func (f *ffmpeg) SetPath(path string) { - f.path = path +func (f *ffmpeg) GetPath() string { + return f.path +} + +func checkFFmpegPath(path string) (bool, error) { + cmd := exec.Command(path, "-version") + utils.PrepareBackgroundCommand(cmd) + out, err := cmd.CombinedOutput() + if err != nil { + return false, err + } + if strings.Contains(strings.TrimSpace(string(out)), "ffmpeg") == false { + return false, nil + } + return true, nil } diff --git a/internal/ffmpeg/ffplay.go b/internal/ffmpeg/ffplay.go index 105359f..6fdf209 100644 --- a/internal/ffmpeg/ffplay.go +++ b/internal/ffmpeg/ffplay.go @@ -1,19 +1,52 @@ package ffmpeg +import ( + "errors" + "fyne.io/fyne/v2/lang" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils" + "os/exec" + "strings" +) + type FFplayContract interface { - SetPath(path string) + GetPath() string } type ffplay struct { path string } -func newFFplay(path string) FFplayContract { +func newFFplay(path string) (FFplayContract, error) { + if path == "" { + return nil, errors.New(lang.L("errorFFplay")) + } + + isCheck, err := checkFFplayPath(path) + if err != nil { + return nil, err + } + if isCheck == false { + return nil, errors.New(lang.L("errorFFplay")) + } + return &ffplay{ path: path, - } + }, nil } -func (f *ffplay) SetPath(path string) { - f.path = path +func (f *ffplay) GetPath() string { + return f.path +} + +func checkFFplayPath(path string) (bool, error) { + cmd := exec.Command(path, "-version") + utils.PrepareBackgroundCommand(cmd) + out, err := cmd.CombinedOutput() + if err != nil { + return false, err + } + if strings.Contains(strings.TrimSpace(string(out)), "ffplay") == false { + return false, nil + } + return true, nil } diff --git a/internal/ffmpeg/ffprobe.go b/internal/ffmpeg/ffprobe.go index ee8d9c8..8aa18fc 100644 --- a/internal/ffmpeg/ffprobe.go +++ b/internal/ffmpeg/ffprobe.go @@ -1,19 +1,52 @@ package ffmpeg +import ( + "errors" + "fyne.io/fyne/v2/lang" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils" + "os/exec" + "strings" +) + type FFprobeContract interface { - SetPath(path string) + GetPath() string } type ffprobe struct { path string } -func newFFprobe(path string) FFprobeContract { +func newFFprobe(path string) (FFprobeContract, error) { + if path == "" { + return nil, errors.New(lang.L("errorFFprobe")) + } + + isCheck, err := checkFFprobePath(path) + if err != nil { + return nil, err + } + if isCheck == false { + return nil, errors.New(lang.L("errorFFprobe")) + } + return &ffprobe{ path: path, - } + }, nil } -func (f *ffprobe) SetPath(path string) { - f.path = path +func (f *ffprobe) GetPath() string { + return f.path +} + +func checkFFprobePath(path string) (bool, error) { + cmd := exec.Command(path, "-version") + utils.PrepareBackgroundCommand(cmd) + out, err := cmd.CombinedOutput() + if err != nil { + return false, err + } + if strings.Contains(strings.TrimSpace(string(out)), "ffprobe") == false { + return false, nil + } + return true, nil } diff --git a/internal/ffmpeg/utilities.go b/internal/ffmpeg/utilities.go index 309019d..c8c6428 100644 --- a/internal/ffmpeg/utilities.go +++ b/internal/ffmpeg/utilities.go @@ -1,13 +1,25 @@ package ffmpeg import ( + "errors" + "fyne.io/fyne/v2/lang" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting" ) type UtilitiesContract interface { - GetFFmpeg() FFmpegContract - GetFFprobe() FFprobeContract - GetFFplay() FFplayContract + UtilityCheck() bool + + GetFFmpeg() (FFmpegContract, error) + GetFFmpegPath() string + ChangeFFmpeg(path string) error + + GetFFprobe() (FFprobeContract, error) + GetFFprobePath() string + ChangeFFprobe(path string) error + + GetFFplay() (FFplayContract, error) + GetFFplayPath() string + ChangeFFplay(path string) error } type utilities struct { @@ -23,26 +35,131 @@ func NewUtilities(setting setting.SettingContract) UtilitiesContract { } } -func (u *utilities) GetFFmpeg() FFmpegContract { +func (u *utilities) UtilityCheck() bool { + var err error + + _, err = u.GetFFmpeg() + if err != nil { + return false + } + + _, err = u.GetFFprobe() + if err != nil { + return false + } + + _, err = u.GetFFplay() + if err != nil { + return false + } + + return true +} + +func (u *utilities) GetFFmpeg() (FFmpegContract, error) { if u.ffmpeg == nil { - u.ffmpeg = newFFmpeg(u.setting.GetFFmpegPath()) + createFFmpeg, err := newFFmpeg(u.setting.GetFFmpegPath()) + if err != nil { + return nil, err + } + u.ffmpeg = createFFmpeg } - return u.ffmpeg + return u.ffmpeg, nil } -func (u *utilities) GetFFprobe() FFprobeContract { - if u.ffprobe != nil { - u.ffprobe = newFFprobe(u.setting.GetFFprobePath()) +func (u *utilities) GetFFmpegPath() string { + ffmpegService, err := u.GetFFmpeg() + if err != nil { + return "" + } + return ffmpegService.GetPath() +} + +func (u *utilities) ChangeFFmpeg(path string) error { + if path == "" { + return errors.New(lang.L("errorFFmpeg")) } - return u.ffprobe + createFFmpeg, err := newFFmpeg(path) + if err != nil { + return err + } + + u.ffmpeg = createFFmpeg + u.setting.SetFFmpegPath(path) + + return nil } -func (u *utilities) GetFFplay() FFplayContract { +func (u *utilities) GetFFprobe() (FFprobeContract, error) { + if u.ffprobe == nil { + createFFprobe, err := newFFprobe(u.setting.GetFFprobePath()) + if err != nil { + return nil, err + } + u.ffprobe = createFFprobe + } + + return u.ffprobe, nil +} + +func (u *utilities) GetFFprobePath() string { + ffprobeService, err := u.GetFFprobe() + if err != nil { + return "" + } + return ffprobeService.GetPath() +} + +func (u *utilities) ChangeFFprobe(path string) error { + if path == "" { + return errors.New(lang.L("errorFFprobe")) + } + + createFFprobe, err := newFFprobe(path) + if err != nil { + return err + } + + u.ffprobe = createFFprobe + u.setting.SetFFprobePath(path) + + return nil +} + +func (u *utilities) GetFFplay() (FFplayContract, error) { if u.ffplay == nil { - u.ffplay = newFFplay(u.setting.GetFFplayPath()) + createFFplay, err := newFFplay(u.setting.GetFFplayPath()) + if err != nil { + return nil, err + } + u.ffplay = createFFplay } - return u.ffplay + return u.ffplay, nil +} + +func (u *utilities) GetFFplayPath() string { + ffplayService, err := u.GetFFplay() + if err != nil { + return "" + } + return ffplayService.GetPath() +} + +func (u *utilities) ChangeFFplay(path string) error { + if path == "" { + return errors.New(lang.L("errorFFplay")) + } + + createFFplay, err := newFFplay(path) + if err != nil { + return err + } + + u.ffplay = createFFplay + u.setting.SetFFplayPath(path) + + return nil } diff --git a/internal/gui/view/configuring_ffmpeg_utilities.go b/internal/gui/view/configuring_ffmpeg_utilities.go new file mode 100644 index 0000000..11d8d80 --- /dev/null +++ b/internal/gui/view/configuring_ffmpeg_utilities.go @@ -0,0 +1,129 @@ +package view + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" + "fyne.io/fyne/v2/storage" + "fyne.io/fyne/v2/widget" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/window" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils" + "image/color" + "net/url" + "path/filepath" +) + +func ConfiguringFFmpegUtilities( + window window.WindowContract, + currentPathFFmpeg string, + currentPathFFprobe string, + currentPathFFplay string, + save func(ffmpegPath string, ffprobePath string, ffplayPath string) error, + cancel func(), + donwloadFFmpeg fyne.CanvasObject, +) fyne.CanvasObject { + errorMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255}) + errorMessage.TextSize = 16 + errorMessage.TextStyle = fyne.TextStyle{Bold: true} + + link := widget.NewHyperlink("https://ffmpeg.org/download.html", &url.URL{ + Scheme: "https", + Host: "ffmpeg.org", + Path: "download.html", + }) + + ffmpegPath, buttonFFmpeg, buttonFFmpegMessage := configuringFFmpegUtilitiesButtonSelectFile(window, currentPathFFmpeg) + ffprobePath, buttonFFprobe, buttonFFprobeMessage := configuringFFmpegUtilitiesButtonSelectFile(window, currentPathFFprobe) + ffplayPath, buttonFFplay, buttonFFplayMessage := configuringFFmpegUtilitiesButtonSelectFile(window, currentPathFFplay) + + form := &widget.Form{ + Items: []*widget.FormItem{ + { + Text: lang.L("titleDownloadLink"), + Widget: link, + }, + { + Text: lang.L("pathToFfmpeg"), + Widget: buttonFFmpeg, + }, + { + Widget: container.NewHScroll(buttonFFmpegMessage), + }, + { + Text: lang.L("pathToFfprobe"), + Widget: buttonFFprobe, + }, + { + Widget: container.NewHScroll(buttonFFprobeMessage), + }, + { + Text: lang.L("pathToFfplay"), + Widget: buttonFFplay, + }, + { + Widget: container.NewHScroll(buttonFFplayMessage), + }, + { + Widget: container.NewHScroll(errorMessage), + }, + }, + SubmitText: lang.L("save"), + OnSubmit: func() { + err := save(*ffmpegPath, *ffprobePath, *ffplayPath) + if err != nil { + errorMessage.Text = err.Error() + } + }, + } + if cancel != nil { + form.OnCancel = cancel + form.CancelText = lang.L("cancel") + } + + selectFFPathTitle := lang.L("selectFFPathTitle") + + return widget.NewCard(selectFFPathTitle, "", container.NewVBox( + form, + donwloadFFmpeg, + )) +} + +func configuringFFmpegUtilitiesButtonSelectFile(window window.WindowContract, path string) (filePath *string, button *widget.Button, buttonMessage *canvas.Text) { + filePath = &path + + buttonMessage = canvas.NewText(path, color.RGBA{R: 49, G: 127, B: 114, A: 255}) + buttonMessage.TextSize = 16 + buttonMessage.TextStyle = fyne.TextStyle{Bold: true} + + buttonTitle := lang.L("choose") + + var locationURI fyne.ListableURI + if len(path) > 0 { + listableURI := storage.NewFileURI(filepath.Dir(path)) + locationURI, _ = storage.ListerForURI(listableURI) + } + + button = widget.NewButton(buttonTitle, func() { + window.NewFileOpen(func(r fyne.URIReadCloser, err error) { + if err != nil { + buttonMessage.Text = err.Error() + utils.SetStringErrorStyle(buttonMessage) + return + } + if r == nil { + return + } + + path = r.URI().Path() + + buttonMessage.Text = r.URI().Path() + utils.SetStringSuccessStyle(buttonMessage) + + listableURI := storage.NewFileURI(filepath.Dir(r.URI().Path())) + locationURI, _ = storage.ListerForURI(listableURI) + }, locationURI) + }) + + return filePath, button, buttonMessage +} diff --git a/internal/gui/window/main.go b/internal/gui/window/main.go index 77f8e51..3dcd187 100644 --- a/internal/gui/window/main.go +++ b/internal/gui/window/main.go @@ -3,15 +3,18 @@ package window import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/widget" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application" + "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils" ) -type MainWindowContract interface { +type WindowContract interface { SetContent(content fyne.CanvasObject) Show() InitLayout() + NewFileOpen(callback func(fyne.URIReadCloser, error), location fyne.ListableURI) *dialog.FileDialog } type mainWindow struct { @@ -20,7 +23,7 @@ type mainWindow struct { progressBarService application.ProgressBarContract } -func NewMainWindow(fyneWindow fyne.Window, progressBarService application.ProgressBarContract) MainWindowContract { +func NewMainWindow(fyneWindow fyne.Window, progressBarService application.ProgressBarContract) WindowContract { fyneWindow.Resize(fyne.Size{Width: 1039, Height: 599}) fyneWindow.CenterOnScreen() @@ -40,6 +43,16 @@ func (w *mainWindow) InitLayout() { }) } +func (w *mainWindow) NewFileOpen(callback func(fyne.URIReadCloser, error), location fyne.ListableURI) *dialog.FileDialog { + fileDialog := dialog.NewFileOpen(callback, w.fyneWindow) + utils.FileDialogResize(fileDialog, w.fyneWindow) + fileDialog.Show() + if location != nil { + fileDialog.SetLocation(location) + } + return fileDialog +} + func (w *mainWindow) SetContent(content fyne.CanvasObject) { fyne.Do(func() { if w.layout == nil { diff --git a/internal/utils/dialog.go b/internal/utils/dialog.go new file mode 100644 index 0000000..992a68a --- /dev/null +++ b/internal/utils/dialog.go @@ -0,0 +1,11 @@ +package utils + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/dialog" +) + +func FileDialogResize(fileDialog *dialog.FileDialog, w fyne.Window) { + contentSize := w.Content().Size() + fileDialog.Resize(fyne.Size{Width: contentSize.Width - 50, Height: contentSize.Height - 50}) +} diff --git a/internal/utils/path_separator.go b/internal/utils/path_separator.go new file mode 100644 index 0000000..9fa65cf --- /dev/null +++ b/internal/utils/path_separator.go @@ -0,0 +1,8 @@ +//go:build !windows +// +build !windows + +package utils + +func PathSeparator() string { + return "/" +} diff --git a/internal/utils/path_separator_window.go b/internal/utils/path_separator_window.go new file mode 100644 index 0000000..2acaa53 --- /dev/null +++ b/internal/utils/path_separator_window.go @@ -0,0 +1,8 @@ +//go:build windows +// +build windows + +package utils + +func PathSeparator() string { + return "\\" +} diff --git a/internal/utils/prepare_background_command.go b/internal/utils/prepare_background_command.go new file mode 100644 index 0000000..da5f9b4 --- /dev/null +++ b/internal/utils/prepare_background_command.go @@ -0,0 +1,12 @@ +//go:build !windows +// +build !windows + +package utils + +import ( + "os/exec" +) + +func PrepareBackgroundCommand(cmd *exec.Cmd) { + +} diff --git a/internal/utils/prepare_background_command_windows.go b/internal/utils/prepare_background_command_windows.go new file mode 100644 index 0000000..00af640 --- /dev/null +++ b/internal/utils/prepare_background_command_windows.go @@ -0,0 +1,13 @@ +//go:build windows +// +build windows + +package utils + +import ( + "os/exec" + "syscall" +) + +func PrepareBackgroundCommand(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} +} diff --git a/internal/utils/text.go b/internal/utils/text.go new file mode 100644 index 0000000..20104dd --- /dev/null +++ b/internal/utils/text.go @@ -0,0 +1,21 @@ +package utils + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "image/color" +) + +func SetStringErrorStyle(text *canvas.Text) { + fyne.Do(func() { + text.Color = color.RGBA{R: 255, G: 0, B: 0, A: 255} + text.Refresh() + }) +} + +func SetStringSuccessStyle(text *canvas.Text) { + fyne.Do(func() { + text.Color = color.RGBA{R: 49, G: 127, B: 114, A: 255} + text.Refresh() + }) +}