Compare commits

..

103 Commits
0.3.0 ... 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
133 changed files with 7041 additions and 3073 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", "конвертер", "видео", "аудио", "кодек"]

View File

@ -431,6 +431,40 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
github.com/fyne-io/oksvg
BSD 3-Clause License
Copyright (c) 2018, Steven R Wiley
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
github.com/go-gl/gl github.com/go-gl/gl
The MIT License (MIT) The MIT License (MIT)
@ -639,40 +673,423 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
github.com/gopherjs/gopherjs github.com/hack-pad/go-indexeddb
Copyright (c) 2013 Richard Musiol. All rights reserved. Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Redistribution and use in source and binary forms, with or without TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright 1. Definitions.
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "License" shall mean the terms and conditions for use, reproduction,
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT and distribution as defined by Sections 1 through 9 of this document.
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT "Licensor" shall mean the copyright owner or entity authorized by
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, the copyright owner that is granting the License.
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, "Legal Entity" shall mean the union of the acting entity and all
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY other entities that control, are controlled by, or are under common
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT control with that entity. For the purposes of this definition,
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE "control" means (i) the power, direct or indirect, to cause the
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
github.com/jinzhu/inflection github.com/hack-pad/safejs
The MIT License (MIT) Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (c) 2015 - Jinzhu TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--------------------------------------------------------------------------------
github.com/jeandeaual/go-locale
MIT License
Copyright (c) 2020 Alexis Jeandeau
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -718,29 +1135,21 @@ THE SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
github.com/mattn/go-sqlite3 github.com/nfnt/resize
The MIT License (MIT) Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
Copyright (c) 2014 Yasuhiro Matsumoto Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
Permission is hereby granted, free of charge, to any person obtaining a copy THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
of this software and associated documentation files (the "Software"), to deal REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
in the Software without restriction, including without limitation the rights FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
copies of the Software, and to permit persons to whom the Software is OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
furnished to do so, subject to the following conditions: TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -800,6 +1209,212 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
github.com/rymdport/portal
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--------------------------------------------------------------------------------
github.com/srwiley/oksvg github.com/srwiley/oksvg
BSD 3-Clause License BSD 3-Clause License
@ -894,29 +1509,34 @@ SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
github.com/tevino/abool github.com/ulikunitz/xz
The MIT License (MIT) Copyright (c) 2014-2022 Ulrich Kunitz
All rights reserved.
Copyright (c) 2016 Tevin Zhang Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Permission is hereby granted, free of charge, to any person obtaining a copy * Redistributions of source code must retain the above copyright notice, this
of this software and associated documentation files (the "Software"), to deal list of conditions and the following disclaimer.
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all * Redistributions in binary form must reproduce the above copyright notice,
copies or substantial portions of the Software. this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * My name, Ulrich Kunitz, may not be used to endorse or promote products
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, derived from this software without specific prior written permission.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
SOFTWARE. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -948,7 +1568,7 @@ SOFTWARE.
golang.org/x/image golang.org/x/image
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -960,39 +1580,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
golang.org/x/mobile
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.
@ -1012,7 +1600,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang.org/x/net golang.org/x/net
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -1024,7 +1612,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.
@ -1044,7 +1632,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang.org/x/sys golang.org/x/sys
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -1056,7 +1644,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.
@ -1076,7 +1664,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang.org/x/text golang.org/x/text
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -1088,7 +1676,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.
@ -1124,60 +1712,9 @@ limitations under the License.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
gorm.io/gorm
The MIT License (MIT)
Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
--------------------------------------------------------------------------------
honnef.co/go/js/dom
Copyright (c) 2014 Dominik Honnef
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
github.com/golang/go github.com/golang/go
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -1189,7 +1726,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.
@ -1208,6 +1745,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
FFmpeg FFmpeg
FFmpeg is a trademark of Fabrice Bellard, originator of the FFmpeg project. FFmpeg is a trademark of Fabrice Bellard, originator of the FFmpeg project.
https://ffmpeg.org/legal.html https://ffmpeg.org/legal.html
@ -1885,3 +2423,4 @@ may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>. <http://www.gnu.org/philosophy/why-not-lgpl.html>.

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,43 +1,39 @@
# GUI for FFmpeg # GUI for FFmpeg
<p>Простенький интерфейс для консольной утилиты FFmpeg. Но я <strong>не являюсь</strong> автором самой утилиты <strong>FFmpeg</strong>.</p> <p>Простенький интерфейс для консольной утилиты FFmpeg. Но я <strong>не являюсь</strong> автором самой утилиты <strong>FFmpeg</strong>.</p>
<p><strong>FFmpeg</strong> — торговая марка <strong><a href="http://bellard.org/" target="_blank">Fabrice Bellard</a></strong>, создателя проекта <strong><a href="https://ffmpeg.org/about.html" target="_blank">FFmpeg</a></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>
<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> <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="images/screenshot-gui-for-ffmpeg.png"> <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> <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: ## Установка через fyne:
1. go install fyne.io/fyne/v2/cmd/fyne@latest 1. go install fyne.io/fyne/v2/cmd/fyne@latest
2. fyne get git.kor-elf.net/kor-elf/gui-for-ffmpeg/src 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 1. git clone https://git.kor-elf.net/kor-elf/gui-for-ffmpeg.git
2. Переходим в папку проекта и там переходим в папку src: **cd gui-for-ffmpeg/src** 2. Переходим в папку проекта и там переходим в папку src: **cd gui-for-ffmpeg**
3. Ознакамливаемся, что нужно ещё установить для Вашей ОС для простого запуска (через go run) тут: https://docs.fyne.io/started/ 3. Ознакамливаемся, что нужно ещё установить для Вашей ОС для простого запуска (через go run) тут: https://docs.fyne.io/started/
4. *(не обязательный шаг)* Просто запустить можно так: **go run main.go** 4. *(не обязательный шаг)* Просто запустить можно так: **go run main.go**
5. go install github.com/fyne-io/fyne-cross@latest 5. go install github.com/fyne-io/fyne-cross@latest
* У Вас так же должен быть установлен docker * У Вас так же должен быть установлен docker
* О fyne-cross можно по подробней почитать тут: https://github.com/fyne-io/fyne-cross * О fyne-cross можно по подробней почитать тут: https://github.com/fyne-io/fyne-cross
6. * fyne-cross windows --icon icon.png --app-id "." -name "gui-for-ffmpeg" 6. * fyne-cross windows
* fyne-cross linux --icon icon.png --app-id "." -name "gui-for-ffmpeg" * fyne-cross linux
7. Создаться папка **fyne-cross/bin** и там будет созданна папка с тем названием под которую Вы компилировали приложения (linux-amd64 или windows-amd64). 7. Создаться папка **fyne-cross/dist** и там будет созданна папка с тем названием под которую Вы компилировали приложения (linux-amd64 или windows-amd64).
8. В папку **fyne-cross/bin/linux-amd64** или **fyne-cross/bin/windows-amd64** копируете: 8. В папке **fyne-cross/bin/linux-amd64** или **fyne-cross/bin/windows-amd64** будут архивы, которые надо распаковать и пользоваться программой.
* src/icon.png
* src/data
* src/languages
* LICENSE
* LICENSE-3RD-PARTY.txt
<p><strong>Структура должна получиться такая:</strong></p>
<img src="images/screenshot-folder-structure.png">
## Работа с переводами:
1. go install -v github.com/nicksnyder/go-i18n/v2/goi18n@latest
2. Переходим в папке проекта в папку src: **cd ./src**
3. goi18n merge -sourceLanguage ru -outdir languages languages/active.\*.toml languages/translate.\*.toml
4. В файлах **languages/translate.\*.toml** переводим текст на нужный язык
5. goi18n merge -sourceLanguage ru -outdir languages languages/active.\*.toml languages/translate.\*.toml
Более подробно можно почитать тут: https://github.com/nicksnyder/go-i18n

View File

Before

Width:  |  Height:  |  Size: 29 KiB

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: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

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

@ -1,22 +1,19 @@
//go:build windows //go:build windows
// +build windows // +build windows
package convertor package gui
import ( import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/image/colornames" "golang.org/x/image/colornames"
"image/color" "image/color"
) )
func (v View) blockDownloadFFmpeg( func DownloadFFmpeg(donwloadFFmpeg func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error) fyne.CanvasObject {
donwloadFFmpeg func(progressBar *widget.ProgressBar, progressMessage *canvas.Text) error,
) *fyne.Container {
errorDownloadFFmpegMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255}) errorDownloadFFmpegMessage := canvas.NewText("", color.RGBA{R: 255, G: 0, B: 0, A: 255})
errorDownloadFFmpegMessage.TextSize = 16 errorDownloadFFmpegMessage.TextSize = 16
errorDownloadFFmpegMessage.TextStyle = fyne.TextStyle{Bold: true} errorDownloadFFmpegMessage.TextStyle = fyne.TextStyle{Bold: true}
@ -29,33 +26,32 @@ func (v View) blockDownloadFFmpeg(
var buttonDownloadFFmpeg *widget.Button var buttonDownloadFFmpeg *widget.Button
buttonDownloadFFmpeg = widget.NewButton(v.localizerService.GetMessage(&i18n.LocalizeConfig{ buttonDownloadFFmpeg = widget.NewButton(lang.L("download"), func() {
MessageID: "download",
}), func() {
buttonDownloadFFmpeg.Disable()
err := donwloadFFmpeg(progressBar, progressDownloadFFmpegMessage) go func() {
if err != nil { fyne.Do(func() {
errorDownloadFFmpegMessage.Text = err.Error() buttonDownloadFFmpeg.Disable()
} })
err := donwloadFFmpeg(progressBar, progressDownloadFFmpegMessage)
buttonDownloadFFmpeg.Enable() if err != nil {
errorDownloadFFmpegMessage.Text = err.Error()
}
fyne.Do(func() {
buttonDownloadFFmpeg.Enable()
})
}()
}) })
downloadFFmpegFromSiteMessage := v.localizerService.GetMessage(&i18n.LocalizeConfig{ downloadFFmpegFromSiteMessage := lang.L("downloadFFmpegFromSite")
MessageID: "downloadFFmpegFromSite",
})
return container.NewVBox( return container.NewVBox(
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
widget.NewCard(v.localizerService.GetMessage(&i18n.LocalizeConfig{ widget.NewCard(lang.L("buttonDownloadFFmpeg"), "", container.NewVBox(
MessageID: "buttonDownloadFFmpeg",
}), "", container.NewVBox(
widget.NewRichTextFromMarkdown( widget.NewRichTextFromMarkdown(
downloadFFmpegFromSiteMessage+" [https://github.com/BtbN/FFmpeg-Builds/releases](https://github.com/BtbN/FFmpeg-Builds/releases)", downloadFFmpegFromSiteMessage+" [https://github.com/BtbN/FFmpeg-Builds/releases](https://github.com/BtbN/FFmpeg-Builds/releases)",
), ),
buttonDownloadFFmpeg, buttonDownloadFFmpeg,
errorDownloadFFmpegMessage, container.NewHScroll(errorDownloadFFmpegMessage),
progressDownloadFFmpegMessage, progressDownloadFFmpegMessage,
progressBar, 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

@ -1,15 +1,16 @@
//go:build windows //go:build windows
// +build windows // +build windows
package handler package service
import ( import (
"archive/zip" "archive/zip"
"errors" "errors"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/convertor" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/application"
"github.com/nicksnyder/go-i18n/v2/i18n"
"io" "io"
"net/http" "net/http"
"os" "os"
@ -17,45 +18,52 @@ import (
"strings" "strings"
) )
func getPathsToFF() []convertor.FFPathUtilities { func startDownload(app application.AppContract, progressBar *widget.ProgressBar, progressMessage *canvas.Text, save func(ffmpegPath string, ffprobePath string, ffplayPath string) error) error {
return []convertor.FFPathUtilities{{"ffmpeg\\bin\\ffmpeg.exe", "ffmpeg\\bin\\ffprobe.exe"}} var err error
}
func (h ConvertorHandler) downloadFFmpeg(progressBar *widget.ProgressBar, progressMessage *canvas.Text) (err error) { dir := os.Getenv("APPDATA")
isDirectoryFFmpeg := isDirectory("ffmpeg") dir = filepath.Join(dir, "fyne", app.FyneApp().UniqueID())
if isDirectoryFFmpeg == false { err = os.MkdirAll(dir, 0755)
err = os.Mkdir("ffmpeg", 0777)
if err != nil {
return err
}
}
progressMessage.Text = h.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "downloadRun",
})
progressMessage.Refresh()
err = downloadFile("ffmpeg/ffmpeg.zip", "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip", progressBar)
if err != nil { if err != nil {
return err return err
} }
progressMessage.Text = h.localizerService.GetMessage(&i18n.LocalizeConfig{ fyne.Do(func() {
MessageID: "unzipRun", progressMessage.Text = lang.L("downloadRun")
progressMessage.Refresh()
}) })
progressMessage.Refresh() err = downloadFile(dir+"/ffmpeg.zip", "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip", progressBar)
err = unZip("ffmpeg/ffmpeg.zip", "ffmpeg")
if err != nil { if err != nil {
return err return err
} }
_ = os.Remove("ffmpeg/ffmpeg.zip")
progressMessage.Text = h.localizerService.GetMessage(&i18n.LocalizeConfig{ fyne.Do(func() {
MessageID: "testFF", progressMessage.Text = lang.L("unzipRun")
progressMessage.Refresh()
}) })
progressMessage.Refresh() err = unZip(dir+"/ffmpeg.zip", dir, progressBar)
err = h.saveSettingFFPath("ffmpeg/ffmpeg-master-latest-win64-gpl/bin/ffmpeg.exe", "ffmpeg/ffmpeg-master-latest-win64-gpl/bin/ffprobe.exe")
if err != nil { if err != nil {
return err 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 return nil
} }
@ -94,19 +102,35 @@ func downloadFile(filepath string, url string, progressBar *widget.ProgressBar)
f.Write(buf[:n]) f.Write(buf[:n])
downloaded += int64(n) downloaded += int64(n)
progressBar.Value = float64(downloaded) / float64(resp.ContentLength) * 100 progressBar.Value = float64(downloaded) / float64(resp.ContentLength) * 100
progressBar.Refresh() fyne.Do(func() {
progressBar.Refresh()
})
} }
} }
return nil return nil
} }
func unZip(fileZip string, directory string) error { 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) archive, err := zip.OpenReader(fileZip)
if err != nil { if err != nil {
return err return err
} }
defer archive.Close() defer archive.Close()
totalBytes := int64(0)
for _, f := range archive.File {
totalBytes += int64(f.UncompressedSize64)
}
unpackedBytes := int64(0)
for _, f := range archive.File { for _, f := range archive.File {
filePath := filepath.Join(directory, f.Name) filePath := filepath.Join(directory, f.Name)
@ -132,22 +156,20 @@ func unZip(fileZip string, directory string) error {
return err return err
} }
if _, err := io.Copy(dstFile, fileInArchive); err != nil { bytesRead, err := io.Copy(dstFile, fileInArchive)
if err != nil {
return err return err
} }
unpackedBytes += bytesRead
progressBar.Value = float64(unpackedBytes) / float64(totalBytes) * 100
fyne.Do(func() {
progressBar.Refresh()
})
dstFile.Close() dstFile.Close()
fileInArchive.Close() fileInArchive.Close()
} }
return nil return nil
} }
func isDirectory(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
return false
}
return fileInfo.IsDir()
}

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

View File

@ -1,139 +1,97 @@
package menu package view
import ( import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/src/localizer" "git.kor-elf.net/kor-elf/gui-for-ffmpeg/internal/resources"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/image/colornames" "golang.org/x/image/colornames"
"net/url" "net/url"
) )
type ViewContract interface { func About(appVersion string, ffmpegVersion string, ffprobeVersion string, ffplayVersion string) fyne.CanvasObject {
About(ffmpegVersion string, ffprobeVersion string)
}
type View struct {
w fyne.Window
app fyne.App
appVersion string
localizerService localizer.ServiceContract
}
func NewView(w fyne.Window, app fyne.App, appVersion string, localizerService localizer.ServiceContract) *View {
return &View{
w: w,
app: app,
appVersion: appVersion,
localizerService: localizerService,
}
}
func (v View) About(ffmpegVersion string, ffprobeVersion string) {
view := v.app.NewWindow(v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "about",
}))
view.Resize(fyne.Size{Width: 793, Height: 550})
view.SetFixedSize(true)
programmName := canvas.NewText(" GUI for FFmpeg", colornames.Darkgreen) programmName := canvas.NewText(" GUI for FFmpeg", colornames.Darkgreen)
programmName.TextStyle = fyne.TextStyle{Bold: true} programmName.TextStyle = fyne.TextStyle{Bold: true}
programmName.TextSize = 20 programmName.TextSize = 20
programmLink := widget.NewHyperlink(v.localizerService.GetMessage(&i18n.LocalizeConfig{ programmLink := widget.NewHyperlink(
MessageID: "programmLink", lang.L("programmLink"),
}), &url.URL{ &url.URL{
Scheme: "https", Scheme: "https",
Host: "git.kor-elf.net", Host: "gui-for-ffmpeg.projects.kor-elf.net",
Path: "kor-elf/gui-for-ffmpeg/releases", Path: "/",
})
licenseLink := widget.NewHyperlink(v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "licenseLink",
}), &url.URL{
Scheme: "https",
Host: "git.kor-elf.net",
Path: "kor-elf/gui-for-ffmpeg/src/branch/main/LICENSE",
})
licenseLinkOther := widget.NewHyperlink(v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "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(v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "programmVersion",
TemplateData: map[string]string{
"Version": v.appVersion,
}, },
})) )
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( aboutText := widget.NewRichText(
&widget.TextSegment{ &widget.TextSegment{
Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{ Text: lang.L("aboutText"),
MessageID: "aboutText",
}),
}, },
) )
image := canvas.NewImageFromFile("icon.png") image := canvas.NewImageFromResource(resources.IconAppLogoResource())
image.SetMinSize(fyne.Size{Width: 100, Height: 100}) image.SetMinSize(fyne.Size{Width: 100, Height: 100})
image.FillMode = canvas.ImageFillContain image.FillMode = canvas.ImageFillContain
ffmpegTrademark := widget.NewRichTextFromMarkdown(v.localizerService.GetMessage(&i18n.LocalizeConfig{ ffmpegTrademark := widget.NewRichTextFromMarkdown(lang.L("ffmpegTrademark"))
MessageID: "ffmpegTrademark", ffmpegLGPL := widget.NewRichTextFromMarkdown(lang.L("ffmpegLGPL"))
}))
ffmpegLGPL := widget.NewRichTextFromMarkdown(v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "ffmpegLGPL",
}))
view.SetContent( return container.NewScroll(container.NewVBox(
container.NewScroll(container.NewVBox( container.NewBorder(nil, nil, container.NewVBox(image), nil, container.NewVBox(
container.NewBorder(nil, nil, container.NewVBox(image), nil, container.NewVBox( programmName,
programmName, programmVersion,
programmVersion, aboutText,
aboutText, ffmpegTrademark,
ffmpegTrademark, ffmpegLGPL,
ffmpegLGPL, widget.NewRichTextFromMarkdown("Copyright (c) 2024 **[Leonid Nikitin (kor-elf)](https://git.kor-elf.net/kor-elf/)**."),
v.getCopyright(), container.NewHBox(programmLink, licenseLink),
container.NewHBox(programmLink, licenseLink), container.NewHBox(licenseLinkOther),
container.NewHBox(licenseLinkOther),
)),
v.getAboutFfmpeg(ffmpegVersion),
v.getAboutFfprobe(ffprobeVersion),
widget.NewCard(v.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "AlsoUsedProgram",
}), "", v.getOther()),
)), )),
) aboutFFmpeg(ffmpegVersion),
view.CenterOnScreen() aboutFFprobe(ffprobeVersion),
view.Show() aboutFFplay(ffplayVersion),
widget.NewCard(lang.L("AlsoUsedProgram"), "", license3RDParty()),
))
} }
func (v View) getCopyright() *widget.RichText { func aboutFFmpeg(version string) *fyne.Container {
return widget.NewRichTextFromMarkdown("Copyright (c) 2024 **[Leonid Nikitin (kor-elf)](https://git.kor-elf.net/kor-elf/)**.")
}
func (v View) getAboutFfmpeg(version string) *fyne.Container {
programmName := canvas.NewText(" FFmpeg", colornames.Darkgreen) programmName := canvas.NewText(" FFmpeg", colornames.Darkgreen)
programmName.TextStyle = fyne.TextStyle{Bold: true} programmName.TextStyle = fyne.TextStyle{Bold: true}
programmName.TextSize = 20 programmName.TextSize = 20
programmLink := widget.NewHyperlink(v.localizerService.GetMessage(&i18n.LocalizeConfig{ programmLink := widget.NewHyperlink(lang.L("programmLink"), &url.URL{
MessageID: "programmLink",
}), &url.URL{
Scheme: "https", Scheme: "https",
Host: "ffmpeg.org", Host: "ffmpeg.org",
Path: "", Path: "",
}) })
licenseLink := widget.NewHyperlink(v.localizerService.GetMessage(&i18n.LocalizeConfig{ licenseLink := widget.NewHyperlink(lang.L("licenseLink"), &url.URL{
MessageID: "licenseLink",
}), &url.URL{
Scheme: "https", Scheme: "https",
Host: "ffmpeg.org", Host: "ffmpeg.org",
Path: "legal.html", Path: "legal.html",
@ -142,28 +100,24 @@ func (v View) getAboutFfmpeg(version string) *fyne.Container {
return container.NewVBox( return container.NewVBox(
programmName, programmName,
widget.NewLabel(version), widget.NewLabel(version),
widget.NewRichTextFromMarkdown("**FFmpeg** is a trademark of **[Fabrice Bellard](http://bellard.org/)**, originator of the **[FFmpeg](https://ffmpeg.org/about.html)** project."), 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](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**."), 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), container.NewHBox(programmLink, licenseLink),
) )
} }
func (v View) getAboutFfprobe(version string) *fyne.Container { func aboutFFprobe(version string) *fyne.Container {
programmName := canvas.NewText(" FFprobe", colornames.Darkgreen) programmName := canvas.NewText(" FFprobe", colornames.Darkgreen)
programmName.TextStyle = fyne.TextStyle{Bold: true} programmName.TextStyle = fyne.TextStyle{Bold: true}
programmName.TextSize = 20 programmName.TextSize = 20
programmLink := widget.NewHyperlink(v.localizerService.GetMessage(&i18n.LocalizeConfig{ programmLink := widget.NewHyperlink(lang.L("programmLink"), &url.URL{
MessageID: "programmLink",
}), &url.URL{
Scheme: "https", Scheme: "https",
Host: "ffmpeg.org", Host: "ffmpeg.org",
Path: "ffprobe.html", Path: "ffprobe.html",
}) })
licenseLink := widget.NewHyperlink(v.localizerService.GetMessage(&i18n.LocalizeConfig{ licenseLink := widget.NewHyperlink(lang.L("licenseLink"), &url.URL{
MessageID: "licenseLink",
}), &url.URL{
Scheme: "https", Scheme: "https",
Host: "ffmpeg.org", Host: "ffmpeg.org",
Path: "legal.html", Path: "legal.html",
@ -172,13 +126,39 @@ func (v View) getAboutFfprobe(version string) *fyne.Container {
return container.NewVBox( return container.NewVBox(
programmName, programmName,
widget.NewLabel(version), widget.NewLabel(version),
widget.NewRichTextFromMarkdown("**FFmpeg** is a trademark of **[Fabrice Bellard](http://bellard.org/)**, originator of the **[FFmpeg](https://ffmpeg.org/about.html)** project."), 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](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)**."), 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), container.NewHBox(programmLink, licenseLink),
) )
} }
func (v View) getOther() *fyne.Container { 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( return container.NewVBox(
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
@ -300,6 +280,19 @@ func (v View) getOther() *fyne.Container {
widget.NewLabel("Copyright (c) 2022, Fyne.io"), widget.NewLabel("Copyright (c) 2022, Fyne.io"),
canvas.NewLine(colornames.Darkgreen), 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{ container.NewHBox(widget.NewHyperlink("github.com/go-gl/gl", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
@ -362,33 +355,44 @@ func (v View) getOther() *fyne.Container {
Host: "github.com", Host: "github.com",
Path: "godbus/dbus/blob/master/LICENSE", Path: "godbus/dbus/blob/master/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google"), widget.NewLabel("Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google. All rights reserved."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/gopherjs/gopherjs", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/hack-pad/go-indexeddb", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "gopherjs/gopherjs", Path: "hack-pad/go-indexeddb",
})), })),
container.NewHBox(widget.NewHyperlink("BSD 2-Clause \"Simplified\" License", &url.URL{ container.NewHBox(widget.NewHyperlink("Apache License 2.0", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "gopherjs/gopherjs/blob/master/LICENSE", Path: "hack-pad/go-indexeddb/blob/main/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2013 Richard Musiol. All rights reserved."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/jinzhu/inflection", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/hack-pad/safejs", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "jinzhu/inflection", Path: "hack-pad/safejs",
})), })),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{ container.NewHBox(widget.NewHyperlink("Apache License 2.0", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "jinzhu/inflection/blob/master/LICENSE", Path: "hack-pad/safejs/blob/main/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2015 - Jinzhu"), 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), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/jsummers/gobmp", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/jsummers/gobmp", &url.URL{
@ -404,17 +408,17 @@ func (v View) getOther() *fyne.Container {
widget.NewLabel("Copyright (c) 2012-2015 Jason Summers"), widget.NewLabel("Copyright (c) 2012-2015 Jason Summers"),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/mattn/go-sqlite3", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/nfnt/resize", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "mattn/go-sqlite3", Path: "nfnt/resize",
})), })),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{ container.NewHBox(widget.NewHyperlink("ISC License", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "mattn/go-sqlite3/blob/master/LICENSE", Path: "nfnt/resize/blob/master/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2014 Yasuhiro Matsumoto"), widget.NewLabel("Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>"),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/nicksnyder/go-i18n/v2", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/nicksnyder/go-i18n/v2", &url.URL{
@ -440,7 +444,19 @@ func (v View) getOther() *fyne.Container {
Host: "github.com", Host: "github.com",
Path: "pmezard/go-difflib/blob/master/LICENSE", Path: "pmezard/go-difflib/blob/master/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2013, Patrick Mezard"), 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), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/srwiley/oksvg", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/srwiley/oksvg", &url.URL{
@ -453,7 +469,7 @@ func (v View) getOther() *fyne.Container {
Host: "github.com", Host: "github.com",
Path: "srwiley/oksvg/blob/master/LICENSE", Path: "srwiley/oksvg/blob/master/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2018, Steven R Wiley"), widget.NewLabel("Copyright (c) 2018, Steven R Wiley. All rights reserved."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/srwiley/rasterx", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/srwiley/rasterx", &url.URL{
@ -466,7 +482,7 @@ func (v View) getOther() *fyne.Container {
Host: "github.com", Host: "github.com",
Path: "srwiley/rasterx/blob/master/LICENSE", Path: "srwiley/rasterx/blob/master/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2018, Steven R Wiley"), widget.NewLabel("Copyright (c) 2018, Steven R Wiley. All rights reserved."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/stretchr/testify", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/stretchr/testify", &url.URL{
@ -482,17 +498,17 @@ func (v View) getOther() *fyne.Container {
widget.NewLabel("Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors."), widget.NewLabel("Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/tevino/abool", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/ulikunitz/xz", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "tevino/abool", Path: "ulikunitz/xz",
})), })),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{ container.NewHBox(widget.NewHyperlink("License", &url.URL{
Scheme: "https", Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "tevino/abool/blob/master/LICENSE", Path: "ulikunitz/xz/blob/master/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2016 Tevin Zhang"), widget.NewLabel("Copyright (c) 2014-2022 Ulrich Kunitz. All rights reserved."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/yuin/goldmark", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/yuin/goldmark", &url.URL{
@ -518,20 +534,7 @@ func (v View) getOther() *fyne.Container {
Host: "cs.opensource.google", Host: "cs.opensource.google",
Path: "go/x/image/+/master:LICENSE", Path: "go/x/image/+/master:LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2009 The Go Authors. All rights reserved."), widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("golang.org/x/mobile", &url.URL{
Scheme: "https",
Host: "pkg.go.dev",
Path: "golang.org/x/mobile",
})),
container.NewHBox(widget.NewHyperlink("License", &url.URL{
Scheme: "https",
Host: "cs.opensource.google",
Path: "go/x/mobile/+/master:LICENSE",
})),
widget.NewLabel("Copyright (c) 2009 The Go Authors. All rights reserved."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("golang.org/x/net", &url.URL{ container.NewHBox(widget.NewHyperlink("golang.org/x/net", &url.URL{
@ -544,7 +547,7 @@ func (v View) getOther() *fyne.Container {
Host: "cs.opensource.google", Host: "cs.opensource.google",
Path: "go/x/net/+/master:LICENSE", Path: "go/x/net/+/master:LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2009 The Go Authors. All rights reserved."), widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("golang.org/x/sys", &url.URL{ container.NewHBox(widget.NewHyperlink("golang.org/x/sys", &url.URL{
@ -557,7 +560,7 @@ func (v View) getOther() *fyne.Container {
Host: "cs.opensource.google", Host: "cs.opensource.google",
Path: "go/x/sys/+/master:LICENSE", Path: "go/x/sys/+/master:LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2009 The Go Authors. All rights reserved."), widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("golang.org/x/text", &url.URL{ container.NewHBox(widget.NewHyperlink("golang.org/x/text", &url.URL{
@ -570,7 +573,7 @@ func (v View) getOther() *fyne.Container {
Host: "cs.opensource.google", Host: "cs.opensource.google",
Path: "go/x/text/+/master:LICENSE", Path: "go/x/text/+/master:LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2009 The Go Authors. All rights reserved."), widget.NewLabel("Copyright 2009 The Go Authors."),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("gopkg.in/yaml.v3", &url.URL{ container.NewHBox(widget.NewHyperlink("gopkg.in/yaml.v3", &url.URL{
@ -578,38 +581,14 @@ func (v View) getOther() *fyne.Container {
Host: "github.com", Host: "github.com",
Path: "go-yaml/yaml/tree/v3.0.1", Path: "go-yaml/yaml/tree/v3.0.1",
})), })),
container.NewHBox(widget.NewHyperlink("Licensed under the Apache License, Version 2.0", &url.URL{ container.NewHBox(widget.NewHyperlink("MIT License and Apache License 2.0", &url.URL{
Scheme: "http", Scheme: "http",
Host: "www.apache.org",
Path: "licenses/LICENSE-2.0",
})),
widget.NewLabel("Copyright 2011-2016 Canonical Ltd."),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("gorm.io/gorm", &url.URL{
Scheme: "https",
Host: "github.com", Host: "github.com",
Path: "go-gorm/gorm", Path: "go-yaml/yaml/blob/v3.0.1/LICENSE",
})), })),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{ widget.NewLabel("Copyright (c) 2006-2010 Kirill Simonov"),
Scheme: "https", widget.NewLabel("Copyright (c) 2006-2011 Kirill Simonov"),
Host: "github.com", widget.NewLabel("Copyright (c) 2011-2019 Canonical Ltd"),
Path: "go-gorm/gorm/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>"),
canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("honnef.co/go/js/dom", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "dominikh/go-js-dom",
})),
container.NewHBox(widget.NewHyperlink("The MIT License (MIT)", &url.URL{
Scheme: "https",
Host: "github.com",
Path: "dominikh/go-js-dom/blob/master/LICENSE",
})),
widget.NewLabel("Copyright (c) 2014 Dominik Honnef"),
canvas.NewLine(colornames.Darkgreen), canvas.NewLine(colornames.Darkgreen),
container.NewHBox(widget.NewHyperlink("github.com/golang/go", &url.URL{ container.NewHBox(widget.NewHyperlink("github.com/golang/go", &url.URL{
@ -622,20 +601,7 @@ func (v View) getOther() *fyne.Container {
Host: "github.com", Host: "github.com",
Path: "golang/go/blob/master/LICENSE", Path: "golang/go/blob/master/LICENSE",
})), })),
widget.NewLabel("Copyright (c) 2009 The Go Authors. All rights reserved."), widget.NewLabel("Copyright 2009 The Go Authors."),
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 (c) 2009 The Go Authors. All rights reserved."),
canvas.NewLine(colornames.Darkgreen), 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