22 Commits

Author SHA1 Message Date
0cd32bb0f0 Merge pull request 'Версия 0.2.0' (#2) from develop into main
Reviewed-on: kor-elf/ffmpeg-gui#2
2024-01-28 22:45:56 +06:00
f3e034356b Merge branch 'main' into develop 2024-01-28 22:10:24 +06:00
6f0bbf7e29 Windows OS fix.
Added import "ffmpeg Gui/convertor" to convertor_windows.go
2024-01-28 22:08:40 +06:00
3c563d1966 Added the ability to select a language. 2024-01-28 22:01:16 +06:00
6df775955f Minor optimization.
Raised "progress=end" after data:= Scanner Out.Text().
2024-01-27 21:17:04 +06:00
d68382e418 Fixed a bug when the video was fully converted, but the progress bar showed not 100, but, for example, 99. 2024-01-27 21:08:27 +06:00
846986279c Added icon. 2024-01-23 21:33:30 +06:00
adf9bc9c27 Code refactoring.
Added files with functions for various OS (//go:build windows, //go:build !windows).
2024-01-23 21:33:01 +06:00
c572a3cabe Merge pull request 'Версия 0.1.1' (#1) from develop into main
Reviewed-on: kor-elf/ffmpeg-gui#1
2024-01-20 17:42:02 +06:00
7cc22e1553 Edit Reademe.md.
Added a screenshot from the program.
2024-01-20 17:39:18 +06:00
9bb19a0263 Sometimes the progressbar is not displayed, so I decided not to hide it. 2024-01-20 17:05:16 +06:00
fc38a1b20c Fix after closing the program so that ffmpeg would also stop if it was running. 2024-01-20 14:58:16 +06:00
2596e822bd Fix progress bar not always shown during conversion. 2024-01-20 14:57:22 +06:00
4fa977347c Fix ffmpeg processes not terminating. 2024-01-20 02:24:14 +06:00
77b847dde6 Fix error in OS windows.
Changed the progressbar to the pipe protocol.
2024-01-20 01:53:25 +06:00
ebc8832d4d Made it possible to choose where to save. 2024-01-18 20:23:23 +06:00
5051c65ec6 Fix path to ffmpeg and ffprobe.
Added the ability to select the path to ffmpeg and ffprobe.
2024-01-18 01:39:20 +06:00
10aa917c24 I made it so that an error would be displayed if ffmpeg was not found. 2024-01-16 18:08:50 +06:00
1070b796cc Fix RunConvert.
Fixed an error converting videos with a file name that has spaces.
2024-01-16 00:15:45 +06:00
176189c9d0 Adjusted display of error text when an error occurs during video conversion. 2024-01-16 00:04:50 +06:00
dddbfa65bc Refactor package "convertor" "Main" method.
Moved the button for selecting a video file into a separate function.
2024-01-15 20:28:02 +06:00
97dd0f4b32 Refactor getSockPath.
Removed extra code.
2024-01-14 17:25:35 +06:00
29 changed files with 1287 additions and 177 deletions

View File

@@ -1,3 +1,5 @@
# ffmpeg-gui
Простенький интерфейс к программе ffmpeg.
Простенький интерфейс к программе ffmpeg.
<img src="images/screenshot-ffmpeg-gui.png">

BIN
images/screenshot-ffmpeg-gui.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,19 +1,43 @@
package convertor
import (
"errors"
"ffmpegGui/helper"
"io"
"os/exec"
"regexp"
"strconv"
"strings"
)
type ServiceContract interface {
RunConvert(setting ConvertSetting) error
GetTotalDuration(file File) (float64, error)
RunConvert(setting ConvertSetting, progress ProgressContract) error
GetTotalDuration(file *File) (float64, error)
GetFFmpegVesrion() (string, error)
GetFFprobeVersion() (string, error)
ChangeFFmpegPath(path string) (bool, error)
ChangeFFprobePath(path string) (bool, error)
GetRunningProcesses() map[int]*exec.Cmd
}
type ProgressContract interface {
GetProtocole() string
Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error
}
type FFPathUtilities struct {
FFmpeg string
FFprobe string
}
type runningProcesses struct {
items map[int]*exec.Cmd
numberOfStarts int
}
type Service struct {
pathFFmpeg string
pathFFprobe string
ffPathUtilities *FFPathUtilities
runningProcesses runningProcesses
}
type File struct {
@@ -23,43 +47,55 @@ type File struct {
}
type ConvertSetting struct {
VideoFileInput File
SocketPath string
VideoFileInput *File
VideoFileOut *File
OverwriteOutputFiles bool
}
type ConvertData struct {
totalDuration float64
}
func NewService(pathFFmpeg string, pathFFprobe string) *Service {
func NewService(ffPathUtilities FFPathUtilities) *Service {
return &Service{
pathFFmpeg: pathFFmpeg,
pathFFprobe: pathFFprobe,
ffPathUtilities: &ffPathUtilities,
runningProcesses: runningProcesses{items: map[int]*exec.Cmd{}, numberOfStarts: 0},
}
}
func (s Service) RunConvert(setting ConvertSetting) error {
//args := strings.Split("-report -n -c:v libx264", " ")
//args := strings.Split("-n -c:v libx264", " ")
//args = append(args, "-progress", "unix://"+setting.SocketPath, "-i", setting.VideoFileInput.Path, "file-out.mp4")
//args := "-report -n -i " + setting.VideoFileInput.Path + " -c:v libx264 -progress unix://" + setting.SocketPath + " output-file.mp4"
//args := "-n -i " + setting.VideoFileInput.Path + " -c:v libx264 -progress unix://" + setting.SocketPath + " output-file.mp4"
args := "-y -i " + setting.VideoFileInput.Path + " -c:v libx264 -progress unix://" + setting.SocketPath + " output-file.mp4"
cmd := exec.Command("ffmpeg", strings.Split(args, " ")...)
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", progress.GetProtocole(), setting.VideoFileOut.Path}
cmd := exec.Command(s.ffPathUtilities.FFmpeg, args...)
helper.PrepareBackgroundCommand(cmd)
//stderr, _ := cmd.StdoutPipe()
err := cmd.Start()
stdOut, err := cmd.StdoutPipe()
if err != nil {
return err
}
stdErr, err := cmd.StderrPipe()
if err != nil {
return err
}
//scanner := bufio.NewScanner(stderr)
////scanner.Split(bufio.ScanWords)
//for scanner.Scan() {
// m := scanner.Text()
// fmt.Println(m)
//}
err = cmd.Start()
if err != nil {
return err
}
index := s.runningProcesses.numberOfStarts
s.runningProcesses.numberOfStarts++
s.runningProcesses.items[index] = cmd
errProgress := progress.Run(stdOut, stdErr)
err = cmd.Wait()
delete(s.runningProcesses.items, index)
if errProgress != nil {
return errProgress
}
if err != nil {
return err
}
@@ -67,12 +103,71 @@ func (s Service) RunConvert(setting ConvertSetting) error {
return nil
}
func (s Service) GetTotalDuration(file File) (duration float64, err error) {
args := "-v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 " + file.Path
cmd := exec.Command(s.pathFFprobe, strings.Split(args, " ")...)
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))
if len(errString) > 1 {
return 0, errors.New(errString)
}
return 0, err
}
return strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
}
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
}
text := regexp.MustCompile("\r?\n").Split(strings.TrimSpace(string(out)), -1)
return text[0], nil
}
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
}
text := regexp.MustCompile("\r?\n").Split(strings.TrimSpace(string(out)), -1)
return text[0], nil
}
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
}
if strings.Contains(strings.TrimSpace(string(out)), "ffmpeg") == false {
return false, nil
}
s.ffPathUtilities.FFmpeg = path
return true, nil
}
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
}
if strings.Contains(strings.TrimSpace(string(out)), "ffprobe") == false {
return false, nil
}
s.ffPathUtilities.FFprobe = path
return true, nil
}
func (s Service) GetRunningProcesses() map[int]*exec.Cmd {
return s.runningProcesses.items
}

View File

@@ -1,53 +1,146 @@
package convertor
import (
"errors"
"ffmpegGui/helper"
"ffmpegGui/localizer"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"github.com/nicksnyder/go-i18n/v2/i18n"
"image/color"
)
type ViewContract interface {
Main(
runConvert func(setting HandleConvertSetting) error,
getSocketPath func(File, *widget.ProgressBar) (string, error),
runConvert func(setting HandleConvertSetting, progressbar *widget.ProgressBar) error,
)
}
type View struct {
w fyne.Window
w fyne.Window
localizerService localizer.ServiceContract
}
type HandleConvertSetting struct {
VideoFileInput File
SocketPath string
VideoFileInput *File
DirectoryForSave string
OverwriteOutputFiles bool
}
func NewView(w fyne.Window) *View {
return &View{w}
type enableFormConversionStruct struct {
fileVideoForConversion *widget.Button
buttonForSelectedDir *widget.Button
form *widget.Form
}
func NewView(w fyne.Window, localizerService localizer.ServiceContract) *View {
return &View{
w: w,
localizerService: localizerService,
}
}
func (v View) Main(
runConvert func(setting HandleConvertSetting) error,
getSocketPath func(File, *widget.ProgressBar) (string, error),
runConvert func(setting HandleConvertSetting, progressbar *widget.ProgressBar) error,
) {
var fileInput File
var form *widget.Form
fileVideoForConversionMessage := canvas.NewText("", color.RGBA{255, 0, 0, 255})
fileVideoForConversionMessage.TextSize = 16
fileVideoForConversionMessage.TextStyle = fyne.TextStyle{Bold: true}
form := &widget.Form{}
conversionMessage := canvas.NewText("", color.RGBA{255, 0, 0, 255})
conversionMessage.TextSize = 16
conversionMessage.TextStyle = fyne.TextStyle{Bold: true}
progress := widget.NewProgressBar()
progress.Hide()
fileVideoForConversion := widget.NewButton("выбрать", func() {
fileVideoForConversion, fileVideoForConversionMessage, fileInput := v.getButtonFileVideoForConversion(form, progress, conversionMessage)
buttonForSelectedDir, buttonForSelectedDirMessage, pathToSaveDirectory := v.getButtonForSelectingDirectoryForSaving()
isOverwriteOutputFiles := false
checkboxOverwriteOutputFilesTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "checkboxOverwriteOutputFilesTitle",
})
checkboxOverwriteOutputFiles := widget.NewCheck(checkboxOverwriteOutputFilesTitle, func(b bool) {
isOverwriteOutputFiles = b
})
form.Items = []*widget.FormItem{
{
Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "fileVideoForConversionTitle"}),
Widget: fileVideoForConversion,
},
{
Widget: fileVideoForConversionMessage,
},
{
Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "buttonForSelectedDirTitle"}),
Widget: buttonForSelectedDir,
},
{
Widget: buttonForSelectedDirMessage,
},
{
Widget: checkboxOverwriteOutputFiles,
},
}
form.SubmitText = v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "converterVideoFilesSubmitTitle",
})
enableFormConversionStruct := enableFormConversionStruct{
fileVideoForConversion: fileVideoForConversion,
buttonForSelectedDir: buttonForSelectedDir,
form: form,
}
form.OnSubmit = func() {
if len(*pathToSaveDirectory) == 0 {
showConversionMessage(conversionMessage, errors.New(v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "errorSelectedFolderSave",
})))
enableFormConversion(enableFormConversionStruct)
return
}
conversionMessage.Text = ""
fileVideoForConversion.Disable()
buttonForSelectedDir.Disable()
form.Disable()
setting := HandleConvertSetting{
VideoFileInput: fileInput,
DirectoryForSave: *pathToSaveDirectory,
OverwriteOutputFiles: isOverwriteOutputFiles,
}
err := runConvert(setting, progress)
if err != nil {
showConversionMessage(conversionMessage, err)
enableFormConversion(enableFormConversionStruct)
return
}
enableFormConversion(enableFormConversionStruct)
}
converterVideoFilesTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "converterVideoFilesTitle",
})
v.w.SetContent(widget.NewCard(converterVideoFilesTitle, "", container.NewVBox(form, conversionMessage, progress)))
form.Disable()
}
func (v View) getButtonFileVideoForConversion(form *widget.Form, progress *widget.ProgressBar, conversionMessage *canvas.Text) (*widget.Button, *canvas.Text, *File) {
fileInput := &File{}
fileVideoForConversionMessage := canvas.NewText("", color.RGBA{255, 0, 0, 255})
fileVideoForConversionMessage.TextSize = 16
fileVideoForConversionMessage.TextStyle = fyne.TextStyle{Bold: true}
buttonTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "choose",
})
button := widget.NewButton(buttonTitle, func() {
fileDialog := dialog.NewFileOpen(
func(r fyne.URIReadCloser, err error) {
if err != nil {
@@ -55,59 +148,63 @@ func (v View) Main(
setStringErrorStyle(fileVideoForConversionMessage)
return
}
if r == nil {
return
}
fileInput = File{
Path: r.URI().Path(),
Name: r.URI().Name(),
Ext: r.URI().Extension(),
}
fileInput.Path = r.URI().Path()
fileInput.Name = r.URI().Name()
fileInput.Ext = r.URI().Extension()
fileVideoForConversionMessage.Text = r.URI().Path()
setStringSuccessStyle(fileVideoForConversionMessage)
form.Enable()
progress.Value = 0
progress.Refresh()
conversionMessage.Text = ""
}, v.w)
helper.FileDialogResize(fileDialog, v.w)
fileDialog.Show()
})
form = &widget.Form{
Items: []*widget.FormItem{
{Text: "Файл для ковертации:", Widget: fileVideoForConversion},
{Widget: fileVideoForConversionMessage},
},
SubmitText: "Конвертировать",
OnSubmit: func() {
fileVideoForConversion.Disable()
form.Disable()
return button, fileVideoForConversionMessage, fileInput
}
socketPath, err := getSocketPath(fileInput, progress)
func (v View) getButtonForSelectingDirectoryForSaving() (button *widget.Button, buttonMessage *canvas.Text, dirPath *string) {
buttonMessage = canvas.NewText("", color.RGBA{255, 0, 0, 255})
buttonMessage.TextSize = 16
buttonMessage.TextStyle = fyne.TextStyle{Bold: true}
if err != nil {
conversionMessage.Text = err.Error()
setStringErrorStyle(conversionMessage)
fileVideoForConversion.Enable()
form.Enable()
}
path := ""
dirPath = &path
setting := HandleConvertSetting{
VideoFileInput: fileInput,
SocketPath: socketPath,
}
err = runConvert(setting)
if err != nil {
conversionMessage.Text = err.Error()
setStringErrorStyle(conversionMessage)
}
fileVideoForConversion.Enable()
form.Enable()
},
}
buttonTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "choose",
})
v.w.SetContent(widget.NewCard("Конвертор видео файлов", "", container.NewVBox(form, conversionMessage, progress)))
form.Disable()
button = widget.NewButton(buttonTitle, func() {
fileDialog := dialog.NewFolderOpen(
func(r fyne.ListableURI, err error) {
if err != nil {
buttonMessage.Text = err.Error()
setStringErrorStyle(buttonMessage)
return
}
if r == nil {
return
}
path = r.Path()
buttonMessage.Text = r.Path()
setStringSuccessStyle(buttonMessage)
}, v.w)
helper.FileDialogResize(fileDialog, v.w)
fileDialog.Show()
})
return
}
func setStringErrorStyle(text *canvas.Text) {
@@ -119,3 +216,14 @@ func setStringSuccessStyle(text *canvas.Text) {
text.Color = color.RGBA{49, 127, 114, 255}
text.Refresh()
}
func showConversionMessage(conversionMessage *canvas.Text, err error) {
conversionMessage.Text = err.Error()
setStringErrorStyle(conversionMessage)
}
func enableFormConversion(enableFormConversionStruct enableFormConversionStruct) {
enableFormConversionStruct.fileVideoForConversion.Enable()
enableFormConversionStruct.buttonForSelectedDir.Enable()
enableFormConversionStruct.form.Enable()
}

2
src/data/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -1,9 +1,11 @@
package error
import (
"ffmpegGui/localizer"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"github.com/nicksnyder/go-i18n/v2/i18n"
)
type ViewContract interface {
@@ -11,16 +13,54 @@ type ViewContract interface {
}
type View struct {
w fyne.Window
w fyne.Window
localizerService localizer.ServiceContract
}
func NewView(w fyne.Window) *View {
return &View{w}
func NewView(w fyne.Window, localizerService localizer.ServiceContract) *View {
return &View{
w: w,
localizerService: localizerService,
}
}
func (v View) PanicError(err error) {
v.w.SetContent(container.NewVBox(
widget.NewLabel("Произошла ошибка!"),
widget.NewLabel("Ошибка: "+err.Error()),
messageHead := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "error",
})
v.w.SetContent(container.NewBorder(
container.NewVBox(
widget.NewLabel(messageHead),
widget.NewLabel(err.Error()),
),
nil,
nil,
nil,
localizer.LanguageSelectionForm(v.localizerService, func(lang localizer.Lang) {
v.PanicError(err)
}),
))
}
func (v View) PanicErrorWriteDirectoryData() {
message := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "errorDatabase",
})
messageHead := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "error",
})
v.w.SetContent(container.NewBorder(
container.NewVBox(
widget.NewLabel(messageHead),
widget.NewLabel(message),
),
nil,
nil,
nil,
localizer.LanguageSelectionForm(v.localizerService, func(lang localizer.Lang) {
v.PanicErrorWriteDirectoryData()
}),
))
}

View File

@@ -5,6 +5,7 @@ go 1.21
require (
fyne.io/fyne/v2 v2.4.3 // indirect
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
@@ -17,7 +18,11 @@ require (
github.com/go-text/typesetting v0.0.0-20230616162802-9c17dd34aa4a // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/mattn/go-sqlite3 v1.14.19 // indirect
github.com/nicksnyder/go-i18n/v2 v2.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
@@ -28,7 +33,9 @@ require (
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/sqlite v1.5.4 // indirect
gorm.io/gorm v1.25.5 // indirect
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
)

View File

@@ -42,6 +42,8 @@ fyne.io/fyne/v2 v2.4.3/go.mod h1:1h3BKxmQYRJlr2g+RGVxedzr6vLVQ/AJmFWcF9CJnoQ=
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzmivNgrclwPgoUdVSrjkP8=
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -191,6 +193,10 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -206,6 +212,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -220,6 +228,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ=
github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -471,6 +481,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -647,6 +659,12 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 h1:sC1Xj4TYrLqg1n3AN10w871An7wJM0gzgcm8jkIkECQ=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -1,112 +1,223 @@
package handler
import (
"bufio"
"errors"
"ffmpegGui/convertor"
myError "ffmpegGui/error"
"fmt"
"ffmpegGui/helper"
"ffmpegGui/localizer"
"ffmpegGui/setting"
"fyne.io/fyne/v2/widget"
"log"
"math/rand"
"net"
"os"
"path"
"github.com/nicksnyder/go-i18n/v2/i18n"
"io"
"regexp"
"strconv"
"strings"
"time"
)
type ConvertorHandler struct {
convertorService convertor.ServiceContract
convertorView convertor.ViewContract
errorView myError.ViewContract
convertorService convertor.ServiceContract
convertorView convertor.ViewContract
settingView setting.ViewContract
localizerView localizer.ViewContract
settingRepository setting.RepositoryContract
localizerService localizer.ServiceContract
}
func NewConvertorHandler(
convertorService convertor.ServiceContract,
convertorView convertor.ViewContract,
errorView myError.ViewContract,
settingView setting.ViewContract,
localizerView localizer.ViewContract,
settingRepository setting.RepositoryContract,
localizerService localizer.ServiceContract,
) *ConvertorHandler {
return &ConvertorHandler{
convertorService,
convertorView,
errorView,
convertorService: convertorService,
convertorView: convertorView,
settingView: settingView,
localizerView: localizerView,
settingRepository: settingRepository,
localizerService: localizerService,
}
}
func (h ConvertorHandler) LanguageSelection() {
h.localizerView.LanguageSelection(func(lang localizer.Lang) {
h.GetConvertor()
})
}
func (h ConvertorHandler) GetConvertor() {
h.convertorView.Main(h.runConvert, h.getSockPath)
if h.checkingFFPathUtilities() == true {
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.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()
progress := NewProgress(totalDuration, progressbar, h.localizerService)
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
progressMessage := ""
for {
_, err := fd.Read(buf)
if err != nil {
return
}
data += string(buf)
a := re.FindAllStringSubmatch(data, -1)
cp := ""
if len(a) > 0 && len(a[len(a)-1]) > 0 {
c, _ := strconv.Atoi(a[len(a)-1][len(a[len(a)-1])-1])
progress = float64(c)
cp = fmt.Sprintf("%.2f", float64(c)/totalDuration*100)
}
if strings.Contains(data, "progress=end") {
cp = "done"
progress = totalDuration
}
if cp == "" {
cp = ".0"
}
if cp != progressMessage {
progressbar.Value = progress
progressbar.Refresh()
progressMessage = cp
fmt.Println("progress: ", progressMessage)
}
}
}()
return sockFileName, nil
}
func (h ConvertorHandler) runConvert(setting convertor.HandleConvertSetting) error {
return h.convertorService.RunConvert(
convertor.ConvertSetting{
VideoFileInput: setting.VideoFileInput,
SocketPath: setting.SocketPath,
VideoFileOut: &convertor.File{
Path: setting.DirectoryForSave + helper.PathSeparator() + setting.VideoFileInput.Name + ".mp4",
Name: setting.VideoFileInput.Name,
Ext: ".mp4",
},
OverwriteOutputFiles: setting.OverwriteOutputFiles,
},
progress,
)
}
func (h ConvertorHandler) getTotalDuration(file convertor.File) (float64, error) {
return h.convertorService.GetTotalDuration(file)
func (h ConvertorHandler) checkingFFPathUtilities() bool {
if h.checkingFFPath() == true {
return true
}
pathsToFF := getPathsToFF()
for _, item := range pathsToFF {
ffmpegChecking, _ := h.convertorService.ChangeFFmpegPath(item.FFmpeg)
if ffmpegChecking == false {
continue
}
ffprobeChecking, _ := h.convertorService.ChangeFFprobePath(item.FFprobe)
if ffprobeChecking == false {
continue
}
ffmpegEntity := setting.Setting{Code: "ffmpeg", Value: item.FFmpeg}
_, _ = h.settingRepository.Create(ffmpegEntity)
ffprobeEntity := setting.Setting{Code: "ffprobe", Value: item.FFprobe}
_, _ = h.settingRepository.Create(ffprobeEntity)
return true
}
return false
}
func (h ConvertorHandler) saveSettingFFPath(ffmpegPath string, ffprobePath string) error {
ffmpegChecking, _ := h.convertorService.ChangeFFmpegPath(ffmpegPath)
if ffmpegChecking == false {
errorText := h.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "errorFFmpeg",
})
return errors.New(errorText)
}
ffprobeChecking, _ := h.convertorService.ChangeFFprobePath(ffprobePath)
if ffprobeChecking == false {
errorText := h.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "errorFFprobe",
})
return errors.New(errorText)
}
ffmpegEntity := setting.Setting{Code: "ffmpeg", Value: ffmpegPath}
_, _ = h.settingRepository.Create(ffmpegEntity)
ffprobeEntity := setting.Setting{Code: "ffprobe", Value: ffprobePath}
_, _ = h.settingRepository.Create(ffprobeEntity)
h.GetConvertor()
return nil
}
func (h ConvertorHandler) checkingFFPath() bool {
_, err := h.convertorService.GetFFmpegVesrion()
if err != nil {
return false
}
_, err = h.convertorService.GetFFprobeVersion()
if err != nil {
return false
}
return true
}
type progress struct {
totalDuration float64
progressbar *widget.ProgressBar
protocol string
localizerService localizer.ServiceContract
}
func NewProgress(totalDuration float64, progressbar *widget.ProgressBar, localizerService localizer.ServiceContract) 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
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() {
data := scannerOut.Text()
if strings.Contains(data, "progress=end") {
p.progressbar.Value = p.totalDuration
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
p.progressbar.Refresh()
}
}
if isProcessCompleted == false {
if len(errorText) == 0 {
errorText = p.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "errorConverter",
})
}
return errors.New(errorText)
}
return nil
}

View File

@@ -0,0 +1,10 @@
//go:build !windows
// +build !windows
package handler
import "ffmpegGui/convertor"
func getPathsToFF() []convertor.FFPathUtilities {
return []convertor.FFPathUtilities{{"ffmpeg/bin/ffmpeg", "ffmpeg/bin/ffprobe"}, {"ffmpeg", "ffprobe"}}
}

View File

@@ -0,0 +1,10 @@
//go:build windows
// +build windows
package handler
import "ffmpegGui/convertor"
func getPathsToFF() []convertor.FFPathUtilities {
return []convertor.FFPathUtilities{{"ffmpeg\\bin\\ffmpeg.exe", "ffmpeg\\bin\\ffprobe.exe"}}
}

11
src/helper/helper.go Normal file
View File

@@ -0,0 +1,11 @@
package helper
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 helper
func PathSeparator() string {
return "/"
}

View File

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

View File

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

View File

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

BIN
src/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,75 @@
[buttonForSelectedDirTitle]
hash = "sha1-52b13f1b13e82d22e8c4102332db5d4ec551247b"
other = "Folder where it will be saved:"
[checkboxOverwriteOutputFilesTitle]
hash = "sha1-5860124bb781e7ef680f573fa93977e96328d4e7"
other = "Allow file to be overwritten"
[choose]
hash = "sha1-f60bb5f761024d973834b5e9d25ceebce2c85f94"
other = "choose"
[converterVideoFilesSubmitTitle]
hash = "sha1-7ac460f3c24c9952082f2db6e4d62f752598709c"
other = "Convert"
[converterVideoFilesTitle]
hash = "sha1-4d972809e4c7f9c9ff2c110a126bbc183c9429ce"
other = "Converter video files to mp4"
[error]
hash = "sha1-a7df8f8b5d754f226ac4cb320577fe692b33e483"
other = "An error has occurred!"
[errorConverter]
hash = "sha1-55ebddceddb8b044e33cc3893ec2eba7bbd9fcf9"
other = "Couldn't convert video"
[errorDatabase]
hash = "sha1-531abc3f0d12727e542df6e5a22de91098380fc1"
other = "could not create file 'database' in folder 'data'"
[errorFFmpeg]
hash = "sha1-ccf0b95c0d1b392dc215258d917eb4e5d0b88ed0"
other = "this is not FFmpeg"
[errorFFprobe]
hash = "sha1-86d1b0b4c4ccd6a4f71e758fc67ce11aff4ba9b8"
other = "this is not FFprobe"
[errorSelectedFolderSave]
hash = "sha1-83da899677cdc90e4344e3b94ee03c46b51bee4c"
other = "You haven't selected a folder to save!"
[fileVideoForConversionTitle]
hash = "sha1-5e727d4a2ff3f21080e51e81641595b2e668f3be"
other = "File for conversion:"
[languageSelectionFormHead]
hash = "sha1-0ff5fa82cf684112660128cba1711297acf11003"
other = "Switch language"
[languageSelectionHead]
hash = "sha1-daf1108fc10d3b1a908288d611f749b3cc651e4b"
other = "Choose language"
[pathToFfmpeg]
hash = "sha1-2eba439f365640ff77e0ed6a7486fcb662573850"
other = "Path to ffmpeg:"
[pathToFfprobe]
hash = "sha1-02ad53337801906f8ebfee4616100dd9f43eabd7"
other = "Path to ffprobe:"
[save]
hash = "sha1-4864057d626a868fa60f999bed3191d61d045ddc"
other = "Save"
[selectFFPathTitle]
hash = "sha1-95581446a28d968ff1a027c623159a7eb08654cf"
other = "Specify the path to FFmpeg and FFprobe"
[titleDownloadLink]
hash = "sha1-92df86371f6c3a06ca1e4754f113142776a32d49"
other = "You can download it from here"

View File

@@ -0,0 +1,75 @@
[buttonForSelectedDirTitle]
hash = "sha1-52b13f1b13e82d22e8c4102332db5d4ec551247b"
other = "Файлды сақтауға арналған каталог:"
[checkboxOverwriteOutputFilesTitle]
hash = "sha1-5860124bb781e7ef680f573fa93977e96328d4e7"
other = "Файлды қайта жазуға рұқсат беріңіз"
[choose]
hash = "sha1-f60bb5f761024d973834b5e9d25ceebce2c85f94"
other = "таңдау"
[converterVideoFilesSubmitTitle]
hash = "sha1-7ac460f3c24c9952082f2db6e4d62f752598709c"
other = "Файлды түрлендіру"
[converterVideoFilesTitle]
hash = "sha1-4d972809e4c7f9c9ff2c110a126bbc183c9429ce"
other = "Бейне файлдарын mp4 форматына түрлендіру"
[error]
hash = "sha1-a7df8f8b5d754f226ac4cb320577fe692b33e483"
other = "Қате орын алды!"
[errorConverter]
hash = "sha1-55ebddceddb8b044e33cc3893ec2eba7bbd9fcf9"
other = "Бейнені түрлендіру мүмкін болмады"
[errorDatabase]
hash = "sha1-531abc3f0d12727e542df6e5a22de91098380fc1"
other = "'data' қалтасында 'database' файлын жасау мүмкін болмады"
[errorFFmpeg]
hash = "sha1-ccf0b95c0d1b392dc215258d917eb4e5d0b88ed0"
other = "бұл FFmpeg емес"
[errorFFprobe]
hash = "sha1-86d1b0b4c4ccd6a4f71e758fc67ce11aff4ba9b8"
other = "бұл FFprobe емес"
[errorSelectedFolderSave]
hash = "sha1-83da899677cdc90e4344e3b94ee03c46b51bee4c"
other = "Сіз сақталатын қалтаны таңдамадыңыз!"
[fileVideoForConversionTitle]
hash = "sha1-5e727d4a2ff3f21080e51e81641595b2e668f3be"
other = "Түрлендіруге арналған файл:"
[languageSelectionFormHead]
hash = "sha1-0ff5fa82cf684112660128cba1711297acf11003"
other = "Тілді ауыстыру"
[languageSelectionHead]
hash = "sha1-daf1108fc10d3b1a908288d611f749b3cc651e4b"
other = "Тілді таңдаңыз"
[pathToFfmpeg]
hash = "sha1-2eba439f365640ff77e0ed6a7486fcb662573850"
other = "ffmpeg жол:"
[pathToFfprobe]
hash = "sha1-02ad53337801906f8ebfee4616100dd9f43eabd7"
other = "ffprobe жол:"
[save]
hash = "sha1-4864057d626a868fa60f999bed3191d61d045ddc"
other = "Сақтау"
[selectFFPathTitle]
hash = "sha1-95581446a28d968ff1a027c623159a7eb08654cf"
other = "FFmpeg және FFprobe жолын көрсетіңіз"
[titleDownloadLink]
hash = "sha1-92df86371f6c3a06ca1e4754f113142776a32d49"
other = "Сіз оны осы жерден жүктей аласыз"

View File

@@ -0,0 +1,19 @@
buttonForSelectedDirTitle = "Папка куда будет сохраняться:"
checkboxOverwriteOutputFilesTitle = "Разрешить перезаписать файл"
choose = "выбрать"
converterVideoFilesSubmitTitle = "Конвертировать"
converterVideoFilesTitle = "Конвертор видео файлов в mp4"
error = "Произошла ошибка!"
errorConverter = "не смогли отконвертировать видео"
errorDatabase = "не смогли создать файл 'database' в папке 'data'"
errorFFmpeg = "это не FFmpeg"
errorFFprobe = "это не FFprobe"
errorSelectedFolderSave = "Не выбрали папку для сохранения!"
fileVideoForConversionTitle = "Файл для ковертации:"
languageSelectionFormHead = "Переключить язык"
languageSelectionHead = "Выберите язык"
pathToFfmpeg = "Путь к ffmpeg:"
pathToFfprobe = "Путь к ffprobe:"
save = "Сохранить"
selectFFPathTitle = "Укажите путь к FFmpeg и к FFprobe"
titleDownloadLink = "Скачать можно от сюда"

View File

View File

128
src/localizer/service.go Normal file
View File

@@ -0,0 +1,128 @@
package localizer
import (
"github.com/BurntSushi/toml"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"golang.org/x/text/language/display"
"path/filepath"
"sort"
)
type ServiceContract interface {
GetLanguages() []Lang
GetMessage(localizeConfig *i18n.LocalizeConfig) string
SetCurrentLanguage(lang Lang) error
GetCurrentLanguage() *CurrentLanguage
}
type Lang struct {
Code string
Title string
}
type CurrentLanguage struct {
Lang Lang
localizer *i18n.Localizer
localizerDefault *i18n.Localizer
}
type Service struct {
bundle *i18n.Bundle
languages []Lang
currentLanguage *CurrentLanguage
}
func NewService(directory string, languageDefault language.Tag) (*Service, error) {
bundle := i18n.NewBundle(languageDefault)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
languages, err := initLanguages(directory, bundle)
if err != nil {
return nil, err
}
localizerDefault := i18n.NewLocalizer(bundle, languageDefault.String())
return &Service{
bundle: bundle,
languages: languages,
currentLanguage: &CurrentLanguage{
Lang: Lang{
Code: languageDefault.String(),
Title: cases.Title(languageDefault).String(display.Self.Name(languageDefault)),
},
localizer: localizerDefault,
localizerDefault: localizerDefault,
},
}, nil
}
func initLanguages(directory string, bundle *i18n.Bundle) ([]Lang, error) {
var languages []Lang
files, err := filepath.Glob(directory + "/active.*.toml")
if err != nil {
return nil, err
}
for _, file := range files {
language, err := bundle.LoadMessageFile(file)
if err != nil {
return nil, err
}
title := cases.Title(language.Tag).String(display.Self.Name(language.Tag))
languages = append(languages, Lang{Code: language.Tag.String(), Title: title})
}
sort.Sort(languagesSort(languages))
return languages, nil
}
func (s Service) GetLanguages() []Lang {
return s.languages
}
func (s Service) GetMessage(localizeConfig *i18n.LocalizeConfig) string {
message, err := s.GetCurrentLanguage().localizer.Localize(localizeConfig)
if err != nil {
message, err = s.GetCurrentLanguage().localizerDefault.Localize(localizeConfig)
if err != nil {
return err.Error()
}
}
return message
}
func (s Service) SetCurrentLanguage(lang Lang) error {
s.currentLanguage.Lang = lang
s.currentLanguage.localizer = i18n.NewLocalizer(s.bundle, lang.Code)
return nil
}
func (s Service) GetCurrentLanguage() *CurrentLanguage {
return s.currentLanguage
}
type languagesSort []Lang
func (l languagesSort) Len() int { return len(l) }
func (l languagesSort) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l languagesSort) Less(i, j int) bool {
return languagePriority(l[i]) < languagePriority(l[j])
}
func languagePriority(l Lang) int {
priority := 0
switch l.Code {
case "ru":
priority = -3
case "kk":
priority = -2
case "en":
priority = -1
}
return priority
}

76
src/localizer/view.go Normal file
View File

@@ -0,0 +1,76 @@
package localizer
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/widget"
"github.com/nicksnyder/go-i18n/v2/i18n"
)
type ViewContract interface {
LanguageSelection(funcSelected func(lang Lang))
}
type View struct {
w fyne.Window
localizerService ServiceContract
}
func NewView(w fyne.Window, localizerService ServiceContract) *View {
return &View{
w: w,
localizerService: localizerService,
}
}
func (v View) LanguageSelection(funcSelected func(lang Lang)) {
languages := v.localizerService.GetLanguages()
listView := widget.NewList(
func() int {
return len(languages)
},
func() fyne.CanvasObject {
return widget.NewLabel("template")
},
func(i widget.ListItemID, o fyne.CanvasObject) {
block := o.(*widget.Label)
block.SetText(languages[i].Title)
})
listView.OnSelected = func(id widget.ListItemID) {
_ = v.localizerService.SetCurrentLanguage(languages[id])
funcSelected(languages[id])
}
messageHead := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "languageSelectionHead",
})
v.w.SetContent(widget.NewCard(messageHead, "", listView))
}
func LanguageSelectionForm(localizerService ServiceContract, funcSelected func(lang Lang)) fyne.CanvasObject {
languages := localizerService.GetLanguages()
currentLanguage := localizerService.GetCurrentLanguage()
listView := widget.NewList(
func() int {
return len(languages)
},
func() fyne.CanvasObject {
return widget.NewLabel("template")
},
func(i widget.ListItemID, o fyne.CanvasObject) {
block := o.(*widget.Label)
block.SetText(languages[i].Title)
if languages[i].Code == currentLanguage.Lang.Code {
block.TextStyle = fyne.TextStyle{Bold: true}
}
})
listView.OnSelected = func(id widget.ListItemID) {
_ = localizerService.SetCurrentLanguage(languages[id])
funcSelected(languages[id])
}
messageHead := localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "languageSelectionFormHead",
})
return widget.NewCard(messageHead, "", listView)
}

View File

@@ -1,30 +1,119 @@
package main
import (
"errors"
"ffmpegGui/convertor"
myError "ffmpegGui/error"
error2 "ffmpegGui/error"
"ffmpegGui/handler"
"ffmpegGui/localizer"
"ffmpegGui/migration"
"ffmpegGui/setting"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
_ "github.com/mattn/go-sqlite3"
"golang.org/x/text/language"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"os"
)
const appVersion string = "0.1.0"
//const appVersion string = "0.2.0"
func main() {
a := app.New()
iconResource, err := fyne.LoadResourceFromPath("icon.png")
if err == nil {
a.SetIcon(iconResource)
}
w := a.NewWindow("GUI FFMpeg!")
w.Resize(fyne.Size{800, 600})
w.Resize(fyne.Size{Width: 800, Height: 600})
w.CenterOnScreen()
errorView := myError.NewView(w)
localizerService, err := localizer.NewService("languages", language.Russian)
if err != nil {
panicErrorLang(w, err)
w.ShowAndRun()
return
}
pathFFmpeg := "ffmpeg"
pathFFprobe := "ffprobe"
errorView := error2.NewView(w, localizerService)
if canCreateFile("data/database") != true {
errorView.PanicErrorWriteDirectoryData()
w.ShowAndRun()
return
}
convertorView := convertor.NewView(w)
convertorService := convertor.NewService(pathFFmpeg, pathFFprobe)
mainHandler := handler.NewConvertorHandler(convertorService, convertorView, errorView)
db, err := gorm.Open(sqlite.Open("data/database"), &gorm.Config{})
if err != nil {
errorView.PanicError(err)
w.ShowAndRun()
return
}
mainHandler.GetConvertor()
defer appCloseWithDb(db)
err = migration.Run(db)
if err != nil {
errorView.PanicError(err)
w.ShowAndRun()
return
}
settingRepository := setting.NewRepository(db)
pathFFmpeg, err := settingRepository.GetValue("ffmpeg")
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) == false {
errorView.PanicError(err)
w.ShowAndRun()
return
}
pathFFprobe, err := settingRepository.GetValue("ffprobe")
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) == false {
errorView.PanicError(err)
w.ShowAndRun()
return
}
ffPathUtilities := convertor.FFPathUtilities{FFmpeg: pathFFmpeg, FFprobe: pathFFprobe}
localizerView := localizer.NewView(w, localizerService)
convertorView := convertor.NewView(w, localizerService)
settingView := setting.NewView(w, localizerService)
convertorService := convertor.NewService(ffPathUtilities)
defer appCloseWithConvert(convertorService)
mainHandler := handler.NewConvertorHandler(convertorService, convertorView, settingView, localizerView, settingRepository, localizerService)
mainHandler.LanguageSelection()
w.ShowAndRun()
}
func appCloseWithDb(db *gorm.DB) {
sqlDB, err := db.DB()
if err == nil {
_ = sqlDB.Close()
}
}
func appCloseWithConvert(convertorService convertor.ServiceContract) {
for _, cmd := range convertorService.GetRunningProcesses() {
_ = cmd.Process.Kill()
}
}
func canCreateFile(path string) bool {
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return false
}
_ = file.Close()
return true
}
func panicErrorLang(w fyne.Window, err error) {
w.SetContent(container.NewVBox(
widget.NewLabel("Произошла ошибка!"),
widget.NewLabel("произошла ошибка при получении языковых переводах. \n\r"+err.Error()),
))
}

View File

@@ -0,0 +1,15 @@
package migration
import (
"ffmpegGui/setting"
"gorm.io/gorm"
)
func Run(db *gorm.DB) error {
err := db.AutoMigrate(&setting.Setting{})
if err != nil {
return err
}
return nil
}

7
src/setting/entity.go Normal file
View File

@@ -0,0 +1,7 @@
package setting
type Setting struct {
ID uint `gorm:"primary_key"`
Code string `gorm:"type:varchar(100);uniqueIndex;not null"`
Value string `gorm:"type:text"`
}

35
src/setting/repository.go Normal file
View File

@@ -0,0 +1,35 @@
package setting
import (
"gorm.io/gorm"
)
type RepositoryContract interface {
Create(setting Setting) (Setting, error)
GetValue(code string) (value string, err error)
}
type Repository struct {
db *gorm.DB
}
func NewRepository(db *gorm.DB) *Repository {
return &Repository{db}
}
func (r Repository) GetValue(code string) (value string, err error) {
var setting Setting
err = r.db.Where("code = ?", code).First(&setting).Error
if err != nil {
return "", err
}
return setting.Value, err
}
func (r Repository) Create(setting Setting) (Setting, error) {
err := r.db.Create(&setting).Error
if err != nil {
return setting, err
}
return setting, err
}

136
src/setting/view.go Normal file
View File

@@ -0,0 +1,136 @@
package setting
import (
"ffmpegGui/helper"
"ffmpegGui/localizer"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"github.com/nicksnyder/go-i18n/v2/i18n"
"image/color"
"net/url"
)
type ViewContract interface {
SelectFFPath(func(ffmpegPath string, ffprobePath string) error)
}
type View struct {
w fyne.Window
localizerService localizer.ServiceContract
}
func NewView(w fyne.Window, localizerService localizer.ServiceContract) *View {
return &View{
w: w,
localizerService: localizerService,
}
}
func (v View) SelectFFPath(save func(ffmpegPath string, ffprobePath string) error) {
errorMessage := canvas.NewText("", color.RGBA{255, 0, 0, 255})
errorMessage.TextSize = 16
errorMessage.TextStyle = fyne.TextStyle{Bold: true}
ffmpegPath, buttonFFmpeg, buttonFFmpegMessage := v.getButtonSelectFile()
ffprobePath, buttonFFprobe, buttonFFprobeMessage := v.getButtonSelectFile()
link := widget.NewHyperlink("https://ffmpeg.org/download.html", &url.URL{
Scheme: "https",
Host: "ffmpeg.org",
Path: "download.html",
})
form := &widget.Form{
Items: []*widget.FormItem{
{
Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "titleDownloadLink",
}),
Widget: link,
},
{
Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "pathToFfmpeg",
}),
Widget: buttonFFmpeg,
},
{
Widget: buttonFFmpegMessage,
},
{
Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "pathToFfprobe",
}),
Widget: buttonFFprobe,
},
{
Widget: buttonFFprobeMessage,
},
{
Widget: errorMessage,
},
},
SubmitText: v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "save",
}),
OnSubmit: func() {
err := save(string(*ffmpegPath), string(*ffprobePath))
if err != nil {
errorMessage.Text = err.Error()
}
},
}
selectFFPathTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "selectFFPathTitle",
})
v.w.SetContent(widget.NewCard(selectFFPathTitle, "", container.NewVBox(form)))
}
func (v View) getButtonSelectFile() (filePath *string, button *widget.Button, buttonMessage *canvas.Text) {
path := ""
filePath = &path
buttonMessage = canvas.NewText("", color.RGBA{255, 0, 0, 255})
buttonMessage.TextSize = 16
buttonMessage.TextStyle = fyne.TextStyle{Bold: true}
buttonTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "choose",
})
button = widget.NewButton(buttonTitle, func() {
fileDialog := dialog.NewFileOpen(
func(r fyne.URIReadCloser, err error) {
if err != nil {
buttonMessage.Text = err.Error()
setStringErrorStyle(buttonMessage)
return
}
if r == nil {
return
}
path = r.URI().Path()
buttonMessage.Text = r.URI().Path()
setStringSuccessStyle(buttonMessage)
}, v.w)
helper.FileDialogResize(fileDialog, v.w)
fileDialog.Show()
})
return
}
func setStringErrorStyle(text *canvas.Text) {
text.Color = color.RGBA{255, 0, 0, 255}
text.Refresh()
}
func setStringSuccessStyle(text *canvas.Text) {
text.Color = color.RGBA{49, 127, 114, 255}
text.Refresh()
}