From f262d5f931d3883b7a6f1b58cda2d931bf323e61 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Tue, 6 May 2025 23:10:29 +0500 Subject: [PATCH] Add Linux support for downloading and setting up FFmpeg. This commit introduces platform-specific functionality for downloading, extracting, and configuring FFmpeg on Linux systems. --- .gitignore | 3 +- ...ew_setting_button_download_ffmpeg_anyos.go | 4 +- ...ew_setting_button_download_ffmpeg_linux.go | 63 ++++++ go.mod | 1 + go.sum | 2 + handler/convertor_anyos.go | 4 +- handler/convertor_linux.go | 208 ++++++++++++++++++ 7 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 convertor/view_setting_button_download_ffmpeg_linux.go create mode 100644 handler/convertor_linux.go diff --git a/.gitignore b/.gitignore index 101d806..aace1be 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -fyne-cross/* \ No newline at end of file +fyne-cross/* +ffmpeg/* \ No newline at end of file diff --git a/convertor/view_setting_button_download_ffmpeg_anyos.go b/convertor/view_setting_button_download_ffmpeg_anyos.go index 5e3dc94..794fd91 100644 --- a/convertor/view_setting_button_download_ffmpeg_anyos.go +++ b/convertor/view_setting_button_download_ffmpeg_anyos.go @@ -1,5 +1,5 @@ -//go:build !windows -// +build !windows +//go:build !windows && !linux +// +build !windows,!linux package convertor diff --git a/convertor/view_setting_button_download_ffmpeg_linux.go b/convertor/view_setting_button_download_ffmpeg_linux.go new file mode 100644 index 0000000..db34d7a --- /dev/null +++ b/convertor/view_setting_button_download_ffmpeg_linux.go @@ -0,0 +1,63 @@ +//go:build linux +// +build linux + +package convertor + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/image/colornames" + "image/color" +) + +func (v View) blockDownloadFFmpeg( + donwloadFFmpeg func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error, +) *fyne.Container { + + 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(v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "download", + }), func() { + buttonDownloadFFmpeg.Disable() + + err := donwloadFFmpeg(progressBar, progressDownloadFFmpegMessage) + if err != nil { + errorDownloadFFmpegMessage.Text = err.Error() + } + + buttonDownloadFFmpeg.Enable() + }) + + downloadFFmpegFromSiteMessage := v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "downloadFFmpegFromSite", + }) + + return container.NewVBox( + canvas.NewLine(colornames.Darkgreen), + widget.NewCard(v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "buttonDownloadFFmpeg", + }), "", container.NewVBox( + widget.NewRichTextFromMarkdown( + downloadFFmpegFromSiteMessage+" [https://github.com/BtbN/FFmpeg-Builds/releases](https://github.com/BtbN/FFmpeg-Builds/releases)", + ), + buttonDownloadFFmpeg, + errorDownloadFFmpegMessage, + progressDownloadFFmpegMessage, + progressBar, + )), + ) +} diff --git a/go.mod b/go.mod index 89427e3..4702cae 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/tevino/abool v1.2.0 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect github.com/yuin/goldmark v1.5.5 // indirect golang.org/x/image v0.11.0 // indirect golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect diff --git a/go.sum b/go.sum index 2a6e311..17bc505 100644 --- a/go.sum +++ b/go.sum @@ -288,6 +288,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/handler/convertor_anyos.go b/handler/convertor_anyos.go index 409c524..5143398 100644 --- a/handler/convertor_anyos.go +++ b/handler/convertor_anyos.go @@ -1,5 +1,5 @@ -//go:build !windows -// +build !windows +//go:build !windows && !linux +// +build !windows,!linux package handler diff --git a/handler/convertor_linux.go b/handler/convertor_linux.go new file mode 100644 index 0000000..015ef69 --- /dev/null +++ b/handler/convertor_linux.go @@ -0,0 +1,208 @@ +//go:build linux +// +build linux + +package handler + +import ( + "archive/tar" + "errors" + "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" + "github.com/ulikunitz/xz" + "io" + "net/http" + "os" + "path/filepath" +) + +func getPathsToFF() []kernel.FFPathUtilities { + return []kernel.FFPathUtilities{{"ffmpeg/bin/ffmpeg", "ffmpeg/bin/ffprobe"}, {"ffmpeg", "ffprobe"}} +} + +func (h ConvertorHandler) downloadFFmpeg(progressBar *widget.ProgressBar, progressMessage *canvas.Text) (err error) { + isDirectoryFFmpeg := isDirectory("ffmpeg") + if isDirectoryFFmpeg == false { + err = os.Mkdir("ffmpeg", 0777) + if err != nil { + return err + } + } + progressMessage.Text = h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "downloadRun", + }) + progressMessage.Refresh() + err = downloadFile("ffmpeg/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 + } + + progressMessage.Text = h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "unzipRun", + }) + progressMessage.Refresh() + err = unTarXz("ffmpeg/ffmpeg.tar.xz", "ffmpeg", progressBar) + if err != nil { + return err + } + _ = os.Remove("ffmpeg/ffmpeg.tar.xz") + + progressMessage.Text = h.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{ + MessageID: "testFF", + }) + progressMessage.Refresh() + + err = h.saveSettingFFPath("ffmpeg/ffmpeg-master-latest-linux64-gpl/bin/ffmpeg", "ffmpeg/ffmpeg-master-latest-linux64-gpl/bin/ffprobe") + if err != nil { + return err + } + + 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 + progressBar.Refresh() + } + } + return nil +} + +func unTarXz(fileTar string, directory string, progressBar *widget.ProgressBar) error { + progressBar.Value = 0 + progressBar.Max = 100 + + 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 + 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 + } + + return nil +} + +func isDirectory(path string) bool { + fileInfo, err := os.Stat(path) + if err != nil { + return false + } + + return fileInfo.IsDir() +}