Add FFmpeg utilities configuration UI and automated downloading

Introduce a new UI for configuring FFmpeg, FFprobe, and FFplay paths with file selection and error handling. Add platform-specific logic for downloading and extracting FFmpeg binaries directly within the application, improving user experience.
This commit is contained in:
Leonid Nikitin 2025-06-07 01:30:32 +05:00
parent b24155caf6
commit c60b9f7b0c
Signed by: kor-elf
GPG Key ID: DAB5355A11C22541
22 changed files with 1118 additions and 37 deletions

View File

@ -1,7 +1,12 @@
package setting package setting
func (s *setting) GetFFmpegPath() string { 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) { func (s *setting) SetFFmpegPath(path string) {
@ -9,7 +14,12 @@ func (s *setting) SetFFmpegPath(path string) {
} }
func (s *setting) GetFFprobePath() 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) { func (s *setting) SetFFprobePath(path string) {
@ -17,7 +27,12 @@ func (s *setting) SetFFprobePath(path string) {
} }
func (s *setting) GetFFplayPath() 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) { func (s *setting) SetFFplayPath(path string) {

View File

@ -1,8 +1,58 @@
package controller 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() { func (c *controller) convertor() {
content := view.Convertor() content := view.Convertor()
c.window.SetContent(content) 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
}

View File

@ -13,7 +13,7 @@ type ControllerContract interface {
type controller struct { type controller struct {
app application.AppContract app application.AppContract
window window.MainWindowContract window window.WindowContract
} }
func NewController(app application.AppContract) ControllerContract { func NewController(app application.AppContract) ControllerContract {
@ -26,11 +26,10 @@ func NewController(app application.AppContract) ControllerContract {
} }
func (c *controller) Start() { func (c *controller) Start() {
c.window.Show()
isDefault, err := c.initLanguage() isDefault, err := c.initLanguage()
if err != nil { if err != nil {
c.startWithError(err) c.startWithError(err)
c.window.Show()
return return
} }
@ -46,14 +45,21 @@ func (c *controller) Start() {
c.verificareaFFmpeg() c.verificareaFFmpeg()
}) })
c.window.SetContent(content) c.window.SetContent(content)
c.window.Show()
return return
} }
c.window.InitLayout() c.window.InitLayout()
c.verificareaFFmpeg() c.verificareaFFmpeg()
c.window.Show()
} }
func (c *controller) verificareaFFmpeg() { func (c *controller) verificareaFFmpeg() {
if !c.app.GetFFmpegService().UtilityCheck() {
c.settingConvertor(false)
return
}
c.convertor() c.convertor()
} }

View File

@ -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()
}

View File

@ -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,
)),
)
}

View File

@ -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,
)),
)
}

View File

@ -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
})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,19 +1,52 @@
package ffmpeg 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 { type FFmpegContract interface {
SetPath(path string) GetPath() string
} }
type ffmpeg struct { type ffmpeg struct {
path string 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{ return &ffmpeg{
path: path, path: path,
} }, nil
} }
func (f *ffmpeg) SetPath(path string) { func (f *ffmpeg) GetPath() string {
f.path = path 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
} }

View File

@ -1,19 +1,52 @@
package ffmpeg 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 { type FFplayContract interface {
SetPath(path string) GetPath() string
} }
type ffplay struct { type ffplay struct {
path string 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{ return &ffplay{
path: path, path: path,
} }, nil
} }
func (f *ffplay) SetPath(path string) { func (f *ffplay) GetPath() string {
f.path = path 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
} }

View File

@ -1,19 +1,52 @@
package ffmpeg 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 { type FFprobeContract interface {
SetPath(path string) GetPath() string
} }
type ffprobe struct { type ffprobe struct {
path string 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{ return &ffprobe{
path: path, path: path,
} }, nil
} }
func (f *ffprobe) SetPath(path string) { func (f *ffprobe) GetPath() string {
f.path = path 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
} }

View File

@ -1,13 +1,25 @@
package ffmpeg package ffmpeg
import ( import (
"errors"
"fyne.io/fyne/v2/lang"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting"
) )
type UtilitiesContract interface { type UtilitiesContract interface {
GetFFmpeg() FFmpegContract UtilityCheck() bool
GetFFprobe() FFprobeContract
GetFFplay() FFplayContract 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 { 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 { 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 { func (u *utilities) GetFFmpegPath() string {
if u.ffprobe != nil { ffmpegService, err := u.GetFFmpeg()
u.ffprobe = newFFprobe(u.setting.GetFFprobePath()) 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 { 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
} }

View File

@ -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
}

View File

@ -3,15 +3,18 @@ package window
import ( import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget" "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/application"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils"
) )
type MainWindowContract interface { type WindowContract interface {
SetContent(content fyne.CanvasObject) SetContent(content fyne.CanvasObject)
Show() Show()
InitLayout() InitLayout()
NewFileOpen(callback func(fyne.URIReadCloser, error), location fyne.ListableURI) *dialog.FileDialog
} }
type mainWindow struct { type mainWindow struct {
@ -20,7 +23,7 @@ type mainWindow struct {
progressBarService application.ProgressBarContract 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.Resize(fyne.Size{Width: 1039, Height: 599})
fyneWindow.CenterOnScreen() 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) { func (w *mainWindow) SetContent(content fyne.CanvasObject) {
fyne.Do(func() { fyne.Do(func() {
if w.layout == nil { if w.layout == nil {

11
internal/utils/dialog.go Normal file
View File

@ -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})
}

View File

@ -0,0 +1,8 @@
//go:build !windows
// +build !windows
package utils
func PathSeparator() string {
return "/"
}

View File

@ -0,0 +1,8 @@
//go:build windows
// +build windows
package utils
func PathSeparator() string {
return "\\"
}

View File

@ -0,0 +1,12 @@
//go:build !windows
// +build !windows
package utils
import (
"os/exec"
)
func PrepareBackgroundCommand(cmd *exec.Cmd) {
}

View File

@ -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}
}

21
internal/utils/text.go Normal file
View File

@ -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()
})
}