diff --git a/.gitignore b/.gitignore
index e43b0f9..b25a863 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
.DS_Store
+.vscode
+.history
diff --git a/docs/assets/linux.gif b/docs/assets/linux.gif
new file mode 100644
index 0000000..2f43b28
Binary files /dev/null and b/docs/assets/linux.gif differ
diff --git a/linux/README.md b/linux/README.md
new file mode 100644
index 0000000..ad8de83
--- /dev/null
+++ b/linux/README.md
@@ -0,0 +1,3 @@
+# FFmpegKit Linux
+
+
diff --git a/linux/test-app-local-dependency/.gitignore b/linux/test-app-local-dependency/.gitignore
new file mode 100644
index 0000000..b560d65
--- /dev/null
+++ b/linux/test-app-local-dependency/.gitignore
@@ -0,0 +1,2 @@
+build
+src/Application.h
diff --git a/linux/test-app-local-dependency/CMakeLists.txt b/linux/test-app-local-dependency/CMakeLists.txt
new file mode 100644
index 0000000..1db570b
--- /dev/null
+++ b/linux/test-app-local-dependency/CMakeLists.txt
@@ -0,0 +1,69 @@
+cmake_minimum_required(VERSION 3.7)
+project(ffmpeg-kit-linux-test VERSION 4.5.1)
+
+include(CTest)
+enable_testing()
+
+set(ENV{PKG_CONFIG_PATH} "${CMAKE_SOURCE_DIR}/../../../ffmpeg-kit/prebuilt/bundle-linux/ffmpeg-kit/pkgconfig")
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(FFMPEG_KIT REQUIRED IMPORTED_TARGET ffmpeg-kit=4.5.1)
+pkg_check_modules(GTKMM REQUIRED IMPORTED_TARGET gtkmm-3.0>=3.0)
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/data/ffmpeg-kit-linux-test-app.sh.in ${CMAKE_CURRENT_BINARY_DIR}/bin/ffmpeg-kit-linux-test-app.sh @ONLY)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/data/com.arthenica.ffmpegkit.test.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/data/com.arthenica.ffmpegkit.test.desktop @ONLY)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Application.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/Application.h @ONLY)
+
+list(APPEND APP_SOURCES
+ "src/Application.cpp"
+ "src/Application.h"
+ "src/AudioTab.cpp"
+ "src/AudioTab.h"
+ "src/CommandTab.cpp"
+ "src/CommandTab.h"
+ "src/Constants.h"
+ "src/ConcurrentExecutionTab.cpp"
+ "src/ConcurrentExecutionTab.h"
+ "src/FFmpegKitTest.cpp"
+ "src/FFmpegKitTest.h"
+ "src/HttpsTab.cpp"
+ "src/HttpsTab.h"
+ "src/main.cpp"
+ "src/MediaInformationParserTest.cpp"
+ "src/MediaInformationParserTest.h"
+ "src/OtherTab.cpp"
+ "src/OtherTab.h"
+ "src/PipeTab.cpp"
+ "src/PipeTab.h"
+ "src/Popup.cpp"
+ "src/Popup.h"
+ "src/ProgressDialog.cpp"
+ "src/ProgressDialog.h"
+ "src/SubtitleTab.cpp"
+ "src/SubtitleTab.h"
+ "src/Util.cpp"
+ "src/Util.h"
+ "src/Video.cpp"
+ "src/Video.h"
+ "src/VideoTab.cpp"
+ "src/VideoTab.h"
+ "src/VidStabTab.cpp"
+ "src/VidStabTab.h"
+)
+
+add_executable(ffmpeg-kit-linux-test ${APP_SOURCES})
+set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "ffmpeg-kit-linux-test-app")
+target_link_libraries(${PROJECT_NAME} PUBLIC PkgConfig::FFMPEG_KIT)
+target_link_libraries(${PROJECT_NAME} PUBLIC PkgConfig::GTKMM)
+target_link_libraries(${PROJECT_NAME} PUBLIC pthread)
+
+set(CPACK_PROJECT_NAME ${PROJECT_NAME})
+set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
+include(CPack)
+
+install (TARGETS ${CMAKE_PROJECT_NAME} RUNTIME DESTINATION bin)
+install (DIRECTORY data/fonts DESTINATION share)
+install (DIRECTORY data/icons DESTINATION share)
+install (DIRECTORY data/images DESTINATION share)
+install (DIRECTORY data/subtitles DESTINATION share)
+install (FILES ${CMAKE_CURRENT_BINARY_DIR}/bin/ffmpeg-kit-linux-test-app.sh DESTINATION bin PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_WRITE WORLD_READ)
+install (FILES ${CMAKE_CURRENT_BINARY_DIR}/data/com.arthenica.ffmpegkit.test.desktop DESTINATION share/applications)
diff --git a/linux/test-app-local-dependency/data/com.arthenica.ffmpegkit.test.desktop.in b/linux/test-app-local-dependency/data/com.arthenica.ffmpegkit.test.desktop.in
new file mode 100644
index 0000000..e080291
--- /dev/null
+++ b/linux/test-app-local-dependency/data/com.arthenica.ffmpegkit.test.desktop.in
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=FFmpegKit Linux Test
+_Comment=FFmpeg for applications
+Icon=ffmpeg-kit-linux-test
+Exec=@CMAKE_INSTALL_PREFIX@/bin/ffmpeg-kit-linux-test-app.sh
+Categories=GNOME;GTK;Utility
+Type=Application
+StartupNotify=true
+Terminal=false
diff --git a/linux/test-app-local-dependency/data/ffmpeg-kit-linux-test-app.sh.in b/linux/test-app-local-dependency/data/ffmpeg-kit-linux-test-app.sh.in
new file mode 100755
index 0000000..33a0424
--- /dev/null
+++ b/linux/test-app-local-dependency/data/ffmpeg-kit-linux-test-app.sh.in
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=@FFMPEG_KIT_LIBRARY_PATH@
+
+./ffmpeg-kit-linux-test-app
\ No newline at end of file
diff --git a/linux/test-app-local-dependency/data/fonts/doppioone_regular.ttf b/linux/test-app-local-dependency/data/fonts/doppioone_regular.ttf
new file mode 100644
index 0000000..e87361e
Binary files /dev/null and b/linux/test-app-local-dependency/data/fonts/doppioone_regular.ttf differ
diff --git a/linux/test-app-local-dependency/data/fonts/sil_open_font_license.txt b/linux/test-app-local-dependency/data/fonts/sil_open_font_license.txt
new file mode 100644
index 0000000..6bbf728
--- /dev/null
+++ b/linux/test-app-local-dependency/data/fonts/sil_open_font_license.txt
@@ -0,0 +1,46 @@
+Copyright (c) 2011-2012, Julieta Ulanovsky (julieta.ulanovsky@gmail.com), with Reserved Font Names 'Montserrat'
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
+
+"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
+
+5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
\ No newline at end of file
diff --git a/linux/test-app-local-dependency/data/fonts/truenorg.otf b/linux/test-app-local-dependency/data/fonts/truenorg.otf
new file mode 100644
index 0000000..82a3952
Binary files /dev/null and b/linux/test-app-local-dependency/data/fonts/truenorg.otf differ
diff --git a/linux/test-app-local-dependency/data/icons/144x144/ffmpeg-kit-linux-test.png b/linux/test-app-local-dependency/data/icons/144x144/ffmpeg-kit-linux-test.png
new file mode 100644
index 0000000..7de6665
Binary files /dev/null and b/linux/test-app-local-dependency/data/icons/144x144/ffmpeg-kit-linux-test.png differ
diff --git a/linux/test-app-local-dependency/data/icons/192x192/ffmpeg-kit-linux-test.png b/linux/test-app-local-dependency/data/icons/192x192/ffmpeg-kit-linux-test.png
new file mode 100644
index 0000000..0d4f005
Binary files /dev/null and b/linux/test-app-local-dependency/data/icons/192x192/ffmpeg-kit-linux-test.png differ
diff --git a/linux/test-app-local-dependency/data/icons/48x48/ffmpeg-kit-linux-test.png b/linux/test-app-local-dependency/data/icons/48x48/ffmpeg-kit-linux-test.png
new file mode 100644
index 0000000..1ac5ab2
Binary files /dev/null and b/linux/test-app-local-dependency/data/icons/48x48/ffmpeg-kit-linux-test.png differ
diff --git a/linux/test-app-local-dependency/data/icons/72x72/ffmpeg-kit-linux-test.png b/linux/test-app-local-dependency/data/icons/72x72/ffmpeg-kit-linux-test.png
new file mode 100644
index 0000000..bf4f8b0
Binary files /dev/null and b/linux/test-app-local-dependency/data/icons/72x72/ffmpeg-kit-linux-test.png differ
diff --git a/linux/test-app-local-dependency/data/icons/96x96/ffmpeg-kit-linux-test.png b/linux/test-app-local-dependency/data/icons/96x96/ffmpeg-kit-linux-test.png
new file mode 100644
index 0000000..254880c
Binary files /dev/null and b/linux/test-app-local-dependency/data/icons/96x96/ffmpeg-kit-linux-test.png differ
diff --git a/linux/test-app-local-dependency/data/images/machupicchu.jpg b/linux/test-app-local-dependency/data/images/machupicchu.jpg
new file mode 100644
index 0000000..f4448e5
Binary files /dev/null and b/linux/test-app-local-dependency/data/images/machupicchu.jpg differ
diff --git a/linux/test-app-local-dependency/data/images/pyramid.jpg b/linux/test-app-local-dependency/data/images/pyramid.jpg
new file mode 100644
index 0000000..c25d21d
Binary files /dev/null and b/linux/test-app-local-dependency/data/images/pyramid.jpg differ
diff --git a/linux/test-app-local-dependency/data/images/stonehenge.jpg b/linux/test-app-local-dependency/data/images/stonehenge.jpg
new file mode 100644
index 0000000..9c04861
Binary files /dev/null and b/linux/test-app-local-dependency/data/images/stonehenge.jpg differ
diff --git a/linux/test-app-local-dependency/data/subtitles/subtitle.srt b/linux/test-app-local-dependency/data/subtitles/subtitle.srt
new file mode 100644
index 0000000..262584b
--- /dev/null
+++ b/linux/test-app-local-dependency/data/subtitles/subtitle.srt
@@ -0,0 +1,11 @@
+1
+00:00:00,021 --> 00:00:04,000
+Machu Picchu is a 15th-century Inca citadel, located in the Eastern Cordillera of southern Peru.
+
+2
+00:00:04,001 --> 00:00:06,927
+大金字塔和哈夫拉金字塔是古埃及最大的金字塔。
+
+3
+00:00:06,928 --> 00:00:09,000
+Stonehenge هو نصب ما قبل التاريخ في ويلتشير ، إنجلترا ، على بعد ميلين (3 كم) غرب Amesbury.
diff --git a/linux/test-app-local-dependency/src/Application.cpp b/linux/test-app-local-dependency/src/Application.cpp
new file mode 100644
index 0000000..db4d6d2
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Application.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "Application.h"
+#include
+#include
+#include
+#include
+
+using namespace ffmpegkit;
+
+static bool fs_exists(const std::string &s, const bool isFile, const bool isDirectory) {
+ struct stat dir_info;
+
+ if (stat(s.c_str(), &dir_info) == 0) {
+ if (isFile && S_ISREG(dir_info.st_mode)) {
+ return true;
+ }
+ if (isDirectory && S_ISDIR(dir_info.st_mode)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool fs_create_dir(const std::string& s) {
+ if (!fs_exists(s, false, true)) {
+ if (mkdir(s.c_str(), S_IRWXU | S_IRWXG | S_IROTH) != 0) {
+ std::cout << "Failed to create directory: " << s << ". Operation failed with " << errno << "." << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+std::ostream& operator<<(std::ostream& out, const std::chrono::time_point& o) {
+ char str[100];
+ std::time_t t = std::chrono::system_clock::to_time_t(o);
+ std::strftime(str, sizeof(str), "%c", std::localtime(&t));
+ return out << str;
+}
+
+ffmpegkittest::Application::Application() {
+ set_title("FFmpegKit Linux");
+ set_default_size(800, 600);
+ set_position(Gtk::WIN_POS_CENTER);
+
+ commandTab.setParentWindow(this);
+ videoTab.setParentWindow(this);
+ httpsTab.setParentWindow(this);
+ audioTab.setParentWindow(this);
+ subtitleTab.setParentWindow(this);
+ vidStabTab.setParentWindow(this);
+ pipeTab.setParentWindow(this);
+ concurrentExecutionTab.setParentWindow(this);
+ otherTab.setParentWindow(this);
+ tabs.append_page(commandTab, "Command");
+ tabs.append_page(videoTab, "Video");
+ tabs.append_page(httpsTab, "HTTPS");
+ tabs.append_page(audioTab, "Audio");
+ tabs.append_page(subtitleTab, "Subtitle");
+ tabs.append_page(vidStabTab, "Vid.Stab");
+ tabs.append_page(pipeTab, "Pipe");
+ tabs.append_page(concurrentExecutionTab, "Concurrent Execution");
+ tabs.append_page(otherTab, "Other");
+ tabs.signal_switch_page().connect(sigc::mem_fun(*this, &Application::onTabSelected));
+
+ add(tabs);
+
+ show_all_children();
+
+ initApplicationCacheDirectory();
+
+ registerApplicationFonts();
+ std::cout << "Application fonts registered." << std::endl;
+
+ FFmpegKitConfig::ignoreSignal(SignalXcpu);
+ FFmpegKitConfig::setLogLevel(LevelAVLogInfo);
+}
+
+void ffmpegkittest::Application::initApplicationCacheDirectory() {
+ auto appCacheDir = ffmpegkittest::Application::getApplicationCacheDirectory();
+
+ if (!fs_exists(appCacheDir, false, true)) {
+ if (mkdir(appCacheDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH) != 0) {
+ std::cout << "Failed to create application cache directory: " << appCacheDir << ". Operation failed with " << errno << "." << std::endl;
+ }
+ }
+}
+
+void ffmpegkittest::Application::onTabSelected(const Widget* page, const guint page_number) {
+ switch (page_number) {
+ case 0:
+ commandTab.setActive();
+ break;
+ case 1:
+ videoTab.setActive();
+ break;
+ case 2:
+ httpsTab.setActive();
+ break;
+ case 3:
+ audioTab.setActive();
+ break;
+ case 4:
+ subtitleTab.setActive();
+ break;
+ case 5:
+ vidStabTab.setActive();
+ break;
+ case 6:
+ pipeTab.setActive();
+ break;
+ case 7:
+ concurrentExecutionTab.setActive();
+ break;
+ case 8:
+ otherTab.setActive();
+ break;
+ default:
+ commandTab.setActive();
+ break;
+ }
+}
+
+void ffmpegkittest::Application::listFFmpegSessions() {
+ auto ffmpegSessions = FFmpegKit::listSessions();
+ std::cout << "Listing FFmpeg sessions." << std::endl;
+ int i = 0;
+ std::for_each(ffmpegSessions->begin(), ffmpegSessions->end(), [&](const std::shared_ptr session) {
+ std::cout << "Session " << i++ << " = id:" << session->getSessionId() << ", startTime:" << session->getStartTime() << ", duration:" << session-> getDuration() << ", state:" << FFmpegKitConfig::sessionStateToString(session->getState()) << ", returnCode:" << session->getReturnCode() << "." << std::endl;
+ });
+ std::cout << "Listed FFmpeg sessions." << std::endl;
+}
+
+void ffmpegkittest::Application::listFFprobeSessions() {
+ auto ffprobeSessions = FFprobeKit::listFFprobeSessions();
+ std::cout << "Listing FFprobe sessions." << std::endl;
+ int i = 0;
+ std::for_each(ffprobeSessions->begin(), ffprobeSessions->end(), [&](const std::shared_ptr session) {
+ std::cout << "Session " << i++ << " = id:" << session->getSessionId() << ", startTime:" << session->getStartTime() << ", duration:" << session-> getDuration() << ", state:" << FFmpegKitConfig::sessionStateToString(session->getState()) << ", returnCode:" << session->getReturnCode() << "." << std::endl;
+ });
+ std::cout << "Listed FFprobe sessions." << std::endl;
+}
+
+std::string ffmpegkittest::Application::getApplicationCacheDirectory() {
+ return Glib::get_user_cache_dir() + "/ffmpegkittest";
+}
+
+void ffmpegkittest::Application::registerApplicationFonts() {
+ auto fontDirectory = Application::getApplicationInstallDirectory() + "/share/fonts";
+ auto reportFile = Application::getApplicationCacheDirectory() + "/ffreport.txt";
+
+ FFmpegKitConfig::setFontDirectoryList(std::list{fontDirectory, "/usr/share/fonts"}, std::map{{"MyFontName", "Doppio One"}});
+ FFmpegKitConfig::setEnvironmentVariable("FFREPORT", reportFile.c_str());
+}
diff --git a/linux/test-app-local-dependency/src/Application.h.in b/linux/test-app-local-dependency/src/Application.h.in
new file mode 100644
index 0000000..f8965d2
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Application.h.in
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_APPLICATION_H
+#define FFMPEG_KIT_TEST_APPLICATION_H
+
+#include
+#include "AudioTab.h"
+#include "CommandTab.h"
+#include "ConcurrentExecutionTab.h"
+#include "HttpsTab.h"
+#include "OtherTab.h"
+#include "PipeTab.h"
+#include "SubtitleTab.h"
+#include "VideoTab.h"
+#include "VidStabTab.h"
+
+namespace ffmpegkittest {
+
+ class Application : public Gtk::Window {
+ public:
+ Application();
+ static void listFFmpegSessions();
+ static void listFFprobeSessions();
+ static std::string getApplicationCacheDirectory();
+ static std::string getApplicationInstallDirectory() {
+ return "@CMAKE_INSTALL_PREFIX@";
+ }
+
+ protected:
+ void initApplicationCacheDirectory();
+ void onTabSelected(const Widget* page, const guint page_number);
+
+ private:
+ void registerApplicationFonts();
+
+ Gtk::Notebook tabs;
+ AudioTab audioTab;
+ CommandTab commandTab;
+ ConcurrentExecutionTab concurrentExecutionTab;
+ HttpsTab httpsTab;
+ OtherTab otherTab;
+ PipeTab pipeTab;
+ SubtitleTab subtitleTab;
+ VideoTab videoTab;
+ VidStabTab vidStabTab;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_APPLICATION_H
diff --git a/linux/test-app-local-dependency/src/AudioTab.cpp b/linux/test-app-local-dependency/src/AudioTab.cpp
new file mode 100644
index 0000000..7bdff8d
--- /dev/null
+++ b/linux/test-app-local-dependency/src/AudioTab.cpp
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "AudioTab.h"
+#include "Application.h"
+#include "Constants.h"
+#include "Popup.h"
+#include
+#include
+
+using namespace ffmpegkit;
+
+static gboolean showEncodeSuccessPopup(const std::pair* parameters) {
+ Gtk::Window* window = parameters->first;
+ auto messageDetail = parameters->second;
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_INFO, messageDetail);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean showEncodeFailedPopup(const std::pair* parameters) {
+ Gtk::Window* window = parameters->first;
+ auto messageDetail = parameters->second;
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_ERROR, messageDetail);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean appendLog(const std::pair>* parameters) {
+ ffmpegkittest::AudioTab* audioTab = parameters->first;
+ auto log = parameters->second;
+ audioTab->appendOutput(log->getMessage());
+ delete parameters;
+ return FALSE;
+}
+
+ffmpegkittest::AudioTab::AudioTab() : selectedCodec(-1) {
+ audioCodecModel = Gtk::ListStore::create(audioCodecModelColumn);
+ audioCodec.set_model(audioCodecModel);
+ audioCodec.set_size_request(240, 30);
+ audioCodec.signal_changed().connect(sigc::mem_fun(*this, &AudioTab::onAudioCodecChanged));
+ Util::applyComboBoxStyle(audioCodec);
+
+ initAudioCodecData();
+
+ encodeButton.set_label("ENCODE");
+ encodeButton.set_size_request(120, 30);
+ encodeButton.set_tooltip_text(Constants::AudioTestTooltipText);
+ encodeButton.signal_clicked().connect(sigc::mem_fun(*this, &AudioTab::encodeAudio));
+ encodeButton.set_sensitive(false);
+ Util::applyButtonStyle(encodeButton);
+ encodeButtonBox.pack_start(encodeButton, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(audioCodecBox, Gtk::PACK_SHRINK);
+ pack_start(encodeButtonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+}
+
+void ffmpegkittest::AudioTab::setActive() {
+ std::cout << "Audio Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback(nullptr);
+ FFmpegKitConfig::enableStatisticsCallback(nullptr);
+ createAudioSample();
+ FFmpegKitConfig::enableLogCallback([this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ });
+}
+
+void ffmpegkittest::AudioTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::AudioTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::AudioTab::createAudioSample() {
+ std::cout << "Creating AUDIO sample before the test." << std::endl;
+
+ auto audioSampleFile = getAudioSampleFile();
+ std::remove(audioSampleFile.c_str());
+
+ std::string ffmpegCommand = "-hide_banner -y -f lavfi -i sine=frequency=1000:duration=5 -c:a pcm_s16le " + audioSampleFile;
+
+ std::cout << "Creating audio sample with '" << ffmpegCommand << "'." << std::endl;
+
+ auto session = FFmpegKit::execute(ffmpegCommand);
+ if (ReturnCode::isSuccess(session->getReturnCode())) {
+ encodeButton.set_sensitive(true);
+ std::cout << "AUDIO sample created." << std::endl;
+ } else {
+ std::cout << "Creating AUDIO sample failed with state " << FFmpegKitConfig::sessionStateToString(session->getState()) << " and rc " << session->getReturnCode() << "." << session->getFailStackTrace() << std::endl;
+ g_idle_add((GSourceFunc)showEncodeFailedPopup, new std::pair(this->parentWindow, "Creating AUDIO sample failed. Please check logs for the details."));
+ }
+}
+
+void ffmpegkittest::AudioTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
+
+void ffmpegkittest::AudioTab::initAudioCodecData() {
+ auto row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "1";
+ row[audioCodecModelColumn.columnName] = "mp2 (twolame)";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "2";
+ row[audioCodecModelColumn.columnName] = "mp3 (liblame)";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "3";
+ row[audioCodecModelColumn.columnName] = "mp3 (libshine)";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "4";
+ row[audioCodecModelColumn.columnName] = "vorbis";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "5";
+ row[audioCodecModelColumn.columnName] = "opus";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "6";
+ row[audioCodecModelColumn.columnName] = "amr-nb";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "7";
+ row[audioCodecModelColumn.columnName] = "amr-wb";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "8";
+ row[audioCodecModelColumn.columnName] = "ilbc";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "9";
+ row[audioCodecModelColumn.columnName] = "soxr";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "10";
+ row[audioCodecModelColumn.columnName] = "speex";
+
+ row = *(audioCodecModel->append());
+ row[audioCodecModelColumn.columnId] = "11";
+ row[audioCodecModelColumn.columnName] = "wavpack";
+
+ audioCodec.pack_start(audioCodecModelColumn.columnName);
+ audioCodec.set_entry_text_column(audioCodecModelColumn.columnId);
+ audioCodec.set_active(0);
+
+ audioCodecBox.pack_start(audioCodec, Gtk::PACK_EXPAND_PADDING);
+}
+
+void ffmpegkittest::AudioTab::onAudioCodecChanged() {
+ int rowNumber = audioCodec.get_active_row_number();
+ if (rowNumber != -1) {
+ selectedCodec = rowNumber;
+ }
+}
+
+std::string ffmpegkittest::AudioTab::getSelectedAudioCodec() {
+ switch(selectedCodec) {
+ case 0: return "mp2 (twolame)";
+ case 1: return "mp3 (liblame)";
+ case 2: return "mp3 (libshine)";
+ case 3: return "vorbis";
+ case 4: return "opus";
+ case 5: return "amr-nb";
+ case 6: return "amr-wb";
+ case 7: return "ilbc";
+ case 8: return "soxr";
+ case 9: return "speex";
+ case 10: return "wavpack";
+ default: return "";
+ }
+}
+
+void ffmpegkittest::AudioTab::encodeAudio() {
+ auto audioOutputFile = getAudioOutputFile();
+ std::remove(audioOutputFile.c_str());
+
+ auto audioCodec = getSelectedAudioCodec();
+
+ std::cout << "Testing AUDIO encoding with '" << audioCodec << "' codec." << std::endl;
+
+ auto ffmpegCommand = generateAudioEncodeScript();
+
+ showProgressDialog();
+
+ clearOutput();
+
+ std::cout << "FFmpeg process started with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ auto session = FFmpegKit::executeAsync(ffmpegCommand, [this](auto session) {
+ const auto state = session->getState();
+ auto returnCode = session->getReturnCode();
+
+ this->hideProgressDialog();
+
+ if (ReturnCode::isSuccess(returnCode)) {
+ g_idle_add((GSourceFunc)showEncodeSuccessPopup, new std::pair(this->parentWindow, "Encode completed successfully."));
+ std::cout << "Encode completed successfully." << std::endl;
+ } else {
+ g_idle_add((GSourceFunc)showEncodeFailedPopup, new std::pair(this->parentWindow, "Encode failed. Please check logs for the details."));
+ std::cout << "Encode failed with state " << FFmpegKitConfig::sessionStateToString(state) << " and rc " << returnCode << "." << session->getFailStackTrace() << std::endl;
+ }
+ });
+}
+
+std::string ffmpegkittest::AudioTab::getAudioOutputFile() {
+ std::string audioCodec = getSelectedAudioCodec();
+
+ std::string extension;
+ if (audioCodec.compare("mp2 (twolame)") == 0) {
+ extension = "mpg";
+ } else if (audioCodec.compare("mp3 (liblame)") == 0 || audioCodec.compare("mp3 (libshine)") == 0) {
+ extension = "mp3";
+ } else if (audioCodec.compare("vorbis") == 0) {
+ extension = "ogg";
+ } else if (audioCodec.compare("opus") == 0) {
+ extension = "opus";
+ } else if (audioCodec.compare("amr-nb") == 0 || audioCodec.compare("amr-wb") == 0) {
+ extension = "amr";
+ } else if (audioCodec.compare("ilbc") == 0) {
+ extension = "lbc";
+ } else if (audioCodec.compare("speex") == 0) {
+ extension = "spx";
+ } else if (audioCodec.compare("wavpack") == 0) {
+ extension = "wv";
+ } else {
+
+ // soxr
+ extension = "wav";
+ }
+
+ return Application::getApplicationCacheDirectory() + "/audio." + extension;
+}
+
+std::string ffmpegkittest::AudioTab::getAudioSampleFile() {
+ return Application::getApplicationCacheDirectory() + "/audio-sample.wav";
+}
+
+void ffmpegkittest::AudioTab::showProgressDialog() {
+ // progressDialog.show(this->get_parent_window());
+}
+
+void ffmpegkittest::AudioTab::hideProgressDialog() {
+ // progressDialog.hide();
+}
+
+std::string ffmpegkittest::AudioTab::generateAudioEncodeScript() {
+ auto audioCodec = getSelectedAudioCodec();
+ auto audioSampleFile = getAudioSampleFile();
+ auto audioOutputFile = getAudioOutputFile();
+
+ if (audioCodec.compare("mp2 (twolame)") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -c:a mp2 -b:a 192k " + audioOutputFile;
+ } else if (audioCodec.compare("mp3 (liblame)") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -c:a libmp3lame -qscale:a 2 " + audioOutputFile;
+ } else if (audioCodec.compare("mp3 (libshine)") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -c:a libshine -qscale:a 2 " + audioOutputFile;
+ } else if (audioCodec.compare("vorbis") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -c:a libvorbis -b:a 64k " + audioOutputFile;
+ } else if (audioCodec.compare("opus") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -c:a libopus -b:a 64k -vbr on -compression_level 10 " + audioOutputFile;
+ } else if (audioCodec.compare("amr-nb") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -ar 8000 -ab 12.2k -c:a libopencore_amrnb " + audioOutputFile;
+ } else if (audioCodec.compare("amr-wb") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -ar 8000 -ab 12.2k -c:a libvo_amrwbenc -strict experimental " + audioOutputFile;
+ } else if (audioCodec.compare("ilbc") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -c:a ilbc -ar 8000 -b:a 15200 " + audioOutputFile;
+ } else if (audioCodec.compare("speex") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -c:a libspeex -ar 16000 " + audioOutputFile;
+ } else if (audioCodec.compare("wavpack") == 0) {
+ return "-hide_banner -y -i " + audioSampleFile + " -c:a wavpack -b:a 64k " + audioOutputFile;
+ } else {
+
+ // soxr
+ return "-hide_banner -y -i " + audioSampleFile + " -af aresample=resampler=soxr -ar 44100 " + audioOutputFile;
+ }
+}
diff --git a/linux/test-app-local-dependency/src/AudioTab.h b/linux/test-app-local-dependency/src/AudioTab.h
new file mode 100644
index 0000000..075ab47
--- /dev/null
+++ b/linux/test-app-local-dependency/src/AudioTab.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_AUDIO_TAB_H
+#define FFMPEG_KIT_TEST_AUDIO_TAB_H
+
+#include "ProgressDialog.h"
+#include "Util.h"
+#include
+
+namespace ffmpegkittest {
+
+ class AudioTab: public Gtk::VBox {
+ public:
+ AudioTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+
+ private:
+ void createAudioSample();
+ void clearOutput();
+ void initAudioCodecData();
+ void onAudioCodecChanged();
+ std::string getSelectedAudioCodec();
+ void encodeAudio();
+ std::string getAudioOutputFile();
+ std::string getAudioSampleFile();
+ void showProgressDialog();
+ void hideProgressDialog();
+ std::string generateAudioEncodeScript();
+
+ Glib::RefPtr audioCodecModel;
+ ComboBoxModelColumn audioCodecModelColumn;
+ Gtk::ComboBox audioCodec;
+ Gtk::HBox audioCodecBox;
+ int selectedCodec;
+ Gtk::Button encodeButton;
+ Gtk::HBox encodeButtonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ ffmpegkittest::ProgressDialog progressDialog;
+ Gtk::Window* parentWindow;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_AUDIO_TAB_H
diff --git a/linux/test-app-local-dependency/src/CommandTab.cpp b/linux/test-app-local-dependency/src/CommandTab.cpp
new file mode 100644
index 0000000..a413356
--- /dev/null
+++ b/linux/test-app-local-dependency/src/CommandTab.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "CommandTab.h"
+#include "Application.h"
+#include "Constants.h"
+#include "Popup.h"
+#include
+#include
+#include
+#include
+#include
+
+using namespace ffmpegkit;
+
+static gboolean showCommandFailedPopup(Gtk::Window* window) {
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_ERROR, "Command failed. Please check output for the details.");
+ return FALSE;
+}
+
+static gboolean appendLog(const std::pair>* parameters) {
+ ffmpegkittest::CommandTab* commandTab = parameters->first;
+ auto log = parameters->second;
+ commandTab->appendOutput(log->getMessage());
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean appendSessionOutput(const std::pair>* parameters) {
+ ffmpegkittest::CommandTab* commandTab = parameters->first;
+ auto session = parameters->second;
+ commandTab->appendOutput(session->getOutput());
+ delete parameters;
+ return FALSE;
+}
+
+ffmpegkittest::CommandTab::CommandTab() : parentWindow(nullptr) {
+ commandText.set_placeholder_text("Enter command");
+ Util::applyEditTextStyle(commandText);
+
+ runFFmpegButton.set_label("RUN FFMPEG");
+ runFFmpegButton.set_size_request(120, 30);
+ runFFmpegButton.set_tooltip_text(Constants::CommandTestFFmpegTooltipText);
+ runFFmpegButton.signal_clicked().connect(sigc::mem_fun(*this, &CommandTab::runFFmpeg));
+ Util::applyButtonStyle(runFFmpegButton);
+ runFFmpegButtonBox.pack_start(runFFmpegButton, Gtk::PACK_EXPAND_PADDING);
+
+ runFFprobeButton.set_label("RUN FFPROBE");
+ runFFprobeButton.set_size_request(120, 30);
+ runFFprobeButton.set_tooltip_text(Constants::CommandTestFFprobeTooltipText);
+ runFFprobeButton.signal_clicked().connect(sigc::mem_fun(*this, &CommandTab::runFFprobe));
+ Util::applyButtonStyle(runFFprobeButton);
+ runFFprobeButtonBox.pack_start(runFFprobeButton, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(commandText, Gtk::PACK_SHRINK);
+ pack_start(runFFmpegButtonBox, Gtk::PACK_SHRINK);
+ pack_start(runFFprobeButtonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+}
+
+void ffmpegkittest::CommandTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::CommandTab::runFFmpeg() {
+ clearOutput();
+
+ std::string ffmpegCommand(commandText.get_text());
+
+ std::cout << "Current log level is " << FFmpegKitConfig::logLevelToString(FFmpegKitConfig::getLogLevel()) << "." << std::endl;
+
+ std::cout << "Testing FFmpeg COMMAND asynchronously." << std::endl;
+
+ std::cout << "FFmpeg process started with arguments: '" << ffmpegCommand << "'" << std::endl;
+
+ FFmpegKit::executeAsync(ffmpegCommand, [this](auto session) {
+ const auto state = session->getState();
+ auto returnCode = session->getReturnCode();
+
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(state) << " and rc " << returnCode << "." << session->getFailStackTrace() << std::endl;
+
+ if (state == SessionStateFailed || !returnCode->isValueSuccess()) {
+ g_idle_add((GSourceFunc)showCommandFailedPopup, this->parentWindow);
+ }
+ }, [this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ }, nullptr);
+}
+
+void ffmpegkittest::CommandTab::runFFprobe() {
+ clearOutput();
+
+ std::string ffprobeCommand(commandText.get_text());
+
+ std::cout << "Testing FFprobe COMMAND asynchronously." << std::endl;
+
+ std::cout << "FFprobe process started with arguments: '" << ffprobeCommand << "'" << std::endl;
+
+ auto session = FFprobeSession::create(FFmpegKitConfig::parseArguments(ffprobeCommand.c_str()), [this](auto session) {
+ const auto state = session->getState();
+ auto returnCode = session->getReturnCode();
+
+ g_idle_add((GSourceFunc)appendSessionOutput, new std::pair>(this, session));
+
+ std::cout << "FFprobe process exited with state " << FFmpegKitConfig::sessionStateToString(state) << " and rc " << returnCode << "." << session->getFailStackTrace() << std::endl;
+
+ if (state == SessionStateFailed || !returnCode->isValueSuccess()) {
+ g_idle_add((GSourceFunc)showCommandFailedPopup, this->parentWindow);
+ }
+
+ }, nullptr, LogRedirectionStrategyNeverPrintLogs);
+
+ FFmpegKitConfig::asyncFFprobeExecute(session);
+
+ ffmpegkittest::Application::listFFprobeSessions();
+}
+
+void ffmpegkittest::CommandTab::setActive() {
+ std::cout << "Command Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback(nullptr);
+}
+
+void ffmpegkittest::CommandTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::CommandTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
diff --git a/linux/test-app-local-dependency/src/CommandTab.h b/linux/test-app-local-dependency/src/CommandTab.h
new file mode 100644
index 0000000..5813b83
--- /dev/null
+++ b/linux/test-app-local-dependency/src/CommandTab.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_COMMAND_TAB_H
+#define FFMPEG_KIT_TEST_COMMAND_TAB_H
+
+#include "Util.h"
+#include
+#include
+#include
+
+namespace ffmpegkittest {
+
+ class CommandTab: public Gtk::VBox {
+ public:
+ CommandTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+
+ private:
+ void clearOutput();
+ void runFFmpeg();
+ void runFFprobe();
+
+ Gtk::Entry commandText;
+ Gtk::Button runFFmpegButton;
+ Gtk::HBox runFFmpegButtonBox;
+ Gtk::Button runFFprobeButton;
+ Gtk::HBox runFFprobeButtonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ Gtk::Window* parentWindow;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_COMMAND_TAB_H
diff --git a/linux/test-app-local-dependency/src/ConcurrentExecutionTab.cpp b/linux/test-app-local-dependency/src/ConcurrentExecutionTab.cpp
new file mode 100644
index 0000000..672214f
--- /dev/null
+++ b/linux/test-app-local-dependency/src/ConcurrentExecutionTab.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "ConcurrentExecutionTab.h"
+#include "Application.h"
+#include "Constants.h"
+#include "Log.h"
+#include "Popup.h"
+#include "Video.h"
+#include
+#include
+
+using namespace ffmpegkit;
+
+static long sessionId1 = -1;
+static long sessionId2 = -1;
+static long sessionId3 = -1;
+
+static gboolean appendLog(const std::pair>* parameters) {
+ ffmpegkittest::ConcurrentExecutionTab* concurrentExecutionTab = parameters->first;
+ auto log = parameters->second;
+ concurrentExecutionTab->appendOutput(log->getMessage());
+ delete parameters;
+ return FALSE;
+}
+
+ffmpegkittest::ConcurrentExecutionTab::ConcurrentExecutionTab() {
+ encodeButton1.set_label("ENCODE 1");
+ encodeButton1.set_size_request(120, 30);
+ encodeButton1.set_tooltip_text(Constants::ConcurrentExecutionTestTooltipText);
+ encodeButton1.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ConcurrentExecutionTab::encodeVideo), 1));
+ Util::applyButtonStyle(encodeButton1);
+ encodeButtonBox.pack_start(encodeButton1, Gtk::PACK_EXPAND_PADDING);
+ encodeButton2.set_label("ENCODE 2");
+ encodeButton2.set_size_request(120, 30);
+ encodeButton2.set_tooltip_text(Constants::ConcurrentExecutionTestTooltipText);
+ encodeButton2.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ConcurrentExecutionTab::encodeVideo), 2));
+ Util::applyButtonStyle(encodeButton2);
+ encodeButtonBox.pack_start(encodeButton2, Gtk::PACK_EXPAND_PADDING);
+ encodeButton3.set_label("ENCODE 3");
+ encodeButton3.set_size_request(120, 30);
+ encodeButton3.set_tooltip_text(Constants::ConcurrentExecutionTestTooltipText);
+ encodeButton3.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ConcurrentExecutionTab::encodeVideo), 3));
+ Util::applyButtonStyle(encodeButton3);
+ encodeButtonBox.pack_start(encodeButton3, Gtk::PACK_EXPAND_PADDING);
+
+ cancelButton1.set_label("CANCEL 1");
+ cancelButton1.set_size_request(120, 30);
+ cancelButton1.set_tooltip_text(Constants::ConcurrentExecutionTestTooltipText);
+ cancelButton1.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ConcurrentExecutionTab::cancel), 1));
+ Util::applyButtonStyle(cancelButton1);
+ cancelButtonBox.pack_start(cancelButton1, Gtk::PACK_EXPAND_PADDING);
+ cancelButton2.set_label("CANCEL 2");
+ cancelButton2.set_size_request(120, 30);
+ cancelButton2.set_tooltip_text(Constants::ConcurrentExecutionTestTooltipText);
+ cancelButton2.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ConcurrentExecutionTab::cancel), 2));
+ Util::applyButtonStyle(cancelButton2);
+ cancelButtonBox.pack_start(cancelButton2, Gtk::PACK_EXPAND_PADDING);
+ cancelButton3.set_label("CANCEL 3");
+ cancelButton3.set_size_request(120, 30);
+ cancelButton3.set_tooltip_text(Constants::ConcurrentExecutionTestTooltipText);
+ cancelButton3.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ConcurrentExecutionTab::cancel), 3));
+ Util::applyButtonStyle(cancelButton3);
+ cancelButtonBox.pack_start(cancelButton3, Gtk::PACK_EXPAND_PADDING);
+ cancelButton4.set_label("CANCEL ALL");
+ cancelButton4.set_size_request(120, 30);
+ cancelButton4.set_tooltip_text(Constants::ConcurrentExecutionTestTooltipText);
+ cancelButton4.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ConcurrentExecutionTab::cancel), 0));
+ Util::applyButtonStyle(cancelButton4);
+ cancelButtonBox.pack_start(cancelButton4, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(encodeButtonBox, Gtk::PACK_SHRINK);
+ pack_start(cancelButtonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+}
+
+void ffmpegkittest::ConcurrentExecutionTab::setActive() {
+ std::cout << "Concurrent Execution Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback([this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ });
+}
+
+void ffmpegkittest::ConcurrentExecutionTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::ConcurrentExecutionTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::ConcurrentExecutionTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
+
+void ffmpegkittest::ConcurrentExecutionTab::encodeVideo(const int buttonNumber) {
+ clearOutput();
+
+ std::string image1File = Application::getApplicationInstallDirectory() + "/share/images/machupicchu.jpg";
+ std::string image2File = Application::getApplicationInstallDirectory() + "/share/images/pyramid.jpg";
+ std::string image3File = Application::getApplicationInstallDirectory() + "/share/images/stonehenge.jpg";
+ std::string videoFile = Application::getApplicationCacheDirectory() + "/video" + std::to_string(buttonNumber) + ".mp4";
+
+ std::cout << "Testing CONCURRENT EXECUTION for button " << buttonNumber << "." << std::endl;
+
+ std::string ffmpegCommand = Video::generateEncodeVideoScript(image1File, image2File, image3File, videoFile, "mpeg4", "");
+
+ std::cout << "FFmpeg process starting for button " << buttonNumber << " with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ auto session = FFmpegKit::executeAsync(ffmpegCommand, [this, buttonNumber](auto session) {
+ const auto state = session->getState();
+ auto returnCode = session->getReturnCode();
+
+ if (ReturnCode::isCancel(returnCode)) {
+ std::cout << "FFmpeg process ended with cancel for button " << buttonNumber << " with sessionId " << session->getSessionId() << "." << std::endl;
+ } else {
+ std::cout << "FFmpeg process ended with state " << FFmpegKitConfig::sessionStateToString(state) << " and rc " << returnCode << " for button " << buttonNumber << " with sessionId " << session->getSessionId() << "." << session->getFailStackTrace() << std::endl;
+ }
+ });
+
+ const long sessionId = session->getSessionId();
+
+ std::cout << "Async FFmpeg process started for button " << buttonNumber << " with sessionId " << session->getSessionId() << "." << std::endl;
+
+ switch (buttonNumber) {
+ case 1: {
+ sessionId1 = sessionId;
+ }
+ break;
+ case 2: {
+ sessionId2 = sessionId;
+ }
+ break;
+ default: {
+ sessionId3 = sessionId;
+ }
+ }
+
+ Application::listFFmpegSessions();
+}
+
+void ffmpegkittest::ConcurrentExecutionTab::cancel(const int buttonNumber) {
+ long sessionId = 0;
+
+ switch (buttonNumber) {
+ case 1: {
+ sessionId = sessionId1;
+ }
+ break;
+ case 2: {
+ sessionId = sessionId2;
+ }
+ break;
+ case 3: {
+ sessionId = sessionId3;
+ }
+ }
+
+ std::cout << "Cancelling FFmpeg process for button " << buttonNumber << " with sessionId " << sessionId << "." << std::endl;
+
+ if (sessionId == 0) {
+ FFmpegKit::cancel();
+ } else {
+ FFmpegKit::cancel(sessionId);
+ }
+}
diff --git a/linux/test-app-local-dependency/src/ConcurrentExecutionTab.h b/linux/test-app-local-dependency/src/ConcurrentExecutionTab.h
new file mode 100644
index 0000000..25a429b
--- /dev/null
+++ b/linux/test-app-local-dependency/src/ConcurrentExecutionTab.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_CONCURRENT_EXECUTION_TAB_H
+#define FFMPEG_KIT_TEST_CONCURRENT_EXECUTION_TAB_H
+
+#include "Util.h"
+#include
+
+namespace ffmpegkittest {
+
+ class ConcurrentExecutionTab: public Gtk::VBox {
+ public:
+ ConcurrentExecutionTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+
+ private:
+ void clearOutput();
+ void encodeVideo(const int buttonNumber);
+ void cancel(const int buttonNumber);
+
+ Gtk::Button encodeButton1;
+ Gtk::Button encodeButton2;
+ Gtk::Button encodeButton3;
+ Gtk::HBox encodeButtonBox;
+ Gtk::Button cancelButton1;
+ Gtk::Button cancelButton2;
+ Gtk::Button cancelButton3;
+ Gtk::Button cancelButton4;
+ Gtk::HBox cancelButtonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ Gtk::Window* parentWindow;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_CONCURRENT_EXECUTION_TAB_H
diff --git a/linux/test-app-local-dependency/src/Constants.h b/linux/test-app-local-dependency/src/Constants.h
new file mode 100644
index 0000000..485f353
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Constants.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_CONSTANTS_H
+#define FFMPEG_KIT_TEST_CONSTANTS_H
+
+#include
+#include
+
+namespace ffmpegkittest {
+
+ class Constants {
+ public:
+
+ static constexpr const char* CommandTestFFmpegTooltipText = "Enter an FFmpeg command without 'ffmpeg' at the beginning and click the RUN button";
+
+ static constexpr const char* CommandTestFFprobeTooltipText = "Enter an FFprobe command without 'ffprobe' at the beginning and click the RUN button";
+
+ static constexpr const char* VideoTestTooltipText = "Select a video codec and press the ENCODE button";
+
+ static constexpr const char* HttpsTestTooltipText = "Enter the https url of a media file and click the button";
+
+ static constexpr const char* AudioTestTooltipText = "Select an audio codec and press the ENCODE button";
+
+ static constexpr const char* SubtitleTestEncodeTooltipText = "Click the button to burn subtitles.";
+
+ static constexpr const char* SubtitleTestCancelTooltipText = "Click cancel to stop.";
+
+ static constexpr const char* VidStabTestTooltipText = "Click the button to stabilize video.";
+
+ static constexpr const char* PipeTestTooltipText = "Click the button to create a video using pipe redirection.";
+
+ static constexpr const char* ConcurrentExecutionTestTooltipText = "Use ENCODE and CANCEL buttons to start/stop multiple executions.";
+
+ static constexpr const char* OtherTestTooltipText = "Select a test and press the RUN button.";
+
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_CONSTANTS_H
diff --git a/linux/test-app-local-dependency/src/FFmpegKitTest.cpp b/linux/test-app-local-dependency/src/FFmpegKitTest.cpp
new file mode 100644
index 0000000..d5f0a79
--- /dev/null
+++ b/linux/test-app-local-dependency/src/FFmpegKitTest.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2019-2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "MediaInformationParserTest.h"
+#include
+#include
+#include
+
+using namespace ffmpegkit;
+
+void testParseSimpleCommand() {
+ auto argumentList = FFmpegKitConfig::parseArguments("-hide_banner -loop 1 -i file.jpg -filter_complex [0:v]setpts=PTS-STARTPTS[video] -map [video] -vsync 2 -async 1 video.mp4");
+
+ assert(argumentList);
+ assert(14 == argumentList->size());
+
+ auto it = argumentList->begin();
+ assertString("-hide_banner", *it++);
+ assertString("-loop", *it++);
+ assertString("1", *it++);
+ assertString("-i", *it++);
+ assertString("file.jpg", *it++);
+ assertString("-filter_complex", *it++);
+ assertString("[0:v]setpts=PTS-STARTPTS[video]", *it++);
+ assertString("-map", *it++);
+ assertString("[video]", *it++);
+ assertString("-vsync", *it++);
+ assertString("2", *it++);
+ assertString("-async", *it++);
+ assertString("1", *it++);
+ assertString("video.mp4", *it++);
+}
+
+void testParseSingleQuotesInCommand() {
+ auto argumentList = FFmpegKitConfig::parseArguments("-loop 1 'file one.jpg' -filter_complex '[0:v]setpts=PTS-STARTPTS[video]' -map [video] video.mp4 ");
+
+ assert(argumentList);
+ assert(8 == argumentList->size());
+
+ auto it = argumentList->begin();
+ assertString("-loop", *it++);
+ assertString("1", *it++);
+ assertString("file one.jpg", *it++);
+ assertString("-filter_complex", *it++);
+ assertString("[0:v]setpts=PTS-STARTPTS[video]", *it++);
+ assertString("-map", *it++);
+ assertString("[video]", *it++);
+ assertString("video.mp4", *it++);
+}
+
+void testParseDoubleQuotesInCommand() {
+ auto argumentList = FFmpegKitConfig::parseArguments("-loop 1 \"file one.jpg\" -filter_complex \"[0:v]setpts=PTS-STARTPTS[video]\" -map [video] video.mp4 ");
+
+ assert(argumentList);
+ assert(8 == argumentList->size());
+
+ auto it = argumentList->begin();
+ assertString("-loop", *it++);
+ assertString("1", *it++);
+ assertString("file one.jpg", *it++);
+ assertString("-filter_complex", *it++);
+ assertString("[0:v]setpts=PTS-STARTPTS[video]", *it++);
+ assertString("-map", *it++);
+ assertString("[video]", *it++);
+ assertString("video.mp4", *it++);
+
+ argumentList = FFmpegKitConfig::parseArguments(" -i file:///tmp/input.mp4 -vcodec libx264 -vf \"scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black\" -acodec copy -q:v 0 -q:a 0 video.mp4");
+
+ assert(argumentList);
+ assert(13 == argumentList->size());
+
+ it = argumentList->begin();
+ assertString("-i", *it++);
+ assertString("file:///tmp/input.mp4", *it++);
+ assertString("-vcodec", *it++);
+ assertString("libx264", *it++);
+ assertString("-vf", *it++);
+ assertString("scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black", *it++);
+ assertString("-acodec", *it++);
+ assertString("copy", *it++);
+ assertString("-q:v", *it++);
+ assertString("0", *it++);
+ assertString("-q:a", *it++);
+ assertString("0", *it++);
+ assertString("video.mp4", *it++);
+}
+
+void testParseDoubleQuotesAndEscapesInCommand() {
+ auto argumentList = FFmpegKitConfig::parseArguments(" -i file:///tmp/input.mp4 -vf \"subtitles=file:///tmp/subtitles.srt:force_style=\'FontSize=16,PrimaryColour=&HFFFFFF&\'\" -vcodec libx264 -acodec copy -q:v 0 -q:a 0 video.mp4");
+
+ assert(argumentList);
+ assert(13 == argumentList->size());
+
+ auto it = argumentList->begin();
+ assertString("-i", *it++);
+ assertString("file:///tmp/input.mp4", *it++);
+ assertString("-vf", *it++);
+ assertString("subtitles=file:///tmp/subtitles.srt:force_style='FontSize=16,PrimaryColour=&HFFFFFF&'", *it++);
+ assertString("-vcodec", *it++);
+ assertString("libx264", *it++);
+ assertString("-acodec", *it++);
+ assertString("copy", *it++);
+ assertString("-q:v", *it++);
+ assertString("0", *it++);
+ assertString("-q:a", *it++);
+ assertString("0", *it++);
+ assertString("video.mp4", *it++);
+
+ argumentList = FFmpegKitConfig::parseArguments(" -i file:///tmp/input.mp4 -vf \"subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"\" -vcodec libx264 -acodec copy -q:v 0 -q:a 0 video.mp4");
+
+ assert(argumentList);
+ assert(13 == argumentList->size());
+
+ it = argumentList->begin();
+ assertString("-i", *it++);
+ assertString("file:///tmp/input.mp4", *it++);
+ assertString("-vf", *it++);
+ assertString("subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"", *it++);
+ assertString("-vcodec", *it++);
+ assertString("libx264", *it++);
+ assertString("-acodec", *it++);
+ assertString("copy", *it++);
+ assertString("-q:v", *it++);
+ assertString("0", *it++);
+ assertString("-q:a", *it++);
+ assertString("0", *it++);
+ assertString("video.mp4", *it++);
+}
+
+void getSessionIdTest() {
+ const std::shared_ptr> TEST_ARGUMENTS = std::make_shared>(std::list{"argument1", "argument2"});
+
+ auto sessions1 = FFmpegSession::create(TEST_ARGUMENTS);
+ auto sessions2 = FFprobeSession::create(TEST_ARGUMENTS);
+ auto sessions3 = MediaInformationSession::create(TEST_ARGUMENTS);
+
+ assert(sessions3->getSessionId() > sessions2->getSessionId());
+ assert(sessions3->getSessionId() > sessions1->getSessionId());
+ assert(sessions2->getSessionId() > sessions1->getSessionId());
+
+ assert(sessions1->getSessionId() > 0);
+ assert(sessions2->getSessionId() > 0);
+ assert(sessions3->getSessionId() > 0);
+}
+
+void testFFmpegKit(void) {
+ testParseSimpleCommand();
+ testParseSingleQuotesInCommand();
+ testParseDoubleQuotesInCommand();
+ testParseDoubleQuotesAndEscapesInCommand();
+ getSessionIdTest();
+
+ std::cout << "FFmpegKitConfigTest passed." << std::endl;
+}
diff --git a/linux/test-app-local-dependency/src/FFmpegKitTest.h b/linux/test-app-local-dependency/src/FFmpegKitTest.h
new file mode 100644
index 0000000..f286b8a
--- /dev/null
+++ b/linux/test-app-local-dependency/src/FFmpegKitTest.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2019-2022 Taner Sener
+ *
+ * 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.
+ */
+
+void testFFmpegKit(void);
diff --git a/linux/test-app-local-dependency/src/HttpsTab.cpp b/linux/test-app-local-dependency/src/HttpsTab.cpp
new file mode 100644
index 0000000..76e0a90
--- /dev/null
+++ b/linux/test-app-local-dependency/src/HttpsTab.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "HttpsTab.h"
+#include "Constants.h"
+#include "Popup.h"
+#include
+#include
+#define RAPIDJSON_ASSERT(x)
+#include "rapidjson/writer.h"
+#include "rapidjson/stringbuffer.h"
+
+using namespace ffmpegkit;
+
+static std::recursive_mutex outputMutex;
+
+std::string toString(const rapidjson::Value& value) {
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer writer(buffer);
+ value.Accept(writer);
+ return std::string(buffer.GetString(), buffer.GetSize());
+}
+
+static gboolean appendLog(const std::pair* parameters) {
+ ffmpegkittest::HttpsTab* httpsTab = parameters->first;
+ auto string = parameters->second;
+ httpsTab->appendOutput(string);
+ delete parameters;
+ return FALSE;
+}
+
+void appendLogToMainLoop(const ffmpegkittest::HttpsTab* httpsTab,const std::string string) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair(httpsTab, string));
+}
+
+ffmpegkittest::HttpsTab::HttpsTab() : parentWindow(nullptr) {
+ urlText.set_placeholder_text("Enter https url");
+ Util::applyEditTextStyle(urlText);
+
+ getInfoFromUrlButton.set_label("GET INFO FROM URL");
+ getInfoFromUrlButton.set_size_request(120, 30);
+ getInfoFromUrlButton.set_tooltip_text(Constants::HttpsTestTooltipText);
+ getInfoFromUrlButton.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &HttpsTab::runGetMediaInformation), 1));
+ Util::applyButtonStyle(getInfoFromUrlButton);
+ getInfoFromUrlButtonBox.pack_start(getInfoFromUrlButton, Gtk::PACK_EXPAND_PADDING);
+
+ getRandomInfoButton1.set_label("GET RANDOM INFO");
+ getRandomInfoButton1.set_size_request(120, 30);
+ getRandomInfoButton1.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &HttpsTab::runGetMediaInformation), 2));
+ Util::applyButtonStyle(getRandomInfoButton1);
+ getRandomInfoButton1Box.pack_start(getRandomInfoButton1, Gtk::PACK_EXPAND_PADDING);
+
+ getRandomInfoButton2.set_label("GET RANDOM INFO");
+ getRandomInfoButton2.set_size_request(120, 30);
+ getRandomInfoButton2.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &HttpsTab::runGetMediaInformation), 3));
+ Util::applyButtonStyle(getRandomInfoButton2);
+ getRandomInfoButton2Box.pack_start(getRandomInfoButton2, Gtk::PACK_EXPAND_PADDING);
+
+ getInfoAndFailButton.set_label("GET INFO AND FAIL");
+ getInfoAndFailButton.set_size_request(120, 30);
+
+ getInfoAndFailButton.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &HttpsTab::runGetMediaInformation), 4));
+ Util::applyButtonStyle(getInfoAndFailButton);
+ getInfoAndFailButtonBox.pack_start(getInfoAndFailButton, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(urlText, Gtk::PACK_SHRINK);
+ pack_start(getInfoFromUrlButtonBox, Gtk::PACK_SHRINK);
+ pack_start(getRandomInfoButton1Box, Gtk::PACK_SHRINK);
+ pack_start(getRandomInfoButton2Box, Gtk::PACK_SHRINK);
+ pack_start(getInfoAndFailButtonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+}
+
+void ffmpegkittest::HttpsTab::setActive() {
+ std::cout << "Https Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback(nullptr);
+ FFmpegKitConfig::enableStatisticsCallback(nullptr);
+}
+
+void ffmpegkittest::HttpsTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::HttpsTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::HttpsTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
+
+void ffmpegkittest::HttpsTab::runGetMediaInformation(const int buttonNumber) {
+
+ // SELECT TEST URL
+ std::string testUrl;
+ switch (buttonNumber) {
+ case 1: {
+ testUrl = urlText.get_text();
+ if (testUrl.empty()) {
+ testUrl = HttpsTestDefaultUrl;
+ urlText.set_text(testUrl);
+ }
+ }
+ break;
+ case 2:
+ case 3: {
+ testUrl = getRandomTestUrl();
+ }
+ break;
+ case 4:
+ default: {
+ testUrl = HttpsTestFailUrl;
+ urlText.set_text(testUrl);
+ }
+ }
+
+ std::cout << "Testing HTTPS with for button " << buttonNumber << " using url " << testUrl << "." << std::endl;
+
+ if (buttonNumber == 4) {
+
+ // ONLY THIS BUTTON CLEARS THE TEXT VIEW
+ clearOutput();
+ }
+
+ // EXECUTE
+ FFprobeKit::getMediaInformationAsync(testUrl, createNewCompleteCallback());
+
+}
+
+std::string ffmpegkittest::HttpsTab::getRandomTestUrl() {
+ switch (std::rand() % 3) {
+ case 0:
+ return HttpsTestRandomUrl1;
+ case 1:
+ return HttpsTestRandomUrl2;
+ default:
+ return HttpsTestRandomUrl3;
+ }
+}
+
+MediaInformationSessionCompleteCallback ffmpegkittest::HttpsTab::createNewCompleteCallback() {
+ return [this](auto session) {
+ std::unique_lock lock(outputMutex);
+ auto information = session->getMediaInformation();
+ if (information == nullptr) {
+ appendLogToMainLoop(this, "Get media information failed\n");
+ appendLogToMainLoop(this, "State: " + FFmpegKitConfig::sessionStateToString(session->getState()) + "\n");
+ appendLogToMainLoop(this, "Duration: " + std::to_string(session->getDuration()) + "\n");
+ if (session->getReturnCode() != nullptr) {
+ appendLogToMainLoop(this, "Return Code: " + std::to_string(session->getReturnCode()->getValue()) + "\n");
+ }
+ appendLogToMainLoop(this, "Fail stack trace: " + session->getFailStackTrace() + "\n");
+ appendLogToMainLoop(this, "Output: " + session->getOutput() + "\n");
+ } else {
+ if (information->getFilename() != nullptr) {
+ appendLogToMainLoop(this, "Media information for " + *information->getFilename() + "\n");
+ }
+ if (information->getFormat() != nullptr) {
+ appendLogToMainLoop(this, "Format: " + *information->getFormat() + "\n");
+ }
+ if (information->getBitrate() != nullptr) {
+ appendLogToMainLoop(this, "Bitrate: " + *information->getBitrate() + "\n");
+ }
+ if (information->getDuration() != nullptr) {
+ appendLogToMainLoop(this, "Duration: " + *information->getDuration() + "\n");
+ }
+ if (information->getStartTime() != nullptr) {
+ appendLogToMainLoop(this, "Start time: " + *information->getStartTime() + "\n");
+ }
+ if (information->getTags() != nullptr) {
+ auto tags = information->getTags();
+ for (auto tagIterator = tags->MemberBegin(); tagIterator != tags->MemberEnd(); ++tagIterator) {
+ const char* tagName = tagIterator->name.GetString();
+ appendLogToMainLoop(this, std::string("Tag: ") + tagName + ":" + toString(tagIterator->value) + "\n");
+ }
+ }
+ if (information->getStreams() != nullptr) {
+ auto streams = information->getStreams();
+ std::for_each(streams->cbegin(), streams->cend(), [this](const std::shared_ptr& stream) {
+ if (stream->getIndex() != nullptr) {
+ appendLogToMainLoop(this, "Stream index: " + std::to_string(*stream->getIndex()) + "\n");
+ }
+ if (stream->getType() != nullptr) {
+ appendLogToMainLoop(this, "Stream type: " + *stream->getType() + "\n");
+ }
+ if (stream->getCodec() != nullptr) {
+ appendLogToMainLoop(this, "Stream codec: " + *stream->getCodec() + "\n");
+ }
+ if (stream->getCodecLong() != nullptr) {
+ appendLogToMainLoop(this, "Stream codec long: " + *stream->getCodecLong() + "\n");
+ }
+ if (stream->getFormat() != nullptr) {
+ appendLogToMainLoop(this, "Stream format: " + *stream->getFormat() + "\n");
+ }
+
+ if (stream->getWidth() != nullptr) {
+ appendLogToMainLoop(this, "Stream width: " + std::to_string(*stream->getWidth()) + "\n");
+ }
+ if (stream->getHeight() != nullptr) {
+ appendLogToMainLoop(this, "Stream height: " + std::to_string(*stream->getHeight()) + "\n");
+ }
+
+ if (stream->getBitrate() != nullptr) {
+ appendLogToMainLoop(this, "Stream bitrate: " + *stream->getBitrate() + "\n");
+ }
+ if (stream->getSampleRate() != nullptr) {
+ appendLogToMainLoop(this, "Stream sample rate: " + *stream->getSampleRate() + "\n");
+ }
+ if (stream->getSampleFormat() != nullptr) {
+ appendLogToMainLoop(this, "Stream sample format: " + *stream->getSampleFormat() + "\n");
+ }
+ if (stream->getChannelLayout() != nullptr) {
+ appendLogToMainLoop(this, "Stream channel layout: " + *stream->getChannelLayout() + "\n");
+ }
+
+ if (stream->getSampleAspectRatio() != nullptr) {
+ appendLogToMainLoop(this, "Stream sample aspect ratio: " + *stream->getSampleAspectRatio() + "\n");
+ }
+ if (stream->getDisplayAspectRatio() != nullptr) {
+ appendLogToMainLoop(this, "Stream display ascpect ratio: " + *stream->getDisplayAspectRatio() + "\n");
+ }
+ if (stream->getAverageFrameRate() != nullptr) {
+ appendLogToMainLoop(this, "Stream average frame rate: " + *stream->getAverageFrameRate() + "\n");
+ }
+ if (stream->getRealFrameRate() != nullptr) {
+ appendLogToMainLoop(this, "Stream real frame rate: " + *stream->getRealFrameRate() + "\n");
+ }
+ if (stream->getTimeBase() != nullptr) {
+ appendLogToMainLoop(this, "Stream time base: " + *stream->getTimeBase() + "\n");
+ }
+ if (stream->getCodecTimeBase() != nullptr) {
+ appendLogToMainLoop(this, "Stream codec time base: " + *stream->getCodecTimeBase() + "\n");
+ }
+
+ if (stream->getTags() != nullptr) {
+ auto tags = stream->getTags();
+ for (auto tagIterator = tags->MemberBegin(); tagIterator != tags->MemberEnd(); ++tagIterator) {
+ const char* tagName = tagIterator->name.GetString();
+ appendLogToMainLoop(this, std::string("Stream tag: ") + tagName + ":" + toString(tagIterator->value) + "\n");
+ }
+ }
+ });
+ }
+
+ if (information->getChapters() != nullptr) {
+ auto chapters = information->getChapters();
+ std::for_each(chapters->cbegin(), chapters->cend(), [this](const std::shared_ptr& chapter) {
+ if (chapter->getId() != nullptr) {
+ appendLogToMainLoop(this, "Chapter id: " + std::to_string(*chapter->getId()) + "\n");
+ }
+ if (chapter->getTimeBase() != nullptr) {
+ appendLogToMainLoop(this, "Chapter time base: " + *chapter->getTimeBase() + "\n");
+ }
+ if (chapter->getStart() != nullptr) {
+ appendLogToMainLoop(this, "Chapter start: " + std::to_string(*chapter->getStart()) + "\n");
+ }
+ if (chapter->getStartTime() != nullptr) {
+ appendLogToMainLoop(this, "Chapter start time: " + *chapter->getStartTime() + "\n");
+ }
+ if (chapter->getEnd() != nullptr) {
+ appendLogToMainLoop(this, "Chapter end: " + std::to_string(*chapter->getEnd()) + "\n");
+ }
+ if (chapter->getEndTime() != nullptr) {
+ appendLogToMainLoop(this, "Chapter end time: " + *chapter->getEndTime() + "\n");
+ }
+ if (chapter->getTags() != nullptr) {
+ auto tags = chapter->getTags();
+ for (auto tagIterator = tags->MemberBegin(); tagIterator != tags->MemberEnd(); ++tagIterator) {
+ const char* tagName = tagIterator->name.GetString();
+ appendLogToMainLoop(this, std::string("Chapter tag: ") + tagName + ":" + toString(tagIterator->value) + "\n");
+ }
+ }
+ });
+ }
+ }
+ };
+}
\ No newline at end of file
diff --git a/linux/test-app-local-dependency/src/HttpsTab.h b/linux/test-app-local-dependency/src/HttpsTab.h
new file mode 100644
index 0000000..d352815
--- /dev/null
+++ b/linux/test-app-local-dependency/src/HttpsTab.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_HTTPS_TAB_H
+#define FFMPEG_KIT_TEST_HTTPS_TAB_H
+
+#include
+#include "Util.h"
+#include
+
+namespace ffmpegkittest {
+
+ class HttpsTab: public Gtk::VBox {
+ public:
+
+ static constexpr const char* HttpsTestDefaultUrl = "https://download.blender.org/peach/trailer/trailer_1080p.ogg";
+
+ static constexpr const char* HttpsTestFailUrl = "https://download2.blender.org/peach/trailer/trailer_1080p.ogg";
+
+ static constexpr const char* HttpsTestRandomUrl1 = "https://file-examples.com/storage/fe2ef7477862f581f9ce264/2018/04/file_example_MOV_640_800kB.mov";
+
+ static constexpr const char* HttpsTestRandomUrl2 = "https://file-examples.com/storage/fe2ef7477862f581f9ce264/2017/11/file_example_MP3_700KB.mp3";
+
+ static constexpr const char* HttpsTestRandomUrl3 = "https://file-examples.com/storage/fe2ef7477862f581f9ce264/2020/03/file_example_WEBP_50kB.webp";
+
+ HttpsTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+
+ private:
+ void clearOutput();
+ void runGetMediaInformation(const int buttonNumber);
+ std::string getRandomTestUrl();
+ ffmpegkit::MediaInformationSessionCompleteCallback createNewCompleteCallback();
+
+ Gtk::Entry urlText;
+ Gtk::Button getInfoFromUrlButton;
+ Gtk::HBox getInfoFromUrlButtonBox;
+ Gtk::Button getRandomInfoButton1;
+ Gtk::HBox getRandomInfoButton1Box;
+ Gtk::Button getRandomInfoButton2;
+ Gtk::HBox getRandomInfoButton2Box;
+ Gtk::Button getInfoAndFailButton;
+ Gtk::HBox getInfoAndFailButtonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ Gtk::Window* parentWindow;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_HTTPS_TAB_H
diff --git a/linux/test-app-local-dependency/src/MediaInformationParserTest.cpp b/linux/test-app-local-dependency/src/MediaInformationParserTest.cpp
new file mode 100644
index 0000000..ef1cd53
--- /dev/null
+++ b/linux/test-app-local-dependency/src/MediaInformationParserTest.cpp
@@ -0,0 +1,751 @@
+/*
+ * Copyright (c) 2018-2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "MediaInformationParserTest.h"
+#include
+
+using namespace ffmpegkit;
+
+const std::string MEDIA_INFORMATION_MP3 = "{\n"
+ " \"streams\": [\n"
+ " {\n"
+ " \"index\": 0,\n"
+ " \"codec_name\": \"mp3\",\n"
+ " \"codec_long_name\": \"MP3 (MPEG audio layer 3)\",\n"
+ " \"codec_type\": \"audio\",\n"
+ " \"codec_time_base\": \"1/44100\",\n"
+ " \"codec_tag_string\": \"[0][0][0][0]\",\n"
+ " \"codec_tag\": \"0x0000\",\n"
+ " \"sample_fmt\": \"fltp\",\n"
+ " \"sample_rate\": \"44100\",\n"
+ " \"channels\": 2,\n"
+ " \"channel_layout\": \"stereo\",\n"
+ " \"bits_per_sample\": 0,\n"
+ " \"r_frame_rate\": \"0/0\",\n"
+ " \"avg_frame_rate\": \"0/0\",\n"
+ " \"time_base\": \"1/14112000\",\n"
+ " \"start_pts\": 169280,\n"
+ " \"start_time\": \"0.011995\",\n"
+ " \"duration_ts\": 4622376960,\n"
+ " \"duration\": \"327.549388\",\n"
+ " \"bit_rate\": \"320000\",\n"
+ " \"disposition\": {\n"
+ " \"default\": 0,\n"
+ " \"dub\": 0,\n"
+ " \"original\": 0,\n"
+ " \"comment\": 0,\n"
+ " \"lyrics\": 0,\n"
+ " \"karaoke\": 0,\n"
+ " \"forced\": 0,\n"
+ " \"hearing_impaired\": 0,\n"
+ " \"visual_impaired\": 0,\n"
+ " \"clean_effects\": 0,\n"
+ " \"attached_pic\": 0,\n"
+ " \"timed_thumbnails\": 0\n"
+ " },\n"
+ " \"tags\": {\n"
+ " \"encoder\": \"Lavf\"\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"chapters\": [\n"
+ " {\n"
+ " \"id\": 0,\n"
+ " \"time_base\": \"1/22050\",\n"
+ " \"start\": 0,\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"end\": 11158238,\n"
+ " \"end_time\": \"506.042540\",\n"
+ " \"tags\": {\n"
+ " \"title\": \"1 Laying Plans - 2 Waging War\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": 1,\n"
+ " \"time_base\": \"1/22050\",\n"
+ " \"start\": 11158238,\n"
+ " \"start_time\": \"506.042540\",\n"
+ " \"end\": 21433051,\n"
+ " \"end_time\": \"972.020454\",\n"
+ " \"tags\": {\n"
+ " \"title\": \"3 Attack By Stratagem - 4 Tactical Dispositions\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": 2,\n"
+ " \"time_base\": \"1/22050\",\n"
+ " \"start\": 21433051,\n"
+ " \"start_time\": \"972.020454\",\n"
+ " \"end\": 35478685,\n"
+ " \"end_time\": \"1609.010658\",\n"
+ " \"tags\": {\n"
+ " \"title\": \"5 Energy - 6 Weak Points and Strong\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": 3,\n"
+ " \"time_base\": \"1/22050\",\n"
+ " \"start\": 35478685,\n"
+ " \"start_time\": \"1609.010658\",\n"
+ " \"end\": 47187043,\n"
+ " \"end_time\": \"2140.001950\",\n"
+ " \"tags\": {\n"
+ " \"title\": \"7 Maneuvering - 8 Variation in Tactics\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": 4,\n"
+ " \"time_base\": \"1/22050\",\n"
+ " \"start\": 47187043,\n"
+ " \"start_time\": \"2140.001950\",\n"
+ " \"end\": 66635594,\n"
+ " \"end_time\": \"3022.022404\",\n"
+ " \"tags\": {\n"
+ " \"title\": \"9 The Army on the March - 10 Terrain\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": 5,\n"
+ " \"time_base\": \"1/22050\",\n"
+ " \"start\": 66635594,\n"
+ " \"start_time\": \"3022.022404\",\n"
+ " \"end\": 83768105,\n"
+ " \"end_time\": \"3799.007029\",\n"
+ " \"tags\": {\n"
+ " \"title\": \"11 The Nine Situations\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": 6,\n"
+ " \"time_base\": \"1/22050\",\n"
+ " \"start\": 83768105,\n"
+ " \"start_time\": \"3799.007029\",\n"
+ " \"end\": 95659008,\n"
+ " \"end_time\": \"4338.277007\",\n"
+ " \"tags\": {\n"
+ " \"title\": \"12 The Attack By Fire - 13 The Use of Spies\"\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"format\": {\n"
+ " \"filename\": \"sample.mp3\",\n"
+ " \"nb_streams\": 1,\n"
+ " \"nb_programs\": 0,\n"
+ " \"format_name\": \"mp3\",\n"
+ " \"format_long_name\": \"MP2/3 (MPEG audio layer 2/3)\",\n"
+ " \"start_time\": \"0.011995\",\n"
+ " \"duration\": \"327.549388\",\n"
+ " \"size\": \"13103064\",\n"
+ " \"bit_rate\": \"320026\",\n"
+ " \"probe_score\": 51,\n"
+ " \"tags\": {\n"
+ " \"encoder\": \"Lavf58.20.100\",\n"
+ " \"album\": \"Impact\",\n"
+ " \"artist\": \"Kevin MacLeod\",\n"
+ " \"comment\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit finito.\",\n"
+ " \"genre\": \"Cinematic\",\n"
+ " \"title\": \"Impact Moderato\"\n"
+ " }\n"
+ " }\n"
+ "}";
+
+const std::string MEDIA_INFORMATION_JPG = "{\n"
+ " \"streams\": [\n"
+ " {\n"
+ " \"index\": 0,\n"
+ " \"codec_name\": \"mjpeg\",\n"
+ " \"codec_long_name\": \"Motion JPEG\",\n"
+ " \"profile\": \"Baseline\",\n"
+ " \"codec_type\": \"video\",\n"
+ " \"codec_time_base\": \"0/1\",\n"
+ " \"codec_tag_string\": \"[0][0][0][0]\",\n"
+ " \"codec_tag\": \"0x0000\",\n"
+ " \"width\": 1496,\n"
+ " \"height\": 1729,\n"
+ " \"coded_width\": 1496,\n"
+ " \"coded_height\": 1729,\n"
+ " \"has_b_frames\": 0,\n"
+ " \"sample_aspect_ratio\": \"1:1\",\n"
+ " \"display_aspect_ratio\": \"1496:1729\",\n"
+ " \"pix_fmt\": \"yuvj444p\",\n"
+ " \"level\": -99,\n"
+ " \"color_range\": \"pc\",\n"
+ " \"color_space\": \"bt470bg\",\n"
+ " \"chroma_location\": \"center\",\n"
+ " \"refs\": 1,\n"
+ " \"r_frame_rate\": \"25/1\",\n"
+ " \"avg_frame_rate\": \"0/0\",\n"
+ " \"time_base\": \"1/25\",\n"
+ " \"start_pts\": 0,\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration_ts\": 1,\n"
+ " \"duration\": \"0.040000\",\n"
+ " \"bits_per_raw_sample\": \"8\",\n"
+ " \"disposition\": {\n"
+ " \"default\": 0,\n"
+ " \"dub\": 0,\n"
+ " \"original\": 0,\n"
+ " \"comment\": 0,\n"
+ " \"lyrics\": 0,\n"
+ " \"karaoke\": 0,\n"
+ " \"forced\": 0,\n"
+ " \"hearing_impaired\": 0,\n"
+ " \"visual_impaired\": 0,\n"
+ " \"clean_effects\": 0,\n"
+ " \"attached_pic\": 0,\n"
+ " \"timed_thumbnails\": 0\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"format\": {\n"
+ " \"filename\": \"sample.jpg\",\n"
+ " \"nb_streams\": 1,\n"
+ " \"nb_programs\": 0,\n"
+ " \"format_name\": \"image2\",\n"
+ " \"format_long_name\": \"image2 sequence\",\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration\": \"0.040000\",\n"
+ " \"size\": \"1659050\",\n"
+ " \"bit_rate\": \"331810000\",\n"
+ " \"probe_score\": 50\n"
+ " }\n"
+ "}";
+
+const std::string MEDIA_INFORMATION_GIF = "{\n"
+ " \"streams\": [\n"
+ " {\n"
+ " \"index\": 0,\n"
+ " \"codec_name\": \"gif\",\n"
+ " \"codec_long_name\": \"CompuServe GIF (Graphics Interchange Format)\",\n"
+ " \"codec_type\": \"video\",\n"
+ " \"codec_time_base\": \"12/133\",\n"
+ " \"codec_tag_string\": \"[0][0][0][0]\",\n"
+ " \"codec_tag\": \"0x0000\",\n"
+ " \"width\": 400,\n"
+ " \"height\": 400,\n"
+ " \"coded_width\": 400,\n"
+ " \"coded_height\": 400,\n"
+ " \"has_b_frames\": 0,\n"
+ " \"pix_fmt\": \"bgra\",\n"
+ " \"level\": -99,\n"
+ " \"refs\": 1,\n"
+ " \"r_frame_rate\": \"100/9\",\n"
+ " \"avg_frame_rate\": \"133/12\",\n"
+ " \"time_base\": \"1/100\",\n"
+ " \"start_pts\": 0,\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration_ts\": 396,\n"
+ " \"duration\": \"3.960000\",\n"
+ " \"nb_frames\": \"44\",\n"
+ " \"disposition\": {\n"
+ " \"default\": 0,\n"
+ " \"dub\": 0,\n"
+ " \"original\": 0,\n"
+ " \"comment\": 0,\n"
+ " \"lyrics\": 0,\n"
+ " \"karaoke\": 0,\n"
+ " \"forced\": 0,\n"
+ " \"hearing_impaired\": 0,\n"
+ " \"visual_impaired\": 0,\n"
+ " \"clean_effects\": 0,\n"
+ " \"attached_pic\": 0,\n"
+ " \"timed_thumbnails\": 0\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"format\": {\n"
+ " \"filename\": \"sample.gif\",\n"
+ " \"nb_streams\": 1,\n"
+ " \"nb_programs\": 0,\n"
+ " \"format_name\": \"gif\",\n"
+ " \"format_long_name\": \"CompuServe Graphics Interchange Format (GIF)\",\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration\": \"3.960000\",\n"
+ " \"size\": \"1001718\",\n"
+ " \"bit_rate\": \"2023672\",\n"
+ " \"probe_score\": 100\n"
+ " }\n"
+ "}";
+
+const std::string MEDIA_INFORMATION_MP4 = "{\n"
+ " \"streams\": [\n"
+ " {\n"
+ " \"index\": 0,\n"
+ " \"codec_name\": \"h264\",\n"
+ " \"codec_long_name\": \"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10\",\n"
+ " \"profile\": \"Main\",\n"
+ " \"codec_type\": \"video\",\n"
+ " \"codec_time_base\": \"1/60\",\n"
+ " \"codec_tag_string\": \"avc1\",\n"
+ " \"codec_tag\": \"0x31637661\",\n"
+ " \"width\": 1280,\n"
+ " \"height\": 720,\n"
+ " \"coded_width\": 1280,\n"
+ " \"coded_height\": 720,\n"
+ " \"has_b_frames\": 0,\n"
+ " \"sample_aspect_ratio\": \"1:1\",\n"
+ " \"display_aspect_ratio\": \"16:9\",\n"
+ " \"pix_fmt\": \"yuv420p\",\n"
+ " \"level\": 42,\n"
+ " \"chroma_location\": \"left\",\n"
+ " \"refs\": 1,\n"
+ " \"is_avc\": \"true\",\n"
+ " \"nal_length_size\": \"4\",\n"
+ " \"r_frame_rate\": \"30/1\",\n"
+ " \"avg_frame_rate\": \"30/1\",\n"
+ " \"time_base\": \"1/15360\",\n"
+ " \"start_pts\": 0,\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration_ts\": 215040,\n"
+ " \"duration\": \"14.000000\",\n"
+ " \"bit_rate\": \"9166570\",\n"
+ " \"bits_per_raw_sample\": \"8\",\n"
+ " \"nb_frames\": \"420\",\n"
+ " \"disposition\": {\n"
+ " \"default\": 1,\n"
+ " \"dub\": 0,\n"
+ " \"original\": 0,\n"
+ " \"comment\": 0,\n"
+ " \"lyrics\": 0,\n"
+ " \"karaoke\": 0,\n"
+ " \"forced\": 0,\n"
+ " \"hearing_impaired\": 0,\n"
+ " \"visual_impaired\": 0,\n"
+ " \"clean_effects\": 0,\n"
+ " \"attached_pic\": 0,\n"
+ " \"timed_thumbnails\": 0\n"
+ " },\n"
+ " \"tags\": {\n"
+ " \"language\": \"und\",\n"
+ " \"handler_name\": \"VideoHandler\"\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"format\": {\n"
+ " \"filename\": \"sample.mp4\",\n"
+ " \"nb_streams\": 1,\n"
+ " \"nb_programs\": 0,\n"
+ " \"format_name\": \"mov,mp4,m4a,3gp,3g2,mj2\",\n"
+ " \"format_long_name\": \"QuickTime / MOV\",\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration\": \"14.000000\",\n"
+ " \"size\": \"16044159\",\n"
+ " \"bit_rate\": \"9168090\",\n"
+ " \"probe_score\": 100,\n"
+ " \"tags\": {\n"
+ " \"major_brand\": \"isom\",\n"
+ " \"minor_version\": \"512\",\n"
+ " \"compatible_brands\": \"isomiso2avc1mp41\",\n"
+ " \"encoder\": \"Lavf58.33.100\"\n"
+ " }\n"
+ " }\n"
+ "}";
+
+const std::string MEDIA_INFORMATION_PNG = "{\n"
+ " \"streams\": [\n"
+ " {\n"
+ " \"index\": 0,\n"
+ " \"codec_name\": \"png\",\n"
+ " \"codec_long_name\": \"PNG (Portable Network Graphics) image\",\n"
+ " \"codec_type\": \"video\",\n"
+ " \"codec_time_base\": \"0/1\",\n"
+ " \"codec_tag_string\": \"[0][0][0][0]\",\n"
+ " \"codec_tag\": \"0x0000\",\n"
+ " \"width\": 1198,\n"
+ " \"height\": 1198,\n"
+ " \"coded_width\": 1198,\n"
+ " \"coded_height\": 1198,\n"
+ " \"has_b_frames\": 0,\n"
+ " \"sample_aspect_ratio\": \"1:1\",\n"
+ " \"display_aspect_ratio\": \"1:1\",\n"
+ " \"pix_fmt\": \"pal8\",\n"
+ " \"level\": -99,\n"
+ " \"color_range\": \"pc\",\n"
+ " \"refs\": 1,\n"
+ " \"r_frame_rate\": \"25/1\",\n"
+ " \"avg_frame_rate\": \"0/0\",\n"
+ " \"time_base\": \"1/25\",\n"
+ " \"disposition\": {\n"
+ " \"default\": 0,\n"
+ " \"dub\": 0,\n"
+ " \"original\": 0,\n"
+ " \"comment\": 0,\n"
+ " \"lyrics\": 0,\n"
+ " \"karaoke\": 0,\n"
+ " \"forced\": 0,\n"
+ " \"hearing_impaired\": 0,\n"
+ " \"visual_impaired\": 0,\n"
+ " \"clean_effects\": 0,\n"
+ " \"attached_pic\": 0,\n"
+ " \"timed_thumbnails\": 0\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"format\": {\n"
+ " \"filename\": \"sample.png\",\n"
+ " \"nb_streams\": 1,\n"
+ " \"nb_programs\": 0,\n"
+ " \"format_name\": \"png_pipe\",\n"
+ " \"format_long_name\": \"piped png sequence\",\n"
+ " \"size\": \"31533\",\n"
+ " \"probe_score\": 99\n"
+ " }\n"
+ "}";
+
+const std::string MEDIA_INFORMATION_OGG = "{\n"
+ " \"streams\": [\n"
+ " {\n"
+ " \"index\": 0,\n"
+ " \"codec_name\": \"theora\",\n"
+ " \"codec_long_name\": \"Theora\",\n"
+ " \"codec_type\": \"video\",\n"
+ " \"codec_time_base\": \"1/25\",\n"
+ " \"codec_tag_string\": \"[0][0][0][0]\",\n"
+ " \"codec_tag\": \"0x0000\",\n"
+ " \"width\": 1920,\n"
+ " \"height\": 1080,\n"
+ " \"coded_width\": 1920,\n"
+ " \"coded_height\": 1088,\n"
+ " \"has_b_frames\": 0,\n"
+ " \"pix_fmt\": \"yuv420p\",\n"
+ " \"level\": -99,\n"
+ " \"color_space\": \"bt470bg\",\n"
+ " \"color_transfer\": \"bt709\",\n"
+ " \"color_primaries\": \"bt470bg\",\n"
+ " \"chroma_location\": \"center\",\n"
+ " \"refs\": 1,\n"
+ " \"r_frame_rate\": \"25/1\",\n"
+ " \"avg_frame_rate\": \"25/1\",\n"
+ " \"time_base\": \"1/25\",\n"
+ " \"start_pts\": 0,\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration_ts\": 813,\n"
+ " \"duration\": \"32.520000\",\n"
+ " \"disposition\": {\n"
+ " \"default\": 0,\n"
+ " \"dub\": 0,\n"
+ " \"original\": 0,\n"
+ " \"comment\": 0,\n"
+ " \"lyrics\": 0,\n"
+ " \"karaoke\": 0,\n"
+ " \"forced\": 0,\n"
+ " \"hearing_impaired\": 0,\n"
+ " \"visual_impaired\": 0,\n"
+ " \"clean_effects\": 0,\n"
+ " \"attached_pic\": 0,\n"
+ " \"timed_thumbnails\": 0\n"
+ " },\n"
+ " \"tags\": {\n"
+ " \"ENCODER\": \"ffmpeg2theora 0.19\"\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"index\": 1,\n"
+ " \"codec_name\": \"vorbis\",\n"
+ " \"codec_long_name\": \"Vorbis\",\n"
+ " \"codec_type\": \"audio\",\n"
+ " \"codec_time_base\": \"1/48000\",\n"
+ " \"codec_tag_string\": \"[0][0][0][0]\",\n"
+ " \"codec_tag\": \"0x0000\",\n"
+ " \"sample_fmt\": \"fltp\",\n"
+ " \"sample_rate\": \"48000\",\n"
+ " \"channels\": 2,\n"
+ " \"channel_layout\": \"stereo\",\n"
+ " \"bits_per_sample\": 0,\n"
+ " \"r_frame_rate\": \"0/0\",\n"
+ " \"avg_frame_rate\": \"0/0\",\n"
+ " \"time_base\": \"1/48000\",\n"
+ " \"start_pts\": 0,\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration_ts\": 1583850,\n"
+ " \"duration\": \"32.996875\",\n"
+ " \"bit_rate\": \"80000\",\n"
+ " \"disposition\": {\n"
+ " \"default\": 0,\n"
+ " \"dub\": 0,\n"
+ " \"original\": 0,\n"
+ " \"comment\": 0,\n"
+ " \"lyrics\": 0,\n"
+ " \"karaoke\": 0,\n"
+ " \"forced\": 0,\n"
+ " \"hearing_impaired\": 0,\n"
+ " \"visual_impaired\": 0,\n"
+ " \"clean_effects\": 0,\n"
+ " \"attached_pic\": 0,\n"
+ " \"timed_thumbnails\": 0\n"
+ " },\n"
+ " \"tags\": {\n"
+ " \"ENCODER\": \"ffmpeg2theora 0.19\"\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"format\": {\n"
+ " \"filename\": \"sample.ogg\",\n"
+ " \"nb_streams\": 2,\n"
+ " \"nb_programs\": 0,\n"
+ " \"format_name\": \"ogg\",\n"
+ " \"format_long_name\": \"Ogg\",\n"
+ " \"start_time\": \"0.000000\",\n"
+ " \"duration\": \"32.996875\",\n"
+ " \"size\": \"27873937\",\n"
+ " \"bit_rate\": \"6757958\",\n"
+ " \"probe_score\": 100\n"
+ " }\n"
+ "}";
+
+void assertNumber(long expected, std::shared_ptr real) {
+ if (real == nullptr) {
+ assert(expected == -1);
+ } else {
+ assert(expected == *real);
+ }
+}
+
+void assertString(std::string expected, std::shared_ptr real) {
+ if (real == nullptr) {
+ assert(expected == "");
+ } else {
+ assert(expected == *real);
+ }
+}
+
+void assertString(std::string expected, std::string real) {
+ assert(expected == real);
+}
+
+void assertVideoStream(std::shared_ptr stream, long index, std::string codec, std::string fullCodec, std::string format, long width, long height, std::string sampleAspectRatio, std::string displayAspectRatio, std::string bitrate, std::string averageFrameRate, std::string realFrameRate, std::string timeBase, std::string codecTimeBase) {
+ assert(stream != nullptr);
+ assertNumber(index, stream->getIndex());
+ assertString("video", stream->getType());
+
+ assertString(codec, stream->getCodec());
+ assertString(fullCodec, stream->getCodecLong());
+
+ assertString(format, stream->getFormat());
+
+ assertNumber(width, stream->getWidth());
+ assertNumber(height, stream->getHeight());
+ assertString(sampleAspectRatio, stream->getSampleAspectRatio());
+ assertString(displayAspectRatio, stream->getDisplayAspectRatio());
+
+ assertString(bitrate, stream->getBitrate());
+
+ assertString(averageFrameRate, stream->getAverageFrameRate());
+ assertString(realFrameRate, stream->getRealFrameRate());
+ assertString(timeBase, stream->getTimeBase());
+ assertString(codecTimeBase, stream->getCodecTimeBase());
+}
+
+void assertAudioStream(std::shared_ptr stream, long index, std::string codec, std::string fullCodec, std::string sampleRate, std::string channelLayout, std::string sampleFormat, std::string bitrate) {
+ assert(stream != nullptr);
+ assertNumber(index, stream->getIndex());
+ assertString("audio", stream->getType());
+
+ assertString(codec, stream->getCodec());
+ assertString(fullCodec, stream->getCodecLong());
+
+ assertString(sampleRate, stream->getSampleRate());
+ assertString(channelLayout, stream->getChannelLayout());
+ assertString(sampleFormat, stream->getSampleFormat());
+ assertString(bitrate, stream->getBitrate());
+}
+
+void assertChapter(std::shared_ptr chapter, long id, std::string timeBase, long start, std::string startTime, long end, std::string endTime) {
+ assert(chapter != nullptr);
+ assertNumber(id, chapter->getId());
+ assertString(timeBase, chapter->getTimeBase());
+
+ assertNumber(start, chapter->getStart());
+ assertString(startTime, chapter->getStartTime());
+
+ assertNumber(end, chapter->getEnd());
+ assertString(endTime, chapter->getEndTime());
+
+ std::shared_ptr tags = chapter->getTags();
+ assert(tags);
+
+ assert(1 == tags->MemberCount());
+}
+
+void assertMediaInput(std::shared_ptr mediaInformation, std::string expectedFormat, std::string expectedFilename) {
+ std::shared_ptr format = mediaInformation->getFormat();
+ std::shared_ptr filename = mediaInformation->getFilename();
+ if (format == nullptr) {
+ assert(expectedFormat == "");
+ } else {
+ assert(*format == expectedFormat);
+ }
+ if (filename == nullptr) {
+ assert(expectedFilename == "");
+ } else {
+ assert(*filename == expectedFilename);
+ }
+}
+
+void assertMediaDuration(std::shared_ptr mediaInformation, std::string expectedDuration, std::string expectedStartTime, std::string expectedBitrate) {
+ std::shared_ptr duration = mediaInformation->getDuration();
+ std::shared_ptr startTime = mediaInformation->getStartTime();
+ std::shared_ptr bitrate = mediaInformation->getBitrate();
+
+ assertString(expectedDuration, duration);
+ assertString(expectedStartTime, startTime);
+ assertString(expectedBitrate, bitrate);
+}
+
+void assertTag(std::shared_ptr mediaInformation, std::string expectedKey, std::string expectedValue) {
+ std::shared_ptr tags = mediaInformation->getTags();
+ assert(tags);
+
+ auto value = (*tags)[expectedKey.c_str()].GetString();
+ assert(value);
+
+ assert(value == expectedValue);
+}
+
+void assertStreamTag(std::shared_ptr streamInformation, std::string expectedKey, std::string expectedValue) {
+ std::shared_ptr tags = streamInformation->getTags();
+ assert(tags);
+
+ auto value = (*tags)[expectedKey.c_str()].GetString();
+ assert(value);
+
+ assert(value == expectedValue);
+}
+
+void testMediaInformationMp3() {
+ std::shared_ptr mediaInformation = MediaInformationJsonParser::from(MEDIA_INFORMATION_MP3);
+
+ assert(mediaInformation);
+ assertMediaInput(mediaInformation, "mp3", "sample.mp3");
+ assertMediaDuration(mediaInformation, "327.549388", "0.011995", "320026");
+
+ assertTag(mediaInformation, "comment", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit finito.");
+ assertTag(mediaInformation, "album", "Impact");
+ assertTag(mediaInformation, "title", "Impact Moderato");
+ assertTag(mediaInformation, "artist", "Kevin MacLeod");
+
+ std::shared_ptr>> streams = mediaInformation->getStreams();
+ assert(streams);
+ assert(1 == streams->size());
+
+ assertAudioStream((*streams)[0], 0, "mp3", "MP3 (MPEG audio layer 3)", "44100", "stereo", "fltp", "320000");
+
+ std::shared_ptr>> chapters = mediaInformation->getChapters();
+ assert(chapters);
+ assert(7 == chapters->size());
+
+ assertChapter((*chapters)[0], 0, "1/22050", 0, "0.000000", 11158238, "506.042540");
+ assertChapter((*chapters)[1], 1, "1/22050", 11158238, "506.042540", 21433051, "972.020454");
+}
+
+void testMediaInformationJpg() {
+ std::shared_ptr mediaInformation = MediaInformationJsonParser::from(MEDIA_INFORMATION_JPG);
+
+ assert(mediaInformation);
+ assertMediaInput(mediaInformation, "image2", "sample.jpg");
+ assertMediaDuration(mediaInformation, "0.040000", "0.000000", "331810000");
+
+ std::shared_ptr>> streams = mediaInformation->getStreams();
+ assert(streams);
+ assert(1 == streams->size());
+
+ assertVideoStream((*streams)[0], 0, "mjpeg", "Motion JPEG", "yuvj444p", 1496, 1729, "1:1", "1496:1729", "", "0/0", "25/1", "1/25", "0/1");
+}
+
+void testMediaInformationGif() {
+ std::shared_ptr mediaInformation = MediaInformationJsonParser::from(MEDIA_INFORMATION_GIF);
+
+ assert(mediaInformation);
+ assertMediaInput(mediaInformation, "gif", "sample.gif");
+ assertMediaDuration(mediaInformation, "3.960000", "0.000000", "2023672");
+
+ std::shared_ptr>> streams = mediaInformation->getStreams();
+ assert(streams);
+ assert(1 == streams->size());
+
+ assertVideoStream((*streams)[0], 0, "gif", "CompuServe GIF (Graphics Interchange Format)", "bgra", 400, 400, "", "", "", "133/12", "100/9", "1/100", "12/133");
+}
+
+void testMediaInformationMp4() {
+ std::shared_ptr mediaInformation = MediaInformationJsonParser::from(MEDIA_INFORMATION_MP4);
+
+ assert(mediaInformation);
+ assertMediaInput(mediaInformation, "mov,mp4,m4a,3gp,3g2,mj2", "sample.mp4");
+ assertMediaDuration(mediaInformation, "14.000000", "0.000000", "9168090");
+
+ assertTag(mediaInformation, "major_brand", "isom");
+ assertTag(mediaInformation, "minor_version", "512");
+ assertTag(mediaInformation, "compatible_brands", "isomiso2avc1mp41");
+ assertTag(mediaInformation, "encoder", "Lavf58.33.100");
+
+ std::shared_ptr>> streams = mediaInformation->getStreams();
+ assert(streams);
+ assert(1 == streams->size());
+
+ assertVideoStream((*streams)[0], 0, "h264", "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", "yuv420p", 1280, 720, "1:1", "16:9", "9166570", "30/1", "30/1", "1/15360", "1/60");
+
+ assertStreamTag((*streams)[0], "language", "und");
+ assertStreamTag((*streams)[0], "handler_name", "VideoHandler");
+}
+
+void testMediaInformationPng() {
+ std::shared_ptr mediaInformation = MediaInformationJsonParser::from(MEDIA_INFORMATION_PNG);
+
+ assert(mediaInformation);
+ assertMediaInput(mediaInformation, "png_pipe", "sample.png");
+ assertMediaDuration(mediaInformation, "", "", "");
+
+ std::shared_ptr>> streams = mediaInformation->getStreams();
+ assert(streams);
+ assert(1 == streams->size());
+
+ assertVideoStream((*streams)[0], 0, "png", "PNG (Portable Network Graphics) image", "pal8", 1198, 1198, "1:1", "1:1", "", "0/0", "25/1", "1/25", "0/1");
+}
+
+void testMediaInformationOgg() {
+ std::shared_ptr mediaInformation = MediaInformationJsonParser::from(MEDIA_INFORMATION_OGG);
+
+ assert(mediaInformation);
+ assertMediaInput(mediaInformation, "ogg", "sample.ogg");
+ assertMediaDuration(mediaInformation, "32.996875", "0.000000", "6757958");
+
+ std::shared_ptr>> streams = mediaInformation->getStreams();
+ assert(streams);
+ assert(2 == streams->size());
+
+ assertVideoStream((*streams)[0], 0, "theora", "Theora", "yuv420p", 1920, 1080, "", "", "", "25/1", "25/1", "1/25", "1/25");
+ assertAudioStream((*streams)[1], 1, "vorbis", "Vorbis", "48000", "stereo", "fltp", "80000");
+
+ assertStreamTag((*streams)[0], "ENCODER", "ffmpeg2theora 0.19");
+ assertStreamTag((*streams)[1], "ENCODER", "ffmpeg2theora 0.19");
+}
+
+void testMediaInformationJsonParser(void) {
+ testMediaInformationMp3();
+ testMediaInformationJpg();
+ testMediaInformationGif();
+ testMediaInformationMp4();
+ testMediaInformationPng();
+ testMediaInformationOgg();
+
+ std::cout << "MediaInformationJsonParserTest passed." << std::endl;
+}
diff --git a/linux/test-app-local-dependency/src/MediaInformationParserTest.h b/linux/test-app-local-dependency/src/MediaInformationParserTest.h
new file mode 100644
index 0000000..85255f4
--- /dev/null
+++ b/linux/test-app-local-dependency/src/MediaInformationParserTest.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018-2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include
+#include
+#include
+
+void assertNumber(long expected, std::shared_ptr real);
+void assertString(std::string expected, std::shared_ptr real);
+void assertString(std::string expected, std::string real);
+
+/**
+ * All json parser tests are initiated from this method
+ */
+void testMediaInformationJsonParser();
diff --git a/linux/test-app-local-dependency/src/OtherTab.cpp b/linux/test-app-local-dependency/src/OtherTab.cpp
new file mode 100644
index 0000000..fb2a128
--- /dev/null
+++ b/linux/test-app-local-dependency/src/OtherTab.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "OtherTab.h"
+#include "Application.h"
+#include "Constants.h"
+#include "Popup.h"
+#include "Video.h"
+#include
+#include
+
+using namespace ffmpegkit;
+
+static gboolean showTestSuccessPopup(const std::pair* parameters) {
+ Gtk::Window* window = parameters->first;
+ auto messageDetail = parameters->second;
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_INFO, messageDetail);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean showTestFailedPopup(const std::pair* parameters) {
+ Gtk::Window* window = parameters->first;
+ auto messageDetail = parameters->second;
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_ERROR, messageDetail);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean appendLog(const std::pair>* parameters) {
+ ffmpegkittest::OtherTab* otherTab = parameters->first;
+ auto log = parameters->second;
+ otherTab->appendOutput(log->getMessage());
+ delete parameters;
+ return FALSE;
+}
+
+ffmpegkittest::OtherTab::OtherTab() : selectedTest(-1) {
+ testModel = Gtk::ListStore::create(testModelColumn);
+ test.set_model(testModel);
+ test.set_size_request(240, 30);
+ test.signal_changed().connect(sigc::mem_fun(*this, &OtherTab::onTestChanged));
+ Util::applyComboBoxStyle(test);
+
+ initTestData();
+
+ runButton.set_label("RUN");
+ runButton.set_size_request(120, 30);
+ runButton.set_tooltip_text(Constants::OtherTestTooltipText);
+ runButton.signal_clicked().connect(sigc::mem_fun(*this, &OtherTab::runTest));
+ Util::applyButtonStyle(runButton);
+ runButtonBox.pack_start(runButton, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(testBox, Gtk::PACK_SHRINK);
+ pack_start(runButtonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+}
+
+void ffmpegkittest::OtherTab::setActive() {
+ std::cout << "Other Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback(nullptr);
+ FFmpegKitConfig::enableStatisticsCallback(nullptr);
+}
+
+void ffmpegkittest::OtherTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::OtherTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::OtherTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
+
+void ffmpegkittest::OtherTab::initTestData() {
+ auto row = *(testModel->append());
+ row[testModelColumn.columnId] = "1";
+ row[testModelColumn.columnName] = "chromaprint";
+
+ row = *(testModel->append());
+ row[testModelColumn.columnId] = "2";
+ row[testModelColumn.columnName] = "dav1d";
+
+ row = *(testModel->append());
+ row[testModelColumn.columnId] = "3";
+ row[testModelColumn.columnName] = "webp";
+
+ row = *(testModel->append());
+ row[testModelColumn.columnId] = "4";
+ row[testModelColumn.columnName] = "zscale";
+
+ test.pack_start(testModelColumn.columnName);
+ test.set_entry_text_column(testModelColumn.columnId);
+ test.set_active(0);
+
+ testBox.pack_start(test, Gtk::PACK_EXPAND_PADDING);
+}
+
+void ffmpegkittest::OtherTab::onTestChanged() {
+ int rowNumber = test.get_active_row_number();
+ if (rowNumber != -1) {
+ selectedTest = rowNumber;
+ }
+}
+
+std::string ffmpegkittest::OtherTab::getSelectedTest() {
+ switch(selectedTest) {
+ case 0: return "chromaprint";
+ case 1: return "dav1d";
+ case 2: return "webp";
+ case 3: return "zscale";
+ default: return "";
+ }
+}
+
+void ffmpegkittest::OtherTab::runTest() {
+ clearOutput();
+
+ std::string selectedTest = this->getSelectedTest();
+ if (selectedTest.compare("chromaprint") == 0) {
+ testChromaprint();
+ } else if (selectedTest.compare("dav1d") == 0) {
+ testDav1d();
+ } else if (selectedTest.compare("webp") == 0) {
+ testWebp();
+ } else if (selectedTest.compare("zscale") == 0) {
+ testZscale();
+ }
+}
+
+void ffmpegkittest::OtherTab::testChromaprint() {
+ std::cout << "Testing 'chromaprint' mutex." << std::endl;
+
+ std::string audioSampleFile = getChromaprintSampleFile();
+ std::remove(audioSampleFile.c_str());
+
+ std::string ffmpegCommand = "-hide_banner -y -f lavfi -i sine=frequency=1000:duration=5 -c:a pcm_s16le " + audioSampleFile;
+
+ std::cout << "Creating audio sample with '" << ffmpegCommand << "'." << std::endl;
+
+ FFmpegKit::executeAsync(ffmpegCommand, [this,audioSampleFile](auto session) {
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(session->getState()) << " and rc " << session->getReturnCode() << "." << session->getFailStackTrace() << std::endl;
+
+ if (ReturnCode::isSuccess(session->getReturnCode())) {
+ std::cout << "AUDIO sample created." << std::endl;
+
+ std::string chromaprintCommand = "-hide_banner -y -i " + audioSampleFile + " -f chromaprint -fp_format 2 " + getChromaprintOutputFile();
+
+ std::cout << "FFmpeg process started with arguments '" << chromaprintCommand << "'." << std::endl;
+
+ FFmpegKit::executeAsync(chromaprintCommand, [this](auto secondSession) {
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(secondSession->getState()) << " and rc " << secondSession->getReturnCode() << "." << secondSession->getFailStackTrace() << std::endl;
+ if (ReturnCode::isSuccess(secondSession->getReturnCode())) {
+ g_idle_add((GSourceFunc)showTestSuccessPopup, new std::pair(this->parentWindow, "Testing chromaprint completed successfully."));
+ } else {
+ g_idle_add((GSourceFunc)showTestFailedPopup, new std::pair(this->parentWindow, "Testing chromaprint failed. Please check logs for the details."));
+ }
+ }, [this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ }, nullptr);
+
+ } else {
+ g_idle_add((GSourceFunc)showTestFailedPopup, new std::pair(this->parentWindow, "Creating AUDIO sample failed. Please check logs for the details."));
+ }
+ });
+}
+
+void ffmpegkittest::OtherTab::testDav1d() {
+ std::cout << "Testing decoding 'av1' codec." << std::endl;
+
+ std::string ffmpegCommand = std::string("-hide_banner -y -i ") + Dav1dTestDefaultUrl + " " + getDav1dOutputFile();
+
+ std::cout << "FFmpeg process started with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ FFmpegKit::executeAsync(ffmpegCommand, [this](auto session) {
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(session->getState()) << " and rc " << session->getReturnCode() << "." << session->getFailStackTrace() << std::endl;
+ }, [this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ }, nullptr);
+}
+
+void ffmpegkittest::OtherTab::testWebp() {
+ std::string imageFile = Application::getApplicationInstallDirectory() + "/share/images/machupicchu.jpg";
+ std::string outputFile = Application::getApplicationCacheDirectory() + "/video.webp";
+
+ std::cout << "Testing 'webp' codec." << std::endl;
+
+ std::string ffmpegCommand = "-hide_banner -y -i " + imageFile + " " + outputFile;
+
+ std::cout << "FFmpeg process started with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ auto session = FFmpegKit::executeAsync(ffmpegCommand, [this](auto session) {
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(session->getState()) << " and rc " << session->getReturnCode() << "." << session->getFailStackTrace() << std::endl;
+
+ if (ReturnCode::isSuccess(session->getReturnCode())) {
+ g_idle_add((GSourceFunc)showTestSuccessPopup, new std::pair(this->parentWindow, "Encode webp completed successfully."));
+ } else {
+ g_idle_add((GSourceFunc)showTestFailedPopup, new std::pair(this->parentWindow, "Encode webp failed. Please check logs for the details."));
+ }
+ }, [this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ }, nullptr);
+}
+
+void ffmpegkittest::OtherTab::testZscale() {
+ std::string videoFile = Application::getApplicationCacheDirectory() + "/video.mp4";
+ std::string zscaledVideoFile = Application::getApplicationCacheDirectory() + "/video-zscaled.mp4";
+
+ std::cout << "Testing 'zscale' filter with video file created on the Video tab." << std::endl;
+
+ std::string ffmpegCommand = Video::generateZscaleVideoScript(videoFile, zscaledVideoFile);
+
+ std::cout << "FFmpeg process started with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ auto session = FFmpegKit::executeAsync(ffmpegCommand, [this](auto session) {
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(session->getState()) << " and rc " << session->getReturnCode() << "." << session->getFailStackTrace() << std::endl;
+
+ if (ReturnCode::isSuccess(session->getReturnCode())) {
+ g_idle_add((GSourceFunc)showTestSuccessPopup, new std::pair(this->parentWindow, "zscale completed successfully."));
+ } else {
+ g_idle_add((GSourceFunc)showTestFailedPopup, new std::pair(this->parentWindow, "zscale failed. Please check logs for the details."));
+ }
+ }, [this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ }, nullptr);
+}
+
+std::string ffmpegkittest::OtherTab::getChromaprintSampleFile() {
+ return Application::getApplicationCacheDirectory() + "/audio-sample.wav";
+}
+
+std::string ffmpegkittest::OtherTab::getDav1dOutputFile() {
+ return Application::getApplicationCacheDirectory() + "/video.mp4";
+}
+
+std::string ffmpegkittest::OtherTab::getChromaprintOutputFile() {
+ return Application::getApplicationCacheDirectory() + "/chromaprint.txt";
+}
diff --git a/linux/test-app-local-dependency/src/OtherTab.h b/linux/test-app-local-dependency/src/OtherTab.h
new file mode 100644
index 0000000..b8d9b24
--- /dev/null
+++ b/linux/test-app-local-dependency/src/OtherTab.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_OTHER_TAB_H
+#define FFMPEG_KIT_TEST_OTHER_TAB_H
+
+#include "Util.h"
+#include
+
+namespace ffmpegkittest {
+
+ class OtherTab: public Gtk::VBox {
+ public:
+
+ static constexpr const char* Dav1dTestDefaultUrl = "http://download.opencontent.netflix.com.s3.amazonaws.com/AV1/Sparks/Sparks-5994fps-AV1-10bit-960x540-film-grain-synthesis-854kbps.obu";
+
+ OtherTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+
+ private:
+ void clearOutput();
+ void initTestData();
+ void onTestChanged();
+ std::string getSelectedTest();
+ void runTest();
+ void testChromaprint();
+ void testDav1d();
+ void testWebp();
+ void testZscale();
+ std::string getChromaprintSampleFile();
+ std::string getDav1dOutputFile();
+ std::string getChromaprintOutputFile();
+
+ Glib::RefPtr testModel;
+ ComboBoxModelColumn testModelColumn;
+ Gtk::ComboBox test;
+ Gtk::HBox testBox;
+ int selectedTest;
+ Gtk::Button runButton;
+ Gtk::HBox runButtonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ Gtk::Window* parentWindow;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_OTHER_TAB_H
diff --git a/linux/test-app-local-dependency/src/PipeTab.cpp b/linux/test-app-local-dependency/src/PipeTab.cpp
new file mode 100644
index 0000000..dd93197
--- /dev/null
+++ b/linux/test-app-local-dependency/src/PipeTab.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "PipeTab.h"
+#include "Application.h"
+#include "Constants.h"
+#include "Log.h"
+#include "Popup.h"
+#include "Statistics.h"
+#include "Video.h"
+#include
+#include
+#include
+
+using namespace ffmpegkit;
+
+static gboolean showCreateFailedPopup(Gtk::Window* window) {
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_ERROR, "Create failed. Please check logs for the details.");
+ return FALSE;
+}
+
+static gboolean saveStatistics(const std::pair>* parameters) {
+ ffmpegkittest::PipeTab* videoTab = parameters->first;
+ auto statistics = parameters->second;
+ videoTab->updateProgressDialog(statistics);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean appendLog(const std::pair>* parameters) {
+ ffmpegkittest::PipeTab* videoTab = parameters->first;
+ auto log = parameters->second;
+ videoTab->appendOutput(log->getMessage());
+ delete parameters;
+ return FALSE;
+}
+
+static void startAsyncCatImageProcess(std::string imagePath, std::shared_ptr namedPipePath) {
+ auto thread = std::thread([imagePath,namedPipePath]() {
+ std::string asyncCommand = "cat " + imagePath + " > " + *namedPipePath;
+
+ std::cout << "Starting async cat image command: " << asyncCommand << std::endl;
+
+ int rc = system(asyncCommand.c_str());
+
+ std::cout << "Async cat image command: " << asyncCommand << " exited with " << rc << "." << std::endl;
+ });
+ thread.detach();
+}
+
+ffmpegkittest::PipeTab::PipeTab() : statistics(nullptr) {
+ createButton.set_label("CREATE");
+ createButton.set_size_request(120, 30);
+ createButton.set_tooltip_text(Constants::PipeTestTooltipText);
+ createButton.signal_clicked().connect(sigc::mem_fun(*this, &PipeTab::createVideo));
+ Util::applyButtonStyle(createButton);
+ createButtonBox.pack_start(createButton, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(createButtonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+}
+
+void ffmpegkittest::PipeTab::setActive() {
+ std::cout << "Pipe Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback([this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ });
+ FFmpegKitConfig::enableStatisticsCallback([this](auto statistics) {
+ g_idle_add((GSourceFunc)saveStatistics, new std::pair>(this, statistics));
+ });
+}
+
+void ffmpegkittest::PipeTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::PipeTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::PipeTab::updateProgressDialog(const std::shared_ptr statistics) {
+ if (statistics == nullptr) {
+ return;
+ }
+
+ this->statistics = statistics;
+ int timeInMilliseconds = this->statistics->getTime();
+ if (timeInMilliseconds > 0) {
+ int totalVideoDuration = 9000;
+ double completePercentage = timeInMilliseconds*100/totalVideoDuration;
+ // progressDialog.update(completePercentage);
+ std::cout << "Creating video: " << completePercentage << "%" << std::endl;
+ }
+}
+
+void ffmpegkittest::PipeTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
+
+void ffmpegkittest::PipeTab::createVideo() {
+ clearOutput();
+
+ std::string image1File = Application::getApplicationInstallDirectory() + "/share/images/machupicchu.jpg";
+ std::string image2File = Application::getApplicationInstallDirectory() + "/share/images/pyramid.jpg";
+ std::string image3File = Application::getApplicationInstallDirectory() + "/share/images/stonehenge.jpg";
+ std::string videoFile = getVideoFile();
+
+ auto pipe1 = FFmpegKitConfig::registerNewFFmpegPipe();
+ auto pipe2 = FFmpegKitConfig::registerNewFFmpegPipe();
+ auto pipe3 = FFmpegKitConfig::registerNewFFmpegPipe();
+
+ std::remove(videoFile.c_str());
+
+ std::cout << "Testing PIPE with 'mpeg4' codec" << std::endl;
+
+ showProgressDialog();
+
+ std::string ffmpegCommand = Video::generateCreateVideoWithPipesScript(*pipe1, *pipe2, *pipe3, videoFile);
+
+ std::cout << "FFmpeg process started with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ auto session = FFmpegKit::executeAsync(ffmpegCommand, [this,pipe1,pipe2,pipe3](auto session) {
+ const auto state = session->getState();
+ auto returnCode = session->getReturnCode();
+
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(state) << " and rc " << returnCode << "." << session->getFailStackTrace() << std::endl;
+
+ this->hideProgressDialog();
+
+ // CLOSE PIPES
+ FFmpegKitConfig::closeFFmpegPipe(pipe1->c_str());
+ FFmpegKitConfig::closeFFmpegPipe(pipe2->c_str());
+ FFmpegKitConfig::closeFFmpegPipe(pipe3->c_str());
+
+ if (ReturnCode::isSuccess(returnCode)) {
+ std::cout << "Create completed successfully." << std::endl;
+ } else {
+ g_idle_add((GSourceFunc)showCreateFailedPopup, this->parentWindow);
+ }
+ }, [this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ }, [this](auto statistics) {
+ g_idle_add((GSourceFunc)saveStatistics, new std::pair>(this, statistics));
+ });
+
+ // START ASYNC PROCESSES AFTER INITIATING FFMPEG COMMAND
+ startAsyncCatImageProcess(image1File, pipe1);
+ startAsyncCatImageProcess(image2File, pipe2);
+ startAsyncCatImageProcess(image3File, pipe3);
+}
+
+std::string ffmpegkittest::PipeTab::getVideoFile() {
+ return Application::getApplicationCacheDirectory() + "/video.mp4";
+}
+
+void ffmpegkittest::PipeTab::showProgressDialog() {
+ // progressDialog.show(this->get_parent_window());
+}
+
+void ffmpegkittest::PipeTab::hideProgressDialog() {
+ // progressDialog.hide();
+}
diff --git a/linux/test-app-local-dependency/src/PipeTab.h b/linux/test-app-local-dependency/src/PipeTab.h
new file mode 100644
index 0000000..262a6d8
--- /dev/null
+++ b/linux/test-app-local-dependency/src/PipeTab.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_PIPE_TAB_H
+#define FFMPEG_KIT_TEST_PIPE_TAB_H
+
+#include "ProgressDialog.h"
+#include "Statistics.h"
+#include "Util.h"
+#include
+
+namespace ffmpegkittest {
+
+ class PipeTab: public Gtk::VBox {
+ public:
+ PipeTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+ void updateProgressDialog(const std::shared_ptr statistics);
+
+ private:
+ void clearOutput();
+ void createVideo();
+ std::string getVideoFile();
+ void showProgressDialog();
+ void hideProgressDialog();
+
+ Gtk::Button createButton;
+ Gtk::HBox createButtonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ ffmpegkittest::ProgressDialog progressDialog;
+ Gtk::Window* parentWindow;
+ std::shared_ptr statistics;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_PIPE_TAB_H
diff --git a/linux/test-app-local-dependency/src/Popup.cpp b/linux/test-app-local-dependency/src/Popup.cpp
new file mode 100644
index 0000000..d18fa20
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Popup.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "Popup.h"
+
+void ffmpegkittest::Popup::show(Gtk::Window* window, enum Gtk::MessageType messageType, const std::string& detail) {
+ if (window != nullptr) {
+ std::string data;
+ if (messageType == Gtk::MESSAGE_INFO) {
+ data = "Information";
+ } else {
+ data = "Error";
+ }
+ Gtk::MessageDialog dialog(*window, data, false, messageType, Gtk::BUTTONS_OK);
+ dialog.set_secondary_text(detail);
+ dialog.run();
+ }
+}
diff --git a/linux/test-app-local-dependency/src/Popup.h b/linux/test-app-local-dependency/src/Popup.h
new file mode 100644
index 0000000..ff0a184
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Popup.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_POPUP_H
+#define FFMPEG_KIT_TEST_POPUP_H
+
+#include
+#include
+
+namespace ffmpegkittest {
+
+ class Popup {
+ public:
+ static void show(Gtk::Window* window, enum Gtk::MessageType messageType, const std::string& detail);
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_POPUP_H
diff --git a/linux/test-app-local-dependency/src/ProgressDialog.cpp b/linux/test-app-local-dependency/src/ProgressDialog.cpp
new file mode 100644
index 0000000..f991134
--- /dev/null
+++ b/linux/test-app-local-dependency/src/ProgressDialog.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "ProgressDialog.h"
+#include
+
+namespace ffmpegkittest {
+
+ gboolean runDialog(ffmpegkittest::ProgressDialog* progressDialog) {
+ progressDialog->dialog.run();
+ return FALSE;
+ }
+
+}
+
+ffmpegkittest::ProgressDialog::ProgressDialog() : dialog("", Gtk::DIALOG_MODAL), alignment(0.5, 0.5, 0, 0) {
+ progressBar.set_show_text(false);
+ progressBar.set_fraction(0.0);
+ progressBar.set_size_request(100, 20);
+ alignment.add(progressBar);
+
+ dialog.set_default_size(300, 60);
+ dialog.get_content_area()->set_border_width(10);
+ dialog.get_content_area()->pack_start(alignment, Gtk::PACK_EXPAND_WIDGET, 5);
+ dialog.show_all_children(true);
+}
+
+void ffmpegkittest::ProgressDialog::show(const Glib::RefPtr parentWindow) {
+ dialog.set_parent_window(parentWindow);
+ g_idle_add((GSourceFunc)runDialog, this);
+}
+
+void ffmpegkittest::ProgressDialog::update(double fraction) {
+ progressBar.set_fraction(fraction);
+}
+
+void ffmpegkittest::ProgressDialog::hide() {
+ dialog.hide();
+}
diff --git a/linux/test-app-local-dependency/src/ProgressDialog.h b/linux/test-app-local-dependency/src/ProgressDialog.h
new file mode 100644
index 0000000..448cfaf
--- /dev/null
+++ b/linux/test-app-local-dependency/src/ProgressDialog.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_PROGRESS_DIALOG_H
+#define FFMPEG_KIT_TEST_PROGRESS_DIALOG_H
+
+#include
+#include
+
+namespace ffmpegkittest {
+
+ class ProgressDialog {
+ public:
+ ProgressDialog();
+ void show(const Glib::RefPtr parentWindow);
+ void update(double fraction);
+ void hide();
+
+ friend gboolean runDialog(ProgressDialog* progressDialog);
+
+ private:
+ void run();
+
+ Gtk::Dialog dialog;
+ Gtk::Alignment alignment;
+ Gtk::ProgressBar progressBar;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_PROGRESS_DIALOG_H
diff --git a/linux/test-app-local-dependency/src/SubtitleTab.cpp b/linux/test-app-local-dependency/src/SubtitleTab.cpp
new file mode 100644
index 0000000..12b767f
--- /dev/null
+++ b/linux/test-app-local-dependency/src/SubtitleTab.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "SubtitleTab.h"
+#include "Application.h"
+#include "Constants.h"
+#include "Log.h"
+#include "Popup.h"
+#include "Statistics.h"
+#include "Video.h"
+#include
+#include
+
+using namespace ffmpegkit;
+
+enum State {
+ StateIdle,
+ StateCreating,
+ StateBurning
+};
+
+static State state = StateIdle;
+
+static long sessionId = -1;
+
+static gboolean showBurningCancelledPopup(const std::pair* parameters) {
+ Gtk::Window* window = parameters->first;
+ auto messageDetail = parameters->second;
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_INFO, messageDetail);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean showBurningFailedPopup(const std::pair* parameters) {
+ Gtk::Window* window = parameters->first;
+ auto messageDetail = parameters->second;
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_ERROR, messageDetail);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean saveStatistics(const std::pair>* parameters) {
+ ffmpegkittest::SubtitleTab* subtitleTab = parameters->first;
+ auto statistics = parameters->second;
+ subtitleTab->updateProgressDialog(statistics);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean appendLog(const std::pair>* parameters) {
+ ffmpegkittest::SubtitleTab* videoTab = parameters->first;
+ auto log = parameters->second;
+ videoTab->appendOutput(log->getMessage());
+ delete parameters;
+ return FALSE;
+}
+
+ffmpegkittest::SubtitleTab::SubtitleTab() : statistics(nullptr) {
+ encodeButton.set_label("BURN SUBTITLES");
+ encodeButton.set_size_request(120, 30);
+ encodeButton.set_tooltip_text(Constants::SubtitleTestEncodeTooltipText);
+ encodeButton.signal_clicked().connect(sigc::mem_fun(*this, &SubtitleTab::burnSubtitles));
+ Util::applyButtonStyle(encodeButton);
+ cancelButton.set_label("CANCEL");
+ cancelButton.set_size_request(120, 30);
+ cancelButton.set_tooltip_text(Constants::SubtitleTestCancelTooltipText);
+ cancelButton.signal_clicked().connect(sigc::mem_fun(*this, &SubtitleTab::cancel));
+ Util::applyButtonStyle(cancelButton);
+ buttonBox.pack_start(encodeButton, Gtk::PACK_EXPAND_PADDING);
+ buttonBox.pack_start(cancelButton, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(buttonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+
+ state = StateIdle;
+}
+
+void ffmpegkittest::SubtitleTab::setActive() {
+ std::cout << "Subtitle Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback([this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ });
+ FFmpegKitConfig::enableStatisticsCallback([this](auto statistics) {
+ g_idle_add((GSourceFunc)saveStatistics, new std::pair>(this, statistics));
+ });
+}
+
+void ffmpegkittest::SubtitleTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::SubtitleTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::SubtitleTab::updateProgressDialog(const std::shared_ptr statistics) {
+ if (statistics == nullptr || statistics->getTime() < 0) {
+ return;
+ }
+
+ this->statistics = statistics;
+ int timeInMilliseconds = this->statistics->getTime();
+ int totalVideoDuration = 9000;
+ double completePercentage = timeInMilliseconds*100/totalVideoDuration;
+ // progressDialog.update(completePercentage);
+ if (state == StateCreating) {
+ std::cout << "Creating video: " << completePercentage << "%" << std::endl;
+ } else if (state == StateBurning) {
+ std::cout << "Burning subtitles: " << completePercentage << "%" << std::endl;
+ }
+}
+
+void ffmpegkittest::SubtitleTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
+
+void ffmpegkittest::SubtitleTab::burnSubtitles() {
+ clearOutput();
+
+ std::string image1File = Application::getApplicationInstallDirectory() + "/share/images/machupicchu.jpg";
+ std::string image2File = Application::getApplicationInstallDirectory() + "/share/images/pyramid.jpg";
+ std::string image3File = Application::getApplicationInstallDirectory() + "/share/images/stonehenge.jpg";
+ std::string videoFile = getVideoFile();
+ std::string videoWithSubtitlesFile = getVideoWithSubtitlesFile();
+
+ std::cout << "Testing SUBTITLE burning." << std::endl;
+
+ showCreateProgressDialog();
+
+ std::string ffmpegCommand = Video::generateEncodeVideoScript(image1File, image2File, image3File, videoFile, "mpeg4", "");
+
+ std::cout << "FFmpeg process started with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ state = StateCreating;
+
+ sessionId = FFmpegKit::executeAsync(ffmpegCommand, [this, videoFile, videoWithSubtitlesFile](auto session) {
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(session->getState()) << " and rc " << session->getReturnCode() << "." << session->getFailStackTrace() << std::endl;
+
+ this->hideCreateProgressDialog();
+
+ if (ReturnCode::isSuccess(session->getReturnCode())) {
+ std::cout << "Create completed successfully; burning subtitles." << std::endl;
+
+ std::string burnSubtitlesCommand = "-y -i " + videoFile + " -vf subtitles=" + getSubtitleFile() + ":force_style='FontName=MyFontName' -c:v mpeg4 " + videoWithSubtitlesFile;
+
+ this->showBurnProgressDialog();
+
+ std::cout << "FFmpeg process started with arguments '" << burnSubtitlesCommand << "'." << std::endl;
+
+ state = StateBurning;
+
+ FFmpegKit::executeAsync(burnSubtitlesCommand, [this](auto secondSession) {
+
+ hideBurnProgressDialog();
+
+ if (ReturnCode::isSuccess(secondSession->getReturnCode())) {
+ std::cout << "Burn subtitles completed successfully." << std::endl;
+ } else if (ReturnCode::isCancel(secondSession->getReturnCode())) {
+ g_idle_add((GSourceFunc)showBurningCancelledPopup, new std::pair(this->parentWindow, "Burn subtitles operation cancelled."));
+ std::cout << "Burn subtitles operation cancelled." << std::endl;
+ } else {
+ g_idle_add((GSourceFunc)showBurningFailedPopup, new std::pair(this->parentWindow, "Burn subtitles failed. Please check logs for the details."));
+ std::cout << "Burn subtitles failed with state " << FFmpegKitConfig::sessionStateToString(secondSession->getState()) << " and rc " << secondSession->getReturnCode() << "." << secondSession->getFailStackTrace() << std::endl;
+ }
+ });
+ }
+ })->getSessionId();
+
+ std::cout << "Async FFmpeg process started with sessionId " << sessionId << "." << std::endl;
+}
+
+void ffmpegkittest::SubtitleTab::cancel() {
+ if (sessionId > -1) {
+ std::cout << "Cancelling FFmpeg execution with sessionId " << sessionId << "." << std::endl;
+ FFmpegKit::cancel(sessionId);
+ }
+}
+
+std::string ffmpegkittest::SubtitleTab::getSubtitleFile() {
+ return Application::getApplicationInstallDirectory() + "/share/subtitles/subtitle.srt";
+}
+
+std::string ffmpegkittest::SubtitleTab::getVideoFile() {
+ return Application::getApplicationCacheDirectory() + "/video.mp4";
+}
+
+std::string ffmpegkittest::SubtitleTab::getVideoWithSubtitlesFile() {
+ return Application::getApplicationCacheDirectory() + "/video-with-subtitles.mp4";
+}
+
+void ffmpegkittest::SubtitleTab::showCreateProgressDialog() {
+ // progressDialog.show(this->get_parent_window());
+}
+
+void ffmpegkittest::SubtitleTab::hideCreateProgressDialog() {
+ // progressDialog.hide();
+}
+
+void ffmpegkittest::SubtitleTab::showBurnProgressDialog() {
+ // progressDialog.show(this->get_parent_window());
+}
+
+void ffmpegkittest::SubtitleTab::hideBurnProgressDialog() {
+ // progressDialog.hide();
+}
diff --git a/linux/test-app-local-dependency/src/SubtitleTab.h b/linux/test-app-local-dependency/src/SubtitleTab.h
new file mode 100644
index 0000000..affc553
--- /dev/null
+++ b/linux/test-app-local-dependency/src/SubtitleTab.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_SUBTITLE_TAB_H
+#define FFMPEG_KIT_TEST_SUBTITLE_TAB_H
+
+#include "ProgressDialog.h"
+#include "Statistics.h"
+#include "Util.h"
+#include
+
+namespace ffmpegkittest {
+
+ class SubtitleTab: public Gtk::VBox {
+ public:
+ SubtitleTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+ void updateProgressDialog(const std::shared_ptr statistics);
+
+ private:
+ void clearOutput();
+ void burnSubtitles();
+ void cancel();
+ std::string getSubtitleFile();
+ std::string getVideoFile();
+ std::string getVideoWithSubtitlesFile();
+ void showCreateProgressDialog();
+ void hideCreateProgressDialog();
+ void showBurnProgressDialog();
+ void hideBurnProgressDialog();
+
+ Gtk::Button encodeButton;
+ Gtk::Button cancelButton;
+ Gtk::HBox buttonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ ffmpegkittest::ProgressDialog progressDialog;
+ Gtk::Window* parentWindow;
+ std::shared_ptr statistics;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_SUBTITLE_TAB_H
diff --git a/linux/test-app-local-dependency/src/Util.cpp b/linux/test-app-local-dependency/src/Util.cpp
new file mode 100644
index 0000000..4f05891
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Util.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "Util.h"
+#include
+
+void applyCssData(Gtk::Widget& widget, const std::string& data) {
+ Glib::RefPtr cssProvider = Gtk::CssProvider::create();
+ cssProvider->load_from_data(data);
+ widget.get_style_context()->add_provider(cssProvider, GTK_STYLE_PROVIDER_PRIORITY_USER);
+}
+
+void ffmpegkittest::Util::applyEditTextStyle(Gtk::Entry& entry) {
+ entry.override_background_color(Gdk::RGBA("White"));
+ entry.override_color(Gdk::RGBA("Black"));
+ entry.set_margin_start(20);
+ entry.set_margin_end(20);
+ entry.set_margin_top(20);
+ entry.set_margin_bottom(10);
+ applyCssData(entry, "entry {border: 1px solid rgba(52, 152, 219, 1.0);}\
+ entry {border: 1px solid rgba(52, 152, 219, 1.0);}");
+}
+
+void ffmpegkittest::Util::applyButtonStyle(Gtk::Button& button) {
+ button.override_color(Gdk::RGBA("White"));
+ button.set_margin_top(10);
+ button.set_margin_bottom(10);
+ applyCssData(button, "button {background-image: image(rgba(46, 204, 113, 1.0)); border: 1px solid rgba(39, 174, 96, 1.0);}\
+ button:active {background-image: image(rgba(46, 174, 113, 1.0)); border: 1px solid rgba(39, 174, 96, 1.0);}");
+}
+
+void ffmpegkittest::Util::applyOutputTextStyle(Gtk::TextView& textView) {
+ textView.set_margin_start(20);
+ textView.set_margin_end(20);
+ textView.set_margin_top(10);
+ textView.set_margin_bottom(20);
+ textView.override_color(Gdk::RGBA("White"));
+ applyCssData(textView, "textview text {background-image: image(rgba(241, 196, 15, 1.0)); border-radius: 5px; border: 1px solid rgba(243, 156, 18, 1.0);}");
+}
+
+void ffmpegkittest::Util::applyComboBoxStyle(Gtk::ComboBox& comboBox) {
+ comboBox.set_margin_start(20);
+ comboBox.set_margin_end(20);
+ comboBox.set_margin_top(20);
+ comboBox.set_margin_bottom(10);
+ comboBox.override_color(Gdk::RGBA("White"));
+ applyCssData(comboBox, "combobox {background-image: image(rgba(155, 89, 182, 1.0)); border-radius: 5px; border: 1px solid rgba(142, 68, 173, 1.0);}");
+}
+
+void ffmpegkittest::Util::applyVideoPlayerFrameStyle(Gtk::Button& button) {
+ button.set_margin_top(10);
+ button.set_margin_bottom(10);
+ applyCssData(button, "button {background-image: image(rgba(236, 240, 241, 1.0)); border: 1px solid rgba(185, 195, 199, 1.0);}");
+}
diff --git a/linux/test-app-local-dependency/src/Util.h b/linux/test-app-local-dependency/src/Util.h
new file mode 100644
index 0000000..b295820
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Util.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_UTIL_H
+#define FFMPEG_KIT_TEST_UTIL_H
+
+#include
+#include
+
+namespace ffmpegkittest {
+
+ class Util {
+ public:
+ static void applyEditTextStyle(Gtk::Entry& entry);
+ static void applyButtonStyle(Gtk::Button& button);
+ static void applyComboBoxStyle(Gtk::ComboBox& comboBox);
+ static void applyOutputTextStyle(Gtk::TextView& textView);
+ static void applyVideoPlayerFrameStyle(Gtk::Button& button);
+ };
+
+ class ComboBoxModelColumn : public Gtk::TreeModel::ColumnRecord {
+ public:
+ ComboBoxModelColumn() {
+ add(columnId);
+ add(columnName);
+ }
+
+ Gtk::TreeModelColumn columnId;
+ Gtk::TreeModelColumn columnName;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_UTIL_H
diff --git a/linux/test-app-local-dependency/src/VidStabTab.cpp b/linux/test-app-local-dependency/src/VidStabTab.cpp
new file mode 100644
index 0000000..5fbfa62
--- /dev/null
+++ b/linux/test-app-local-dependency/src/VidStabTab.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "VidStabTab.h"
+#include "Application.h"
+#include "Constants.h"
+#include "Log.h"
+#include "Popup.h"
+#include "Video.h"
+#include
+#include
+
+using namespace ffmpegkit;
+
+static gboolean showStabilizeFailedPopup(const std::pair* parameters) {
+ Gtk::Window* window = parameters->first;
+ auto messageDetail = parameters->second;
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_ERROR, messageDetail);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean appendLog(const std::pair>* parameters) {
+ ffmpegkittest::VidStabTab* videoTab = parameters->first;
+ auto log = parameters->second;
+ videoTab->appendOutput(log->getMessage());
+ delete parameters;
+ return FALSE;
+}
+
+ffmpegkittest::VidStabTab::VidStabTab() {
+ stabilizeVideoButton.set_label("STABILIZE VIDEO");
+ stabilizeVideoButton.set_size_request(120, 30);
+ stabilizeVideoButton.set_tooltip_text(Constants::VidStabTestTooltipText);
+ stabilizeVideoButton.signal_clicked().connect(sigc::mem_fun(*this, &VidStabTab::stabilizeVideo));
+ Util::applyButtonStyle(stabilizeVideoButton);
+ stabilizeVideoButtonBox.pack_start(stabilizeVideoButton, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(stabilizeVideoButtonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+}
+
+void ffmpegkittest::VidStabTab::setActive() {
+ std::cout << "VidStab Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback([this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ });
+ FFmpegKitConfig::enableStatisticsCallback(nullptr);
+}
+
+void ffmpegkittest::VidStabTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::VidStabTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::VidStabTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
+
+void ffmpegkittest::VidStabTab::stabilizeVideo() {
+ clearOutput();
+
+ std::string image1File = Application::getApplicationInstallDirectory() + "/share/images/machupicchu.jpg";
+ std::string image2File = Application::getApplicationInstallDirectory() + "/share/images/pyramid.jpg";
+ std::string image3File = Application::getApplicationInstallDirectory() + "/share/images/stonehenge.jpg";
+ std::string shakeResultsFile = getShakeResultsFile();
+ std::string videoFile = getVideoFile();
+ std::string stabilizedVideoFile = getStabilizedVideoFile();
+
+ std::remove(shakeResultsFile.c_str());
+ std::remove(videoFile.c_str());
+ std::remove(stabilizedVideoFile.c_str());
+
+ std::cout << "Testing VID.STAB." << std::endl;
+
+ showCreateProgressDialog();
+
+ std::string ffmpegCommand = Video::generateShakingVideoScript(image1File, image2File, image3File, videoFile);
+
+ std::cout << "FFmpeg process started with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ FFmpegKit::executeAsync(ffmpegCommand, [this, videoFile, shakeResultsFile, stabilizedVideoFile](auto session) {
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(session->getState()) << " and rc " << session->getReturnCode() << "." << session->getFailStackTrace() << std::endl;
+
+ this->hideCreateProgressDialog();
+
+ if (ReturnCode::isSuccess(session->getReturnCode())) {
+ std::cout << "Create completed successfully; stabilizing video." << std::endl;
+
+ std::string analyzeVideoCommand = "-y -i " + videoFile + " -vf vidstabdetect=shakiness=10:accuracy=15:result=" + shakeResultsFile + " -f null -";
+
+ this->showStabilizeProgressDialog();
+
+ std::cout << "FFmpeg process started with arguments '" << analyzeVideoCommand << "'." << std::endl;
+
+ FFmpegKit::executeAsync(analyzeVideoCommand, [this, videoFile, shakeResultsFile, stabilizedVideoFile](auto secondSession) {
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(secondSession->getState()) << " and rc " << secondSession->getReturnCode() << "." << secondSession->getFailStackTrace() << std::endl;
+
+ if (ReturnCode::isSuccess(secondSession->getReturnCode())) {
+ std::string stabilizeVideoCommand = "-y -i " + videoFile + " -vf vidstabtransform=smoothing=30:input=" + shakeResultsFile + " -c:v mpeg4 " + stabilizedVideoFile;
+
+ std::cout << "FFmpeg process started with arguments '" << stabilizeVideoCommand << "'." << std::endl;
+
+ FFmpegKit::executeAsync(stabilizeVideoCommand, [this](auto thirdSession) {
+
+ std::cout << "FFmpeg process exited with state " << FFmpegKitConfig::sessionStateToString(thirdSession->getState()) << " and rc " << thirdSession->getReturnCode() << "." << thirdSession->getFailStackTrace() << std::endl;
+
+ this->hideStabilizeProgressDialog();
+
+ if (ReturnCode::isSuccess(thirdSession->getReturnCode())) {
+ std::cout << "Stabilize video completed successfully." << std::endl;
+ } else {
+ g_idle_add((GSourceFunc)showStabilizeFailedPopup, new std::pair(this->parentWindow, "Stabilize video failed. Please check logs for the details."));
+ }
+ });
+
+ } else {
+ this->hideCreateProgressDialog();
+ g_idle_add((GSourceFunc)showStabilizeFailedPopup, new std::pair(this->parentWindow, "Stabilize video failed. Please check logs for the details."));
+ }
+ });
+ } else {
+ g_idle_add((GSourceFunc)showStabilizeFailedPopup, new std::pair(this->parentWindow, "Create video failed. Please check logs for the details."));
+ }
+ });
+}
+
+std::string ffmpegkittest::VidStabTab::getShakeResultsFile() {
+ return Application::getApplicationCacheDirectory() + "/transforms.trf";
+}
+
+std::string ffmpegkittest::VidStabTab::getVideoFile() {
+ return Application::getApplicationCacheDirectory() + "/video.mp4";
+}
+
+std::string ffmpegkittest::VidStabTab::getStabilizedVideoFile() {
+ return Application::getApplicationCacheDirectory() + "/video-stabilized.mp4";
+}
+
+void ffmpegkittest::VidStabTab::showCreateProgressDialog() {
+ // progressDialog.show(this->get_parent_window());
+}
+
+void ffmpegkittest::VidStabTab::hideCreateProgressDialog() {
+ // progressDialog.hide();
+}
+
+void ffmpegkittest::VidStabTab::showStabilizeProgressDialog() {
+ // progressDialog.show(this->get_parent_window());
+}
+
+void ffmpegkittest::VidStabTab::hideStabilizeProgressDialog() {
+ // progressDialog.hide();
+}
diff --git a/linux/test-app-local-dependency/src/VidStabTab.h b/linux/test-app-local-dependency/src/VidStabTab.h
new file mode 100644
index 0000000..2ff5dd1
--- /dev/null
+++ b/linux/test-app-local-dependency/src/VidStabTab.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_VIDSTAB_TAB_H
+#define FFMPEG_KIT_TEST_VIDSTAB_TAB_H
+
+#include "ProgressDialog.h"
+#include "Statistics.h"
+#include "Util.h"
+#include
+
+namespace ffmpegkittest {
+
+ class VidStabTab: public Gtk::VBox {
+ public:
+ VidStabTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+
+ private:
+ void clearOutput();
+ void stabilizeVideo();
+ std::string getShakeResultsFile();
+ std::string getVideoFile();
+ std::string getStabilizedVideoFile();
+ void showCreateProgressDialog();
+ void hideCreateProgressDialog();
+ void showStabilizeProgressDialog();
+ void hideStabilizeProgressDialog();
+
+ Gtk::Button stabilizeVideoButton;
+ Gtk::HBox stabilizeVideoButtonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ ffmpegkittest::ProgressDialog progressDialog;
+ Gtk::Window* parentWindow;
+ std::shared_ptr statistics;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_VIDSTAB_TAB_H
diff --git a/linux/test-app-local-dependency/src/Video.cpp b/linux/test-app-local-dependency/src/Video.cpp
new file mode 100644
index 0000000..efb2a34
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Video.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "Video.h"
+
+std::string ffmpegkittest::Video::generateCreateVideoWithPipesScript(std::string image1Pipe, std::string image2Pipe, std::string image3Pipe, std::string videoFilePath) {
+ return
+ "-hide_banner -y -i \"" + image1Pipe + "\" " +
+ "-i '" + image2Pipe + "' " +
+ "-i " + image3Pipe + " " +
+ "-filter_complex \"" +
+ "[0:v]loop=loop=-1:size=1:start=0,setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream1out1][stream1out2];" +
+ "[1:v]loop=loop=-1:size=1:start=0,setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream2out1][stream2out2];" +
+ "[2:v]loop=loop=-1:size=1:start=0,setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream3out1][stream3out2];" +
+ "[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];" +
+ "[stream1out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream1ending];" +
+ "[stream2out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream2overlaid];" +
+ "[stream2out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30),split=2[stream2starting][stream2ending];" +
+ "[stream3out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream3overlaid];" +
+ "[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];" +
+ "[stream2starting][stream1ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream2blended];" +
+ "[stream3starting][stream2ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream3blended];" +
+ "[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]\"" +
+ " -map [video] -vsync 2 -async 1 -c:v mpeg4 -r 30 " + videoFilePath;
+}
+
+std::string ffmpegkittest::Video::generateEncodeVideoScript(std::string image1Path, std::string image2Path, std::string image3Path, std::string videoFilePath, std::string videoCodec, std::string customOptions) {
+ return ffmpegkittest::Video::generateEncodeVideoScript(image1Path, image2Path, image3Path, videoFilePath, videoCodec, "yuv420p", customOptions);
+}
+
+std::string ffmpegkittest::Video::generateEncodeVideoScript(std::string image1Path, std::string image2Path, std::string image3Path, std::string videoFilePath, std::string videoCodec, std::string pixelFormat, std::string customOptions) {
+ return
+ "-hide_banner -y -loop 1 -i \"" + image1Path + "\" " +
+ "-loop 1 -i '" + image2Path + "' " +
+ "-loop 1 -i \"" + image3Path + "\" " +
+ "-filter_complex \"" +
+ "[0:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream1out1][stream1out2];" +
+ "[1:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream2out1][stream2out2];" +
+ "[2:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream3out1][stream3out2];" +
+ "[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];" +
+ "[stream1out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream1ending];" +
+ "[stream2out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream2overlaid];" +
+ "[stream2out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30),split=2[stream2starting][stream2ending];" +
+ "[stream3out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream3overlaid];" +
+ "[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];" +
+ "[stream2starting][stream1ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream2blended];" +
+ "[stream3starting][stream2ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream3blended];" +
+ "[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=" + pixelFormat + "[video]\"" +
+ " -map [video] -vsync 2 -async 1 " + customOptions + "-c:v " + videoCodec + " -r 30 " + videoFilePath;
+}
+
+std::string ffmpegkittest::Video::generateShakingVideoScript(std::string image1Path, std::string image2Path, std::string image3Path, std::string videoFilePath) {
+ return
+ "-hide_banner -y -loop 1 -i \"" + image1Path + "\" " +
+ "-loop 1 -i '" + image2Path + "' " +
+ "-loop 1 -i " + image3Path + " " +
+ "-f lavfi -i color=black:s=640x427 " +
+ "-filter_complex \"" +
+ "[0:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1[stream1out];" +
+ "[1:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1[stream2out];" +
+ "[2:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1[stream3out];" +
+ "[stream1out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream1overlaid];" +
+ "[stream2out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream2overlaid];" +
+ "[stream3out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream3overlaid];" +
+ "[3:v][stream1overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream1shaking];" +
+ "[3:v][stream2overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream2shaking];" +
+ "[3:v][stream3overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream3shaking];" +
+ "[stream1shaking][stream2shaking][stream3shaking]concat=n=3:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]\"" +
+ " -map [video] -vsync 2 -async 1 -c:v mpeg4 -r 30 " + videoFilePath;
+}
+
+std::string ffmpegkittest::Video::generateZscaleVideoScript(std::string inputVideoFilePath, std::string outputVideoFilePath) {
+ return "-y -i " +
+ inputVideoFilePath +
+ " -vf zscale=tin=smpte2084:min=bt2020nc:pin=bt2020:rin=tv:t=smpte2084:m=bt2020nc:p=bt2020:r=tv,zscale=t=linear,tonemap=tonemap=clip,zscale=t=bt709,format=yuv420p " +
+ outputVideoFilePath;
+}
diff --git a/linux/test-app-local-dependency/src/Video.h b/linux/test-app-local-dependency/src/Video.h
new file mode 100644
index 0000000..4caf8c0
--- /dev/null
+++ b/linux/test-app-local-dependency/src/Video.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_VIDEO_H
+#define FFMPEG_KIT_TEST_VIDEO_H
+
+#include
+
+namespace ffmpegkittest {
+
+ class Video {
+ public:
+ static std::string generateCreateVideoWithPipesScript(std::string image1Pipe, std::string image2Pipe, std::string image3Pipe, std::string videoFilePath);
+ static std::string generateEncodeVideoScript(std::string image1Path, std::string image2Path, std::string image3Path, std::string videoFilePath, std::string videoCodec, std::string customOptions);
+ static std::string generateEncodeVideoScript(std::string image1Path, std::string image2Path, std::string image3Path, std::string videoFilePath, std::string videoCodec, std::string pixelFormat, std::string customOptions);
+ static std::string generateShakingVideoScript(std::string image1Path, std::string image2Path, std::string image3Path, std::string videoFilePath);
+ static std::string generateZscaleVideoScript(std::string inputVideoFilePath, std::string outputVideoFilePath);
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_VIDEO_H
diff --git a/linux/test-app-local-dependency/src/VideoTab.cpp b/linux/test-app-local-dependency/src/VideoTab.cpp
new file mode 100644
index 0000000..9d01250
--- /dev/null
+++ b/linux/test-app-local-dependency/src/VideoTab.cpp
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "VideoTab.h"
+#include "Application.h"
+#include "Constants.h"
+#include "Log.h"
+#include "Popup.h"
+#include "Statistics.h"
+#include "Video.h"
+#include
+#include
+#include
+
+using namespace ffmpegkit;
+
+static gboolean showEncodeFailedPopup(Gtk::Window* window) {
+ ffmpegkittest::Popup::show(window, Gtk::MESSAGE_ERROR, "Encode failed. Please check logs for the details.");
+ return FALSE;
+}
+
+static gboolean saveStatistics(const std::pair>* parameters) {
+ ffmpegkittest::VideoTab* videoTab = parameters->first;
+ auto statistics = parameters->second;
+ videoTab->updateProgressDialog(statistics);
+ delete parameters;
+ return FALSE;
+}
+
+static gboolean appendLog(const std::pair>* parameters) {
+ ffmpegkittest::VideoTab* videoTab = parameters->first;
+ auto log = parameters->second;
+ videoTab->appendOutput(log->getMessage());
+ delete parameters;
+ return FALSE;
+}
+
+ffmpegkittest::VideoTab::VideoTab() : selectedCodec(-1), statistics(nullptr) {
+ videoCodecModel = Gtk::ListStore::create(videoCodecModelColumn);
+ videoCodec.set_model(videoCodecModel);
+ videoCodec.set_size_request(240, 30);
+ videoCodec.signal_changed().connect(sigc::mem_fun(*this, &VideoTab::onVideoCodecChanged));
+ Util::applyComboBoxStyle(videoCodec);
+
+ initVideoCodecData();
+
+ encodeButton.set_label("ENCODE");
+ encodeButton.set_size_request(120, 30);
+ encodeButton.set_tooltip_text(Constants::VideoTestTooltipText);
+ encodeButton.signal_clicked().connect(sigc::mem_fun(*this, &VideoTab::encodeVideo));
+ Util::applyButtonStyle(encodeButton);
+ encodeButtonBox.pack_start(encodeButton, Gtk::PACK_EXPAND_PADDING);
+
+ outputText.set_editable(false);
+ Util::applyOutputTextStyle(outputText);
+ outputTextWindow.add(outputText);
+
+ pack_start(videoCodecBox, Gtk::PACK_SHRINK);
+ pack_start(encodeButtonBox, Gtk::PACK_SHRINK);
+ add(outputTextWindow);
+}
+
+void ffmpegkittest::VideoTab::setActive() {
+ std::cout << "Video Tab Activated" << std::endl;
+ FFmpegKitConfig::enableLogCallback(nullptr);
+ FFmpegKitConfig::enableStatisticsCallback(nullptr);
+}
+
+void ffmpegkittest::VideoTab::setParentWindow(Gtk::Window* parentWindow) {
+ this->parentWindow = parentWindow;
+}
+
+void ffmpegkittest::VideoTab::appendOutput(const std::string& string) {
+ outputText.get_buffer()->set_text(outputText.get_buffer()->get_text() + string);
+ Glib::RefPtr adj = outputText.get_vadjustment();
+ adj->set_value(adj->get_upper());
+}
+
+void ffmpegkittest::VideoTab::updateProgressDialog(const std::shared_ptr statistics) {
+ if (statistics == nullptr) {
+ return;
+ }
+
+ this->statistics = statistics;
+ int timeInMilliseconds = this->statistics->getTime();
+ if (timeInMilliseconds > 0) {
+ int totalVideoDuration = 9000;
+ double completePercentage = timeInMilliseconds*100/totalVideoDuration;
+ // progressDialog.update(completePercentage);
+ std::cout << "Encoding completed " << completePercentage << "%" << std::endl;
+ }
+}
+
+void ffmpegkittest::VideoTab::clearOutput() {
+ outputText.get_buffer()->set_text("");
+}
+
+void ffmpegkittest::VideoTab::initVideoCodecData() {
+ auto row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "1";
+ row[videoCodecModelColumn.columnName] = "mpeg4";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "2";
+ row[videoCodecModelColumn.columnName] = "x264";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "3";
+ row[videoCodecModelColumn.columnName] = "openh264";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "4";
+ row[videoCodecModelColumn.columnName] = "x265";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "5";
+ row[videoCodecModelColumn.columnName] = "xvid";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "6";
+ row[videoCodecModelColumn.columnName] = "vp8";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "7";
+ row[videoCodecModelColumn.columnName] = "vp9";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "8";
+ row[videoCodecModelColumn.columnName] = "aom";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "9";
+ row[videoCodecModelColumn.columnName] = "kvazaar";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "10";
+ row[videoCodecModelColumn.columnName] = "theora";
+
+ row = *(videoCodecModel->append());
+ row[videoCodecModelColumn.columnId] = "11";
+ row[videoCodecModelColumn.columnName] = "hap";
+
+ videoCodec.pack_start(videoCodecModelColumn.columnName);
+ videoCodec.set_entry_text_column(videoCodecModelColumn.columnId);
+ videoCodec.set_active(0);
+
+ videoCodecBox.pack_start(videoCodec, Gtk::PACK_EXPAND_PADDING);
+}
+
+void ffmpegkittest::VideoTab::onVideoCodecChanged() {
+ int rowNumber = videoCodec.get_active_row_number();
+ if (rowNumber != -1) {
+ selectedCodec = rowNumber;
+ }
+}
+
+std::string ffmpegkittest::VideoTab::getSelectedVideoCodec() {
+ switch(selectedCodec) {
+ case 0: return "mpeg4";
+ case 1: return "libx264";
+ case 2: return "libopenh264";
+ case 3: return "libx265";
+ case 4: return "libxvid";
+ case 5: return "vp8";
+ case 6: return "vp9";
+ case 7: return "libaom-av1";
+ case 8: return "libkvazaar";
+ case 9: return "theora";
+ case 10: return "hap";
+ default: return "";
+ }
+}
+
+void ffmpegkittest::VideoTab::encodeVideo() {
+ clearOutput();
+
+ std::string image1File = Application::getApplicationInstallDirectory() + "/share/images/machupicchu.jpg";
+ std::string image2File = Application::getApplicationInstallDirectory() + "/share/images/pyramid.jpg";
+ std::string image3File = Application::getApplicationInstallDirectory() + "/share/images/stonehenge.jpg";
+ std::string videoFile = getVideoFile();
+
+ std::remove(videoFile.c_str());
+
+ std::string videoCodec = this->getSelectedVideoCodec();
+
+ std::cout << "Testing VIDEO encoding with '" << videoCodec << "' codec" << std::endl;
+
+ showProgressDialog();
+
+ std::string ffmpegCommand = Video::generateEncodeVideoScript(image1File, image2File, image3File, videoFile, getSelectedVideoCodec(), getPixelFormat(), getCustomOptions());
+
+ std::cout << "FFmpeg process started with arguments '" << ffmpegCommand << "'." << std::endl;
+
+ auto session = FFmpegKit::executeAsync(ffmpegCommand, [this](auto session) {
+ const auto state = session->getState();
+ auto returnCode = session->getReturnCode();
+
+ this->hideProgressDialog();
+
+ if (ReturnCode::isSuccess(returnCode)) {
+ std::cout << "Encode completed successfully in " << session->getDuration() << " milliseconds." << std::endl;
+ } else {
+ g_idle_add((GSourceFunc)showEncodeFailedPopup, this->parentWindow);
+ std::cout << "Encode failed with state " << FFmpegKitConfig::sessionStateToString(state) << " and rc " << returnCode << "." << session->getFailStackTrace() << std::endl;
+ }
+ }, [this](auto log) {
+ g_idle_add((GSourceFunc)appendLog, new std::pair>(this, log));
+ }, [this](auto statistics) {
+ g_idle_add((GSourceFunc)saveStatistics, new std::pair>(this, statistics));
+ });
+
+ std::cout << "Async FFmpeg process started with sessionId " << session->getSessionId() << "." << std::endl;
+}
+
+std::string ffmpegkittest::VideoTab::getPixelFormat() {
+ std::string videoCodec = this->getSelectedVideoCodec();
+
+ std::string pixelFormat;
+ if (videoCodec.compare("libx265") == 0) {
+ pixelFormat = "yuv420p10le";
+ } else {
+ pixelFormat = "yuv420p";
+ }
+
+ return pixelFormat;
+}
+
+std::string ffmpegkittest::VideoTab::getVideoFile() {
+ std::string videoCodec = this->getSelectedVideoCodec();
+
+ std::string extension;
+ if (videoCodec.compare("vp8") == 0 || videoCodec.compare("vp9") == 0) {
+ extension = "webm";
+ } else if (videoCodec.compare("libaom-av1") == 0) {
+ extension = "mkv";
+ } else if (videoCodec.compare("theora") == 0) {
+ extension = "ogv";
+ } else if (videoCodec.compare("hap") == 0) {
+ extension = "mov";
+ } else {
+
+ // mpeg4, libx264, libx265, libxvid, kvazaar, libopenh264
+ extension = "mp4";
+ }
+
+ return Application::getApplicationCacheDirectory() + "/video." + extension;
+}
+
+std::string ffmpegkittest::VideoTab::getCustomOptions() {
+ std::string videoCodec = this->getSelectedVideoCodec();
+
+ if (videoCodec.compare("libx265") == 0) {
+ return "-crf 28 -preset fast ";
+ } else if (videoCodec.compare("vp8") == 0) {
+ return "-b:v 1M -crf 10 ";
+ } else if (videoCodec.compare("vp9") == 0) {
+ return "-b:v 2M ";
+ } else if (videoCodec.compare("libaom-av1") == 0) {
+ return "-crf 30 -strict experimental ";
+ } else if (videoCodec.compare("theora") == 0) {
+ return "-qscale:v 7 ";
+ } else if (videoCodec.compare("hap") == 0) {
+ return "-format hap_q ";
+ } else {
+
+ // kvazaar, mpeg4, libx264, libxvid, libopenh264
+ return "";
+ }
+}
+
+void ffmpegkittest::VideoTab::showProgressDialog() {
+ // progressDialog.show(this->get_parent_window());
+}
+
+void ffmpegkittest::VideoTab::hideProgressDialog() {
+ // progressDialog.hide();
+}
diff --git a/linux/test-app-local-dependency/src/VideoTab.h b/linux/test-app-local-dependency/src/VideoTab.h
new file mode 100644
index 0000000..4a21c68
--- /dev/null
+++ b/linux/test-app-local-dependency/src/VideoTab.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#ifndef FFMPEG_KIT_TEST_VIDEO_TAB_H
+#define FFMPEG_KIT_TEST_VIDEO_TAB_H
+
+#include "ProgressDialog.h"
+#include "Statistics.h"
+#include "Util.h"
+#include
+
+namespace ffmpegkittest {
+
+ class VideoTab: public Gtk::VBox {
+ public:
+ VideoTab();
+ void setActive();
+ void setParentWindow(Gtk::Window* parentWindow);
+ void appendOutput(const std::string& string);
+ void updateProgressDialog(const std::shared_ptr statistics);
+
+ private:
+ void clearOutput();
+ void initVideoCodecData();
+ void onVideoCodecChanged();
+ std::string getSelectedVideoCodec();
+ void encodeVideo();
+ std::string getPixelFormat();
+ std::string getVideoFile();
+ std::string getCustomOptions();
+ void showProgressDialog();
+ void hideProgressDialog();
+
+ Glib::RefPtr videoCodecModel;
+ ComboBoxModelColumn videoCodecModelColumn;
+ Gtk::ComboBox videoCodec;
+ Gtk::HBox videoCodecBox;
+ int selectedCodec;
+ Gtk::Button encodeButton;
+ Gtk::HBox encodeButtonBox;
+ Gtk::TextView outputText;
+ Gtk::ScrolledWindow outputTextWindow;
+ ffmpegkittest::ProgressDialog progressDialog;
+ Gtk::Window* parentWindow;
+ std::shared_ptr statistics;
+ };
+
+}
+
+#endif // FFMPEG_KIT_TEST_VIDEO_TAB_H
diff --git a/linux/test-app-local-dependency/src/main.cpp b/linux/test-app-local-dependency/src/main.cpp
new file mode 100644
index 0000000..a02f5c7
--- /dev/null
+++ b/linux/test-app-local-dependency/src/main.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022 Taner Sener
+ *
+ * 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.
+ */
+
+#include "Application.h"
+#include "MediaInformationParserTest.h"
+#include "FFmpegKitTest.h"
+#include
+
+int main(int argc, char** argv) {
+ auto app = Gtk::Application::create(argc, argv, "com.arthenica.ffmpegkit");
+ ffmpegkittest::Application application;
+ application.set_default_icon_name("ffmpeg-kit-linux-test");
+ application.set_icon_name("ffmpeg-kit-linux-test");
+
+ // RUN UNIT TESTS BEFORE STARTING THE APPLICATION
+ testMediaInformationJsonParser();
+ testFFmpegKit();
+
+ app->run(application);
+ ffmpegkit::FFmpegKitConfig::disableRedirection();
+ app->quit();
+ return 0;
+}
diff --git a/scripts/clean.sh b/scripts/clean.sh
index 7604745..b6c3cda 100755
--- a/scripts/clean.sh
+++ b/scripts/clean.sh
@@ -44,3 +44,5 @@ rm -rf ../macos/test-app-local-dependency/*.xcframework
rm -rf ../tvos/test-app-cocoapods/Pods
rm -rf ../tvos/test-app-local-dependency/*.framework
rm -rf ../tvos/test-app-local-dependency/*.xcframework
+
+rm -rf ../linux/test-app-local-dependency/build