diff --git a/src/convertor/service.go b/src/convertor/service.go index e1828a2..1fead4a 100644 --- a/src/convertor/service.go +++ b/src/convertor/service.go @@ -2,6 +2,8 @@ package convertor import ( "errors" + "ffmpegGui/helper" + "io" "os/exec" "regexp" "strconv" @@ -9,7 +11,7 @@ import ( ) type ServiceContract interface { - RunConvert(setting ConvertSetting) error + RunConvert(setting ConvertSetting, progress ProgressContract) error GetTotalDuration(file *File) (float64, error) GetFFmpegVesrion() (string, error) GetFFprobeVersion() (string, error) @@ -17,6 +19,11 @@ type ServiceContract interface { ChangeFFprobePath(path string) (bool, error) } +type ProgressContract interface { + GetProtocole() string + Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error +} + type FFPathUtilities struct { FFmpeg string FFprobe string @@ -35,7 +42,6 @@ type File struct { type ConvertSetting struct { VideoFileInput *File VideoFileOut *File - SocketPath string OverwriteOutputFiles bool } @@ -49,20 +55,36 @@ func NewService(ffPathUtilities FFPathUtilities) *Service { } } -func (s Service) RunConvert(setting ConvertSetting) error { +func (s Service) RunConvert(setting ConvertSetting, progress ProgressContract) error { overwriteOutputFiles := "-n" if setting.OverwriteOutputFiles == true { overwriteOutputFiles = "-y" } - args := []string{overwriteOutputFiles, "-i", setting.VideoFileInput.Path, "-c:v", "libx264", "-progress", "unix://" + setting.SocketPath, setting.VideoFileOut.Path} + args := []string{overwriteOutputFiles, "-i", setting.VideoFileInput.Path, "-c:v", "libx264", "-progress", progress.GetProtocole(), setting.VideoFileOut.Path} cmd := exec.Command(s.ffPathUtilities.FFmpeg, args...) + helper.PrepareBackgroundCommand(cmd) - out, err := cmd.CombinedOutput() + stdOut, err := cmd.StdoutPipe() + if err != nil { + return err + } + stdErr, err := cmd.StderrPipe() + if err != nil { + return err + } + + err = cmd.Start() + if err != nil { + return err + } + + err = progress.Run(stdOut, stdErr) + if err != nil { + return err + } + + err = cmd.Wait() if err != nil { - errStringArr := regexp.MustCompile("\r?\n").Split(strings.TrimSpace(string(out)), -1) - if len(errStringArr) > 1 { - return errors.New(errStringArr[len(errStringArr)-1]) - } return err } @@ -72,6 +94,7 @@ func (s Service) RunConvert(setting ConvertSetting) error { func (s Service) GetTotalDuration(file *File) (duration float64, err error) { args := []string{"-v", "error", "-select_streams", "v:0", "-count_packets", "-show_entries", "stream=nb_read_packets", "-of", "csv=p=0", file.Path} cmd := exec.Command(s.ffPathUtilities.FFprobe, args...) + helper.PrepareBackgroundCommand(cmd) out, err := cmd.CombinedOutput() if err != nil { errString := strings.TrimSpace(string(out)) @@ -85,6 +108,7 @@ func (s Service) GetTotalDuration(file *File) (duration float64, err error) { func (s Service) GetFFmpegVesrion() (string, error) { cmd := exec.Command(s.ffPathUtilities.FFmpeg, "-version") + helper.PrepareBackgroundCommand(cmd) out, err := cmd.CombinedOutput() if err != nil { return "", err @@ -95,6 +119,7 @@ func (s Service) GetFFmpegVesrion() (string, error) { func (s Service) GetFFprobeVersion() (string, error) { cmd := exec.Command(s.ffPathUtilities.FFprobe, "-version") + helper.PrepareBackgroundCommand(cmd) out, err := cmd.CombinedOutput() if err != nil { return "", err @@ -105,6 +130,7 @@ func (s Service) GetFFprobeVersion() (string, error) { func (s Service) ChangeFFmpegPath(path string) (bool, error) { cmd := exec.Command(path, "-version") + helper.PrepareBackgroundCommand(cmd) out, err := cmd.CombinedOutput() if err != nil { return false, err @@ -118,6 +144,7 @@ func (s Service) ChangeFFmpegPath(path string) (bool, error) { func (s Service) ChangeFFprobePath(path string) (bool, error) { cmd := exec.Command(path, "-version") + helper.PrepareBackgroundCommand(cmd) out, err := cmd.CombinedOutput() if err != nil { return false, err diff --git a/src/convertor/view.go b/src/convertor/view.go index e5325dd..0ab80cc 100644 --- a/src/convertor/view.go +++ b/src/convertor/view.go @@ -12,8 +12,7 @@ import ( type ViewContract interface { Main( - runConvert func(setting HandleConvertSetting) error, - getSocketPath func(*File, *widget.ProgressBar) (string, error), + runConvert func(setting HandleConvertSetting, progressbar *widget.ProgressBar) error, ) } @@ -24,7 +23,6 @@ type View struct { type HandleConvertSetting struct { VideoFileInput *File DirectoryForSave string - SocketPath string OverwriteOutputFiles bool } @@ -39,8 +37,7 @@ func NewView(w fyne.Window) *View { } func (v View) Main( - runConvert func(setting HandleConvertSetting) error, - getSocketPath func(*File, *widget.ProgressBar) (string, error), + runConvert func(setting HandleConvertSetting, progressbar *widget.ProgressBar) error, ) { form := &widget.Form{} @@ -61,7 +58,7 @@ func (v View) Main( form.Items = []*widget.FormItem{ {Text: "Файл для ковертации:", Widget: fileVideoForConversion}, {Widget: fileVideoForConversionMessage}, - {Text: "Папка куда будет сохранятся:", Widget: buttonForSelectedDir}, + {Text: "Папка куда будет сохраняться:", Widget: buttonForSelectedDir}, {Widget: buttonForSelectedDirMessage}, {Widget: checkboxOverwriteOutputFiles}, } @@ -85,21 +82,12 @@ func (v View) Main( buttonForSelectedDir.Disable() form.Disable() - socketPath, err := getSocketPath(fileInput, progress) - - if err != nil { - showConversionMessage(conversionMessage, err) - enableFormConversion(enableFormConversionStruct) - return - } - setting := HandleConvertSetting{ VideoFileInput: fileInput, DirectoryForSave: *pathToSaveDirectory, - SocketPath: socketPath, OverwriteOutputFiles: isOverwriteOutputFiles, } - err = runConvert(setting) + err := runConvert(setting, progress) if err != nil { showConversionMessage(conversionMessage, err) enableFormConversion(enableFormConversionStruct) @@ -108,8 +96,9 @@ func (v View) Main( enableFormConversion(enableFormConversionStruct) } - v.w.SetContent(widget.NewCard("Конвертор видео файлов", "", container.NewVBox(form, conversionMessage, progress))) + v.w.SetContent(widget.NewCard("Конвертор видео файлов в mp4", "", container.NewVBox(form, conversionMessage, progress))) form.Disable() + progress.Hide() } func (v View) getButtonFileVideoForConversion(form *widget.Form, progress *widget.ProgressBar, conversionMessage *canvas.Text) (*widget.Button, *canvas.Text, *File) { diff --git a/src/handler/convertor.go b/src/handler/convertor.go index 3c3a8e0..88df064 100644 --- a/src/handler/convertor.go +++ b/src/handler/convertor.go @@ -1,22 +1,17 @@ package handler import ( + "bufio" "errors" "ffmpegGui/convertor" "ffmpegGui/helper" "ffmpegGui/setting" - "fmt" "fyne.io/fyne/v2/widget" - "log" - "math/rand" - "net" - "os" - "path" + "io" "regexp" "runtime" "strconv" "strings" - "time" ) type ConvertorHandler struct { @@ -42,67 +37,18 @@ func NewConvertorHandler( func (h ConvertorHandler) GetConvertor() { if h.checkingFFPathUtilities() == true { - h.convertorView.Main(h.runConvert, h.getSockPath) + h.convertorView.Main(h.runConvert) return } h.settingView.SelectFFPath(h.saveSettingFFPath) } -func (h ConvertorHandler) getSockPath(file *convertor.File, progressbar *widget.ProgressBar) (string, error) { - totalDuration, err := h.convertorService.GetTotalDuration(file) - +func (h ConvertorHandler) runConvert(setting convertor.HandleConvertSetting, progressbar *widget.ProgressBar) error { + totalDuration, err := h.convertorService.GetTotalDuration(setting.VideoFileInput) if err != nil { - return "", err + return err } - progressbar.Value = 0 - progressbar.Max = totalDuration - progressbar.Show() - progressbar.Refresh() - - rand.Seed(time.Now().Unix()) - sockFileName := path.Join(os.TempDir(), fmt.Sprintf("%d_sock", rand.Int())) - l, err := net.Listen("unix", sockFileName) - if err != nil { - return "", err - } - - go func() { - re := regexp.MustCompile(`frame=(\d+)`) - fd, err := l.Accept() - if err != nil { - log.Fatal("accept error:", err) - } - buf := make([]byte, 16) - data := "" - progress := 0.0 - for { - _, err := fd.Read(buf) - if err != nil { - return - } - data += string(buf) - 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 { - return - } - progress = float64(c) - } - if strings.Contains(data, "progress=end") { - progress = totalDuration - } - if progressbar.Value != progress { - progressbar.Value = progress - progressbar.Refresh() - } - } - }() - - return sockFileName, nil -} - -func (h ConvertorHandler) runConvert(setting convertor.HandleConvertSetting) error { + progress := NewProgress(totalDuration, progressbar) return h.convertorService.RunConvert( convertor.ConvertSetting{ @@ -112,9 +58,9 @@ func (h ConvertorHandler) runConvert(setting convertor.HandleConvertSetting) err Name: setting.VideoFileInput.Name, Ext: ".mp4", }, - SocketPath: setting.SocketPath, OverwriteOutputFiles: setting.OverwriteOutputFiles, }, + progress, ) } @@ -182,3 +128,79 @@ func (h ConvertorHandler) checkingFFPath() bool { return true } + +type progress struct { + totalDuration float64 + progressbar *widget.ProgressBar + protocol string +} + +func NewProgress(totalDuration float64, progressbar *widget.ProgressBar) progress { + return progress{ + totalDuration: totalDuration, + progressbar: progressbar, + protocol: "pipe:", + } +} + +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 + p.progressbar.Show() + p.progressbar.Refresh() + + progress := 0.0 + + go func() { + scannerErr := bufio.NewScanner(stdErr) + for scannerErr.Scan() { + errorText = scannerErr.Text() + } + if err := scannerErr.Err(); err != nil { + errorText = err.Error() + } + }() + + scannerOut := bufio.NewScanner(stdOut) + for scannerOut.Scan() { + if isProcessCompleted != true { + isProcessCompleted = true + } + data := scannerOut.Text() + 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 strings.Contains(data, "progress=end") { + p.progressbar.Value = p.totalDuration + p.progressbar.Refresh() + isProcessCompleted = true + } + if p.progressbar.Value != progress { + p.progressbar.Value = progress + p.progressbar.Refresh() + } + } + + if isProcessCompleted == false { + if len(errorText) == 0 { + errorText = "не смогли отконвертировать видео" + } + return errors.New(errorText) + } + + return nil +} diff --git a/src/helper/prepare_background_command.go b/src/helper/prepare_background_command.go new file mode 100644 index 0000000..f8aab96 --- /dev/null +++ b/src/helper/prepare_background_command.go @@ -0,0 +1,12 @@ +//go:build !windows +// +build !windows + +package helper + +import ( + "os/exec" +) + +func PrepareBackgroundCommand(cmd *exec.Cmd) { + +} diff --git a/src/helper/prepare_background_command_windows.go b/src/helper/prepare_background_command_windows.go new file mode 100644 index 0000000..3e3ebbc --- /dev/null +++ b/src/helper/prepare_background_command_windows.go @@ -0,0 +1,13 @@ +//go:build windows +// +build windows + +package helper + +import ( + "os/exec" + "syscall" +) + +func PrepareBackgroundCommand(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} +} diff --git a/src/main.go b/src/main.go index d04a154..e4955c5 100644 --- a/src/main.go +++ b/src/main.go @@ -15,12 +15,13 @@ import ( "os" ) -//const appVersion string = "0.1.0" +//const appVersion string = "0.1.1" func main() { a := app.New() w := a.NewWindow("GUI FFMpeg!") w.Resize(fyne.Size{Width: 800, Height: 600}) + w.CenterOnScreen() errorView := myError.NewView(w)