Compare commits

..

126 Commits
0.1.1 ... main

Author SHA1 Message Date
597e9f75cf Merge pull request 'Версия 1.0.0' (#11) from develop into main
Reviewed-on: #11
2025-06-14 22:56:20 +05:00
26827d5ccd
Window title updated to "GUI for FFmpeg". 2025-06-14 22:46:00 +05:00
b56199fe8b
Simplified fyne-cross build commands in Makefile by removing redundant flags. Adjusted Windows binary naming during packaging. 2025-06-14 21:55:52 +05:00
f6958ffa97
Standardized application name in .desktop file (Name field updated to "GUI for FFmpeg"). 2025-06-14 21:55:30 +05:00
7f410ef700
Simplified README build instructions by removing redundant --icon and --name flags from fyne-cross commands. 2025-06-14 21:54:50 +05:00
c4d205a79e
Updated FyneApp.toml with new Website, updated Icon path, bumped version to 1.0.0, added Linux/BSD-specific fields (GenericName, Categories, Comment, Keywords). 2025-06-14 19:52:29 +05:00
c45c106f2f
Updated README for link fixes, new Makefile build instructions, and adjusted folder structure documentation. Deleted obsolete folder structure screenshot. 2025-06-14 19:34:47 +05:00
63c13de181
Add Makefile to automate build process for Linux and Windows 2025-06-14 19:32:29 +05:00
57767de4b3
Added files that will be copied during the build of the application for Linux. 2025-06-14 19:32:05 +05:00
c59c87d109
Duplicated: icon.png and screenshot-gui-for-ffmpeg.png in assets, so that I could later delete them from their current locations after the links to the images have changed everywhere. 2025-06-14 19:30:14 +05:00
0a22377cd6
Removed references to bbolt and its license info from "About" screen and LICENSE-3RD-PARTY.txt as it's no longer used. Added FFmpeg license details to LICENSE-3RD-PARTY.txt. 2025-06-10 23:41:37 +05:00
7930a907f1
Removed unused AppMetadata configuration and refactored imports in main.go. Because the data is duplicated with FyneApp.toml. 2025-06-10 22:25:21 +05:00
850cbbaf70
Removed the old program structure. 2025-06-09 23:42:09 +05:00
f4604f94c6
Set Fyne window as master during controller initialization. 2025-06-09 23:38:50 +05:00
077d7a82a9
Moved the menu to a new structure. 2025-06-09 23:30:05 +05:00
e6db590937
Add GetFFplayVersion method to retrieve FFplay version details
Extended `FFplayContract` with `GetVersion` to fetch FFplay version. Implemented version extraction in `utilities` and `ffplay` to support version retrieval functionality.
2025-06-09 23:28:25 +05:00
d7428683e4
Add GetFFprobeVersion method to retrieve FFprobe version details
Extended `FFprobeContract` with `GetVersion` to fetch FFprobe version. Implemented version extraction in `utilities` and `ffprobe` to support version retrieval functionality.
2025-06-09 23:25:13 +05:00
a9c59137af
Rename GetFFmpegVersion to GetVersion in FFmpegContract for consistency and clarity. 2025-06-09 23:21:19 +05:00
c8619cdc7f
Added the ability to get the FFmpeg version. 2025-06-09 23:19:48 +05:00
c49957e583
Moved the "About" window to a new structure. 2025-06-09 23:12:20 +05:00
39080cac14
Refactor HelpFFplay to simplify array initialization. 2025-06-09 22:56:04 +05:00
cae996a141
Moved the display of FFplay key descriptions to a new structure. 2025-06-09 22:54:27 +05:00
fc4e585620
Add main settings view and theme management functionality
Introduce a new `MainSettings` view for managing application settings, including language and theme selection. Implement theme management methods in the `setting` package to handle theme initialization, retrieval, and updates.
2025-06-09 00:27:40 +05:00
690f84e2c8
Fix display of queues during conversion
Fixed the error of displaying queues when they want to see only a certain status.
2025-06-08 22:54:50 +05:00
568d8f0897
Removed duplicate isChecked, since there is IsChecked. 2025-06-08 22:28:11 +05:00
2909ef7cea
Introduce progress bar updates and queue processing logic
Implemented progress bar integration with `ProgressBarContract` for real-time conversion tracking and status updates. Added queue management functionality to process files sequentially with error and completion handling. Extended `ConvertorContract` and `FFmpegContract` to support tracking of running processes and conversion progress.
2025-06-08 22:19:28 +05:00
1b1cdd5c22
Add RunConvert method to FFmpegContract and implementation
Extend `FFmpegContract` with `RunConvert` for handling file conversion, including progress tracking and callback support before and after execution.
2025-06-08 21:47:28 +05:00
eb43669ae7
Add GetTotalDuration method to FFprobeContract and implementation
Extend `FFprobeContract` interface with `GetTotalDuration` for retrieving the duration of media files.
2025-06-08 21:38:29 +05:00
29ca392880
Made it so that files for conversion are added to the queue. 2025-06-08 20:42:43 +05:00
df8095fb16
Added an action to the submit button. 2025-06-08 19:19:50 +05:00
e48f363de0
Fix the bug where the file selection button for conversion disappears. 2025-06-08 18:30:08 +05:00
9bb835beaf
Make a choice of the encoder in the form
Made a choice of the encoder by categories: video, audio and photo.
2025-06-08 17:26:49 +05:00
9cdfa18fc8
Add initial implementations for encoder handling and conversion logic
Introduce encoder modules for various codecs and formats (e.g., h264_nvenc, libx264, libmp3lame). Add `Convertor` logic to retrieve supported formats via FFmpeg utilities and manage encoders for audio, video, and image processing.
2025-06-08 17:26:17 +05:00
6c0abac1c5
Add directory selection for saving converted files
Introduce logic and UI for selecting and persisting a directory to save converted files. Extend existing components with directory selection buttons, message updates, and storage preferences.
2025-06-08 00:18:56 +05:00
394824ce88
Add layout system and file selection logic
Introduce a new layout system for managing main window content and tabs. Integrate file selection and drag-and-drop functionality for adding files to the conversion list, with automatic tab switching to "Added Files". Refactor existing components to support these features.
2025-06-07 23:44:47 +05:00
6e8b148c81
Made it so that you can play files through FFplay. 2025-06-07 23:43:32 +05:00
57637606c0
Revive the right block of the program
Moved the right part of the program from the old version. Slightly reworked the structure.
2025-06-07 21:27:55 +05:00
c60b9f7b0c
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.
2025-06-07 01:30:32 +05:00
b24155caf6
Refactor application structure and initialize core components
I decided to rewrite the program taking into account the experience gained.
2025-06-06 14:50:16 +05:00
43d794373a
Embed application icon as a resource and refactor icon usage
Replaced the external `icon.png` file with an embedded resource using Go's embed functionality. Updated all references to use the new resource, eliminating the need for the standalone icon file.
2025-06-01 19:58:28 +05:00
3241b88158
Refactor localization system and migrate to Fyne's built-in support
Replaced the `i18n` and `toml` dependencies with Fyne's built-in language system for localization management. Updated the `Localizer` implementation to handle translations using JSON files and embed functionality. Simplified language selection and persisted settings via Fyne's preferences API.
2025-06-01 15:20:33 +05:00
d69767f5e9
Remove bbolt database dependency
Replaced bbolt-based database handling with Fyne built-in preferences for storing application settings. Deleted migration logic, database initialization, and error handling related to bbolt, simplifying the codebase and reducing external dependencies.
2025-05-30 00:34:33 +05:00
24446559b4 Merge pull request 'Версия 0.9.0' (#10) from develop into main
Reviewed-on: #10
2025-05-25 23:13:25 +05:00
7340f43d6e
Remove unused helper function from ffplay.go
The `PrepareBackgroundCommand` function has been removed because it prevents the program window from being displayed on Windows.
2025-05-25 22:51:59 +05:00
5ab11922b9
Select "Added Files" tab after dragging and dropping files.
Ensure the "Added Files" tab is automatically selected after users drag and drop files for conversion. This improves user experience by guiding them to the newly added content.
2025-05-25 21:46:19 +05:00
5f72ce8c56
Update screenshot for GUI in FFmpeg documentation
Replaced the GUI screenshot to reflect the latest interface changes. This ensures alignment with the current functionality and improves user clarity.
2025-05-25 01:48:33 +05:00
5b15848048
Update version to 0.9.0
Bump the application version from 0.8.0 to 0.9.0 in both `main.go` and `FyneApp.toml`. This reflects the latest changes and prepares the app for the next release.
2025-05-25 01:29:14 +05:00
84b36dd29e
Make it possible to drag and drop multiple files
It is now possible to add multiple files before sending them to the processing queue.
2025-05-25 01:25:40 +05:00
82167f042f
Add theme management functionality to the application
Implemented a theme management system allowing users to select and persist themes (default, light, dark) in the settings menu.
2025-05-23 20:18:05 +05:00
712ec2f182
Remove language selection to a new settings section. 2025-05-22 21:42:45 +05:00
883bf376b0
Add FFplay help feature and keyboard shortcut guide
Introduced a new "Help FFplay" section in the help menu to provide information about FFplay player keyboard shortcuts and actions.
2025-05-21 00:22:42 +05:00
306383449a
Add FFplay support to the application
Integrated FFplay functionality across the application. This includes support for setting up the FFplay path and invoking FFplay for media playback.
2025-05-19 22:49:09 +05:00
a831d56d93
Add localized error handling for database timeout
Introduced a new localized error message "errorDatabaseTimeout" in multiple languages (English, Kazakh, Russian) and updated the `PanicError` method to handle database timeout errors more gracefully. This improves user feedback by providing context-specific error messages.
2025-05-18 19:32:57 +05:00
9d46db43c2
Default language
I made it so that if the OS language matches the language into which there is a translation, it would be used by default. And if not, then I would suggest choosing which language to use.
2025-05-18 19:31:59 +05:00
46d210d6d5
Added return after kernel.PanicErrorLang(err, appMetadata) to avoid unpredictable results during an error. 2025-05-18 19:28:16 +05:00
a053ffbed6 Merge pull request 'Версия 0.8.0' (#9) from develop into main
Reviewed-on: #9
2025-05-11 19:45:39 +05:00
3149ca25e1
Add version and build information to FyneApp.toml
Updated the FyneApp.toml file to include the application version (0.8.0) and build number (4). These additions help in tracking application releases and builds efficiently.
2025-05-11 19:01:36 +05:00
992762ef0a
Add gratitude view and menu item
Introduce a new "Gratitude" view with localized messages and display functionality.
2025-05-11 18:35:21 +05:00
411e6c554a
Update menu view and third-party licenses
Added new dependencies and license details to both `menu/view.go` and `LICENSE-3RD-PARTY.txt`. Updated copyright notices, hyperlinks, and license text to reflect the latest projects and comply with their licensing terms.
2025-05-11 16:25:03 +05:00
bf3340e526
Add drag-and-drop support for single file selection
Implemented functionality to handle single file drag-and-drop in the UI, including error handling for multiple files and directories. Updated localization files to support new messages related to drag-and-drop usage and errors.
2025-05-11 15:07:37 +05:00
6be10dbd75
Add FyneApp.toml configuration file
Introduce a new FyneApp.toml file to configure application metadata. This includes details like the app's icon, name, and ID, as well as migration settings. These changes prepare the app for Fyne framework integration.
2025-05-11 01:44:17 +05:00
16b32e0167
Add persistent storage for directory saving setting
Introduced `DirectoryForSaving` for managing directory paths persistently. Integrated the new setting into relevant modules, ensuring the selected directory is saved and loaded across sessions.
2025-05-11 01:29:07 +05:00
2a7d860cbf
Add missing fyne import in convertor_windows.go
The fyne package import was added to fix missing dependencies in the file.
2025-05-11 00:50:23 +05:00
cf2a0933b4
Update program link URL in menu/view.go
Updated the host and path for the program link to reflect the correct URL structure. This ensures users are directed to the proper project page.
2025-05-10 18:36:17 +05:00
fa6c929ec8
Update app version to 0.8.0
Bumped the application version from 0.7.0 to 0.8.0 in `main.go`. This prepares the app for the new release and reflects recent changes or improvements.
2025-05-10 18:35:26 +05:00
cce45ee791
Update dependencies in go.sum to latest versions
This commit removes outdated dependencies and adds updated versions of required modules in the `go.sum` file. The changes ensure the project uses the latest compatible releases, improving stability and maintaining compatibility with current updates.
2025-05-10 18:09:38 +05:00
da7d9c8035
Fix .mts duration parsing by extracting leading digits
This update addresses a parsing issue for .mts files by introducing a fallback to extract and parse only the leading digits when converting frame data to a float. A new helper function `getFirstDigits` is added to isolate and handle numeric values correctly.
2025-05-10 00:55:16 +05:00
17c570bf1d
Fixed error handling for setting default duration on failure
Previously, failures in getting the total duration resulted in queue status updates and interruptions due to errors. Now, if we could not determine the duration, we return zero. This will at least allow us to simply convert without a progress bar.
2025-05-10 00:54:15 +05:00
86886fb5d9
Replace GORM with bbolt for database operations
Migrated from GORM to bbolt for lightweight key-value storage, better aligning with project requirements. Updated repository methods to utilize bbolt's native APIs and removed dependencies associated with GORM.
2025-05-09 23:58:48 +05:00
f262d5f931
Add Linux support for downloading and setting up FFmpeg.
This commit introduces platform-specific functionality for downloading, extracting, and configuring FFmpeg on Linux systems.
2025-05-06 23:10:29 +05:00
12dc5c8ef9
Add progress tracking to unzip operation on Windows
Introduced a progress bar to display the extraction progress when unzipping files using the `unZip` function. This enhancement provides visual feedback by updating the progress as file data is unpacked, improving user experience.
2025-05-06 23:08:29 +05:00
40848a70a5 Merge pull request 'Версия 0.7.0' (#8) from develop into main
Reviewed-on: #8
2024-04-28 14:57:07 +05:00
9aaee4c183
Changed the version to 0.7.0 2024-04-28 14:52:18 +05:00
2017617614
Added preset option for libx265.
Added preset option for h264_nvenc.
2024-04-28 14:43:18 +05:00
f17104595d Merge pull request 'Версия 0.6.0' (#7) from develop into main
Reviewed-on: #7
2024-03-17 20:52:37 +05:00
21d4afcedb
Made it possible for each encoder to add its own parameters.
Added preset option for libx264.
2024-03-17 20:28:35 +05:00
8347e9fbb2
Changed Readme.md.
Updated instructions for working with translations.
2024-03-17 00:46:39 +05:00
8d17a52f00
Added .gitignore for fyne-cross/*. 2024-03-17 00:39:56 +05:00
4668da3223
Added .gitignore for translate.*.toml. 2024-03-17 00:39:32 +05:00
a3b30c4543
Deleted temporary languages/translate.*.toml files. 2024-03-17 00:32:21 +05:00
3f358c8b7d
Refactoring.
I have moved the conversion form to a separate conversion file.
2024-03-17 00:30:02 +05:00
7b7c15ad27
Fixed bug related to incorrect window size.
Sometimes when starting a program the program window was small.
2024-03-16 21:45:14 +05:00
24d80779ee Merge pull request 'Версия 0.5.0' (#6) from develop into main
Reviewed-on: #6
2024-03-08 00:59:41 +05:00
a95692196e
Changed screenshot-gui-for-ffmpeg.png. 2024-03-07 23:17:55 +05:00
68d9c4bb66
I fixed it in OS Windows so that the console window would not appear. 2024-03-07 22:50:00 +05:00
1ece1e443d
Added the ability to convert files to different extensions. 2024-03-07 22:18:35 +05:00
1eb7ea4a93
Refactoring LocalizerContract.
Instead of AddListener I made AddChangeCallback. And removed unnecessary dependencies.
2024-03-05 20:13:58 +05:00
e766c6d465
Added the ability to show and hide elements by status type. 2024-03-05 00:58:43 +05:00
1f9f646f51
Refactoring.
type File struct and type ConvertSetting struct moved to convertor.go.
2024-02-25 23:38:16 +06:00
d88586739a
Edited README.md.
src/icon.png to icon.png
src/data to data
src/languages ​​to languages
2024-02-17 21:22:20 +06:00
a3db2b8f89 Merge pull request 'Версия 0.4.0.' (#5) from develop into main
Reviewed-on: #5
2024-02-17 20:33:18 +06:00
d0539f5e90
Fixed an error when building an assembly in fyne-cross. 2024-02-17 20:06:09 +06:00
240ae7aa96
Windows OS fix. 2024-02-17 20:04:11 +06:00
0d05fdb307
Changed the screenshot in README.md. 2024-02-17 19:48:50 +06:00
8e6fb90482
Code improvement.
And added a comment to the code.
2024-02-17 19:45:28 +06:00
bab8c1f383
Bug fixed.
When starting the program, sometimes the window was displayed incorrectly.
2024-02-17 19:18:25 +06:00
a1c9143685
Added queues.
Reworked the architecture.
2024-02-17 19:08:58 +06:00
c4ec958576
Moved the code from src to the root. 2024-02-12 22:21:47 +06:00
359db74251 Merge pull request 'Версия 0.3.1' (#4) from develop into main
Reviewed-on: #4
2024-02-10 21:35:08 +06:00
154cd1c7dd
Fixed convertor_windows.go.
Changed "git.kor-elf.net/kor-elf/gui-for-ffmpeg/convertor" to "git.kor-elf.net/kor-elf/gui-for-ffmpeg/src/convertor".
2024-02-10 20:32:29 +06:00
a617635911
Changed the version to 0.3.1. 2024-02-10 20:23:41 +06:00
48f8c577c0
Fixed a buffer overflow error when converting videos when the video is more than 2 hours long. 2024-02-10 20:13:17 +06:00
ee0a305972
I made sure that the current folder was saved. 2024-02-10 20:11:26 +06:00
3dc59344cb
Fix command "fyne get ..."
Renamed the module to git.kor-elf.net/kor-elf/gui-for-ffmpeg/src.
2024-02-04 21:00:55 +06:00
f631c55eae Merge pull request '0.3.0' (#3) from develop into main
Reviewed-on: #3
2024-02-04 20:50:54 +06:00
d46d642e61
Added the ability to automatically download FFmpeg from the site https://github.com/BtbN/FFmpeg-Builds/releases. 2024-02-04 20:16:15 +06:00
a82d283c1a
Added licensed third party. 2024-02-04 15:57:37 +06:00
b7c363faaa
Изменил файл README.md.
Added installation instructions and other useful information.
2024-02-03 20:16:16 +06:00
2c0e20210f
Переименовал модуль в git.kor-elf.net/kor-elf/gui-for-ffmpeg. 2024-02-03 18:40:44 +06:00
eec298bfd7
Added a window with information about the program. 2024-02-03 17:47:32 +06:00
2afc5f5b1a
Fixed minor errors reported by the analyzer. 2024-02-01 00:31:28 +06:00
fd7ce5fa08
Fixed minor errors reported by the analyzer. 2024-02-01 00:29:11 +06:00
05553f06b8
Fixed minor errors reported by the analyzer. 2024-02-01 00:28:06 +06:00
0d5cfab38d
Made it possible to change the path to FFmpeg and FFprobe. 2024-02-01 00:23:28 +06:00
d86c0d37af
Updated some dependencies. 2024-01-31 22:27:50 +06:00
f09dd01b6d
Fixed minor errors reported by the analyzer. 2024-01-31 21:22:38 +06:00
6358d5d8cc
The language selection is remembered.
Added a setting for changing the language.
2024-01-31 21:02:12 +06:00
5025807b14
Changed a.New Window.
Changed "GUI FFMpeg!" to "FFMpeg GUI!".
2024-01-30 20:38:12 +06:00
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
121 changed files with 9410 additions and 1575 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
fyne-cross/*
ffmpeg/*

14
FyneApp.toml Normal file
View File

@ -0,0 +1,14 @@
Website = "https://gui-for-ffmpeg.projects.kor-elf.net/language/en"
[Details]
Icon = "assets/icon.png"
Name = "GUI for FFmpeg"
ID = "net.kor-elf.projects.gui-for-ffmpeg"
Version = "1.0.0"
Build = 75
[LinuxAndBSD]
GenericName = "GUI for FFmpeg"
Categories = ["AudioVideo", "Utility"]
Comment = "A simple interface for the FFmpeg console utility."
Keywords = ["ffmpeg", "media", "convert", "transcode", "audio", "video", "конвертер", "видео", "аудио", "кодек"]

2426
LICENSE-3RD-PARTY.txt Normal file

File diff suppressed because it is too large Load Diff

88
Makefile Normal file
View File

@ -0,0 +1,88 @@
VERSION ?= $(shell grep '^ *Version *= *' FyneApp.toml | sed -E "s/.*=[[:space:]]*\"([0-9\.]+)\".*/\1/")
BUILD_TMP := fyne-cross/tmp
WINDOWS_AMD64 := gui-for-ffmpeg-$(VERSION)-windows-amd64
BUILD_TMP_WINDOWS_AMD64 := $(BUILD_TMP)/$(WINDOWS_AMD64)
LINUX_AMD64 := gui-for-ffmpeg-$(VERSION)-linux-amd64
BUILD_TMP_LINUX_AMD64 := $(BUILD_TMP)/$(LINUX_AMD64)
RELEASES := fyne-cross/releases/$(VERSION)
default:
# Run "make build-for-linux_amd64"
# Run "make build-for-windows_amd64"
# Build for all
# Run "make build"
build:
make build-for-linux_amd64
make build-for-windows_amd64
# $(RELEASES)/$(LINUX_AMD64).tar.gz
# $(RELEASES)/$(LINUX_AMD64).tar.gz.sha256
# $(RELEASES)/$(WINDOWS_AMD64).zip
# $(RELEASES)/$(WINDOWS_AMD64).zip.sha256
build-for-windows_amd64:
fyne-cross windows
@if [ -d $(BUILD_TMP_WINDOWS_AMD64) ]; then \
rm -rf $(BUILD_TMP_WINDOWS_AMD64)/*; \
else \
mkdir -p $(BUILD_TMP_WINDOWS_AMD64); \
fi
cp LICENSE $(BUILD_TMP_WINDOWS_AMD64)/LICENSE
cp LICENSE-3RD-PARTY.txt $(BUILD_TMP_WINDOWS_AMD64)/LICENSE-3RD-PARTY.txt
cp "fyne-cross/bin/windows-amd64/GUI for FFmpeg.exe" $(BUILD_TMP_WINDOWS_AMD64)/gui-for-ffmpeg.exe
cd $(BUILD_TMP) && 7z a -tzip $(WINDOWS_AMD64).zip $(WINDOWS_AMD64)
@if [ ! -d $(RELEASES) ]; then \
mkdir -p $(RELEASES); \
fi
@if [ -f $(RELEASES)/$(WINDOWS_AMD64).zip ]; then \
rm $(RELEASES)/$(WINDOWS_AMD64).zip; \
fi
@if [ -f $(RELEASES)/$(WINDOWS_AMD64).zip.sha256 ]; then \
rm $(RELEASES)/$(WINDOWS_AMD64).zip.sha256; \
fi
mv $(BUILD_TMP)/$(WINDOWS_AMD64).zip $(RELEASES)/$(WINDOWS_AMD64).zip
cd $(RELEASES) && sha256sum $(WINDOWS_AMD64).zip > $(WINDOWS_AMD64).zip.sha256
# $(RELEASES)/$(WINDOWS_AMD64).zip
# $(RELEASES)/$(WINDOWS_AMD64).zip.sha256
build-for-linux_amd64:
fyne-cross linux
@if [ -d $(BUILD_TMP_LINUX_AMD64) ]; then \
rm -rf $(BUILD_TMP_LINUX_AMD64)/*; \
else \
mkdir -p $(BUILD_TMP_LINUX_AMD64); \
fi
cp -r dist/linux/* $(BUILD_TMP_LINUX_AMD64)/
cp LICENSE $(BUILD_TMP_LINUX_AMD64)/LICENSE
cp LICENSE-3RD-PARTY.txt $(BUILD_TMP_LINUX_AMD64)/LICENSE-3RD-PARTY.txt
cp fyne-cross/bin/linux-amd64/gui-for-ffmpeg $(BUILD_TMP_LINUX_AMD64)/usr/local/bin/gui-for-ffmpeg
cp assets/icon.png $(BUILD_TMP_LINUX_AMD64)/usr/local/share/pixmaps/gui-for-ffmpeg.png
cd $(BUILD_TMP) && tar -czvf $(LINUX_AMD64).tar.gz $(LINUX_AMD64)
@if [ ! -d $(RELEASES) ]; then \
mkdir -p $(RELEASES); \
fi
@if [ -f $(RELEASES)/$(LINUX_AMD64).tar.gz ]; then \
rm $(RELEASES)/$(LINUX_AMD64).tar.gz; \
fi
@if [ -f $(RELEASES)/$(LINUX_AMD64).tar.gz.sha256 ]; then \
rm $(RELEASES)/$(LINUX_AMD64).tar.gz.sha256; \
fi
mv $(BUILD_TMP)/$(LINUX_AMD64).tar.gz $(RELEASES)/$(LINUX_AMD64).tar.gz
cd $(RELEASES) && sha256sum $(LINUX_AMD64).tar.gz > $(LINUX_AMD64).tar.gz.sha256
# $(RELEASES)/$(LINUX_AMD64).tar.gz
# $(RELEASES)/$(LINUX_AMD64).tar.gz.sha256

View File

@ -1,5 +1,39 @@
# ffmpeg-gui # GUI for FFmpeg
Простенький интерфейс к программе ffmpeg. <p>Простенький интерфейс для консольной утилиты FFmpeg. Но я <strong>не являюсь</strong> автором самой утилиты <strong>FFmpeg</strong>.</p>
<p><strong>FFmpeg</strong> — торговая марка <strong><a href="https://bellard.org/" target="_blank">Fabrice Bellard</a></strong>, создателя проекта <strong><a href="https://ffmpeg.org/about.html" target="_blank">FFmpeg</a></strong>.</p>
<img src="images/screenshot-ffmpeg-gui.png"> <p>Программное обеспечение является MIT (см. <a href="https://git.kor-elf.net/kor-elf/gui-for-ffmpeg/src/branch/main/LICENSE">LICENSE</a>) и использует сторонние библиотеки, которые распространяются на их собственных условиях (см. <a href="https://git.kor-elf.net/kor-elf/gui-for-ffmpeg/src/branch/main/LICENSE-3RD-PARTY.txt">LICENSE-3RD-PARTY.txt</a>).</p>
<img src="assets/screenshot-gui-for-ffmpeg.png" alt="Скриншот программы">
<p>Скачать скомпилированные готовые версии можно тут: <a href="https://git.kor-elf.net/kor-elf/gui-for-ffmpeg/releases">https://git.kor-elf.net/kor-elf/gui-for-ffmpeg/releases</a>.</p>
## Установка через fyne:
1. go install fyne.io/fyne/v2/cmd/fyne@latest
2. fyne get git.kor-elf.net/kor-elf/gui-for-ffmpeg
## Скомпилировать через Makefile:
1. git clone https://git.kor-elf.net/kor-elf/gui-for-ffmpeg.git
2. Переходим в папку проекта и там переходим в папку src: **cd gui-for-ffmpeg**
3. Ознакамливаемся, что нужно ещё установить для Вашей ОС для простого запуска (через go run) тут: https://docs.fyne.io/started/
4. go install github.com/fyne-io/fyne-cross@latest
* У Вас так же должен быть установлен docker
* О fyne-cross можно по подробней почитать тут: https://github.com/fyne-io/fyne-cross
5. * make build-for-linux_amd64
* make build-for-windows_amd64
* Или просто **make build**
6. Создаться папка с архивом в **fyne-cross/releases**
## Скомпилировать через исходники:
1. git clone https://git.kor-elf.net/kor-elf/gui-for-ffmpeg.git
2. Переходим в папку проекта и там переходим в папку src: **cd gui-for-ffmpeg**
3. Ознакамливаемся, что нужно ещё установить для Вашей ОС для простого запуска (через go run) тут: https://docs.fyne.io/started/
4. *(не обязательный шаг)* Просто запустить можно так: **go run main.go**
5. go install github.com/fyne-io/fyne-cross@latest
* У Вас так же должен быть установлен docker
* О fyne-cross можно по подробней почитать тут: https://github.com/fyne-io/fyne-cross
6. * fyne-cross windows
* fyne-cross linux
7. Создаться папка **fyne-cross/dist** и там будет созданна папка с тем названием под которую Вы компилировали приложения (linux-amd64 или windows-amd64).
8. В папке **fyne-cross/bin/linux-amd64** или **fyne-cross/bin/windows-amd64** будут архивы, которые надо распаковать и пользоваться программой.

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

39
dist/linux/Makefile vendored Normal file
View File

@ -0,0 +1,39 @@
# If PREFIX isn't provided, we check for $(DESTDIR)/usr/local and use that if it exists.
# Otherwice we fall back to using /usr.
LOCAL != test -d $(DESTDIR)/usr/local && echo -n "/local" || echo -n ""
LOCAL ?= $(shell test -d $(DESTDIR)/usr/local && echo "/local" || echo "")
PREFIX ?= /usr$(LOCAL)
Name := "gui-for-ffmpeg"
Exec := "gui-for-ffmpeg"
Icon := "gui-for-ffmpeg.png"
default:
# User install
# Run "make user-install" to install in ~/.local/
# Run "make user-uninstall" to uninstall from ~/.local/
#
# System install
# Run "sudo make install" to install the application.
# Run "sudo make uninstall" to uninstall the application.
install:
install -Dm00644 usr/local/share/applications/$(Name).desktop $(DESTDIR)$(PREFIX)/share/applications/$(Name).desktop
install -Dm00755 usr/local/bin/$(Exec) $(DESTDIR)$(PREFIX)/bin/$(Exec)
install -Dm00644 usr/local/share/pixmaps/$(Icon) $(DESTDIR)$(PREFIX)/share/pixmaps/$(Icon)
uninstall:
-rm $(DESTDIR)$(PREFIX)/share/applications/$(Name).desktop
-rm $(DESTDIR)$(PREFIX)/bin/$(Exec)
-rm $(DESTDIR)$(PREFIX)/share/pixmaps/$(Icon)
user-install:
install -Dm00644 usr/local/share/applications/$(Name).desktop $(DESTDIR)$(HOME)/.local/share/applications/$(Name).desktop
install -Dm00755 usr/local/bin/$(Exec) $(DESTDIR)$(HOME)/.local/bin/$(Exec)
install -Dm00644 usr/local/share/pixmaps/$(Icon) $(DESTDIR)$(HOME)/.local/share/icons/$(Icon)
sed -i -e "s,Exec=$(Exec),Exec=$(DESTDIR)$(HOME)/.local/bin/$(Exec),g" $(DESTDIR)$(HOME)/.local/share/applications/$(Name).desktop
user-uninstall:
-rm $(DESTDIR)$(HOME)/.local/share/applications/$(Name).desktop
-rm $(DESTDIR)$(HOME)/.local/bin/$(Exec)
-rm $(DESTDIR)$(HOME)/.local/share/icons/$(Icon)

7
dist/linux/Readme-eng.txt vendored Normal file
View File

@ -0,0 +1,7 @@
User install
Run "make user-install" to install in ~/.local/
Run "make user-uninstall" to uninstall from ~/.local/
System install
Run "sudo make install" to install the application.
Run "sudo make uninstall" to uninstall the application.

7
dist/linux/Readme-rus.txt vendored Normal file
View File

@ -0,0 +1,7 @@
Установить для пользователя (рекомендуется)
Запустите "make user-install" для установки в домашнюю папку ~/.local/
Запустите "make user-uninstall" для удаления из домашней папки ~/.local/
Установить для всей системы
Запустить "sudo make install" Для установки в систему.
Запустить "sudo make uninstall" Для удаления из системы.

View File

@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=GUI for FFmpeg
GenericName=GUI for FFmpeg
Exec=gui-for-ffmpeg
Icon=gui-for-ffmpeg
Comment=A simple interface for the FFmpeg console utility.
Categories=AudioVideo;Utility;
Keywords=ffmpeg;media;convert;transcode;audio;video;конвертер;видео;аудио;кодек;

45
go.mod Normal file
View File

@ -0,0 +1,45 @@
module git.kor-elf.net/kor-elf/gui-for-ffmpeg
go 1.23.0
toolchain go1.23.9
require (
fyne.io/fyne/v2 v2.6.1
github.com/ulikunitz/xz v0.5.12
golang.org/x/image v0.27.0
)
require (
fyne.io/systray v1.11.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fyne-io/gl-js v0.1.0 // indirect
github.com/fyne-io/glfw-js v0.2.0 // indirect
github.com/fyne-io/image v0.1.1 // indirect
github.com/fyne-io/oksvg v0.1.0 // indirect
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect
github.com/go-text/render v0.2.0 // indirect
github.com/go-text/typesetting v0.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
github.com/hack-pad/safejs v0.1.1 // indirect
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 // indirect
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nicksnyder/go-i18n/v2 v2.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rymdport/portal v0.4.1 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/yuin/goldmark v1.7.11 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

82
go.sum Normal file
View File

@ -0,0 +1,82 @@
fyne.io/fyne/v2 v2.6.1 h1:kjPJD4/rBS9m2nHJp+npPSuaK79yj6ObMTuzR6VQ1Is=
fyne.io/fyne/v2 v2.6.1/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM=
github.com/fyne-io/gl-js v0.1.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
github.com/fyne-io/glfw-js v0.2.0 h1:8GUZtN2aCoTPNqgRDxK5+kn9OURINhBEBc7M4O1KrmM=
github.com/fyne-io/glfw-js v0.2.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8=
github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 h1:vFdvrlsVU+p/KFBWTq0lTG4fvWvG88sawGlCzM+RUEU=
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo=
github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

137
internal/application/app.go Normal file
View File

@ -0,0 +1,137 @@
package application
import (
"fyne.io/fyne/v2"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
"time"
)
type AppContract interface {
FyneApp() fyne.App
GetSetting() setting.SettingContract
GetProgressBarService() convertor.ProgressBarContract
GetFFmpegService() ffmpeg.UtilitiesContract
GetConvertorService() convertor.ConvertorContract
GetItemsToConvert() convertor.ItemsToConvertContract
GetQueueService() convertor.QueueListContract
Run()
AfterClosing()
RunConvertor()
}
type application struct {
fyneApp fyne.App
setting setting.SettingContract
progressBarService convertor.ProgressBarContract
ffmpegService ffmpeg.UtilitiesContract
itemsToConvert convertor.ItemsToConvertContract
queueService convertor.QueueListContract
convertorService convertor.ConvertorContract
}
func NewApp(
fyneApp fyne.App,
setting setting.SettingContract,
progressBarService convertor.ProgressBarContract,
ffmpegService ffmpeg.UtilitiesContract,
itemsToConvert convertor.ItemsToConvertContract,
queueService convertor.QueueListContract,
convertorService convertor.ConvertorContract,
) AppContract {
return &application{
fyneApp: fyneApp,
setting: setting,
progressBarService: progressBarService,
ffmpegService: ffmpegService,
itemsToConvert: itemsToConvert,
queueService: queueService,
convertorService: convertorService,
}
}
func (a *application) FyneApp() fyne.App {
return a.fyneApp
}
func (a *application) GetSetting() setting.SettingContract {
return a.setting
}
func (a *application) GetProgressBarService() convertor.ProgressBarContract {
return a.progressBarService
}
func (a *application) GetFFmpegService() ffmpeg.UtilitiesContract {
return a.ffmpegService
}
func (a *application) GetItemsToConvert() convertor.ItemsToConvertContract {
return a.itemsToConvert
}
func (a *application) GetQueueService() convertor.QueueListContract {
return a.queueService
}
func (a *application) GetConvertorService() convertor.ConvertorContract {
return a.convertorService
}
func (a *application) Run() {
a.fyneApp.Run()
}
func (a *application) RunConvertor() {
go func() {
for {
time.Sleep(time.Millisecond * 3000)
queueId, queue := a.queueService.Next()
if queue == nil {
continue
}
queue.Status = convertor.StatusType(convertor.InProgress)
a.queueService.EventChangeQueue(queueId, queue)
if a.progressBarService.GetContainer().Hidden {
a.progressBarService.GetContainer().Show()
}
totalDuration := float64(0)
ffprobe, err := a.ffmpegService.GetFFprobe()
if err == nil {
totalDuration, err = ffprobe.GetTotalDuration(&queue.Setting.FileInput)
if err != nil {
totalDuration = float64(0)
}
}
progress := a.progressBarService.GetProgressbar(
totalDuration,
queue.Setting.FileInput.Path,
)
err = a.convertorService.RunConvert(*queue.Setting, progress)
if err != nil {
queue.Status = convertor.StatusType(convertor.Error)
queue.Error = err
a.queueService.EventChangeQueue(queueId, queue)
a.progressBarService.ProcessEndedWithError(err.Error())
continue
}
queue.Status = convertor.StatusType(convertor.Completed)
a.queueService.EventChangeQueue(queueId, queue)
a.progressBarService.ProcessEndedWithSuccess(&queue.Setting.FileOut)
}
}()
}
func (a *application) AfterClosing() {
for _, cmd := range a.convertorService.GetRunningProcesses() {
_ = cmd.Process.Kill()
}
}

View File

@ -0,0 +1,87 @@
package convertor
import (
"bufio"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
"io"
"os/exec"
"strings"
)
type ConvertorContract interface {
RunConvert(setting ffmpeg.ConvertSetting, progress ffmpeg.ProgressContract) error
GetSupportFormats() (encoder.ConvertorFormatsContract, error)
GetRunningProcesses() map[int]*exec.Cmd
}
type runningProcesses struct {
items map[int]*exec.Cmd
numberOfStarts int
}
type convertor struct {
ffmpegService ffmpeg.UtilitiesContract
runningProcesses *runningProcesses
}
func NewConvertor(
ffmpegService ffmpeg.UtilitiesContract,
) ConvertorContract {
return &convertor{
ffmpegService: ffmpegService,
runningProcesses: &runningProcesses{items: map[int]*exec.Cmd{}, numberOfStarts: 0},
}
}
func (c *convertor) RunConvert(setting ffmpeg.ConvertSetting, progress ffmpeg.ProgressContract) error {
ffmpegService, err := c.ffmpegService.GetFFmpeg()
if err != nil {
return err
}
index := c.runningProcesses.numberOfStarts
beforeWait := func(cmd *exec.Cmd) {
c.runningProcesses.numberOfStarts++
c.runningProcesses.items[index] = cmd
}
afterWait := func(cmd *exec.Cmd) {
delete(c.runningProcesses.items, index)
}
return ffmpegService.RunConvert(setting, progress, beforeWait, afterWait)
}
func (c *convertor) GetSupportFormats() (encoder.ConvertorFormatsContract, error) {
var err error
formats := encoder.NewConvertorFormats()
ffmpeg, err := c.ffmpegService.GetFFmpeg()
if err != nil {
return formats, err
}
err = ffmpeg.GetEncoders(func(scanner *bufio.Reader) {
for {
line, _, err := scanner.ReadLine()
if err != nil {
if err == io.EOF {
break
}
continue
}
text := strings.Split(strings.TrimSpace(string(line)), " ")
encoderType := string(text[0][0])
if len(text) < 2 || (encoderType != "V" && encoderType != "A") {
continue
}
formats.NewEncoder(text[1])
}
})
return formats, err
}
func (c *convertor) GetRunningProcesses() map[int]*exec.Cmd {
return c.runningProcesses.items
}

View File

@ -0,0 +1,84 @@
package encoder
import (
"errors"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
type ConvertorFormatContract interface {
GetTitle() string
AddEncoder(encoder encoder.EncoderDataContract)
GetFileType() encoder.FileTypeContract
GetEncoders() map[int]encoder.EncoderDataContract
}
type ConvertorFormat struct {
title string
fileType encoder.FileTypeContract
encoders map[int]encoder.EncoderDataContract
}
func NewConvertorFormat(title string, fileType encoder.FileTypeContract) ConvertorFormatContract {
return &ConvertorFormat{
title: title,
fileType: fileType,
encoders: map[int]encoder.EncoderDataContract{},
}
}
func (f *ConvertorFormat) GetTitle() string {
return f.title
}
func (f *ConvertorFormat) AddEncoder(encoder encoder.EncoderDataContract) {
f.encoders[len(f.encoders)] = encoder
}
func (f *ConvertorFormat) GetEncoders() map[int]encoder.EncoderDataContract {
return f.encoders
}
func (f *ConvertorFormat) GetFileType() encoder.FileTypeContract {
return f.fileType
}
type ConvertorFormatsContract interface {
NewEncoder(encoderName string) bool
GetFormats() map[string]ConvertorFormatContract
GetFormat(format string) (ConvertorFormatContract, error)
}
type ConvertorFormats struct {
formats map[string]ConvertorFormatContract
}
func NewConvertorFormats() ConvertorFormatsContract {
return &ConvertorFormats{
formats: map[string]ConvertorFormatContract{},
}
}
func (f *ConvertorFormats) NewEncoder(encoderName string) bool {
if supportEncoders[encoderName] == nil {
return false
}
data := supportEncoders[encoderName]()
for _, format := range data.GetFormats() {
if f.formats[format] == nil {
f.formats[format] = NewConvertorFormat(format, data.GetFileType())
}
f.formats[format].AddEncoder(data)
}
return true
}
func (f *ConvertorFormats) GetFormats() map[string]ConvertorFormatContract {
return f.formats
}
func (f *ConvertorFormats) GetFormat(format string) (ConvertorFormatContract, error) {
if f.formats[format] == nil {
return &ConvertorFormat{}, errors.New("not found ConvertorFormat")
}
return f.formats[format], nil
}

View File

@ -0,0 +1,74 @@
package encoder
import (
encoder2 "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/apng"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/bmp"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/flv"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/gif"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/h264_nvenc"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libmp3lame"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libshine"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libtwolame"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libvpx"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libvpx_vp9"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libwebp"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libwebp_anim"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libx264"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libx265"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libxvid"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/mjpeg"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/mp2"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/mp2fixed"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/mpeg1video"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/mpeg2video"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/mpeg4"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/msmpeg4"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/msmpeg4v2"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/msvideo1"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/png"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/qtrle"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/sgi"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/tiff"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/wmav1"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/wmav2"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/wmv1"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/wmv2"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/xbm"
)
var supportEncoders = map[string]func() encoder2.EncoderDataContract{
"libx264": libx264.NewData,
"h264_nvenc": h264_nvenc.NewData,
"libx265": libx265.NewData,
"png": png.NewData,
"gif": gif.NewData,
"flv": flv.NewData,
"apng": apng.NewData,
"bmp": bmp.NewData,
"mjpeg": mjpeg.NewData,
"mpeg1video": mpeg1video.NewData,
"mpeg2video": mpeg2video.NewData,
"mpeg4": mpeg4.NewData,
"libxvid": libxvid.NewData,
"msmpeg4v2": msmpeg4v2.NewData,
"msmpeg4": msmpeg4.NewData,
"msvideo1": msvideo1.NewData,
"qtrle": qtrle.NewData,
"tiff": tiff.NewData,
"sgi": sgi.NewData,
"libvpx": libvpx.NewData,
"libvpx-vp9": libvpx_vp9.NewData,
"libwebp_anim": libwebp_anim.NewData,
"libwebp": libwebp.NewData,
"wmv1": wmv1.NewData,
"wmv2": wmv2.NewData,
"xbm": xbm.NewData,
"mp2": mp2.NewData,
"mp2fixed": mp2fixed.NewData,
"libtwolame": libtwolame.NewData,
"libmp3lame": libmp3lame.NewData,
"libshine": libshine.NewData,
"wmav1": wmav1.NewData,
"wmav2": wmav2.NewData,
}

View File

@ -0,0 +1,139 @@
package convertor
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
)
type ItemsToConvertContract interface {
Add(file *ffmpeg.File)
Clear()
GetItems() map[int]ItemToConvertContract
GetItemsContainer() *fyne.Container
AfterAddingQueue()
GetIsAutoRemove() bool
SetIsAutoRemove(isAutoRemove bool)
}
type itemsToConvert struct {
ffmpeg ffmpeg.UtilitiesContract
nextId int
items map[int]ItemToConvertContract
itemsContainer *fyne.Container
isAutoRemove bool
}
func NewItemsToConvert(ffmpeg ffmpeg.UtilitiesContract) ItemsToConvertContract {
return &itemsToConvert{
ffmpeg: ffmpeg,
nextId: 0,
items: map[int]ItemToConvertContract{},
itemsContainer: container.NewVBox(),
isAutoRemove: true,
}
}
func (items *itemsToConvert) GetItemsContainer() *fyne.Container {
return items.itemsContainer
}
func (items *itemsToConvert) Add(file *ffmpeg.File) {
nextId := items.nextId
var content *fyne.Container
var buttonPlay *widget.Button
buttonPlay = widget.NewButtonWithIcon("", theme.Icon(theme.IconNameMediaPlay), func() {
buttonPlay.Disable()
go func() {
ffplay, err := items.ffmpeg.GetFFplay()
if err != nil {
fyne.Do(func() {
buttonPlay.Enable()
})
return
}
_ = ffplay.Play(file)
fyne.Do(func() {
buttonPlay.Enable()
})
}()
})
buttonRemove := widget.NewButtonWithIcon("", theme.Icon(theme.IconNameDelete), func() {
items.itemsContainer.Remove(content)
items.itemsContainer.Refresh()
delete(items.items, nextId)
})
buttonRemove.Importance = widget.DangerImportance
content = container.NewVBox(
container.NewBorder(
nil,
nil,
buttonPlay,
buttonRemove,
container.NewHScroll(widget.NewLabel(file.Name)),
),
container.NewHScroll(widget.NewLabel(file.Path)),
container.NewPadded(),
canvas.NewLine(theme.Color(theme.ColorNameFocus)),
container.NewPadded(),
)
items.itemsContainer.Add(content)
items.items[nextId] = newItemToConvert(file, content)
items.nextId++
}
func (items *itemsToConvert) GetIsAutoRemove() bool {
return items.isAutoRemove
}
func (items *itemsToConvert) SetIsAutoRemove(isAutoRemove bool) {
items.isAutoRemove = isAutoRemove
}
func (items *itemsToConvert) GetItems() map[int]ItemToConvertContract {
return items.items
}
func (items *itemsToConvert) AfterAddingQueue() {
if items.isAutoRemove {
items.Clear()
}
}
func (items *itemsToConvert) Clear() {
items.itemsContainer.RemoveAll()
items.items = map[int]ItemToConvertContract{}
}
type ItemToConvertContract interface {
GetFile() *ffmpeg.File
GetContent() *fyne.Container
}
type itemToConvert struct {
file *ffmpeg.File
content *fyne.Container
}
func newItemToConvert(file *ffmpeg.File, content *fyne.Container) ItemToConvertContract {
return &itemToConvert{
file: file,
content: content,
}
}
func (item *itemToConvert) GetFile() *ffmpeg.File {
return item.file
}
func (item *itemToConvert) GetContent() *fyne.Container {
return item.content
}

View File

@ -0,0 +1,217 @@
package convertor
import (
"bufio"
"errors"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
"image/color"
"io"
"regexp"
"strconv"
"strings"
)
type ProgressBarContract interface {
GetContainer() *fyne.Container
GetProgressbar(totalDuration float64, filePath string) ffmpeg.ProgressContract
ProcessEndedWithError(errorText string)
ProcessEndedWithSuccess(file *ffmpeg.File)
}
type progressBar struct {
container *fyne.Container
label *widget.Label
progressbar *widget.ProgressBar
errorBlock *container.Scroll
messageError *canvas.Text
statusMessage *canvas.Text
buttonPlay *widget.Button
ffmpegService ffmpeg.UtilitiesContract
}
func NewProgressBar(ffmpegService ffmpeg.UtilitiesContract) ProgressBarContract {
label := widget.NewLabel("")
progressbar := widget.NewProgressBar()
statusMessage := canvas.NewText("", theme.Color(theme.ColorNamePrimary))
messageError := canvas.NewText("", theme.Color(theme.ColorNameError))
buttonPlay := widget.NewButtonWithIcon("", theme.Icon(theme.IconNameMediaPlay), func() {
})
buttonPlay.Hide()
errorBlock := container.NewHScroll(messageError)
errorBlock.Hide()
content := container.NewVBox(
container.NewHScroll(label),
progressbar,
container.NewHScroll(container.NewHBox(
buttonPlay,
statusMessage,
)),
errorBlock,
)
content.Hide()
return &progressBar{
container: content,
label: label,
progressbar: progressbar,
errorBlock: errorBlock,
messageError: messageError,
statusMessage: statusMessage,
buttonPlay: buttonPlay,
ffmpegService: ffmpegService,
}
}
func (p *progressBar) GetContainer() *fyne.Container {
return p.container
}
func (p *progressBar) GetProgressbar(totalDuration float64, filePath string) ffmpeg.ProgressContract {
p.label.Text = filePath
p.statusMessage.Color = theme.Color(theme.ColorNamePrimary)
p.statusMessage.Text = lang.L("inProgressQueue")
p.messageError.Text = ""
fyne.Do(func() {
p.buttonPlay.Hide()
if p.errorBlock.Visible() {
p.errorBlock.Hide()
}
p.statusMessage.Refresh()
p.container.Refresh()
p.errorBlock.Refresh()
})
p.progressbar.Value = 0
return NewProgress(totalDuration, p.progressbar)
}
func (p *progressBar) ProcessEndedWithError(errorText string) {
fyne.Do(func() {
p.statusMessage.Color = theme.Color(theme.ColorNameError)
p.statusMessage.Text = lang.L("errorQueue")
p.messageError.Text = errorText
p.errorBlock.Show()
})
}
func (p *progressBar) ProcessEndedWithSuccess(file *ffmpeg.File) {
fyne.Do(func() {
p.statusMessage.Color = color.RGBA{R: 49, G: 127, B: 114, A: 255}
p.statusMessage.Text = lang.L("completedQueue")
p.buttonPlay.Show()
p.buttonPlay.OnTapped = func() {
p.buttonPlay.Disable()
go func() {
ffplay, err := p.ffmpegService.GetFFplay()
if err == nil {
_ = ffplay.Play(file)
}
fyne.Do(func() {
p.buttonPlay.Enable()
})
}()
}
})
}
type Progress struct {
totalDuration float64
progressbar *widget.ProgressBar
protocol string
}
func NewProgress(totalDuration float64, progressbar *widget.ProgressBar) ffmpeg.ProgressContract {
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
fyne.Do(func() {
p.progressbar.Refresh()
})
progress := 0.0
go func() {
scannerErr := bufio.NewReader(stdErr)
for {
line, _, err := scannerErr.ReadLine()
if err != nil {
if err == io.EOF {
break
}
continue
}
data := strings.TrimSpace(string(line))
errorText = data
}
}()
scannerOut := bufio.NewReader(stdOut)
for {
line, _, err := scannerOut.ReadLine()
if err != nil {
if err == io.EOF {
break
}
continue
}
data := strings.TrimSpace(string(line))
if strings.Contains(data, "progress=end") {
p.progressbar.Value = p.totalDuration
fyne.Do(func() {
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
fyne.Do(func() {
p.progressbar.Refresh()
})
}
}
if isProcessCompleted == false {
if len(errorText) == 0 {
errorText = lang.L("errorConverter")
}
return errors.New(errorText)
}
return nil
}

View File

@ -0,0 +1,152 @@
package convertor
import (
"errors"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
)
type Queue struct {
Setting *ffmpeg.ConvertSetting
Status StatusContract
Error error
}
type StatusContract interface {
Name() string
Ordinal() int
}
const (
Waiting = iota
InProgress
Completed
Error
)
type StatusType uint
var statusTypeStrings = []string{
"waiting",
"inProgress",
"completed",
"error",
}
func (status StatusType) Name() string {
return statusTypeStrings[status]
}
func (status StatusType) Ordinal() int {
return int(status)
}
type QueueListenerContract interface {
AddQueue(key int, queue *Queue)
ChangeQueue(key int, queue *Queue)
RemoveQueue(key int, status StatusContract)
}
type QueueListContract interface {
AddListener(queueListener QueueListenerContract)
GetItems() map[int]*Queue
Add(setting *ffmpeg.ConvertSetting)
EventChangeQueue(key int, queue *Queue)
Remove(key int)
GetItem(key int) (*Queue, error)
Next() (key int, queue *Queue)
}
type queueList struct {
currentKey int
items map[int]*Queue
queue map[int]int
queueListener map[int]QueueListenerContract
}
func NewQueueList() QueueListContract {
return &queueList{
currentKey: 0,
items: map[int]*Queue{},
queue: map[int]int{},
queueListener: map[int]QueueListenerContract{},
}
}
func (l *queueList) GetItems() map[int]*Queue {
return l.items
}
func (l *queueList) Add(setting *ffmpeg.ConvertSetting) {
queue := Queue{
Setting: setting,
Status: StatusType(Waiting),
}
l.currentKey += 1
l.items[l.currentKey] = &queue
l.queue[l.currentKey] = l.currentKey
l.eventAdd(l.currentKey, &queue)
}
func (l *queueList) EventChangeQueue(key int, queue *Queue) {
l.eventChange(key, queue)
}
func (l *queueList) Remove(key int) {
if _, ok := l.queue[key]; ok {
delete(l.queue, key)
}
if _, ok := l.items[key]; ok {
status := l.items[key].Status
l.eventRemove(key, status)
delete(l.items, key)
}
}
func (l *queueList) GetItem(key int) (*Queue, error) {
if item, ok := l.items[key]; ok {
return item, nil
}
return nil, errors.New("key not found")
}
func (l *queueList) AddListener(queueListener QueueListenerContract) {
l.queueListener[len(l.queueListener)] = queueListener
}
func (l *queueList) Next() (key int, queue *Queue) {
statusWaiting := StatusType(Waiting)
for key, queueId := range l.queue {
if queue, ok := l.items[queueId]; ok {
if queue.Status == statusWaiting {
return queueId, queue
}
}
if _, ok := l.queue[key]; ok {
delete(l.queue, key)
}
}
return -1, nil
}
func (l *queueList) eventAdd(key int, queue *Queue) {
for _, listener := range l.queueListener {
listener.AddQueue(key, queue)
}
}
func (l *queueList) eventChange(key int, queue *Queue) {
for _, listener := range l.queueListener {
listener.ChangeQueue(key, queue)
}
}
func (l *queueList) eventRemove(key int, status StatusContract) {
for _, listener := range l.queueListener {
listener.RemoveQueue(key, status)
}
}

View File

@ -0,0 +1,40 @@
package setting
func (s *setting) GetFFmpegPath() string {
path := s.fyneApp.Preferences().String("ffmpegPath")
if path == "" {
return "ffmpeg"
}
return path
}
func (s *setting) SetFFmpegPath(path string) {
s.fyneApp.Preferences().SetString("ffmpegPath", path)
}
func (s *setting) GetFFprobePath() string {
path := s.fyneApp.Preferences().String("ffprobePath")
if path == "" {
return "ffprobe"
}
return path
}
func (s *setting) SetFFprobePath(path string) {
s.fyneApp.Preferences().SetString("ffprobePath", path)
}
func (s *setting) GetFFplayPath() string {
path := s.fyneApp.Preferences().String("ffplayPath")
if path == "" {
return "ffplay"
}
return path
}
func (s *setting) SetFFplayPath(path string) {
s.fyneApp.Preferences().SetString("ffplayPath", path)
}

View File

@ -0,0 +1,66 @@
package setting
import (
"encoding/json"
"fyne.io/fyne/v2"
fyneLang "fyne.io/fyne/v2/lang"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/resources"
)
var supportedLanguages = map[string]Lang{
"ru": {Code: "ru", Title: "Русский"},
"kk": {Code: "kk", Title: "Қазақ Тілі"},
"en": {Code: "en", Title: "English"},
}
type Lang struct {
Code string
Title string
}
func ChangeLang(lang Lang) error {
translationsData, err := getTranslations(lang)
if err != nil {
return err
}
name := fyneLang.SystemLocale().LanguageString()
return fyneLang.AddTranslations(fyne.NewStaticResource(name+".json", translationsData))
}
func defaultLang() Lang {
return supportedLanguages["ru"]
}
func getTranslations(language Lang) ([]byte, error) {
translations := resources.GetTranslations()
baseJson, err := translations.ReadFile("translations/base." + language.Code + ".json")
if err != nil {
return nil, err
}
appJson, err := translations.ReadFile("translations/app." + language.Code + ".json")
if err != nil {
return nil, err
}
return mergeTranslations(baseJson, appJson)
}
func mergeTranslations(baseJson []byte, appJson []byte) ([]byte, error) {
base := map[string]interface{}{}
custom := map[string]interface{}{}
err := json.Unmarshal(baseJson, &base)
if err != nil {
return nil, err
}
err = json.Unmarshal(appJson, &custom)
if err != nil {
return nil, err
}
for k, v := range custom {
base[k] = v
}
return json.Marshal(base)
}

View File

@ -0,0 +1,84 @@
package setting
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"golang.org/x/text/language"
)
type SettingContract interface {
GetLanguages() []Lang
GetCurrentLangOrDefaultLang() (currentLang Lang, isDefault bool)
SetLang(language Lang) error
GetDirectoryForSaving() string
SetDirectoryForSaving(path string)
GetFFmpegPath() string
SetFFmpegPath(path string)
GetFFprobePath() string
SetFFprobePath(path string)
GetFFplayPath() string
SetFFplayPath(path string)
ThemeInit()
GetThemes() map[string]ThemeInfoContract
GetTheme() ThemeInfoContract
SetTheme(themeInfo ThemeInfoContract)
}
type setting struct {
fyneApp fyne.App
}
func NewSetting(fyneApp fyne.App) SettingContract {
return &setting{
fyneApp: fyneApp,
}
}
func (s *setting) GetLanguages() []Lang {
items := []Lang{}
for _, item := range supportedLanguages {
items = append(items, item)
}
return items
}
func (s *setting) GetCurrentLangOrDefaultLang() (currentLang Lang, isDefault bool) {
languageCode := s.fyneApp.Preferences().String("language")
if languageCode == "" {
languageTag, err := language.Parse(lang.SystemLocale().LanguageString())
if err != nil {
return currentLang, true
}
base, _ := languageTag.Base()
languageCode = base.String()
}
if currentLang, ok := supportedLanguages[languageCode]; ok {
return currentLang, false
}
return defaultLang(), true
}
func (s *setting) SetLang(language Lang) error {
err := ChangeLang(language)
if err != nil {
return err
}
s.fyneApp.Preferences().SetString("language", language.Code)
return nil
}
func (s *setting) GetDirectoryForSaving() string {
return s.fyneApp.Preferences().String("directoryForSaving")
}
func (s *setting) SetDirectoryForSaving(path string) {
s.fyneApp.Preferences().SetString("directoryForSaving", path)
}

View File

@ -0,0 +1,110 @@
package setting
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/theme"
"image/color"
)
func (s *setting) GetTheme() ThemeInfoContract {
name := s.fyneApp.Preferences().String("theme")
if name != "" {
if _, ok := s.GetThemes()[name]; ok {
return s.GetThemes()[name]
}
}
return s.GetThemes()["default"]
}
func (s *setting) SetTheme(themeInfo ThemeInfoContract) {
s.fyneApp.Preferences().SetString("theme", themeInfo.GetName())
if themeInfo.GetName() == "default" {
s.fyneApp.Settings().SetTheme(theme.DefaultTheme())
return
}
s.fyneApp.Settings().SetTheme(&forcedVariant{theme: theme.DefaultTheme(), variant: themeInfo.GetVariant()})
}
func (s *setting) ThemeInit() {
themeInfo := s.GetTheme()
if themeInfo.GetName() == "default" {
s.fyneApp.Settings().SetTheme(theme.DefaultTheme())
return
}
s.fyneApp.Settings().SetTheme(&forcedVariant{theme: theme.DefaultTheme(), variant: themeInfo.GetVariant()})
}
func (s *setting) GetThemes() map[string]ThemeInfoContract {
themesNameDefault := &themeInfo{
name: "default",
title: lang.L("themesNameDefault"),
}
themesNameLight := &themeInfo{
name: "light",
title: lang.L("themesNameLight"),
variant: theme.VariantLight,
}
themesNameDark := &themeInfo{
name: "dark",
title: lang.L("themesNameDark"),
variant: theme.VariantDark,
}
list := map[string]ThemeInfoContract{
"default": themesNameDefault,
"light": themesNameLight,
"dark": themesNameDark,
}
return list
}
type ThemeInfoContract interface {
GetName() string
GetTitle() string
GetVariant() fyne.ThemeVariant
}
type themeInfo struct {
name string
title string
variant fyne.ThemeVariant
}
func (inf *themeInfo) GetName() string {
return inf.name
}
func (inf *themeInfo) GetTitle() string {
return inf.title
}
func (inf *themeInfo) GetVariant() fyne.ThemeVariant {
return inf.variant
}
type forcedVariant struct {
theme fyne.Theme
variant fyne.ThemeVariant
}
func (f *forcedVariant) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color {
return f.theme.Color(name, f.variant)
}
func (f *forcedVariant) Font(style fyne.TextStyle) fyne.Resource {
return theme.DefaultTheme().Font(style)
}
func (f *forcedVariant) Icon(name fyne.ThemeIconName) fyne.Resource {
return theme.DefaultTheme().Icon(name)
}
func (f *forcedVariant) Size(name fyne.ThemeSizeName) float32 {
return theme.DefaultTheme().Size(name)
}

View File

@ -0,0 +1,111 @@
package controller
import (
"errors"
"fyne.io/fyne/v2/lang"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
"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"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils"
)
func (c *controller) convertor() {
formats, err := c.app.GetConvertorService().GetSupportFormats()
if err != nil {
c.startWithError(err)
return
}
content := view.Convertor(
c.window,
c.addFileForConversion,
c.app.GetSetting().GetDirectoryForSaving(),
c.setDirectoryForSaving,
formats,
c.addToConversion,
)
c.window.SetContent(content)
}
func (c *controller) addFileForConversion(file ffmpeg.File) {
c.app.GetItemsToConvert().Add(&file)
c.window.GetLayout().GetRContainer().SelectAddedFilesTab()
}
func (c *controller) setDirectoryForSaving(path string) {
c.app.GetSetting().SetDirectoryForSaving(path)
}
func (c *controller) addToConversion(convertSetting view.ConvertSetting) error {
if len(c.app.GetItemsToConvert().GetItems()) == 0 {
return errors.New(lang.L("errorNoFilesAddedForConversion"))
}
c.window.GetLayout().GetRContainer().SelectFileQueueTab()
for _, item := range c.app.GetItemsToConvert().GetItems() {
file := item.GetFile()
if file == nil {
continue
}
c.app.GetQueueService().Add(&ffmpeg.ConvertSetting{
FileInput: *file,
FileOut: ffmpeg.File{
Path: convertSetting.DirectoryForSave + utils.PathSeparator() + file.Name + "." + convertSetting.Format,
Name: file.Name,
Ext: "." + convertSetting.Format,
},
OverwriteOutputFiles: convertSetting.OverwriteOutputFiles,
Encoder: convertSetting.Encoder,
})
}
c.app.GetItemsToConvert().AfterAddingQueue()
return nil
}
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

@ -0,0 +1,16 @@
package controller
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view"
)
func (c *controller) startWithError(err error) {
languages := c.app.GetSetting().GetLanguages()
content := view.StartWithError(err, languages, func(lang setting.Lang) {
_ = setting.ChangeLang(lang)
c.startWithError(err)
})
c.window.SetContent(content)
}

View File

@ -0,0 +1,96 @@
package controller
import (
"fyne.io/fyne/v2"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/menu"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/window"
)
type ControllerContract interface {
Start()
}
type controller struct {
app application.AppContract
window window.WindowContract
}
func NewController(app application.AppContract) ControllerContract {
fyneWindow := app.FyneApp().NewWindow("GUI for FFmpeg")
fyneWindow.SetMaster()
queueLayout := window.NewQueueLayout(app.GetFFmpegService())
app.GetQueueService().AddListener(queueLayout)
return &controller{
app: app,
window: window.NewMainWindow(
fyneWindow,
app.GetProgressBarService(),
app.GetItemsToConvert(),
queueLayout,
),
}
}
func (c *controller) Start() {
isDefault, err := c.initLanguage()
if err != nil {
c.startWithError(err)
c.window.Show()
return
}
c.app.GetSetting().ThemeInit()
if isDefault {
languages := c.app.GetSetting().GetLanguages()
content := view.StartWithoutSupportLang(languages, func(lang setting.Lang) {
err = c.app.GetSetting().SetLang(lang)
if err != nil {
c.startWithError(err)
return
}
c.initLayout()
c.verificareaFFmpeg()
})
c.window.SetContent(content)
c.window.Show()
return
}
c.initLayout()
c.verificareaFFmpeg()
c.window.Show()
}
func (c *controller) verificareaFFmpeg() {
if !c.app.GetFFmpegService().UtilityCheck() {
c.settingConvertor(false)
return
}
c.convertor()
}
func (c *controller) initLanguage() (isDefault bool, err error) {
lang, isDefault := c.app.GetSetting().GetCurrentLangOrDefaultLang()
err = setting.ChangeLang(lang)
return isDefault, err
}
func (c *controller) initLayout() {
c.window.SetMainMenu(fyne.NewMainMenu(
menu.MainMenuSettings(
c.actionMainSettings,
c.actionSettingConvertor,
),
menu.MainMenuHelp(
c.actionAbout,
c.actionHelpFFplay,
),
))
c.window.InitLayout()
}

View File

@ -0,0 +1,67 @@
package controller
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view"
)
func (c *controller) actionSettingConvertor() {
c.settingConvertor(true)
}
func (c *controller) actionMainSettings() {
currentLang, _ := c.app.GetSetting().GetCurrentLangOrDefaultLang()
content := view.MainSettings(
currentLang,
c.app.GetSetting().GetLanguages(),
c.app.GetSetting().GetTheme(),
c.app.GetSetting().GetThemes(),
c.actionMainSettingsSave,
c.convertor,
)
c.window.SetContent(content)
}
func (c *controller) actionMainSettingsSave(setting *view.MainSettingForm) error {
err := c.app.GetSetting().SetLang(setting.Language)
if err != nil {
return err
}
c.app.GetSetting().SetTheme(setting.ThemeInfo)
c.initLayout()
c.convertor()
return nil
}
func (c *controller) actionAbout() {
ffmpegVersion := c.app.GetFFmpegService().GetFFmpegVersion()
ffprobeVersion := c.app.GetFFmpegService().GetFFprobeVersion()
ffplayVersion := c.app.GetFFmpegService().GetFFplayVersion()
appVersion := c.app.FyneApp().Metadata().Version
window := c.app.FyneApp().NewWindow(lang.L("about"))
window.Resize(fyne.Size{Width: 793, Height: 550})
window.SetFixedSize(true)
content := view.About(appVersion, ffmpegVersion, ffprobeVersion, ffplayVersion)
window.SetContent(content)
window.CenterOnScreen()
window.Show()
}
func (c *controller) actionHelpFFplay() {
window := c.app.FyneApp().NewWindow(lang.L("helpFFplay"))
window.Resize(fyne.Size{Width: 800, Height: 550})
window.SetFixedSize(true)
content := view.HelpFFplay()
window.SetContent(content)
window.CenterOnScreen()
window.Show()
}

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

@ -0,0 +1,21 @@
package apng
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "apng"}
}
return encoder.NewEncoder("apng", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "apng"
formats := []string{"apng"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package bmp
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "bmp"}
}
return encoder.NewEncoder("bmp", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "bmp"
formats := []string{"bmp"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,169 @@
package encoder
import "errors"
type EncoderContract interface {
GetName() string
GetParams() []string
GetParameter(name string) (ParameterContract, error)
}
type ParameterContract interface {
GetName() string
Set(string) error
Get() string
IsEnabled() bool
SetEnable()
SetDisable()
}
type EncoderDataContract interface {
GetTitle() string
GetFormats() []string
GetFileType() FileTypeContract
NewEncoder() EncoderContract
}
type data struct {
title string
formats []string
fileType FileTypeContract
encoder func() EncoderContract
}
func NewData(title string, formats []string, fileType FileTypeContract, encoder func() EncoderContract) EncoderDataContract {
return &data{
title: title,
formats: formats,
fileType: fileType,
encoder: encoder,
}
}
func (data *data) GetTitle() string {
return data.title
}
func (data *data) GetFormats() []string {
return data.formats
}
func (data *data) NewEncoder() EncoderContract {
return data.encoder()
}
func (data *data) GetFileType() FileTypeContract {
return data.fileType
}
type FileTypeContract interface {
Name() string
Ordinal() int
}
const (
Video = iota
Audio
Image
)
type FileType uint
var fileTypeStrings = []string{
"video",
"audio",
"image",
}
func (fileType FileType) Name() string {
return fileTypeStrings[fileType]
}
func (fileType FileType) Ordinal() int {
return int(fileType)
}
func GetListFileType() []FileTypeContract {
return []FileTypeContract{
FileType(Video),
FileType(Audio),
FileType(Image),
}
}
type encoder struct {
name string
parameters map[string]ParameterContract
getParams func(parameters map[string]ParameterContract) []string
}
func NewEncoder(name string, parameters map[string]ParameterContract, getParams func(parameters map[string]ParameterContract) []string) EncoderContract {
return &encoder{
name: name,
parameters: parameters,
getParams: getParams,
}
}
func (e *encoder) GetName() string {
return e.name
}
func (e *encoder) GetParams() []string {
return e.getParams(e.parameters)
}
func (e *encoder) GetParameter(name string) (ParameterContract, error) {
if e.parameters[name] == nil {
return nil, errors.New("parameter not found")
}
return e.parameters[name], nil
}
type parameter struct {
name string
isEnabled bool
parameter string
setParameter func(string) (string, error)
}
func NewParameter(name string, isEnabled bool, defaultParameter string, setParameter func(string) (string, error)) ParameterContract {
return &parameter{
name: name,
isEnabled: isEnabled,
parameter: defaultParameter,
setParameter: setParameter,
}
}
func (p *parameter) GetName() string {
return p.name
}
func (p *parameter) Set(s string) (err error) {
if p.setParameter != nil {
s, err = p.setParameter(s)
if err != nil {
return err
}
}
p.parameter = s
return nil
}
func (p *parameter) Get() string {
return p.parameter
}
func (p *parameter) IsEnabled() bool {
return p.isEnabled
}
func (p *parameter) SetEnable() {
p.isEnabled = true
}
func (p *parameter) SetDisable() {
p.isEnabled = false
}

View File

@ -0,0 +1,21 @@
package flv
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "flv"}
}
return encoder.NewEncoder("flv", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "flv"
formats := []string{"flv"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package gif
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "gif"}
}
return encoder.NewEncoder("gif", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "gif"
formats := []string{"gif"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,58 @@
package h264_nvenc
import (
"errors"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
var Presets = []string{
"default",
"slow",
"medium",
"fast",
"hp",
"hq",
"bd",
"ll",
"llhq",
"llhp",
"lossless",
"losslesshp",
}
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{
"preset": newParameterPreset(),
}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
params := []string{"-c:v", "h264_nvenc"}
if parameters["preset"] != nil && parameters["preset"].IsEnabled() {
params = append(params, "-preset", parameters["preset"].Get())
}
return params
}
return encoder.NewEncoder("h264_nvenc", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "h264_nvenc"
formats := []string{"mp4"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}
func newParameterPreset() encoder.ParameterContract {
setParameter := func(s string) (string, error) {
for _, value := range Presets {
if value == s {
return value, nil
}
}
return "", errors.New("preset not found")
}
return encoder.NewParameter("preset", false, "default", setParameter)
}

View File

@ -0,0 +1,21 @@
package libmp3lame
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:a", "libmp3lame"}
}
return encoder.NewEncoder("libmp3lame", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libmp3lame"
formats := []string{"mp3"}
fileType := encoder.FileType(encoder.Audio)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package libshine
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:a", "libshine"}
}
return encoder.NewEncoder("libshine", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libshine"
formats := []string{"mp3"}
fileType := encoder.FileType(encoder.Audio)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package libtwolame
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:a", "libtwolame"}
}
return encoder.NewEncoder("libtwolame", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libtwolame"
formats := []string{"mp2"}
fileType := encoder.FileType(encoder.Audio)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package libvpx
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "libvpx"}
}
return encoder.NewEncoder("libvpx", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libvpx"
formats := []string{"webm", "mkv"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package libvpx_vp9
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "libvpx-vp9"}
}
return encoder.NewEncoder("libvpx_vp9", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libvpx-vp9"
formats := []string{"webm", "mkv"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package libwebp
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "libwebp"}
}
return encoder.NewEncoder("libwebp", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libwebp"
formats := []string{"webp"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package libwebp_anim
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "libwebp_anim"}
}
return encoder.NewEncoder("libwebp_anim", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libwebp_anim"
formats := []string{"webp"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,56 @@
package libx264
import (
"errors"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
var Presets = []string{
"ultrafast",
"superfast",
"veryfast",
"faster",
"fast",
"medium",
"slow",
"slower",
"veryslow",
"placebo",
}
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{
"preset": newParameterPreset(),
}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
params := []string{"-c:v", "libx264"}
if parameters["preset"] != nil && parameters["preset"].IsEnabled() {
params = append(params, "-preset", parameters["preset"].Get())
}
return params
}
return encoder.NewEncoder("libx264", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libx264"
formats := []string{"mp4"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}
func newParameterPreset() encoder.ParameterContract {
setParameter := func(s string) (string, error) {
for _, value := range Presets {
if value == s {
return value, nil
}
}
return "", errors.New("preset not found")
}
return encoder.NewParameter("preset", false, "medium", setParameter)
}

View File

@ -0,0 +1,56 @@
package libx265
import (
"errors"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
var Presets = []string{
"ultrafast",
"superfast",
"veryfast",
"faster",
"fast",
"medium",
"slow",
"slower",
"veryslow",
"placebo",
}
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{
"preset": newParameterPreset(),
}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
params := []string{"-c:v", "libx265"}
if parameters["preset"] != nil && parameters["preset"].IsEnabled() {
params = append(params, "-preset", parameters["preset"].Get())
}
return params
}
return encoder.NewEncoder("libx265", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libx265"
formats := []string{"mp4"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}
func newParameterPreset() encoder.ParameterContract {
setParameter := func(s string) (string, error) {
for _, value := range Presets {
if value == s {
return value, nil
}
}
return "", errors.New("preset not found")
}
return encoder.NewParameter("preset", false, "medium", setParameter)
}

View File

@ -0,0 +1,21 @@
package libxvid
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "libxvid"}
}
return encoder.NewEncoder("libxvid", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "libxvid"
formats := []string{"avi"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package mjpeg
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "mjpeg"}
}
return encoder.NewEncoder("mjpeg", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "mjpeg"
formats := []string{"jpg"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package mp2
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:a", "mp2"}
}
return encoder.NewEncoder("mp2", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "mp2"
formats := []string{"mp2"}
fileType := encoder.FileType(encoder.Audio)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package mp2fixed
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:a", "mp2fixed"}
}
return encoder.NewEncoder("mp2fixed", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "mp2fixed"
formats := []string{"mp2"}
fileType := encoder.FileType(encoder.Audio)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package mpeg1video
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "mpeg1video"}
}
return encoder.NewEncoder("mpeg1video", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "mpeg1video"
formats := []string{"mpg", "mpeg"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package mpeg2video
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "mpeg2video"}
}
return encoder.NewEncoder("mpeg2video", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "mpeg2video"
formats := []string{"mpg", "mpeg"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package mpeg4
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "mpeg4"}
}
return encoder.NewEncoder("mpeg4", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "mpeg4"
formats := []string{"avi"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package msmpeg4
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "msmpeg4"}
}
return encoder.NewEncoder("msmpeg4", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "msmpeg4"
formats := []string{"avi"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package msmpeg4v2
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "msmpeg4v2"}
}
return encoder.NewEncoder("msmpeg4v2", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "msmpeg4v2"
formats := []string{"avi"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package msvideo1
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "msvideo1"}
}
return encoder.NewEncoder("msvideo1", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "msvideo1"
formats := []string{"avi"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package png
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "png"}
}
return encoder.NewEncoder("png", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "png"
formats := []string{"png"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package qtrle
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "qtrle"}
}
return encoder.NewEncoder("qtrle", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "qtrle"
formats := []string{"mov"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package sgi
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "sgi"}
}
return encoder.NewEncoder("sgi", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "sgi"
formats := []string{"sgi"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package tiff
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "tiff"}
}
return encoder.NewEncoder("tiff", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "tiff"
formats := []string{"tiff"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package wmav1
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:a", "wmav1"}
}
return encoder.NewEncoder("wmav1", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "wmav1"
formats := []string{"wma"}
fileType := encoder.FileType(encoder.Audio)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package wmav2
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:a", "wmav2"}
}
return encoder.NewEncoder("wmav2", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "wmav2"
formats := []string{"wma"}
fileType := encoder.FileType(encoder.Audio)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package wmv1
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "wmv1"}
}
return encoder.NewEncoder("wmv1", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "wmv1"
formats := []string{"wmv"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package wmv2
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "wmv2"}
}
return encoder.NewEncoder("wmv2", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "wmv2"
formats := []string{"wmv"}
fileType := encoder.FileType(encoder.Video)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

View File

@ -0,0 +1,21 @@
package xbm
import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
)
func NewEncoder() encoder.EncoderContract {
parameters := map[string]encoder.ParameterContract{}
getParams := func(parameters map[string]encoder.ParameterContract) []string {
return []string{"-c:v", "xbm"}
}
return encoder.NewEncoder("xbm", parameters, getParams)
}
func NewData() encoder.EncoderDataContract {
title := "xbm"
formats := []string{"xbm"}
fileType := encoder.FileType(encoder.Image)
return encoder.NewData(title, formats, fileType, NewEncoder)
}

139
internal/ffmpeg/ffmpeg.go Normal file
View File

@ -0,0 +1,139 @@
package ffmpeg
import (
"bufio"
"errors"
"fyne.io/fyne/v2/lang"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils"
"io"
"os/exec"
"regexp"
"strings"
)
type ProgressContract interface {
GetProtocole() string
Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error
}
type FFmpegContract interface {
GetPath() string
GetVersion() (string, error)
GetEncoders(scanner func(scanner *bufio.Reader)) error
RunConvert(setting ConvertSetting, progress ProgressContract, beforeWait func(cmd *exec.Cmd), afterWait func(cmd *exec.Cmd)) error
}
type ffmpeg struct {
path string
}
func newFFmpeg(path string) (FFmpegContract, error) {
if path == "" {
return nil, errors.New(lang.L("errorFFmpeg"))
}
isCheck, err := checkFFmpegPath(path)
if err != nil {
return nil, err
}
if isCheck == false {
return nil, errors.New(lang.L("errorFFmpeg"))
}
return &ffmpeg{
path: path,
}, nil
}
func (f *ffmpeg) GetPath() string {
return f.path
}
func (f *ffmpeg) GetVersion() (string, error) {
cmd := exec.Command(f.path, "-version")
utils.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 (f *ffmpeg) RunConvert(setting ConvertSetting, progress ProgressContract, beforeWait func(cmd *exec.Cmd), afterWait func(cmd *exec.Cmd)) error {
overwriteOutputFiles := "-n"
if setting.OverwriteOutputFiles == true {
overwriteOutputFiles = "-y"
}
args := []string{overwriteOutputFiles, "-i", setting.FileInput.Path}
args = append(args, setting.Encoder.GetParams()...)
args = append(args, "-progress", progress.GetProtocole(), setting.FileOut.Path)
cmd := exec.Command(f.path, args...)
utils.PrepareBackgroundCommand(cmd)
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
}
if beforeWait != nil {
beforeWait(cmd)
}
errProgress := progress.Run(stdOut, stdErr)
err = cmd.Wait()
if afterWait != nil {
afterWait(cmd)
}
if errProgress != nil {
return errProgress
}
if err != nil {
return err
}
return nil
}
func (f *ffmpeg) GetEncoders(scanner func(scanner *bufio.Reader)) error {
cmd := exec.Command(f.path, "-encoders")
utils.PrepareBackgroundCommand(cmd)
stdOut, err := cmd.StdoutPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
scannerErr := bufio.NewReader(stdOut)
scanner(scannerErr)
return cmd.Wait()
}
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
}

73
internal/ffmpeg/ffplay.go Normal file
View File

@ -0,0 +1,73 @@
package ffmpeg
import (
"errors"
"fyne.io/fyne/v2/lang"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils"
"os/exec"
"regexp"
"strings"
)
type FFplayContract interface {
GetPath() string
GetVersion() (string, error)
Play(file *File) error
}
type ffplay struct {
path string
}
func newFFplay(path string) (FFplayContract, error) {
if path == "" {
return nil, errors.New(lang.L("errorFFplay"))
}
isCheck, err := checkFFplayPath(path)
if err != nil {
return nil, err
}
if isCheck == false {
return nil, errors.New(lang.L("errorFFplay"))
}
return &ffplay{
path: path,
}, nil
}
func (f *ffplay) GetPath() string {
return f.path
}
func (f *ffplay) GetVersion() (string, error) {
cmd := exec.Command(f.path, "-version")
utils.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 (f *ffplay) Play(file *File) error {
args := []string{file.Path}
cmd := exec.Command(f.GetPath(), args...)
return cmd.Run()
}
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
}

124
internal/ffmpeg/ffprobe.go Normal file
View File

@ -0,0 +1,124 @@
package ffmpeg
import (
"errors"
"fyne.io/fyne/v2/lang"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils"
"os/exec"
"regexp"
"strconv"
"strings"
"unicode"
)
type FFprobeContract interface {
GetPath() string
GetVersion() (string, error)
GetTotalDuration(file *File) (float64, error)
}
type ffprobe struct {
path string
}
func newFFprobe(path string) (FFprobeContract, error) {
if path == "" {
return nil, errors.New(lang.L("errorFFprobe"))
}
isCheck, err := checkFFprobePath(path)
if err != nil {
return nil, err
}
if isCheck == false {
return nil, errors.New(lang.L("errorFFprobe"))
}
return &ffprobe{
path: path,
}, nil
}
func (f *ffprobe) GetPath() string {
return f.path
}
func (f *ffprobe) GetVersion() (string, error) {
cmd := exec.Command(f.path, "-version")
utils.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 (f *ffprobe) 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(f.path, args...)
utils.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
}
frames := strings.TrimSpace(string(out))
if len(frames) == 0 {
return f.getAlternativeTotalDuration(file)
}
duration, err = strconv.ParseFloat(frames, 64)
if err != nil {
// fix .mts duration
return strconv.ParseFloat(getFirstDigits(frames), 64)
}
return duration, err
}
func (f *ffprobe) getAlternativeTotalDuration(file *File) (duration float64, err error) {
args := []string{"-v", "error", "-select_streams", "a:0", "-count_packets", "-show_entries", "stream=nb_read_packets", "-of", "csv=p=0", file.Path}
cmd := exec.Command(f.path, args...)
utils.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
}
frames := strings.TrimSpace(string(out))
if len(frames) == 0 {
return 0, errors.New("error getting number of frames")
}
return strconv.ParseFloat(frames, 64)
}
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
}
func getFirstDigits(s string) string {
result := ""
for _, r := range s {
if unicode.IsDigit(r) {
result += string(r)
} else {
break
}
}
return result
}

View File

@ -0,0 +1,221 @@
package ffmpeg
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/ffmpeg/encoder"
)
type File struct {
Path string
Name string
Ext string
}
type ConvertSetting struct {
FileInput File
FileOut File
OverwriteOutputFiles bool
Encoder encoder.EncoderContract
}
type UtilitiesContract interface {
UtilityCheck() bool
GetFFmpeg() (FFmpegContract, error)
GetFFmpegVersion() string
GetFFmpegPath() string
ChangeFFmpeg(path string) error
GetFFprobe() (FFprobeContract, error)
GetFFprobeVersion() string
GetFFprobePath() string
ChangeFFprobe(path string) error
GetFFplay() (FFplayContract, error)
GetFFplayVersion() string
GetFFplayPath() string
ChangeFFplay(path string) error
}
type utilities struct {
setting setting.SettingContract
ffmpeg FFmpegContract
ffprobe FFprobeContract
ffplay FFplayContract
}
func NewUtilities(setting setting.SettingContract) UtilitiesContract {
return &utilities{
setting: setting,
}
}
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 {
createFFmpeg, err := newFFmpeg(u.setting.GetFFmpegPath())
if err != nil {
return nil, err
}
u.ffmpeg = createFFmpeg
}
return u.ffmpeg, nil
}
func (u *utilities) GetFFmpegVersion() string {
ffmpegService, err := u.GetFFmpeg()
if err != nil {
return lang.L("errorFFmpegVersion")
}
version, err := ffmpegService.GetVersion()
if err != nil {
return lang.L("errorFFmpegVersion")
}
return version
}
func (u *utilities) GetFFmpegPath() string {
ffmpegService, err := u.GetFFmpeg()
if err != nil {
return ""
}
return ffmpegService.GetPath()
}
func (u *utilities) ChangeFFmpeg(path string) error {
if path == "" {
return errors.New(lang.L("errorFFmpeg"))
}
createFFmpeg, err := newFFmpeg(path)
if err != nil {
return err
}
u.ffmpeg = createFFmpeg
u.setting.SetFFmpegPath(path)
return nil
}
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) GetFFprobeVersion() string {
ffprobeService, err := u.GetFFprobe()
if err != nil {
return lang.L("errorFFprobeVersion")
}
ffprobeVersion, err := ffprobeService.GetVersion()
if err != nil {
return lang.L("errorFFprobeVersion")
}
return ffprobeVersion
}
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 {
createFFplay, err := newFFplay(u.setting.GetFFplayPath())
if err != nil {
return nil, err
}
u.ffplay = createFFplay
}
return u.ffplay, nil
}
func (u *utilities) GetFFplayVersion() string {
ffplayService, err := u.GetFFplay()
if err != nil {
return lang.L("errorFFplayVersion")
}
ffplayVersion, err := ffplayService.GetVersion()
if err != nil {
return lang.L("errorFFplayVersion")
}
return ffplayVersion
}
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
}

29
internal/gui/menu/main.go Normal file
View File

@ -0,0 +1,29 @@
package menu
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
)
func MainMenuSettings(
actionMainSettings func(),
actionFFPathSelection func(),
) *fyne.Menu {
quit := fyne.NewMenuItem(lang.L("exit"), nil)
quit.IsQuit = true
settingsSelection := fyne.NewMenuItem(lang.L("settings"), actionMainSettings)
ffPathSelection := fyne.NewMenuItem(lang.L("changeFFPath"), actionFFPathSelection)
return fyne.NewMenu(lang.L("settings"), settingsSelection, ffPathSelection, quit)
}
func MainMenuHelp(
actionAbout func(),
actionHelpFFplay func(),
) *fyne.Menu {
about := fyne.NewMenuItem(lang.L("about"), actionAbout)
helpFFplay := fyne.NewMenuItem(lang.L("helpFFplay"), actionHelpFFplay)
return fyne.NewMenu(lang.L("help"), helpFFplay, about)
}

607
internal/gui/view/about.go Normal file
View File

@ -0,0 +1,607 @@
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/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/resources"
"golang.org/x/image/colornames"
"net/url"
)
func About(appVersion string, ffmpegVersion string, ffprobeVersion string, ffplayVersion string) fyne.CanvasObject {
programmName := canvas.NewText(" GUI for FFmpeg", colornames.Darkgreen)
programmName.TextStyle = fyne.TextStyle{Bold: true}
programmName.TextSize = 20
programmLink := widget.NewHyperlink(
lang.L("programmLink"),
&url.URL{
Scheme: "https",
Host: "gui-for-ffmpeg.projects.kor-elf.net",
Path: "/",
},
)
licenseLink := widget.NewHyperlink(
lang.L("licenseLink"),
&url.URL{
Scheme: "https",
Host: "git.kor-elf.net",
Path: "kor-elf/gui-for-ffmpeg/src/branch/main/LICENSE",
},
)
licenseLinkOther := widget.NewHyperlink(
lang.L("licenseLinkOther"),
&url.URL{
Scheme: "https",
Host: "git.kor-elf.net",
Path: "kor-elf/gui-for-ffmpeg/src/branch/main/LICENSE-3RD-PARTY.txt",
},
)
programmVersion := widget.NewRichTextFromMarkdown(
lang.L(
"programmVersion",
map[string]any{"Version": appVersion},
),
)
aboutText := widget.NewRichText(
&widget.TextSegment{
Text: lang.L("aboutText"),
},
)
image := canvas.NewImageFromResource(resources.IconAppLogoResource())
image.SetMinSize(fyne.Size{Width: 100, Height: 100})
image.FillMode = canvas.ImageFillContain
ffmpegTrademark := widget.NewRichTextFromMarkdown(lang.L("ffmpegTrademark"))
ffmpegLGPL := widget.NewRichTextFromMarkdown(lang.L("ffmpegLGPL"))
return container.NewScroll(container.NewVBox(
container.NewBorder(nil, nil, container.NewVBox(image), nil, container.NewVBox(
programmName,
programmVersion,
aboutText,
ffmpegTrademark,
ffmpegLGPL,
widget.NewRichTextFromMarkdown("Copyright (c) 2024 **[Leonid Nikitin (kor-elf)](https://git.kor-elf.net/kor-elf/)**."),
container.NewHBox(programmLink, licenseLink),
container.NewHBox(licenseLinkOther),
)),
aboutFFmpeg(ffmpegVersion),
aboutFFprobe(ffprobeVersion),
aboutFFplay(ffplayVersion),
widget.NewCard(lang.L("AlsoUsedProgram"), "", license3RDParty()),
))
}
func aboutFFmpeg(version string) *fyne.Container {
programmName := canvas.NewText(" FFmpeg", colornames.Darkgreen)
programmName.TextStyle = fyne.TextStyle{Bold: true}
programmName.TextSize = 20
programmLink := widget.NewHyperlink(lang.L("programmLink"), &url.URL{
Scheme: "https",
Host: "ffmpeg.org",
Path: "",
})
licenseLink := widget.NewHyperlink(lang.L("licenseLink"), &url.URL{
Scheme: "https",
Host: "ffmpeg.org",
Path: "legal.html",
})
return container.NewVBox(
programmName,
widget.NewLabel(version),
widget.NewRichTextFromMarkdown("**FFmpeg** is a trademark of **[Fabrice Bellard](https://bellard.org/)**, originator of the **[FFmpeg](https://ffmpeg.org/about.html)** project."),
widget.NewRichTextFromMarkdown("This software uses libraries from the **FFmpeg** project under the **[LGPLv2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**."),
container.NewHBox(programmLink, licenseLink),
)
}
func aboutFFprobe(version string) *fyne.Container {
programmName := canvas.NewText(" FFprobe", colornames.Darkgreen)
programmName.TextStyle = fyne.TextStyle{Bold: true}
programmName.TextSize = 20
programmLink := widget.NewHyperlink(lang.L("programmLink"), &url.URL{
Scheme: "https",
Host: "ffmpeg.org",
Path: "ffprobe.html",
})
licenseLink := widget.NewHyperlink(lang.L("licenseLink"), &url.URL{
Scheme: "https",
Host: "ffmpeg.org",
Path: "legal.html",
})
return container.NewVBox(
programmName,
widget.NewLabel(version),
widget.NewRichTextFromMarkdown("**FFmpeg** is a trademark of **[Fabrice Bellard](https://bellard.org/)**, originator of the **[FFmpeg](https://ffmpeg.org/about.html)** project."),
widget.NewRichTextFromMarkdown("This software uses libraries from the **FFmpeg** project under the **[LGPLv2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**."),
container.NewHBox(programmLink, licenseLink),
)
}
func aboutFFplay(version string) *fyne.Container {
programmName := canvas.NewText(" FFplay", colornames.Darkgreen)
programmName.TextStyle = fyne.TextStyle{Bold: true}
programmName.TextSize = 20
programmLink := widget.NewHyperlink(lang.L("programmLink"), &url.URL{
Scheme: "https",
Host: "ffmpeg.org",
Path: "ffplay.html",
})
licenseLink := widget.NewHyperlink(lang.L("licenseLink"), &url.URL{
Scheme: "https",
Host: "ffmpeg.org",
Path: "legal.html",
})
return container.NewVBox(
programmName,
widget.NewLabel(version),
widget.NewRichTextFromMarkdown("**FFmpeg** is a trademark of **[Fabrice Bellard](https://bellard.org/)**, originator of the **[FFmpeg](https://ffmpeg.org/about.html)** project."),
widget.NewRichTextFromMarkdown("This software uses libraries from the **FFmpeg** project under the **[LGPLv2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**."),
container.NewHBox(programmLink, licenseLink),
)
}
func license3RDParty() *fyne.Container {
return container.NewVBox(
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("fyne.io/fyne/v2", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/fyne",
})),
container.NewHBox(widget.NewHyperlink("BSD 3-Clause License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/fyne/blob/master/LICENSE",
})),
widget.NewRichTextFromMarkdown("Copyright (C) 2018 Fyne.io developers (see [AUTHORS](https://github.com/fyne-io/fyne/blob/master/AUTHORS))"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("fyne.io/systray", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/systray",
})),
container.NewHBox(widget.NewHyperlink("Apache License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/systray/blob/master/LICENSE",
})),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/BurntSushi/toml", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "BurntSushi/toml",
})),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "BurntSushi/toml/blob/master/COPYING",
})),
widget.NewLabel("Copyright (c) 2013 TOML authors"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/davecgh/go-spew", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "davecgh/go-spew",
})),
container.NewHBox(widget.NewHyperlink("ISC License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "davecgh/go-spew/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2012-2016 Dave Collins <dave@davec.name>"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/fredbi/uri", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fredbi/uri",
})),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fredbi/uri/blob/master/LICENSE.md",
})),
widget.NewLabel("Copyright (c) 2018 Frederic Bidon"),
widget.NewLabel("Copyright (c) 2015 Trey Tacon"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/fsnotify/fsnotify", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fsnotify/fsnotify",
})),
container.NewHBox(widget.NewHyperlink("BSD-3-Clause license", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fsnotify/fsnotify/blob/main/LICENSE",
})),
widget.NewLabel("Copyright © 2012 The Go Authors. All rights reserved."),
widget.NewLabel("Copyright © fsnotify Authors. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/fyne-io/gl-js", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/gl-js",
})),
container.NewHBox(widget.NewHyperlink("BSD-3-Clause license", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/gl-js/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2009 The Go Authors. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/fyne-io/glfw-js", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/glfw-js",
})),
container.NewHBox(widget.NewHyperlink("MIT License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/glfw-js/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2014 Dmitri Shuralyov"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/fyne-io/image", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/image",
})),
container.NewHBox(widget.NewHyperlink("BSD 3-Clause License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/image/blob/main/LICENSE",
})),
widget.NewLabel("Copyright (c) 2022, Fyne.io"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/fyne-io/oksvg", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/oksvg",
})),
container.NewHBox(widget.NewHyperlink("BSD 3-Clause License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "fyne-io/oksvg/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2018, Steven R Wiley. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/go-gl/gl", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-gl/gl",
})),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-gl/gl/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2014 Eric Woroshow"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/go-gl/glfw/v3.3/glfw", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-gl/glfw/",
})),
container.NewHBox(widget.NewHyperlink("BSD-3-Clause license", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-gl/glfw/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2012 The glfw3-go Authors. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/go-text/render", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-text/render",
})),
container.NewHBox(widget.NewHyperlink("Unlicense OR BSD-3-Clause", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-text/render/blob/main/LICENSE",
})),
widget.NewLabel("Copyright 2021 The go-text authors"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/go-text/typesetting", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-text/typesetting",
})),
container.NewHBox(widget.NewHyperlink("Unlicense OR BSD-3-Clause", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-text/typesetting/blob/main/LICENSE",
})),
widget.NewLabel("Copyright 2021 The go-text authors"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/godbus/dbus/v5", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "godbus/dbus",
})),
container.NewHBox(widget.NewHyperlink("BSD 2-Clause \"Simplified\" License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "godbus/dbus/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/hack-pad/go-indexeddb", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "hack-pad/go-indexeddb",
})),
container.NewHBox(widget.NewHyperlink("Apache License 2.0", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "hack-pad/go-indexeddb/blob/main/LICENSE",
})),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/hack-pad/safejs", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "hack-pad/safejs",
})),
container.NewHBox(widget.NewHyperlink("Apache License 2.0", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "hack-pad/safejs/blob/main/LICENSE",
})),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/jeandeaual/go-locale", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "jeandeaual/go-locale",
})),
container.NewHBox(widget.NewHyperlink("MIT License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "jeandeaual/go-locale/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2020 Alexis Jeandeau"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/jsummers/gobmp", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "jsummers/gobmp",
})),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "jsummers/gobmp/blob/master/COPYING.txt",
})),
widget.NewLabel("Copyright (c) 2012-2015 Jason Summers"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/nfnt/resize", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "nfnt/resize",
})),
container.NewHBox(widget.NewHyperlink("ISC License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "nfnt/resize/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/nicksnyder/go-i18n/v2", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "nicksnyder/go-i18n",
})),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "nicksnyder/go-i18n/blob/main/LICENSE",
})),
widget.NewRichTextFromMarkdown("Copyright (c) 2014 Nick Snyder [https://github.com/nicksnyder](https://github.com/nicksnyder)"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/pmezard/go-difflib", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "pmezard/go-difflib",
})),
container.NewHBox(widget.NewHyperlink("License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "pmezard/go-difflib/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2013, Patrick Mezard. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/rymdport/portal", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "rymdport/portal",
})),
container.NewHBox(widget.NewHyperlink("Apache License 2.0", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "rymdport/portal/blob/main/LICENSE",
})),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/srwiley/oksvg", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "srwiley/oksvg",
})),
container.NewHBox(widget.NewHyperlink("BSD 3-Clause License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "srwiley/oksvg/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2018, Steven R Wiley. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/srwiley/rasterx", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "srwiley/rasterx",
})),
container.NewHBox(widget.NewHyperlink("BSD 3-Clause License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "srwiley/rasterx/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2018, Steven R Wiley. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/stretchr/testify", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "stretchr/testify",
})),
container.NewHBox(widget.NewHyperlink("MIT License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "stretchr/testify/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/ulikunitz/xz", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "ulikunitz/xz",
})),
container.NewHBox(widget.NewHyperlink("License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "ulikunitz/xz/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2014-2022 Ulrich Kunitz. All rights reserved."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/yuin/goldmark", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "yuin/goldmark",
})),
container.NewHBox(widget.NewHyperlink("MIT License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "yuin/goldmark/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2019 Yusuke Inuzuka"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("golang.org/x/image", &url.URL{
Scheme: "https",
Host: "pkg.go.dev",
Path: "golang.org/x/image",
})),
container.NewHBox(widget.NewHyperlink("License", &url.URL{
Scheme: "https",
Host: "cs.opensource.google",
Path: "go/x/image/+/master:LICENSE",
})),
widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("golang.org/x/net", &url.URL{
Scheme: "https",
Host: "pkg.go.dev",
Path: "golang.org/x/net",
})),
container.NewHBox(widget.NewHyperlink("License", &url.URL{
Scheme: "https",
Host: "cs.opensource.google",
Path: "go/x/net/+/master:LICENSE",
})),
widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("golang.org/x/sys", &url.URL{
Scheme: "https",
Host: "pkg.go.dev",
Path: "golang.org/x/sys",
})),
container.NewHBox(widget.NewHyperlink("License", &url.URL{
Scheme: "https",
Host: "cs.opensource.google",
Path: "go/x/sys/+/master:LICENSE",
})),
widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("golang.org/x/text", &url.URL{
Scheme: "https",
Host: "pkg.go.dev",
Path: "golang.org/x/text",
})),
container.NewHBox(widget.NewHyperlink("License", &url.URL{
Scheme: "https",
Host: "cs.opensource.google",
Path: "go/x/text/+/master:LICENSE",
})),
widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("gopkg.in/yaml.v3", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "go-yaml/yaml/tree/v3.0.1",
})),
container.NewHBox(widget.NewHyperlink("MIT License and Apache License 2.0", &url.URL{
Scheme: "http",
Host: "github.com",
Path: "go-yaml/yaml/blob/v3.0.1/LICENSE",
})),
widget.NewLabel("Copyright (c) 2006-2010 Kirill Simonov"),
widget.NewLabel("Copyright (c) 2006-2011 Kirill Simonov"),
widget.NewLabel("Copyright (c) 2011-2019 Canonical Ltd"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/golang/go", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "golang/go",
})),
container.NewHBox(widget.NewHyperlink("BSD 3-Clause \"New\" or \"Revised\" License", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "golang/go/blob/master/LICENSE",
})),
widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen),
)
}

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

@ -0,0 +1,414 @@
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/application/convertor/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
encoder2 "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view/convertor/encoders"
"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"
"os"
"path/filepath"
)
type ConvertSetting struct {
DirectoryForSave string
OverwriteOutputFiles bool
Format string
Encoder encoder2.EncoderContract
}
func Convertor(
window window.WindowContract,
addFileForConversion func(file ffmpeg.File),
directoryForSavingPath string,
directoryForSaving func(path string),
formats encoder.ConvertorFormatsContract,
addToConversion func(convertSetting ConvertSetting) error,
) fyne.CanvasObject {
conversionMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255})
conversionMessage.TextSize = 16
conversionMessage.TextStyle = fyne.TextStyle{Bold: true}
form := newFormConvertor(
window,
addFileForConversion,
directoryForSavingPath,
directoryForSaving,
formats,
addToConversion,
conversionMessage,
)
converterVideoFilesTitle := lang.L("converterVideoFilesTitle")
return widget.NewCard(converterVideoFilesTitle, "", container.NewVScroll(form.getForm()))
}
type formConvertor struct {
form *widget.Form
items []*widget.FormItem
conversionMessage *canvas.Text
window window.WindowContract
addFileForConversion func(file ffmpeg.File)
directoryForSaving func(path string)
}
func newFormConvertor(
window window.WindowContract,
addFileForConversion func(file ffmpeg.File),
directoryForSavingPath string,
directoryForSaving func(path string),
formats encoder.ConvertorFormatsContract,
addToConversion func(convertSetting ConvertSetting) error,
conversionMessage *canvas.Text,
) *formConvertor {
f := widget.NewForm()
f.SubmitText = lang.L("converterVideoFilesSubmitTitle")
formConvertor := &formConvertor{
form: f,
window: window,
addFileForConversion: addFileForConversion,
directoryForSaving: directoryForSaving,
conversionMessage: conversionMessage,
}
fileForConversion := formConvertor.newFileForConversion()
directoryForSavingButton := formConvertor.newDirectoryForSaving(directoryForSavingPath)
isOverwriteOutputFiles := false
checkboxOverwriteOutputFiles := widget.NewCheck(lang.L("checkboxOverwriteOutputFilesTitle"), func(b bool) {
isOverwriteOutputFiles = b
})
checkboxOverwriteOutputFiles.SetChecked(isOverwriteOutputFiles)
selectEncoder := formConvertor.newSelectEncoder(formats)
items := []*widget.FormItem{
{
Text: lang.L("fileForConversionTitle"),
Widget: fileForConversion.button,
},
{
Widget: container.NewHScroll(fileForConversion.message),
},
{
Text: lang.L("buttonForSelectedDirTitle"),
Widget: directoryForSavingButton.button,
},
{
Widget: container.NewHScroll(directoryForSavingButton.message),
},
{
Widget: checkboxOverwriteOutputFiles,
},
{
Widget: selectEncoder.SelectFileType,
},
{
Text: lang.L("selectFormat"),
Widget: selectEncoder.SelectFormat,
},
{
Text: lang.L("selectEncoder"),
Widget: selectEncoder.SelectEncoder,
},
}
formConvertor.form.Items = items
formConvertor.items = items
formConvertor.changeEncoder(selectEncoder.Encoder)
selectEncoder.ChangeEncoder = formConvertor.changeEncoder
formConvertor.form.OnSubmit = func() {
formConvertor.conversionMessage.Text = ""
if len(directoryForSavingButton.path) == 0 {
formConvertor.conversionMessage.Text = lang.L("errorSelectedFolderSave")
return
}
if len(selectEncoder.SelectFormat.Selected) == 0 {
formConvertor.conversionMessage.Text = lang.L("errorSelectedFormat")
return
}
if selectEncoder.Encoder == nil {
formConvertor.conversionMessage.Text = lang.L("errorSelectedEncoder")
return
}
fileForConversion.button.Disable()
directoryForSavingButton.button.Disable()
formConvertor.form.Disable()
fyne.Do(func() {
err := addToConversion(ConvertSetting{
DirectoryForSave: directoryForSavingButton.path,
OverwriteOutputFiles: isOverwriteOutputFiles,
Format: selectEncoder.SelectFormat.Selected,
Encoder: selectEncoder.Encoder,
})
if err != nil {
formConvertor.conversionMessage.Text = err.Error()
}
fileForConversion.button.Enable()
directoryForSavingButton.button.Enable()
formConvertor.form.Enable()
})
}
return formConvertor
}
func (f *formConvertor) getForm() fyne.CanvasObject {
return container.NewVBox(
f.form,
container.NewHScroll(f.conversionMessage),
)
}
type fileForConversion struct {
button *widget.Button
message *canvas.Text
file *ffmpeg.File
}
func (f *formConvertor) newFileForConversion() *fileForConversion {
message := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255})
fileForConversion := &fileForConversion{
message: message,
}
buttonTitle := lang.L("choose") + "\n" +
lang.L("or") + "\n" +
lang.L("dragAndDropFiles")
var locationURI fyne.ListableURI
fileForConversion.button = widget.NewButton(buttonTitle, func() {
f.window.NewFileOpen(func(r fyne.URIReadCloser, err error) {
fyne.Do(func() {
fileForConversion.message.Text = ""
fileForConversion.message.Refresh()
})
if err != nil {
fyne.Do(func() {
fileForConversion.message.Text = err.Error()
fileForConversion.message.Refresh()
})
return
}
if r == nil {
return
}
f.addFileForConversion(ffmpeg.File{
Path: r.URI().Path(),
Name: r.URI().Name(),
Ext: r.URI().Extension(),
})
listableURI := storage.NewFileURI(filepath.Dir(r.URI().Path()))
locationURI, _ = storage.ListerForURI(listableURI)
}, locationURI)
})
f.window.SetOnDropped(func(position fyne.Position, uris []fyne.URI) {
if len(uris) == 0 {
return
}
isError := false
for _, uri := range uris {
info, err := os.Stat(uri.Path())
if err != nil {
isError = true
continue
}
if info.IsDir() {
isError = true
continue
}
f.addFileForConversion(ffmpeg.File{
Path: uri.Path(),
Name: uri.Name(),
Ext: uri.Extension(),
})
listableURI := storage.NewFileURI(filepath.Dir(uri.Path()))
locationURI, _ = storage.ListerForURI(listableURI)
}
if isError {
fileForConversion.message.Text = lang.L("errorDragAndDropFile")
utils.SetStringErrorStyle(fileForConversion.message)
} else {
fyne.Do(func() {
fileForConversion.message.Text = ""
fileForConversion.message.Refresh()
})
}
})
return fileForConversion
}
type directoryForSaving struct {
button *widget.Button
message *canvas.Text
path string
}
func (f *formConvertor) newDirectoryForSaving(directoryForSavingPath string) *directoryForSaving {
directoryForSaving := &directoryForSaving{
path: "",
}
directoryForSaving.message = canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255})
directoryForSaving.message.TextSize = 16
directoryForSaving.message.TextStyle = fyne.TextStyle{Bold: true}
buttonTitle := lang.L("choose")
locationURI, err := utils.PathToListableURI(directoryForSavingPath)
if err == nil {
directoryForSaving.path = locationURI.Path()
directoryForSaving.message.Text = locationURI.Path()
utils.SetStringSuccessStyle(directoryForSaving.message)
}
directoryForSaving.button = widget.NewButton(buttonTitle, func() {
f.window.NewFolderOpen(func(r fyne.ListableURI, err error) {
if err != nil {
directoryForSaving.message.Text = err.Error()
utils.SetStringErrorStyle(directoryForSaving.message)
return
}
if r == nil {
return
}
directoryForSaving.path = r.Path()
directoryForSaving.message.Text = r.Path()
utils.SetStringSuccessStyle(directoryForSaving.message)
locationURI, err = storage.ListerForURI(r)
if err == nil {
f.directoryForSaving(locationURI.Path())
}
}, locationURI)
})
return directoryForSaving
}
type selectEncoder struct {
SelectFileType *widget.RadioGroup
SelectFormat *widget.Select
SelectEncoder *widget.Select
Encoder encoder2.EncoderContract
ChangeEncoder func(encoder encoder2.EncoderContract)
}
func (f *formConvertor) newSelectEncoder(formats encoder.ConvertorFormatsContract) *selectEncoder {
selectEncoder := &selectEncoder{}
encoderMap := map[int]encoder2.EncoderDataContract{}
selectEncoder.SelectEncoder = widget.NewSelect([]string{}, func(s string) {
if encoderMap[selectEncoder.SelectEncoder.SelectedIndex()] == nil {
return
}
selectEncoderData := encoderMap[selectEncoder.SelectEncoder.SelectedIndex()]
selectEncoder.Encoder = selectEncoderData.NewEncoder()
if selectEncoder.ChangeEncoder != nil {
selectEncoder.ChangeEncoder(selectEncoder.Encoder)
}
})
formatSelected := ""
selectEncoder.SelectFormat = widget.NewSelect([]string{}, func(s string) {
if formatSelected == s {
return
}
formatSelected = s
format, err := formats.GetFormat(s)
if err != nil {
return
}
var encoderOptions []string
encoderMap = map[int]encoder2.EncoderDataContract{}
for _, e := range format.GetEncoders() {
encoderMap[len(encoderMap)] = e
encoderOptions = append(encoderOptions, lang.L("encoder_"+e.GetTitle()))
}
selectEncoder.SelectEncoder.SetOptions(encoderOptions)
selectEncoder.SelectEncoder.SetSelectedIndex(0)
})
var fileTypeOptions []string
for _, fileType := range encoder2.GetListFileType() {
fileTypeOptions = append(fileTypeOptions, fileType.Name())
}
encoderGroupVideo := lang.L("encoderGroupVideo")
encoderGroupAudio := lang.L("encoderGroupAudio")
encoderGroupImage := lang.L("encoderGroupImage")
encoderGroup := map[string]string{
encoderGroupVideo: "video",
encoderGroupAudio: "audio",
encoderGroupImage: "image",
}
selectEncoder.SelectFileType = widget.NewRadioGroup([]string{encoderGroupVideo, encoderGroupAudio, encoderGroupImage}, func(s string) {
groupCode := encoderGroup[s]
var formatOptions []string
for _, f := range formats.GetFormats() {
if groupCode != f.GetFileType().Name() {
continue
}
formatOptions = append(formatOptions, f.GetTitle())
}
selectEncoder.SelectFormat.SetOptions(formatOptions)
if groupCode == encoder2.FileType(encoder2.Video).Name() {
selectEncoder.SelectFormat.SetSelected("mp4")
} else {
selectEncoder.SelectFormat.SetSelectedIndex(0)
}
})
selectEncoder.SelectFileType.Horizontal = true
selectEncoder.SelectFileType.Required = true
selectEncoder.SelectFileType.SetSelected(encoderGroupVideo)
return selectEncoder
}
func (f *formConvertor) changeEncoder(encoder encoder2.EncoderContract) {
var items []*widget.FormItem
if encoders.Views[encoder.GetName()] != nil {
items = encoders.Views[encoder.GetName()](encoder)
}
f.changeItems(items)
}
func (f *formConvertor) changeItems(items []*widget.FormItem) {
fyne.Do(func() {
f.form.Items = f.items
f.form.Refresh()
f.form.Items = append(f.form.Items, items...)
f.form.Refresh()
})
}

View File

@ -0,0 +1,15 @@
package encoders
import (
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view/convertor/encoders/h264_nvenc"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view/convertor/encoders/libx264"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/gui/view/convertor/encoders/libx265"
)
var Views = map[string]func(encoder encoder.EncoderContract) []*widget.FormItem{
"libx264": libx264.View,
"h264_nvenc": h264_nvenc.View,
"libx265": libx265.View,
}

View File

@ -0,0 +1,64 @@
package h264_nvenc
import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/h264_nvenc"
)
func View(encoder encoder.EncoderContract) []*widget.FormItem {
items := []*widget.FormItem{}
items = append(items, presetParameter(encoder)...)
return items
}
func presetParameter(encoder encoder.EncoderContract) []*widget.FormItem {
parameter, err := encoder.GetParameter("preset")
if err != nil {
return nil
}
presets := map[string]string{}
presetsForSelect := []string{}
presetDefault := ""
for _, name := range h264_nvenc.Presets {
title := name
presetsForSelect = append(presetsForSelect, name)
presets[title] = name
if name == parameter.Get() {
presetDefault = title
}
}
elementSelect := widget.NewSelect(presetsForSelect, func(s string) {
if presets[s] == "" {
return
}
parameter.Set(presets[s])
})
elementSelect.SetSelected(presetDefault)
elementSelect.Hide()
checkboxTitle := lang.L("parameterCheckbox")
elementCheckbox := widget.NewCheck(checkboxTitle, func(b bool) {
if b == true {
parameter.SetEnable()
elementSelect.Show()
return
}
parameter.SetDisable()
elementSelect.Hide()
})
return []*widget.FormItem{
{
Text: lang.L("formPreset"),
Widget: container.NewVBox(elementCheckbox, elementSelect),
},
}
}

View File

@ -0,0 +1,64 @@
package libx264
import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libx264"
)
func View(encoder encoder.EncoderContract) []*widget.FormItem {
items := []*widget.FormItem{}
items = append(items, presetParameter(encoder)...)
return items
}
func presetParameter(encoder encoder.EncoderContract) []*widget.FormItem {
parameter, err := encoder.GetParameter("preset")
if err != nil {
return nil
}
presets := map[string]string{}
presetsForSelect := []string{}
presetDefault := ""
for _, name := range libx264.Presets {
title := lang.L("preset_" + name)
presetsForSelect = append(presetsForSelect, title)
presets[title] = name
if name == parameter.Get() {
presetDefault = title
}
}
elementSelect := widget.NewSelect(presetsForSelect, func(s string) {
if presets[s] == "" {
return
}
parameter.Set(presets[s])
})
elementSelect.SetSelected(presetDefault)
elementSelect.Hide()
checkboxTitle := lang.L("parameterCheckbox")
elementCheckbox := widget.NewCheck(checkboxTitle, func(b bool) {
if b == true {
parameter.SetEnable()
elementSelect.Show()
return
}
parameter.SetDisable()
elementSelect.Hide()
})
return []*widget.FormItem{
{
Text: lang.L("formPreset"),
Widget: container.NewVBox(elementCheckbox, elementSelect),
},
}
}

View File

@ -0,0 +1,64 @@
package libx265
import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg/encoder/libx265"
)
func View(encoder encoder.EncoderContract) []*widget.FormItem {
items := []*widget.FormItem{}
items = append(items, presetParameter(encoder)...)
return items
}
func presetParameter(encoder encoder.EncoderContract) []*widget.FormItem {
parameter, err := encoder.GetParameter("preset")
if err != nil {
return nil
}
presets := map[string]string{}
presetsForSelect := []string{}
presetDefault := ""
for _, name := range libx265.Presets {
title := lang.L("preset_" + name)
presetsForSelect = append(presetsForSelect, title)
presets[title] = name
if name == parameter.Get() {
presetDefault = title
}
}
elementSelect := widget.NewSelect(presetsForSelect, func(s string) {
if presets[s] == "" {
return
}
parameter.Set(presets[s])
})
elementSelect.SetSelected(presetDefault)
elementSelect.Hide()
checkboxTitle := lang.L("parameterCheckbox")
elementCheckbox := widget.NewCheck(checkboxTitle, func(b bool) {
if b == true {
parameter.SetEnable()
elementSelect.Show()
return
}
parameter.SetDisable()
elementSelect.Hide()
})
return []*widget.FormItem{
{
Text: lang.L("formPreset"),
Widget: container.NewVBox(elementCheckbox, elementSelect),
},
}
}

View File

@ -0,0 +1,39 @@
package view
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting"
)
func StartWithError(err error, languages []setting.Lang, funcSelected func(lang setting.Lang)) fyne.CanvasObject {
messageHead := lang.L("error")
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) {
funcSelected(languages[id])
}
return container.NewBorder(
container.NewVBox(
widget.NewLabel(messageHead),
widget.NewLabel(err.Error()),
),
nil,
nil,
nil,
listView,
)
}

View File

@ -0,0 +1,98 @@
package view
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
func HelpFFplay() fyne.CanvasObject {
data := [][]string{
{
lang.L("helpFFplayKeys"),
lang.L("helpFFplayDescription"),
},
{
"Q, ESC",
lang.L("helpFFplayQuit"),
},
{
"F, " + lang.L("helpFFplayDoubleClickLeftMouseButton"),
lang.L("helpFFplayToggleFullScreen"),
},
{
"P, " + lang.L("helpFFplayKeySpace"),
lang.L("helpFFplayPause"),
},
{
"M",
lang.L("helpFFplayToggleMute"),
},
{
"9, /",
lang.L("helpFFplayDecreaseVolume"),
},
{
"0, *",
lang.L("helpFFplayIncreaseVolume"),
},
{
lang.L("helpFFplayKeyLeft"),
lang.L("helpFFplaySeekBackward10Seconds"),
},
{
lang.L("helpFFplayKeyRight"),
lang.L("helpFFplaySeekForward10Seconds"),
},
{
lang.L("helpFFplayKeyDown"),
lang.L("helpFFplaySeekBackward1Minute"),
},
{
lang.L("helpFFplayKeyUp"),
lang.L("helpFFplaySeekBForward1Minute"),
},
{
"Page Down",
lang.L("helpFFplaySeekBackward10Minutes"),
},
{
"Page Up",
lang.L("helpFFplaySeekBForward10Minutes"),
},
{
"S, " + lang.L("helpFFplayKeyHoldS"),
lang.L("helpFFplayActivateFrameStepMode"),
},
{
"W",
lang.L("helpFFplayCycleVideoFiltersOrShowModes"),
},
}
list := widget.NewTable(
func() (int, int) {
return len(data), len(data[0])
},
func() fyne.CanvasObject {
return widget.NewLabel("")
},
func(i widget.TableCellID, o fyne.CanvasObject) {
if i.Row == 0 {
o.(*widget.Label).TextStyle.Bold = true
o.(*widget.Label).SizeName = theme.SizeNameSubHeadingText
}
if i.Col == 0 {
o.(*widget.Label).TextStyle.Bold = true
}
o.(*widget.Label).SetText(data[i.Row][i.Col])
})
list.SetRowHeight(0, 40)
list.SetColumnWidth(0, 200)
list.SetColumnWidth(1, 585)
list.SetRowHeight(2, 55)
return container.NewScroll(list)
}

View File

@ -0,0 +1,92 @@
package view
import (
"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/setting"
"image/color"
)
type MainSettingForm struct {
Language setting.Lang
ThemeInfo setting.ThemeInfoContract
}
func MainSettings(
currentLang setting.Lang,
langList []setting.Lang,
themeInfo setting.ThemeInfoContract,
themeList map[string]setting.ThemeInfoContract,
save func(form *MainSettingForm) error,
cancel func(),
) fyne.CanvasObject {
errorMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255})
errorMessage.TextSize = 16
errorMessage.TextStyle = fyne.TextStyle{Bold: true}
viewSettingForm := &MainSettingForm{
Language: currentLang,
ThemeInfo: themeInfo,
}
var languageItems []string
langByTitle := map[string]setting.Lang{}
for _, language := range langList {
languageItems = append(languageItems, language.Title)
langByTitle[language.Title] = language
}
selectLanguage := widget.NewSelect(languageItems, func(s string) {
if lang, ok := langByTitle[s]; ok {
viewSettingForm.Language = lang
}
})
selectLanguage.Selected = currentLang.Title
var themeItems []string
themeByTitle := map[string]setting.ThemeInfoContract{}
for _, themeInfo := range themeList {
themeItems = append(themeItems, themeInfo.GetTitle())
themeByTitle[themeInfo.GetTitle()] = themeInfo
}
selectTheme := widget.NewSelect(themeItems, func(s string) {
if themeInfo, ok := themeByTitle[s]; ok {
viewSettingForm.ThemeInfo = themeInfo
}
})
selectTheme.Selected = themeInfo.GetTitle()
form := &widget.Form{
Items: []*widget.FormItem{
{
Text: lang.L("menuSettingsLanguage"),
Widget: selectLanguage,
},
{
Text: lang.L("menuSettingsTheme"),
Widget: selectTheme,
},
{
Widget: errorMessage,
},
},
SubmitText: lang.L("save"),
OnSubmit: func() {
err := save(viewSettingForm)
if err != nil {
errorMessage.Text = err.Error()
}
},
}
if cancel != nil {
form.OnCancel = cancel
form.CancelText = lang.L("cancel")
}
messageHead := lang.L("settings")
return widget.NewCard(messageHead, "", form)
}

View File

@ -0,0 +1,28 @@
package view
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/setting"
)
func StartWithoutSupportLang(languages []setting.Lang, funcSelected func(lang setting.Lang)) fyne.CanvasObject {
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) {
funcSelected(languages[id])
}
messageHead := lang.L("languageSelectionHead")
return widget.NewCard(messageHead, "", listView)
}

View File

@ -0,0 +1,172 @@
package window
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor"
)
type LayoutContract interface {
SetContent(content fyne.CanvasObject) fyne.CanvasObject
GetRContainer() RightMainContainerContract
}
type layout struct {
layoutContainer *fyne.Container
rContainer RightMainContainerContract
}
func NewLayout(progressBarService convertor.ProgressBarContract, itemsToConvert convertor.ItemsToConvertContract, queueLayout QueueLayoutContract) LayoutContract {
rContainer := newRightContainer(progressBarService.GetContainer(), itemsToConvert, queueLayout)
layoutContainer := container.NewAdaptiveGrid(2, widget.NewLabel(""), rContainer.GetCanvasObject())
return &layout{
layoutContainer: layoutContainer,
rContainer: rContainer,
}
}
func (l *layout) SetContent(content fyne.CanvasObject) fyne.CanvasObject {
l.layoutContainer.Objects[0] = content
return l.layoutContainer
}
func (l *layout) GetRContainer() RightMainContainerContract {
return l.rContainer
}
type RightMainContainerContract interface {
GetCanvasObject() fyne.CanvasObject
GetTabs() *container.AppTabs
SelectFileQueueTab()
SelectAddedFilesTab()
}
type rightMainContainer struct {
container fyne.CanvasObject
tabs *container.AppTabs
addedFilesTab *container.TabItem
fileQueueTab *container.TabItem
}
func newRightContainer(blockProgressbar *fyne.Container, itemsToConvert convertor.ItemsToConvertContract, queueLayout QueueLayoutContract) RightMainContainerContract {
addedFilesTab := container.NewTabItem(lang.L("addedFilesTitle"), addedFilesContainer(itemsToConvert))
fileQueueTab := container.NewTabItem(lang.L("fileQueueTitle"), fileQueueContainer(queueLayout))
tabs := container.NewAppTabs(
addedFilesTab,
fileQueueTab,
)
rightContainer := container.NewBorder(
container.NewVBox(
blockProgressbar,
widget.NewSeparator(),
),
nil,
nil,
nil,
tabs,
)
return &rightMainContainer{
container: rightContainer,
tabs: tabs,
addedFilesTab: addedFilesTab,
fileQueueTab: fileQueueTab,
}
}
func (r *rightMainContainer) GetCanvasObject() fyne.CanvasObject {
return r.container
}
func (r *rightMainContainer) GetTabs() *container.AppTabs {
return r.tabs
}
func (r *rightMainContainer) SelectFileQueueTab() {
fyne.Do(func() {
r.tabs.Select(r.fileQueueTab)
})
}
func (r *rightMainContainer) SelectAddedFilesTab() {
fyne.Do(func() {
r.tabs.Select(r.addedFilesTab)
})
}
func addedFilesContainer(itemsToConvert convertor.ItemsToConvertContract) *fyne.Container {
line := canvas.NewLine(theme.Color(theme.ColorNameFocus))
line.StrokeWidth = 5
checkboxAutoRemove := widget.NewCheck(
lang.L("autoClearAfterAddingToQueue"),
func(checked bool) {
itemsToConvert.SetIsAutoRemove(checked)
},
)
checkboxAutoRemove.SetChecked(itemsToConvert.GetIsAutoRemove())
buttonClear := widget.NewButton(
lang.L("clearAll"),
func() {
itemsToConvert.Clear()
},
)
buttonClear.Importance = widget.DangerImportance
return container.NewBorder(
container.NewVBox(
container.NewPadded(),
container.NewBorder(nil, nil, nil, buttonClear, container.NewHScroll(checkboxAutoRemove)),
container.NewPadded(),
line,
), nil, nil, nil,
container.NewVScroll(
container.NewBorder(
nil, nil, nil, container.NewPadded(),
container.NewVBox(
container.NewPadded(),
itemsToConvert.GetItemsContainer(),
),
),
),
)
}
func fileQueueContainer(queueLayout QueueLayoutContract) *fyne.Container {
title := widget.NewLabel(lang.L("queue"))
title.TextStyle.Bold = true
line := canvas.NewLine(theme.Color(theme.ColorNameFocus))
line.StrokeWidth = 5
queueLayout.GetQueueStatistics().GetWaiting().SetTitle(lang.L("waitingQueue"))
queueLayout.GetQueueStatistics().GetInProgress().SetTitle(lang.L("inProgressQueue"))
queueLayout.GetQueueStatistics().GetCompleted().SetTitle(lang.L("completedQueue"))
queueLayout.GetQueueStatistics().GetError().SetTitle(lang.L("errorQueue"))
queueLayout.GetQueueStatistics().GetTotal().SetTitle(lang.L("total"))
return container.NewBorder(
container.NewVBox(
container.NewPadded(),
container.NewHBox(title, queueLayout.GetQueueStatistics().GetCompleted().GetCheckbox(), queueLayout.GetQueueStatistics().GetError().GetCheckbox()),
container.NewHBox(queueLayout.GetQueueStatistics().GetInProgress().GetCheckbox(), queueLayout.GetQueueStatistics().GetWaiting().GetCheckbox(), queueLayout.GetQueueStatistics().GetTotal().GetCheckbox()),
container.NewPadded(),
line,
), nil, nil, nil,
container.NewVScroll(
container.NewBorder(
nil, nil, nil, container.NewPadded(),
container.NewVBox(
container.NewPadded(),
queueLayout.GetItemsContainer(),
),
),
),
)
}

101
internal/gui/window/main.go Normal file
View File

@ -0,0 +1,101 @@
package window
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/dialog"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/utils"
)
type WindowContract interface {
SetContent(content fyne.CanvasObject)
SetMainMenu(menu *fyne.MainMenu)
Show()
InitLayout()
NewFileOpen(callback func(fyne.URIReadCloser, error), location fyne.ListableURI) *dialog.FileDialog
NewFolderOpen(callback func(fyne.ListableURI, error), location fyne.ListableURI) *dialog.FileDialog
SetOnDropped(callback func(position fyne.Position, uris []fyne.URI))
GetLayout() LayoutContract
}
type mainWindow struct {
fyneWindow fyne.Window
layout LayoutContract
itemsToConvert convertor.ItemsToConvertContract
progressBarService convertor.ProgressBarContract
queueLayout QueueLayoutContract
}
func NewMainWindow(
fyneWindow fyne.Window,
progressBarService convertor.ProgressBarContract,
itemsToConvert convertor.ItemsToConvertContract,
queueLayout QueueLayoutContract,
) WindowContract {
fyneWindow.Resize(fyne.Size{Width: 1039, Height: 599})
fyneWindow.CenterOnScreen()
return &mainWindow{
fyneWindow: fyneWindow,
progressBarService: progressBarService,
itemsToConvert: itemsToConvert,
queueLayout: queueLayout,
}
}
func (w *mainWindow) SetMainMenu(menu *fyne.MainMenu) {
fyne.Do(func() {
w.fyneWindow.SetMainMenu(menu)
})
}
func (w *mainWindow) InitLayout() {
fyne.Do(func() {
w.layout = NewLayout(w.progressBarService, w.itemsToConvert, w.queueLayout)
})
}
func (w *mainWindow) GetLayout() LayoutContract {
return w.layout
}
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) NewFolderOpen(callback func(fyne.ListableURI, error), location fyne.ListableURI) *dialog.FileDialog {
fileDialog := dialog.NewFolderOpen(callback, w.fyneWindow)
utils.FileDialogResize(fileDialog, w.fyneWindow)
fileDialog.Show()
if location != nil {
fileDialog.SetLocation(location)
}
return fileDialog
}
func (w *mainWindow) SetContent(content fyne.CanvasObject) {
fyne.Do(func() {
if w.layout == nil {
w.fyneWindow.SetContent(content)
return
}
w.fyneWindow.SetContent(w.layout.SetContent(content))
})
}
func (w *mainWindow) Show() {
w.fyneWindow.Show()
}
func (w *mainWindow) SetOnDropped(callback func(position fyne.Position, uris []fyne.URI)) {
fyne.Do(func() {
w.fyneWindow.SetOnDropped(callback)
})
}

View File

@ -0,0 +1,459 @@
package window
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application/convertor"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/ffmpeg"
"image/color"
"strconv"
"strings"
)
type QueueLayoutContract interface {
GetItemsContainer() *fyne.Container
GetQueueStatistics() QueueStatisticsAllContract
AddQueue(key int, queue *convertor.Queue)
ChangeQueue(key int, queue *convertor.Queue)
RemoveQueue(key int, status convertor.StatusContract)
}
type queueLayout struct {
itemsContainer *fyne.Container
queueAllStatistics QueueStatisticsAllContract
items map[int]queueLayoutItem
ffmpegService ffmpeg.UtilitiesContract
}
func NewQueueLayout(ffmpegService ffmpeg.UtilitiesContract) QueueLayoutContract {
items := map[int]queueLayoutItem{}
return &queueLayout{
itemsContainer: container.NewVBox(),
queueAllStatistics: newQueueAllStatistics(&items),
items: items,
ffmpegService: ffmpegService,
}
}
func (l *queueLayout) GetItemsContainer() *fyne.Container {
return l.itemsContainer
}
func (l *queueLayout) GetQueueStatistics() QueueStatisticsAllContract {
return l.queueAllStatistics
}
func (l *queueLayout) AddQueue(queueID int, queue *convertor.Queue) {
statusMessage := canvas.NewText(l.getStatusTitle(queue.Status), theme.Color(theme.ColorNamePrimary))
messageError := canvas.NewText("", theme.Color(theme.ColorNameError))
buttonPlay := widget.NewButtonWithIcon("", theme.Icon(theme.IconNameMediaPlay), func() {
})
buttonPlay.Hide()
blockMessageError := container.NewHScroll(messageError)
blockMessageError.Hide()
content := container.NewVBox(
container.NewHScroll(widget.NewLabel(queue.Setting.FileInput.Name)),
container.NewHBox(
buttonPlay,
statusMessage,
),
blockMessageError,
container.NewPadded(),
canvas.NewLine(theme.Color(theme.ColorNameFocus)),
container.NewPadded(),
)
l.addQueueStatistics()
if l.GetQueueStatistics().IsChecked(queue.Status) == false {
content.Hide()
}
l.items[queueID] = queueLayoutItem{
CanvasObject: content,
StatusMessage: statusMessage,
BlockMessageError: blockMessageError,
MessageError: messageError,
buttonPlay: buttonPlay,
status: &queue.Status,
}
l.itemsContainer.Add(content)
}
func (l *queueLayout) ChangeQueue(queueID int, queue *convertor.Queue) {
if item, ok := l.items[queueID]; ok {
statusColor := l.getStatusColor(queue.Status)
fyne.Do(func() {
item.StatusMessage.Text = l.getStatusTitle(queue.Status)
item.StatusMessage.Color = statusColor
item.StatusMessage.Refresh()
})
if queue.Error != nil {
fyne.Do(func() {
item.MessageError.Text = queue.Error.Error()
item.MessageError.Color = statusColor
item.BlockMessageError.Show()
item.MessageError.Refresh()
})
}
if queue.Status == convertor.StatusType(convertor.Completed) {
item.buttonPlay.Show()
item.buttonPlay.OnTapped = func() {
item.buttonPlay.Disable()
go func() {
ffplay, err := l.ffmpegService.GetFFplay()
if err == nil {
_ = ffplay.Play(&queue.Setting.FileOut)
}
fyne.Do(func() {
item.buttonPlay.Enable()
})
}()
}
}
if l.GetQueueStatistics().IsChecked(queue.Status) == false && item.CanvasObject.Visible() == true {
item.CanvasObject.Hide()
}
if l.GetQueueStatistics().IsChecked(queue.Status) == true && item.CanvasObject.Visible() == false {
item.CanvasObject.Show()
}
l.changeQueueStatistics(queue.Status)
}
}
func (l *queueLayout) RemoveQueue(queueID int, status convertor.StatusContract) {
if item, ok := l.items[queueID]; ok {
l.itemsContainer.Remove(item.CanvasObject)
l.removeQueueStatistics(status)
l.items[queueID] = queueLayoutItem{}
}
}
func (l *queueLayout) addQueueStatistics() {
l.GetQueueStatistics().GetWaiting().Add()
l.GetQueueStatistics().GetTotal().Add()
}
func (l *queueLayout) changeQueueStatistics(status convertor.StatusContract) {
if status == convertor.StatusType(convertor.InProgress) {
l.GetQueueStatistics().GetWaiting().Remove()
l.GetQueueStatistics().GetInProgress().Add()
return
}
if status == convertor.StatusType(convertor.Completed) {
l.GetQueueStatistics().GetInProgress().Remove()
l.GetQueueStatistics().GetCompleted().Add()
return
}
if status == convertor.StatusType(convertor.Error) {
l.GetQueueStatistics().GetInProgress().Remove()
l.GetQueueStatistics().GetError().Add()
return
}
}
func (l *queueLayout) removeQueueStatistics(status convertor.StatusContract) {
l.GetQueueStatistics().GetTotal().Remove()
if status == convertor.StatusType(convertor.Completed) {
l.GetQueueStatistics().GetCompleted().Remove()
return
}
if status == convertor.StatusType(convertor.Error) {
l.GetQueueStatistics().GetError().Remove()
return
}
if status == convertor.StatusType(convertor.InProgress) {
l.GetQueueStatistics().GetInProgress().Remove()
return
}
if status == convertor.StatusType(convertor.Waiting) {
l.GetQueueStatistics().GetWaiting().Remove()
return
}
}
func (l *queueLayout) getStatusTitle(status convertor.StatusContract) string {
return lang.L(status.Name() + "Queue")
}
func (l *queueLayout) getStatusColor(status convertor.StatusContract) color.Color {
if status == convertor.StatusType(convertor.Error) {
return theme.Color(theme.ColorNameError)
}
if status == convertor.StatusType(convertor.Completed) {
return color.RGBA{R: 49, G: 127, B: 114, A: 255}
}
return theme.Color(theme.ColorNamePrimary)
}
type QueueStatisticsAllContract interface {
GetWaiting() QueueStatisticsContract
GetInProgress() QueueStatisticsContract
GetCompleted() QueueStatisticsContract
GetError() QueueStatisticsContract
GetTotal() QueueStatisticsContract
IsChecked(status convertor.StatusContract) bool
}
type queueAllStatistics struct {
waiting QueueStatisticsContract
inProgress QueueStatisticsContract
completed QueueStatisticsContract
error QueueStatisticsContract
total QueueStatisticsContract
}
func newQueueAllStatistics(queueItems *map[int]queueLayoutItem) QueueStatisticsAllContract {
checkboxWaiting := newQueueStatistics()
checkboxInProgress := newQueueStatistics()
checkboxCompleted := newQueueStatistics()
checkboxError := newQueueStatistics()
CheckboxTotal := newQueueStatistics()
queueAllStatistics := &queueAllStatistics{
waiting: checkboxWaiting,
inProgress: checkboxInProgress,
completed: checkboxCompleted,
error: checkboxError,
total: CheckboxTotal,
}
CheckboxTotal.GetCheckbox().OnChanged = func(b bool) {
if b == true {
queueAllStatistics.allCheckboxChecked()
} else {
queueAllStatistics.allUnCheckboxChecked()
}
queueAllStatistics.redrawingQueueItems(queueItems)
}
checkboxWaiting.GetCheckbox().OnChanged = func(b bool) {
if b == true {
queueAllStatistics.checkboxChecked()
} else {
queueAllStatistics.unCheckboxChecked()
}
queueAllStatistics.redrawingQueueItems(queueItems)
}
checkboxInProgress.GetCheckbox().OnChanged = func(b bool) {
if b == true {
queueAllStatistics.checkboxChecked()
} else {
queueAllStatistics.unCheckboxChecked()
}
queueAllStatistics.redrawingQueueItems(queueItems)
}
checkboxCompleted.GetCheckbox().OnChanged = func(b bool) {
if b == true {
queueAllStatistics.checkboxChecked()
} else {
queueAllStatistics.unCheckboxChecked()
}
queueAllStatistics.redrawingQueueItems(queueItems)
}
checkboxError.GetCheckbox().OnChanged = func(b bool) {
if b == true {
queueAllStatistics.checkboxChecked()
} else {
queueAllStatistics.unCheckboxChecked()
}
queueAllStatistics.redrawingQueueItems(queueItems)
}
return queueAllStatistics
}
func (s *queueAllStatistics) GetWaiting() QueueStatisticsContract {
return s.waiting
}
func (s *queueAllStatistics) GetInProgress() QueueStatisticsContract {
return s.inProgress
}
func (s *queueAllStatistics) GetCompleted() QueueStatisticsContract {
return s.completed
}
func (s *queueAllStatistics) GetError() QueueStatisticsContract {
return s.error
}
func (s *queueAllStatistics) GetTotal() QueueStatisticsContract {
return s.total
}
func (s *queueAllStatistics) IsChecked(status convertor.StatusContract) bool {
if status == convertor.StatusType(convertor.InProgress) {
return s.inProgress.GetCheckbox().Checked
}
if status == convertor.StatusType(convertor.Completed) {
return s.completed.GetCheckbox().Checked
}
if status == convertor.StatusType(convertor.Error) {
return s.error.GetCheckbox().Checked
}
if status == convertor.StatusType(convertor.Waiting) {
return s.waiting.GetCheckbox().Checked
}
return true
}
func (s *queueAllStatistics) redrawingQueueItems(queueItems *map[int]queueLayoutItem) {
for _, item := range *queueItems {
if s.IsChecked(*item.status) == true && item.CanvasObject.Visible() == false {
item.CanvasObject.Show()
continue
}
if s.IsChecked(*item.status) == false && item.CanvasObject.Visible() == true {
item.CanvasObject.Hide()
}
}
}
func (s *queueAllStatistics) checkboxChecked() {
if s.total.GetCheckbox().Checked == true {
return
}
if s.waiting.GetCheckbox().Checked == false {
return
}
if s.inProgress.GetCheckbox().Checked == false {
return
}
if s.completed.GetCheckbox().Checked == false {
return
}
if s.error.GetCheckbox().Checked == false {
return
}
s.total.GetCheckbox().Checked = true
s.total.GetCheckbox().Refresh()
}
func (s *queueAllStatistics) unCheckboxChecked() {
if s.total.GetCheckbox().Checked == false {
return
}
s.total.GetCheckbox().Checked = false
s.total.GetCheckbox().Refresh()
}
func (s *queueAllStatistics) allCheckboxChecked() {
s.waiting.GetCheckbox().Checked = true
s.waiting.GetCheckbox().Refresh()
s.inProgress.GetCheckbox().Checked = true
s.inProgress.GetCheckbox().Refresh()
s.completed.GetCheckbox().Checked = true
s.completed.GetCheckbox().Refresh()
s.error.GetCheckbox().Checked = true
s.error.GetCheckbox().Refresh()
}
func (s *queueAllStatistics) allUnCheckboxChecked() {
s.waiting.GetCheckbox().Checked = false
s.waiting.GetCheckbox().Refresh()
s.inProgress.GetCheckbox().Checked = false
s.inProgress.GetCheckbox().Refresh()
s.completed.GetCheckbox().Checked = false
s.completed.GetCheckbox().Refresh()
s.error.GetCheckbox().Checked = false
s.error.GetCheckbox().Refresh()
}
type QueueStatisticsContract interface {
SetTitle(title string)
GetCheckbox() *widget.Check
Add()
Remove()
}
type queueStatistics struct {
checkbox *widget.Check
title string
count int64
}
func newQueueStatistics() QueueStatisticsContract {
checkbox := widget.NewCheck(": 0", nil)
checkbox.Checked = true
return &queueStatistics{
checkbox: checkbox,
title: "",
count: 0,
}
}
func (s *queueStatistics) SetTitle(title string) {
s.title = strings.ToLower(title)
s.checkbox.Text = title + ": " + strconv.FormatInt(s.count, 10)
}
func (s *queueStatistics) GetCheckbox() *widget.Check {
return s.checkbox
}
func (s *queueStatistics) Add() {
s.count += 1
s.formatText()
}
func (s *queueStatistics) Remove() {
if s.count == 0 {
return
}
s.count -= 1
s.formatText()
}
func (s *queueStatistics) formatText() {
fyne.Do(func() {
s.checkbox.Text = s.title + ": " + strconv.FormatInt(s.count, 10)
s.checkbox.Refresh()
})
}
type queueLayoutItem struct {
CanvasObject fyne.CanvasObject
BlockMessageError *container.Scroll
StatusMessage *canvas.Text
MessageError *canvas.Text
buttonPlay *widget.Button
status *convertor.StatusContract
}

View File

@ -0,0 +1,13 @@
package resources
import (
_ "embed"
"fyne.io/fyne/v2"
)
//go:embed icons/logo.png
var iconAppLogo []byte
func IconAppLogoResource() *fyne.StaticResource {
return fyne.NewStaticResource("icon.png", iconAppLogo)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,12 @@
package resources
import (
"embed"
)
//go:embed translations
var translations embed.FS
func GetTranslations() embed.FS {
return translations
}

View File

@ -0,0 +1,143 @@
{
"AlsoUsedProgram": "The program also uses:",
"about": "About",
"aboutText": "A simple interface for the FFmpeg console utility. \nBut I am not the author of the FFmpeg utility itself.",
"addedFilesTitle": "Added files",
"autoClearAfterAddingToQueue": "Auto-clear after adding to queue",
"buttonDownloadFFmpeg": "Download FFmpeg automatically",
"buttonForSelectedDirTitle": "Save to folder:",
"cancel": "Cancel",
"changeFFPath": "FFmpeg, FFprobe and FFplay",
"changeLanguage": "Change language",
"checkboxOverwriteOutputFilesTitle": "Allow file to be overwritten",
"choose": "choose",
"clearAll": "Clear List",
"completedQueue": "Completed",
"converterVideoFilesSubmitTitle": "Convert",
"converterVideoFilesTitle": "Video, audio and picture converter",
"download": "Download",
"downloadFFmpegFromSite": "Will be downloaded from the site:",
"downloadRun": "Downloading...",
"dragAndDropFiles": "drag and drop files",
"encoderGroupAudio": "Audio",
"encoderGroupImage": "Images",
"encoderGroupVideo": "Video",
"encoder_apng": "APNG image",
"encoder_bmp": "BMP image",
"encoder_flv": "FLV",
"encoder_gif": "GIF image",
"encoder_h264_nvenc": "H.264 with NVIDIA support",
"encoder_libmp3lame": "libmp3lame MP3 (MPEG audio layer 3)",
"encoder_libshine": "libshine MP3 (MPEG audio layer 3)",
"encoder_libtwolame": "libtwolame MP2 (MPEG audio layer 2)",
"encoder_libvpx": "libvpx VP8 (codec vp8)",
"encoder_libvpx-vp9": "libvpx VP9 (codec vp9)",
"encoder_libwebp": "libwebp WebP image",
"encoder_libwebp_anim": "libwebp_anim WebP image",
"encoder_libx264": "H.264 libx264",
"encoder_libx265": "H.265 libx265",
"encoder_libxvid": "libxvidcore MPEG-4 part 2",
"encoder_mjpeg": "MJPEG (Motion JPEG)",
"encoder_mp2": "MP2 (MPEG audio layer 2)",
"encoder_mp2fixed": "MP2 fixed point (MPEG audio layer 2)",
"encoder_mpeg1video": "MPEG-1",
"encoder_mpeg2video": "MPEG-2",
"encoder_mpeg4": "MPEG-4 part 2",
"encoder_msmpeg4": "MPEG-4 part 2 Microsoft variant version 3",
"encoder_msmpeg4v2": "MPEG-4 part 2 Microsoft variant version 2",
"encoder_msvideo1": "Microsoft Video-1",
"encoder_png": "PNG image",
"encoder_qtrle": "QuickTime Animation (RLE) video",
"encoder_sgi": "SGI image",
"encoder_tiff": "TIFF image",
"encoder_wmav1": "Windows Media Audio 1",
"encoder_wmav2": "Windows Media Audio 2",
"encoder_wmv1": "Windows Media Video 7",
"encoder_wmv2": "Windows Media Video 8",
"encoder_xbm": "XBM (X BitMap) image",
"error": "An error has occurred!",
"errorConverter": "Couldn't convert video",
"errorDragAndDropFile": "Not all files were added",
"errorFFmpeg": "this is not FFmpeg",
"errorFFmpegVersion": "Could not determine FFmpeg version",
"errorFFplay": "this is not FFplay",
"errorFFplayVersion": "Could not determine FFplay version",
"errorFFprobe": "this is not FFprobe",
"errorFFprobeVersion": "Failed to determine FFprobe version",
"errorNoFilesAddedForConversion": "There are no files to convert",
"errorQueue": "Error",
"errorSelectedEncoder": "Converter not selected",
"errorSelectedFolderSave": "No save folder selected!",
"errorSelectedFormat": "File extension not selected",
"exit": "Exit",
"ffmpegLGPL": "This software uses libraries from the **FFmpeg** project under the **[LGPLv2.1](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**.",
"ffmpegTrademark": "**FFmpeg** is a trademark of **[Fabrice Bellard](http://bellard.org/)**, originator of the **[FFmpeg](https://ffmpeg.org/about.html)** project.",
"fileForConversionTitle": "File:",
"fileQueueTitle": "Queue",
"formPreset": "Preset",
"gratitude": "Gratitude",
"gratitudeText": "I sincerely thank you for your invaluable\n\r and timely assistance:",
"help": "Help",
"helpFFplay": "FFplay Player Keys",
"helpFFplayActivateFrameStepMode": "Activate frame-by-frame mode.",
"helpFFplayCycleVideoFiltersOrShowModes": "A cycle of video filters or display modes.",
"helpFFplayDecreaseVolume": "Decrease the volume.",
"helpFFplayDescription": "Description",
"helpFFplayDoubleClickLeftMouseButton": "double click\nleft mouse button",
"helpFFplayIncreaseVolume": "Increase the volume.",
"helpFFplayKeyDown": "down",
"helpFFplayKeyHoldS": "hold S",
"helpFFplayKeyLeft": "left",
"helpFFplayKeyRight": "right",
"helpFFplayKeySpace": "SPACE",
"helpFFplayKeyUp": "up",
"helpFFplayKeys": "Keys",
"helpFFplayPause": "Pause or continue playing.",
"helpFFplayQuit": "Close the player.",
"helpFFplaySeekBForward10Minutes": "Fast forward 10 minutes.",
"helpFFplaySeekBForward1Minute": "Fast forward 1 minute.",
"helpFFplaySeekBackward10Minutes": "Rewind 10 minutes.",
"helpFFplaySeekBackward10Seconds": "Rewind 10 seconds.",
"helpFFplaySeekBackward1Minute": "Rewind 1 minute.",
"helpFFplaySeekForward10Seconds": "Fast forward 10 seconds.",
"helpFFplayToggleFullScreen": "Switch to full screen or exit full screen.",
"helpFFplayToggleMute": "Mute or unmute.",
"inProgressQueue": "In Progress",
"languageSelectionFormHead": "Switch language",
"languageSelectionHead": "Choose language",
"licenseLink": "License information",
"licenseLinkOther": "Licenses from other products used in the program",
"menuSettingsLanguage": "Language",
"menuSettingsTheme": "Theme",
"or": "or",
"parameterCheckbox": "Enable option",
"pathToFfmpeg": "Path to FFmpeg:",
"pathToFfplay": "Path to FFplay:",
"pathToFfprobe": "Path to FFprobe:",
"preset_fast": "fast (slower than \"faster\", but the file will weigh less)",
"preset_faster": "faster (slower than \"veryfast\", but the file will weigh less)",
"preset_medium": "medium (slower than \"fast\", but the file will weigh less)",
"preset_placebo": "placebo (not recommended)",
"preset_slow": "slow (slower than \"medium\", but the file will weigh less)",
"preset_slower": "slower (slower than \"slow\", but the file will weigh less)",
"preset_superfast": "superfast (slower than \"ultrafast\", but the file will weigh less)",
"preset_ultrafast": "ultrafast (fast, but the file will weigh a lot)",
"preset_veryfast": "veryfast (slower than \"superfast\", but the file will weigh less)",
"preset_veryslow": "veryslow (slower than \"slower\", but the file will weigh less)",
"programmLink": "Project website",
"programmVersion": "**Program version:** {{.Version}}",
"queue": "Queue",
"save": "Save",
"selectEncoder": "Encoder:",
"selectFFPathTitle": "Specify the path to FFmpeg and FFprobe",
"selectFormat": "File extension:",
"settings": "Settings",
"testFF": "Checking FFmpeg for serviceability...",
"themesNameDark": "Dark",
"themesNameDefault": "Default",
"themesNameLight": "Light",
"titleDownloadLink": "You can download it from here",
"total": "Total",
"unzipRun": "Unpacked...",
"waitingQueue": "Waiting"
}

View File

@ -0,0 +1,143 @@
{
"AlsoUsedProgram": "Бағдарлама сонымен қатар пайдаланады:",
"about": "Бағдарлама туралы",
"aboutText": "FFmpeg консоль утилитасы үшін қарапайым интерфейс. \nБірақ мен FFmpeg утилитасының авторы емеспін.",
"addedFilesTitle": "Қосылған файлдар",
"autoClearAfterAddingToQueue": "Кезекке қосқаннан кейін тазалаңыз",
"buttonDownloadFFmpeg": "FFmpeg автоматты түрде жүктеп алыңыз",
"buttonForSelectedDirTitle": "Қалтаға сақтаңыз:",
"cancel": "Болдырмау",
"changeFFPath": "FFmpeg, FFprobe және FFplay",
"changeLanguage": "Тілді өзгерту",
"checkboxOverwriteOutputFilesTitle": "Файлды қайта жазуға рұқсат беріңіз",
"choose": "таңдау",
"clearAll": "Тізімді өшіру",
"completedQueue": "Дайын",
"converterVideoFilesSubmitTitle": "Файлды түрлендіру",
"converterVideoFilesTitle": "Бейне, аудио және суретті түрлендіргіш",
"download": "Жүктеп алу",
"downloadFFmpegFromSite": "Сайттан жүктеледі:",
"downloadRun": "Жүктеп алынуда...",
"dragAndDropFiles": "файлдарды сүйреп апарыңыз",
"encoderGroupAudio": "Аудио",
"encoderGroupImage": "Суреттер",
"encoderGroupVideo": "Бейне",
"encoder_apng": "APNG image",
"encoder_bmp": "BMP image",
"encoder_flv": "FLV",
"encoder_gif": "GIF image",
"encoder_h264_nvenc": "NVIDIA қолдауымен H.264",
"encoder_libmp3lame": "libmp3lame MP3 (MPEG audio layer 3)",
"encoder_libshine": "libshine MP3 (MPEG audio layer 3)",
"encoder_libtwolame": "libtwolame MP2 (MPEG audio layer 2)",
"encoder_libvpx": "libvpx VP8 (codec vp8)",
"encoder_libvpx-vp9": "libvpx VP9 (codec vp9)",
"encoder_libwebp": "libwebp WebP image",
"encoder_libwebp_anim": "libwebp_anim WebP image",
"encoder_libx264": "H.264 libx264",
"encoder_libx265": "H.265 libx265",
"encoder_libxvid": "libxvidcore MPEG-4 part 2",
"encoder_mjpeg": "MJPEG (Motion JPEG)",
"encoder_mp2": "MP2 (MPEG audio layer 2)",
"encoder_mp2fixed": "MP2 fixed point (MPEG audio layer 2)",
"encoder_mpeg1video": "MPEG-1",
"encoder_mpeg2video": "MPEG-2",
"encoder_mpeg4": "MPEG-4 part 2",
"encoder_msmpeg4": "MPEG-4 part 2 Microsoft variant version 3",
"encoder_msmpeg4v2": "MPEG-4 part 2 Microsoft variant version 2",
"encoder_msvideo1": "Microsoft Video-1",
"encoder_png": "PNG image",
"encoder_qtrle": "QuickTime Animation (RLE) video",
"encoder_sgi": "SGI image",
"encoder_tiff": "TIFF image",
"encoder_wmav1": "Windows Media Audio 1",
"encoder_wmav2": "Windows Media Audio 2",
"encoder_wmv1": "Windows Media Video 7",
"encoder_wmv2": "Windows Media Video 8",
"encoder_xbm": "XBM (X BitMap) image",
"error": "Қате орын алды!",
"errorConverter": "Бейнені түрлендіру мүмкін болмады",
"errorDragAndDropFile": "Барлық файлдар қосылмаған",
"errorFFmpeg": "бұл FFmpeg емес",
"errorFFmpegVersion": "FFmpeg нұсқасын анықтау мүмкін болмады",
"errorFFplay": "бұл FFplay емес",
"errorFFplayVersion": "FFplay нұсқасын анықтау мүмкін болмады",
"errorFFprobe": "бұл FFprobe емес",
"errorFFprobeVersion": "FFprobe нұсқасын анықтау мүмкін болмады",
"errorNoFilesAddedForConversion": "Түрлендіруге арналған файлдар жоқ",
"errorQueue": "Қате",
"errorSelectedEncoder": "Түрлендіргіш таңдалмаған",
"errorSelectedFolderSave": "Сақтау қалтасы таңдалмаған!",
"errorSelectedFormat": "Файл кеңейтімі таңдалмаған",
"exit": "Шығу",
"ffmpegLGPL": "Бұл бағдарламалық құрал **[LGPLv2.1](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)** астында **FFmpeg** жобасының кітапханаларын пайдаланады.",
"ffmpegTrademark": "FFmpeg — **[FFmpeg](https://ffmpeg.org/about.html)** жобасын жасаушы **[Fabrice Bellard](http://bellard.org/)** сауда белгісі.",
"fileForConversionTitle": "Файл:",
"fileQueueTitle": "Кезек",
"formPreset": "Алдын ала орнатылған",
"gratitude": "Алғыс",
"gratitudeText": "Сізге баға жетпес және уақтылы көмектескеніңіз\n\r үшін шын жүректен алғыс айтамын:",
"help": "Анықтама",
"helpFFplay": "FFplay ойнатқышының пернелері",
"helpFFplayActivateFrameStepMode": "Уақыт аралығын іске қосыңыз.",
"helpFFplayCycleVideoFiltersOrShowModes": "Бейне сүзгілерінің немесе дисплей режимдерінің циклі.",
"helpFFplayDecreaseVolume": "Дыбыс деңгейін төмендетіңіз.",
"helpFFplayDescription": "Сипаттама",
"helpFFplayDoubleClickLeftMouseButton": "тінтуірдің сол жақ\nбатырмасын екі рет басу",
"helpFFplayIncreaseVolume": "Дыбыс деңгейін арттыру.",
"helpFFplayKeyDown": "төмен",
"helpFFplayKeyHoldS": "ұстау S",
"helpFFplayKeyLeft": "сол",
"helpFFplayKeyRight": "құқық",
"helpFFplayKeySpace": "SPACE (пробел)",
"helpFFplayKeyUp": "жоғары",
"helpFFplayKeys": "Кілттер",
"helpFFplayPause": "Кідіртіңіз немесе жоғалтуды жалғастырыңыз.",
"helpFFplayQuit": "Ойнатқышты жабыңыз.",
"helpFFplaySeekBForward10Minutes": "10 минутқа алға айналдырыңыз.",
"helpFFplaySeekBForward1Minute": "1 минутқа алға айналдырыңыз.",
"helpFFplaySeekBackward10Minutes": "10 минутқа артқа айналдырыңыз.",
"helpFFplaySeekBackward10Seconds": "10 секундқа артқа айналдырыңыз.",
"helpFFplaySeekBackward1Minute": "1 минутқа артқа айналдырыңыз.",
"helpFFplaySeekForward10Seconds": "10 секунд алға айналдырыңыз.",
"helpFFplayToggleFullScreen": "Толық экранға ауысу немесе толық экраннан шығу.",
"helpFFplayToggleMute": "Дыбысты өшіріңіз немесе дыбысты қосыңыз.",
"inProgressQueue": "Орындалуда",
"languageSelectionFormHead": "Тілді ауыстыру",
"languageSelectionHead": "Тілді таңдаңыз",
"licenseLink": "Лицензия туралы ақпарат",
"licenseLinkOther": "Бағдарламада пайдаланылатын басқа өнімдердің лицензиялары",
"menuSettingsLanguage": "Тіл",
"menuSettingsTheme": "Тақырып",
"or": "немесе",
"parameterCheckbox": "Опцияны қосу",
"pathToFfmpeg": "FFmpeg жол:",
"pathToFfplay": "FFplay жол:",
"pathToFfprobe": "FFprobe жол:",
"preset_fast": "fast («faster» қарағанда баяуырақ, бірақ файлдың салмағы аз болады)",
"preset_faster": "faster («veryfast» қарағанда баяуырақ, бірақ файлдың салмағы аз болады)",
"preset_medium": "medium («fast» қарағанда баяуырақ, бірақ файлдың салмағы аз болады)",
"preset_placebo": "placebo (ұсынылмайды)",
"preset_slow": "slow («medium» қарағанда баяуырақ, бірақ файлдың салмағы аз болады)",
"preset_slower": "slower («slow» қарағанда баяуырақ, бірақ файлдың салмағы аз болады)",
"preset_superfast": "superfast («ultrafast» қарағанда баяуырақ, бірақ файлдың салмағы аз болады)",
"preset_ultrafast": "ultrafast (жылдам, бірақ файлдың салмағы көп болады)",
"preset_veryfast": "veryfast («superfast» қарағанда баяуырақ, бірақ файлдың салмағы аз болады)",
"preset_veryslow": "veryslow («slower» қарағанда баяуырақ, бірақ файлдың салмағы аз болады)",
"programmLink": "Жобаның веб-сайты",
"programmVersion": "**Бағдарлама нұсқасы:** {{.Version}}",
"queue": "Кезек",
"save": "Сақтау",
"selectEncoder": "Кодировщик:",
"selectFFPathTitle": "FFmpeg және FFprobe жолын көрсетіңіз",
"selectFormat": "Файл кеңейтімі:",
"settings": "Параметрлер",
"testFF": "FFmpeg функционалдығы тексерілуде...",
"themesNameDark": "Қараңғы тақырып",
"themesNameDefault": "Әдепкі бойынша",
"themesNameLight": "Жеңіл тақырып",
"titleDownloadLink": "Сіз оны осы жерден жүктей аласыз",
"total": "Барлығы",
"unzipRun": "Орамнан шығарылуда...",
"waitingQueue": "Күту"
}

View File

@ -0,0 +1,143 @@
{
"AlsoUsedProgram": "Также в программе используется:",
"about": "О программе",
"aboutText": "Простенький интерфейс для консольной утилиты FFmpeg. \nНо я не являюсь автором самой утилиты FFmpeg.",
"addedFilesTitle": "Добавленные файлы",
"autoClearAfterAddingToQueue": "Очищать после добавления в очередь",
"buttonDownloadFFmpeg": "Скачать автоматически FFmpeg",
"buttonForSelectedDirTitle": "Сохранить в папку:",
"cancel": "Отмена",
"changeFFPath": "FFmpeg, FFprobe и FFplay",
"changeLanguage": "Поменять язык",
"checkboxOverwriteOutputFilesTitle": "Разрешить перезаписать файл",
"choose": "выбрать",
"clearAll": "Очистить список",
"completedQueue": "Готово",
"converterVideoFilesSubmitTitle": "Конвертировать",
"converterVideoFilesTitle": "Конвертер видео, аудио и картинок",
"download": "Скачать",
"downloadFFmpegFromSite": "Будет скачано с сайта:",
"downloadRun": "Скачивается...",
"dragAndDropFiles": "перетащить файлы",
"encoderGroupAudio": "Аудио",
"encoderGroupImage": "Картинки",
"encoderGroupVideo": "Видео",
"encoder_apng": "APNG image",
"encoder_bmp": "BMP image",
"encoder_flv": "FLV",
"encoder_gif": "GIF image",
"encoder_h264_nvenc": "H.264 с поддержкой NVIDIA",
"encoder_libmp3lame": "libmp3lame MP3 (MPEG audio layer 3)",
"encoder_libshine": "libshine MP3 (MPEG audio layer 3)",
"encoder_libtwolame": "libtwolame MP2 (MPEG audio layer 2)",
"encoder_libvpx": "libvpx VP8 (codec vp8)",
"encoder_libvpx-vp9": "libvpx VP9 (codec vp9)",
"encoder_libwebp": "libwebp WebP image",
"encoder_libwebp_anim": "libwebp_anim WebP image",
"encoder_libx264": "H.264 libx264",
"encoder_libx265": "H.265 libx265",
"encoder_libxvid": "libxvidcore MPEG-4 part 2",
"encoder_mjpeg": "MJPEG (Motion JPEG)",
"encoder_mp2": "MP2 (MPEG audio layer 2)",
"encoder_mp2fixed": "MP2 fixed point (MPEG audio layer 2)",
"encoder_mpeg1video": "MPEG-1",
"encoder_mpeg2video": "MPEG-2",
"encoder_mpeg4": "MPEG-4 part 2",
"encoder_msmpeg4": "MPEG-4 part 2 Microsoft variant version 3",
"encoder_msmpeg4v2": "MPEG-4 part 2 Microsoft variant version 2",
"encoder_msvideo1": "Microsoft Video-1",
"encoder_png": "PNG image",
"encoder_qtrle": "QuickTime Animation (RLE) video",
"encoder_sgi": "SGI image",
"encoder_tiff": "TIFF image",
"encoder_wmav1": "Windows Media Audio 1",
"encoder_wmav2": "Windows Media Audio 2",
"encoder_wmv1": "Windows Media Video 7",
"encoder_wmv2": "Windows Media Video 8",
"encoder_xbm": "XBM (X BitMap) image",
"error": "Произошла ошибка!",
"errorConverter": "не смогли отконвертировать видео",
"errorDragAndDropFile": "Не все файлы добавились",
"errorFFmpeg": "это не FFmpeg",
"errorFFmpegVersion": "Не смогли определить версию FFmpeg",
"errorFFplay": "это не FFplay",
"errorFFplayVersion": "Не смогли определить версию FFplay",
"errorFFprobe": "это не FFprobe",
"errorFFprobeVersion": "Не смогли определить версию FFprobe",
"errorNoFilesAddedForConversion": "Нет файлов для конвертации",
"errorQueue": "Ошибка",
"errorSelectedEncoder": "Конвертер не выбран",
"errorSelectedFolderSave": "Папка для сохранения не выбрана!",
"errorSelectedFormat": "Расширение файла не выбрана",
"exit": "Выход",
"ffmpegLGPL": "Это программное обеспечение использует библиотеки из проекта **FFmpeg** под **[LGPLv2.1](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**.",
"ffmpegTrademark": "**FFmpeg** — торговая марка **[Fabrice Bellard](http://bellard.org/)** , создателя проекта **[FFmpeg](https://ffmpeg.org/about.html)**.",
"fileForConversionTitle": "Файл:",
"fileQueueTitle": "Очередь",
"formPreset": "Предустановка",
"gratitude": "Благодарность",
"gratitudeText": "Я искренне благодарю вас за неоценимую\n\rи своевременную помощь:",
"help": "Справка",
"helpFFplay": "Клавиши проигрывателя FFplay",
"helpFFplayActivateFrameStepMode": "Активировать покадровый режим.",
"helpFFplayCycleVideoFiltersOrShowModes": "Цикл видеофильтров или режимов показа.",
"helpFFplayDecreaseVolume": "Уменьшить громкость.",
"helpFFplayDescription": "Описание",
"helpFFplayDoubleClickLeftMouseButton": "двойной щелчок\nлевой кнопкой мыши",
"helpFFplayIncreaseVolume": "Увеличить громкость.",
"helpFFplayKeyDown": "вниз",
"helpFFplayKeyHoldS": "держать S",
"helpFFplayKeyLeft": "лево",
"helpFFplayKeyRight": "право",
"helpFFplayKeySpace": "SPACE (пробел)",
"helpFFplayKeyUp": "вверх",
"helpFFplayKeys": "Клавиши",
"helpFFplayPause": "Поставить на паузу или продолжить проигрывать.",
"helpFFplayQuit": "Закрыть проигрыватель.",
"helpFFplaySeekBForward10Minutes": "Перемотать вперёд на 10 минут.",
"helpFFplaySeekBForward1Minute": "Перемотать вперёд на 1 минуту.",
"helpFFplaySeekBackward10Minutes": "Перемотать назад на 10 минут.",
"helpFFplaySeekBackward10Seconds": "Перемотать назад на 10 секунд.",
"helpFFplaySeekBackward1Minute": "Перемотать назад на 1 минуту.",
"helpFFplaySeekForward10Seconds": "Перемотать вперёд на 10 секунд.",
"helpFFplayToggleFullScreen": "Переключиться на полный экран или выйти с полного экрана.",
"helpFFplayToggleMute": "Отключить звук или включить звук.",
"inProgressQueue": "Выполняется",
"languageSelectionFormHead": "Переключить язык",
"languageSelectionHead": "Выберите язык",
"licenseLink": "Сведения о лицензии",
"licenseLinkOther": "Лицензии от других продуктов, которые используются в программе",
"menuSettingsLanguage": "Язык",
"menuSettingsTheme": "Тема",
"or": "или",
"parameterCheckbox": "Включить параметр",
"pathToFfmpeg": "Путь к FFmpeg:",
"pathToFfplay": "Путь к FFplay:",
"pathToFfprobe": "Путь к FFprobe:",
"preset_fast": "fast (медленней чем faster, но будет файл и меньше весить)",
"preset_faster": "faster (медленней чем veryfast, но будет файл и меньше весить)",
"preset_medium": "medium (медленней чем fast, но будет файл и меньше весить)",
"preset_placebo": "placebo (не рекомендуется)",
"preset_slow": "slow (медленней чем medium, но будет файл и меньше весить)",
"preset_slower": "slower (медленней чем slow, но будет файл и меньше весить)",
"preset_superfast": "superfast (медленней чем ultrafast, но будет файл и меньше весить)",
"preset_ultrafast": "ultrafast (быстро, но файл будет много весить)",
"preset_veryfast": "veryfast (медленней чем superfast, но будет файл и меньше весить)",
"preset_veryslow": "veryslow (медленней чем slower, но будет файл и меньше весить)",
"programmLink": "Сайт проекта",
"programmVersion": "**Версия программы:** {{.Version}}",
"queue": "Очередь",
"save": "Сохранить",
"selectEncoder": "Кодировщик:",
"selectFFPathTitle": "Укажите путь к FFmpeg и к FFprobe",
"selectFormat": "Расширение файла:",
"settings": "Настройки",
"testFF": "Проверка FFmpeg на работоспособность...",
"themesNameDark": "Тёмная",
"themesNameDefault": "По умолчанию",
"themesNameLight": "Светлая",
"titleDownloadLink": "Скачать можно от сюда",
"total": "Всего",
"unzipRun": "Распаковывается...",
"waitingQueue": "В очереди"
}

View File

@ -0,0 +1,45 @@
{
"Advanced": "Advanced",
"Cancel": "Cancel",
"Confirm": "Confirm",
"Copy": "Copy",
"Create Folder": "Create Folder",
"Cut": "Cut",
"Enter filename": "Enter filename",
"Error": "Error",
"Favourites": "Favourites",
"File": "File",
"Folder": "Folder",
"New Folder": "New Folder",
"No": "No",
"OK": "OK",
"Open": "Open",
"Paste": "Paste",
"Quit": "Quit",
"Redo": "Redo",
"Save": "Save",
"Select all": "Select all",
"Show Hidden Files": "Show Hidden Files",
"Undo": "Undo",
"Yes": "Yes",
"file.name": {
"other": "Name"
},
"file.parent": {
"other": "Parent"
},
"friday": "Friday",
"friday.short": "Fri",
"monday": "Monday",
"monday.short": "Mon",
"saturday": "Saturday",
"saturday.short": "Sat",
"sunday": "Sunday",
"sunday.short": "Sun",
"thursday": "Thursday",
"thursday.short": "Thu",
"tuesday": "Tuesday",
"tuesday.short": "Tue",
"wednesday": "Wednesday",
"wednesday.short": "Wed"
}

View File

@ -0,0 +1,45 @@
{
"Advanced": "Кеңейтілген",
"Cancel": "Бас тарту",
"Confirm": "Растау",
"Copy": "Көшіру",
"Create Folder": "Қалта жасау",
"Cut": "Кесу",
"Enter filename": "Файл атауын енгізіңіз",
"Error": "Қате",
"Favourites": "Таңдаулылар",
"File": "Файл",
"Folder": "Қалта",
"New Folder": "Жаңа қалта",
"No": "Жоқ",
"OK": "ОК",
"Open": "Ашу",
"Paste": "Кірістіру",
"Quit": "Шығу",
"Redo": "Қайталау",
"Save": "Сақтау",
"Select all": "Барлығын таңдаңыз",
"Show Hidden Files": "Жасырын файлдарды көрсету",
"Undo": "Бас тарту",
"Yes": "Иә",
"file.name": {
"other": "Аты"
},
"file.parent": {
"other": "Жоғары"
},
"friday": "Жұма",
"friday.short": "Жұ",
"monday": "Дүйсенбі",
"monday.short": "Дү",
"saturday": "Сенбі",
"saturday.short": "Сен",
"sunday": "Жексенбі",
"sunday.short": "Же",
"thursday": "Сейсенбі",
"thursday.short": "Се",
"tuesday": "Бейсенбі",
"tuesday.short": "Бе",
"wednesday": "Сәрсенбі",
"wednesday.short": "Сә"
}

View File

@ -0,0 +1,45 @@
{
"Advanced": "Расширенные",
"Cancel": "Отмена",
"Confirm": "Подтвердить",
"Copy": "Копировать",
"Create Folder": "Создать папку",
"Cut": "Вырезать",
"Enter filename": "Введите имя файла",
"Error": "Ошибка",
"Favourites": "Избранное",
"File": "Файл",
"Folder": "Папка",
"New Folder": "Новая папка",
"No": "Нет",
"OK": "ОК",
"Open": "Открыть",
"Paste": "Вставить",
"Quit": "Выйти",
"Redo": "Повторить",
"Save": "Сохранить",
"Select all": "Выбрать всё",
"Show Hidden Files": "Показать скрытые файлы",
"Undo": "Отменить",
"Yes": "Да",
"file.name": {
"other": "Имя"
},
"file.parent": {
"other": "Вверх"
},
"friday": "Пятница",
"friday.short": "Пт",
"monday": "Понедельник",
"monday.short": "Пн",
"saturday": "Суббота",
"saturday.short": "Сб",
"sunday": "Воскресенье",
"sunday.short": "Вс",
"thursday": "Вторник",
"thursday.short": "Вт",
"tuesday": "Четверг",
"tuesday.short": "Чт",
"wednesday": "Среда",
"wednesday.short": "Ср"
}

Some files were not shown because too many files have changed in this diff Show More