Skip to content
Snippets Groups Projects
Commit 03e13290 authored by Julien Robert's avatar Julien Robert
Browse files

SDK: Add a new Example plugin with documentation

Gitlab: #63

Change-Id: I347661661c188089291b9707c44d4b804899c0ab
parent 99c44c61
No related branches found
No related tags found
No related merge requests found
Showing
with 1702 additions and 66 deletions
/build/ build/
build-local/
*.jpl *.jpl
*msvc* *msvc*
*build*
*android-toolchain-* *android-toolchain-*
config.mak config.mak
/contrib/Libs/ /contrib/Libs/
...@@ -13,3 +13,5 @@ config.mak ...@@ -13,3 +13,5 @@ config.mak
*.key *.key
*.sign *.sign
/plugin-builder/ /plugin-builder/
CMakeFiles/
*.so
\ No newline at end of file
# Copyright (C) 2023-2024 Savoir-faire Linux Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
# set the project name # set the project name
...@@ -6,6 +21,12 @@ set (Version 2.0.0) ...@@ -6,6 +21,12 @@ set (Version 2.0.0)
project(${ProjectName} VERSION ${Version}) project(${ProjectName} VERSION ${Version})
option(VIDEO_FFMPEG "If you'd like to listen to Jami's video stream or modify it, set to ON." OFF) # {FILL}
option(AUDIO_FFMPEG "If you'd like to listen to Jami's audio stream or modify it, set to ON." OFF) # {FILL}
option(ARCHIVE_JPL "If you'd like to end the build process before the generation of a JPL archive, set to OFF." ON) # {FILL}
option(NVIDIA "To disable hardware acceleration and use the graphics card for ONNX computation (useful for AI plugins), set to ON." OFF) # {FILL}
option(CERTIFICATION "If you'd like to certify the JPL archive created by ARCHIVE_JPL, set to ON. Requires ARCHIVE_JPL to be ON." ON) # {FILL}
set (DAEMON ${PROJECT_SOURCE_DIR}/../daemon) set (DAEMON ${PROJECT_SOURCE_DIR}/../daemon)
set (JPL_FILE_NAME ${ProjectName}.jpl) set (JPL_FILE_NAME ${ProjectName}.jpl)
set (DAEMON_SRC ${DAEMON}/src) set (DAEMON_SRC ${DAEMON}/src)
...@@ -43,35 +64,40 @@ message(Build path: ${PROJECT_BINARY_DIR}) ...@@ -43,35 +64,40 @@ message(Build path: ${PROJECT_BINARY_DIR})
message(JPL assembling path: ${JPL_DIRECTORY}) message(JPL assembling path: ${JPL_DIRECTORY})
message(JPL path: ${JPL_DIRECTORY}/../../../build/${ProjectName}/${JPL_FILE_NAME}) message(JPL path: ${JPL_DIRECTORY}/../../../build/${ProjectName}/${JPL_FILE_NAME})
# This is specifically to disable hardware acceleration and do computing on the graphics card for computationally expensive AI plugins.
if(NVIDIA)
add_definitions(-DNVIDIA)
set(ONNX_DIR ${ONNX_DIR}/nvidia-gpu)
message(Provider:\ NVIDIA)
set(EXTRA_PATH nvidia-gpu)
set (PREFERENCESFILENAME ${PREFERENCESFILENAME}-accel)
else()
set(ONNX_DIR ${ONNX_DIR}/cpu)
message(Provider:\ NONE)
set(EXTRA_PATH cpu)
endif()
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /DMSGPACK_NO_BOOST /MT") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /DMSGPACK_NO_BOOST /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DMSGPACK_NO_BOOST /MTd") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DMSGPACK_NO_BOOST /MTd")
# Android-specific flags set(SOURCES "")
if(ANDROID) file(GLOB CPP_SOURCES "*.cpp") # All .cpp files in the directory of your plugin ({root}/PLUGIN_NAME) will be collected by cmake.
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a) # Set the desired ABI list(APPEND SOURCES ${CPP_SOURCES})
set(CMAKE_ANDROID_STL_TYPE c++_shared) # Use C++ shared library
set(CMAKE_ANDROID_NDK_TOOLCHAIN_FILE ${CONTRIB_PATH}/build/cmake/android.toolchain.cmake) if(VIDEO_FFMPEG) # If you previously turned ON video stream capture, these will be imported.
set(CMAKE_ANDROID_STL_INCLUDE_DIR ${CONTRIB_PATH}/sysroot/usr/include) list(APPEND SOURCES "./../lib/accel.cpp")
set(CMAKE_ANDROID_STL_LIBRARIES ${CONTRIB_PATH}/sysroot/usr/lib) list(APPEND SOURCES "./../lib/frameUtils.cpp")
endif() endif()
set(plugin_SRC BotPeerChatSubscriber.cpp if(AUDIO_FFMPEG) # If you previously turned ON audio stream capture, these will be imported.
BotChatHandler.cpp list(APPEND SOURCES "./../lib/frameUtils.cpp")
PluginPreferenceHandler.cpp endif()
main.cpp
)
set(plugin_HDR BotPeerChatSubscriber.h # list(APPEND SOURCES "./../lib/EDIT_ME.cpp") # {FILL} If you'd like to import any other .cpp files, uncomment this line, and copy it for each import.
PluginPreferenceHandler.h
BotChatHandler.h
./../lib/pluglog.h
)
add_library(${ProjectName} SHARED ${plugin_SRC} add_library(${ProjectName} SHARED ${SOURCES})
${plugin_HDR}
)
target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR} target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR}
${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}
...@@ -81,11 +107,9 @@ target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR} ...@@ -81,11 +107,9 @@ target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR}
${CONTRIB_PATH}/build/fmt/include ${CONTRIB_PATH}/build/fmt/include
${CONTRIB_PATH}/build/opendht/include ${CONTRIB_PATH}/build/opendht/include
${CONTRIB_PATH}/build/msgpack-c/include ${CONTRIB_PATH}/build/msgpack-c/include
) )
target_link_directories(${ProjectName} PUBLIC ${CONTRIB_PATH} target_link_directories(${ProjectName} PUBLIC ${CONTRIB_PATH}
${CONTRIB_PATH}/build/fmt/msvc/Release ${CONTRIB_PATH}/build/fmt/msvc/Release
) )
target_link_libraries(${ProjectName} PUBLIC ) target_link_libraries(${ProjectName} PUBLIC )
...@@ -139,9 +163,33 @@ else() ...@@ -139,9 +163,33 @@ else()
) )
endif() endif()
if (NOT ARCHIVE_JPL)
exit()
endif()
add_custom_command( add_custom_command(
TARGET ${ProjectName} TARGET ${ProjectName}
POST_BUILD POST_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --assemble --plugin=${ProjectName} COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --assemble --plugin=${ProjectName}
COMMENT "Generating JPL archive" COMMENT "Generating JPL archive"
) )
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/../.cert")
file(MAKE_DIRECTORY "${PROJECT_SOURCE_DIR}/../.cert")
endif()
if (CERTIFICATION)
add_custom_command(
TARGET ${ProjectName}
POST_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/certKey.py create --subject Dev ../.cert/Dev
COMMENT "Generating developer certificate"
)
add_custom_command(
TARGET ${ProjectName}
POST_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/certKey.py create --issuer ../.cert/Dev --subject ${ProjectName} ../.cert/${ProjectName}/${ProjectName}
COMMENT "Generating plugin certificate"
)
endif()
/**
* Copyright (C) 2023-2024 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "BotChatHandler.h"
#include "pluglog.h"
const char sep = separator();
const std::string TAG = "Bot";
#define NAME "Bot"
namespace jami {
BotChatHandler::BotChatHandler(const JAMI_PluginAPI* api, // Construct the handler.
std::string&& dataPath,
PluginPreferenceHandler* prefHandler)
: api_ {api}
, datapath_ {dataPath}
{
setId(datapath_);
aph_ = prefHandler;
peerChatSubscriber_ = std::make_shared<BotPeerChatSubscriber>(api_, aph_);
};
void
BotChatHandler::notifyChatSubject(std::pair<std::string, std::string>& subjectConnection, // When a new chat needs to be handled, this is the function responsible for it.
chatSubjectPtr subject)
{
if (peerChatSubscriber_ && subjects.find(subject) == subjects.end()) {
std::ostringstream oss;
oss << "NEW SUBJECT: account = " << subjectConnection.first
<< " peer = " << subjectConnection.second << std::endl;
Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
subject->attach(peerChatSubscriber_.get()); // Attach the bot to the chat session.
subjects.insert(subject);
}
}
std::map<std::string, std::string>
BotChatHandler::getChatHandlerDetails()
{
return {{"name", NAME}, {"iconPath", datapath_ + sep + "icon.svg"}, {"pluginId", id()}}; // Fetch the bot's info, to represent it in the extensions UI.
}
void
BotChatHandler::detach(chatSubjectPtr subject) // Detach the bot from a chat stream.
{
auto it = subjects.find(subject);
if (it != subjects.end()) {
subject->detach(peerChatSubscriber_.get());
subjects.erase(it);
}
}
BotChatHandler::~BotChatHandler() // Deconstruct the handler.
{
const auto copy = subjects;
for (const auto& subject : copy) {
detach(subject);
}
}
} // namespace jami
/**
* Copyright (C) 2023-2024 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "BotPeerChatSubscriber.h"
#include "plugin/jamiplugin.h"
#include "plugin/chathandler.h"
#include <string>
#include <map>
#include <memory>
#include <set>
using chatSubjectPtr = std::shared_ptr<jami::PublishObservable<jami::pluginMessagePtr>>;
namespace jami {
class BotChatHandler : public jami::ChatHandler
{
public:
BotChatHandler(const JAMI_PluginAPI* api,
std::string&& dataPath,
PluginPreferenceHandler* prefHandler);
~BotChatHandler();
void notifyChatSubject(std::pair<std::string, std::string>& subjectConnection,
chatSubjectPtr subject) override;
std::map<std::string, std::string> getChatHandlerDetails() override;
void detach(chatSubjectPtr subject) override;
void setPreferenceAttribute(const std::string& key, const std::string& value) override {}
bool preferenceMapHasKey(const std::string& key) override { return false; }
std::shared_ptr<BotPeerChatSubscriber> peerChatSubscriber_ {};
private:
const JAMI_PluginAPI* api_;
const std::string datapath_;
PluginPreferenceHandler* aph_;
std::set<chatSubjectPtr> subjects;
};
} // namespace jami
/**
* Copyright (c) 2023-2024 savoir-faire linux inc.
*
* this program is free software; you can redistribute it and/or modify
* it under the terms of the gnu general public license as published by
* the free software foundation; either version 3 of the license, or
* (at your option) any later version.
*
* this program is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* merchantability or fitness for a particular purpose. see the
* gnu general public license for more details.
*
* you should have received a copy of the gnu general public license
* along with this program; if not, write to the free software
* foundation, inc., 51 franklin street, fifth floor, boston, ma 02110-1301 usa.
*/
#include "botpeerchatsubscriber.h"
#include "pluglog.h"
const std::string tag = "bot";
namespace jami {
botpeerchatsubscriber::botpeerchatsubscriber(const jami_pluginapi* api, // Construct the subscriber.
pluginpreferencehandler* prefhandler)
: api_ {api}
{
aph_ = prefhandler;
}
botpeerchatsubscriber::~botpeerchatsubscriber() // Deconstruct the subscriber.
{
std::ostringstream oss;
oss << "~botchatprocessor" << std::endl;
plog::log(plog::logpriority::info, tag, oss.str());
}
void
botpeerchatsubscriber::update(observable<pluginmessageptr>*, const pluginmessageptr& message)
{
if (!aph_) // If the preference handler is not initialized, return.
return;
std::string input = aph_->getpreferences(message->accountid, "intext"); // Fetch the input text from preferences.
std::string answer = aph_->getpreferences(message->accountid, "answer"); // Fetch how to answer the input text from preferences.
if (isattached) { // If the stream is attached...
if (message->direction) {
std::map<std::string, std::string> sendmsg;
if (message->fromhistory) // A message coming from the chat history should not be answered.
return;
if (!message->isswarm) // In a swarm chat...
for (auto& pair : message->data) {
if (pair.first == "text/plain" && pair.second == input) { // If the input matches the preference trigger, answer it.
sendmsg[pair.first] = answer;
}
}
else if (message->data.at("type") == "text/plain" && message->data.at("body") == input) { // In a normal chat, also appropriately answer input text.
sendmsg["type"] = "text/plain";
sendmsg["body"] = answer;
#ifdef __debug__
plog::log(plog::logpriority::info, tag, "input " + message->data.at("body"));
plog::log(plog::logpriority::info, tag, "ouput " + answer);
#endif
}
if (!sendmsg.empty()) {
sendtext(message->accountid, message->peerid, sendmsg, message->isswarm);
}
}
}
}
void
botpeerchatsubscriber::attached(observable<pluginmessageptr>* observable)
{
if (observables_.find(observable) == observables_.end()) {
std::ostringstream oss;
oss << "::attached ! " << std::endl;
plog::log(plog::logpriority::info, tag, oss.str());
observables_.insert(observable);
isattached = true;
}
}
void
botpeerchatsubscriber::detached(observable<pluginmessageptr>* observable)
{
auto it = observables_.find(observable);
if (it != observables_.end()) {
observables_.erase(it);
std::ostringstream oss;
oss << "::detached()" << std::endl;
plog::log(plog::logpriority::info, tag, oss.str());
if (observables_.empty())
isattached = false;
}
}
void
botpeerchatsubscriber::sendtext(std::string& accountid,
std::string& peerid,
std::map<std::string, std::string>& sendmsg,
bool swarm)
{
pluginmessageptr botanswer = std::make_shared<jamimessage>(accountid, // Send the message from this account.
peerid,
false,
sendmsg,
true);
botanswer->isswarm = swarm;
#ifndef __debug__
api_->invokeservice(api_, "sendtextmessage", botanswer.get());
#endif
}
} // namespace jami
/**
* Copyright (C) 2023-2024 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "observer.h"
#include "plugin/streamdata.h"
#include "plugin/jamiplugin.h"
#include "plugin/chathandler.h"
#include "PluginPreferenceHandler.h"
#include <map>
#include <set>
namespace jami {
class BotPeerChatSubscriber : public Observer<pluginMessagePtr>
{
public:
BotPeerChatSubscriber(const JAMI_PluginAPI* api,
PluginPreferenceHandler* prefHandler);
~BotPeerChatSubscriber();
virtual void update(Observable<pluginMessagePtr>*, pluginMessagePtr const&) override;
virtual void attached(Observable<pluginMessagePtr>*) override;
virtual void detached(Observable<pluginMessagePtr>*) override;
void sendText(std::string& accountId,
std::string& peerId,
std::map<std::string, std::string>& sendMsg,
bool swarm);
protected:
// Observer pattern
std::set<Observable<pluginMessagePtr>*> observables_;
bool isAttached {false};
const JAMI_PluginAPI* api_;
PluginPreferenceHandler* aph_;
};
} // namespace jami
# Copyright (C) 2023-2024 Savoir-faire Linux Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
cmake_minimum_required(VERSION 3.10)
set (PROJECT_NAME Example) # {FILL} Replace Example with the name of your plugin.
set (VERSION 0.0.0) # {FILL} Replace 0.0.0 with the version of your plugin.
project(${PROJECT_NAME} VERSION ${VERSION})
option(VIDEO_FFMPEG "If you'd like to listen to Jami's video stream or modify it, set to ON." OFF) # {FILL}
option(AUDIO_FFMPEG "If you'd like to listen to Jami's audio stream or modify it, set to ON." OFF) # {FILL}
option(ARCHIVE_JPL "If you'd like to end the build process before the generation of a JPL archive, set to OFF." ON) # {FILL}
option(NVIDIA "If you'd like to disable hardware acceleration and use the graphics card for ONNX computation (useful for AI plugins), set to ON." OFF) # {FILL}
option(DEBUG_SIGN "If you'd like to sign the JPL archive created by ARCHIVE_JPL to test it in your Jami client, set to ON. Requires ARCHIVE_JPL to be ON." ON) # {FILL}
set (DAEMON ${PROJECT_SOURCE_DIR}/../daemon)
set (JPL_FILE_NAME ${ProjectName}.jpl)
set (DAEMON_SRC ${DAEMON}/src)
set (CONTRIB_PATH ${DAEMON}/contrib)
set (PLUGINS_LIB ${PROJECT_SOURCE_DIR}/../lib)/
set (JPL_DIRECTORY ${PROJECT_BINARY_DIR}/jpl)
# Detect the operating system
if(WIN32)
set(OS_NAME "WINDOWS")
set(OUTPUT_JPL "x64-windows")
elseif(ANDROID)
set(OS_NAME "ANDROID")
set(OUTPUT_JPL "x86_64-linux-gnu")
elseif(DARWIN)
set(OS_NAME "UNIX")
set(OUTPUT_JPL "x86_64-apple-Darwin")
else()
set(OS_NAME "UNIX")
set(OUTPUT_JPL "x86_64-linux-gnu")
endif()
# Detect the architecture
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(ARCH "x64")
else()
message(FATAL_ERROR "Unsupported architecture. Only x64 is supported.")
endif()
# Set platform-specific variables
set(CONTRIB_PLATFORM_CURT ${ARCH})
set(CONTRIB_PLATFORM ${CONTRIB_PLATFORM_CURT}-${OS_NAME})
message(OS: ${OS_NAME} ${ARCH})
message(Building: ${ProjectName} ${Version})
message(Build path: ${PROJECT_BINARY_DIR})
message(JPL assembling path: ${JPL_DIRECTORY})
message(JPL path: ${JPL_DIRECTORY}/../../../build/${ProjectName}/${JPL_FILE_NAME})
# This is specifically to disable hardware acceleration and do computing on the graphics card for computationally expensive AI plugins.
if(NVIDIA)
add_definitions(-DNVIDIA)
set(ONNX_DIR ${ONNX_DIR}/nvidia-gpu)
message(Provider:\ NVIDIA)
set(EXTRA_PATH nvidia-gpu)
set (PREFERENCESFILENAME ${PREFERENCESFILENAME}-accel)
else()
set(ONNX_DIR ${ONNX_DIR}/cpu)
message(Provider:\ NONE)
set(EXTRA_PATH cpu)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /DMSGPACK_NO_BOOST /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DMSGPACK_NO_BOOST /MTd")
set(SOURCES "")
list(APPEND SOURCES "FILL.cpp"
"FILL.cpp" # {FILL} Import manually all .cpp files here in your plugin directory.
)
# Alternatively, you may comment the list command above, and uncomment the lines below, to automatically include all files ending with .cpp in your project.
# file(GLOB CPP_SOURCES "*.cpp") # All .cpp files in the directory of your plugin ({root}/PLUGIN_NAME) will be collected by cmake.
# list(APPEND SOURCES ${CPP_SOURCES})
if(VIDEO_FFMPEG) # If you previously turned ON video stream capture, these will be imported.
list(APPEND SOURCES "./../lib/accel.cpp")
list(APPEND SOURCES "./../lib/frameUtils.cpp")
endif()
if(AUDIO_FFMPEG) # If you previously turned ON audio stream capture, these will be imported.
list(APPEND SOURCES "./../lib/frameUtils.cpp")
endif()
# list(APPEND SOURCES "./../lib/EDIT_ME.cpp") # {FILL} If you'd like to import any other .cpp files, uncomment this line, and copy it for each import.
add_library(${ProjectName} SHARED ${SOURCES})
target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR}
${PROJECT_SOURCE_DIR}
${PLUGINS_LIB}
${DAEMON_SRC}
${CONTRIB_PATH}
${CONTRIB_PATH}/build/fmt/include
${CONTRIB_PATH}/build/opendht/include
${CONTRIB_PATH}/build/msgpack-c/include
# {FILL} Plugins with computer vision or AI-related functions may be interested in importing the following.
# ${FFMPEG}/include
# ${CONTRIB_PATH}/build/opencv/build/install/include
# ${ONNX_DIR}/../include/session
# ${ONNX_DIR}/../include/providers/cuda
)
target_link_directories(${ProjectName} PUBLIC ${CONTRIB_PATH}
${CONTRIB_PATH}/build/fmt/msvc/Release
# ${FFMPEG}/bin # {FILL} If using FFMPEG.
)
target_link_libraries(${ProjectName} PUBLIC ---FFMPEGLIBS---)
if(CMAKE_CXX_FLAGS_DEBUG)
set(OUTPUT "${ProjectName}")
set(CLANG_OPTS "-g -fsanitize=address")
set(EXTRA_DEBUG_LIBRARIES "-lyaml-cpp")
set(EXTRA_DEFINES "-D__DEBUG__")
else()
add_custom_command(
TARGET ${ProjectName}
PRE_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --preassemble --plugin=${ProjectName}
COMMENT "Assembling Plugin files"
)
endif()
add_custom_command(
TARGET ${ProjectName}
PRE_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --preassemble --plugin=${ProjectName}
COMMENT "Assembling Plugin files"
)
if(WIN32)
# Windows-specific file copying
add_custom_command(
TARGET ${ProjectName}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/${ProjectName}.lib ${JPL_DIRECTORY}/lib/${CONTRIB_PLATFORM}
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/${ProjectName}.dll ${JPL_DIRECTORY}/lib/${CONTRIB_PLATFORM}
COMMENT "Copying files to jpl directory for Windows"
)
elseif(APPLE)
# macOS-specific file copying
add_custom_command(
TARGET ${ProjectName}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/lib${ProjectName}.dylib ${JPL_DIRECTORY}/lib/${CONTRIB_PLATFORM}
COMMENT "Copying files to jpl directory for macOS"
)
else()
# Unix-like systems (Linux, etc.)
add_custom_command(
TARGET ${ProjectName}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/lib${ProjectName}.so ${JPL_DIRECTORY}/lib/${CONTRIB_PLATFORM}
COMMENT "Copying files to jpl directory for Unix-like systems or Android"
)
endif()
if (NOT ARCHIVE_JPL)
exit()
endif()
add_custom_command(
TARGET ${ProjectName}
POST_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --assemble --plugin=${ProjectName}
COMMENT "Generating JPL archive"
)
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/../.cert")
file(MAKE_DIRECTORY "${PROJECT_SOURCE_DIR}/.cert")
endif()
if (DEBUG_SIGN)
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/../.cert and sign/deb")
add_custom_command(
TARGET ${ to test it in your Jami clientProjectName}
POST_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/certKey.py create --subject debug ../.cert/debug
COMMENT "Generating developer certificate"
)
endif()
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/../.cert/${ProjectName}")
add_custom_command(
TARGET ${ProjectName}
POST_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/certKey.py create --issuer ../.cert/debug --subject ${ProjectName} ../.cert/${ProjectName}/${ProjectName}
COMMENT "Generating plugin certificate"
)
endif()
add_custom_command(
TARGET ${ProjectName}
POST_BUILD
COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/certKey.py sign --path ../build/${OUTPUT_JPL}/${ProjectName} --issuer ../.cert/debug ../build/${OUTPUT_JPL}/${ProjectName}
COMMENT "Generating plugin certificate"
)
python3 ./SDK/certKey.py --plugin sign --path /tmp/plugins/foo --issuer /tmp/foo /tmp/plugins/foo
endif()
/**
* Copyright (C) 2023-2024 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ExampleAudioSubscriber.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
#include <frameUtils.h>
#include <pluglog.h>
const std::string TAG = "Example";
const char sep = separator();
namespace jami {
ExampleAudioSubscriber::ExampleAudioSubscriber(const std::string& dataPath, const std::string& irFile) // Construct the audio stream subscriber.
: path_ {dataPath}
{
setIRFile(irFile);
}
ExampleAudioSubscriber::~ExampleAudioSubscriber() // Deconstruct the audio stream subscriber.
{
if(pFormatCtx_) {
avformat_close_input(&pFormatCtx_);
avformat_free_context(pFormatCtx_);
}
std::ostringstream oss;
oss << "~ExampleMediaProcessor" << std::endl;
Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
}
void
ExampleAudioSubscriber::setIRFile(const std::string& irFile) // Initialize a file to use for the reverb effect.
{
irFile_ = path_ + "/" + irFile;
firstRun = true;
reverbExample_.clean();
}
void
ExampleAudioSubscriber::setExampleDescription(const int pSampleRate, const int pSamples, const int pFormat)
// Constructs a filter description string based on the sample rate, number of samples, and format of the incoming audio frames.
// This string describes how the audio should be processed (resampled and applied with the IR).
{
int rSamples = 1024; // due to afir internal fifo
int midSampleRate = pSampleRate * rSamples / pSamples;
std::string outFormat = av_get_sample_fmt_name((AVSampleFormat)pFormat);
filterDescription_
= "[ input ] aformat=sample_fmts=s16:sample_rates=" + std::to_string(midSampleRate)
+ ":channel_layouts=stereo [ resample1 ] , "
+ "[ resample1 ] [ ir0 ] afir=maxir=1:wet=10:dry=10:irgain=1:irfmt=mono:maxp="
+ std::to_string(rSamples) + ":minp=" + std::to_string(rSamples) + " [ reverb ] , "
+ "[ reverb ] aformat=sample_fmts=" + outFormat + ":sample_rates="
+ std::to_string(pSampleRate) + ":channel_layouts=stereo ";
}
AudioFormat
ExampleAudioSubscriber::getIRAVFrameInfos() // Find the audio stream.
{
AudioFormat rAudioFormat = AudioFormat(0, 0);
int i;
pFormatCtx_ = avformat_alloc_context();
// Open
if (avformat_open_input(&pFormatCtx_, irFile_.c_str(), NULL, NULL) != 0) {
Plog::log(Plog::LogPriority::INFO, TAG, "Couldn't open input stream.");
return rAudioFormat;
}
// Retrieve stream information
if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) {
Plog::log(Plog::LogPriority::INFO, TAG, "Couldn't find stream information.");
return rAudioFormat;
}
// Dump valid information onto standard error
av_dump_format(pFormatCtx_, 0, irFile_.c_str(), false);
// Find the first audio stream
audioStream_ = -1;
for (i = 0; i < static_cast<int>(pFormatCtx_->nb_streams); i++)
if (pFormatCtx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audioStream_ = i;
break;
}
if (audioStream_ == -1) {
Plog::log(Plog::LogPriority::INFO, TAG, "Didn't find a audio stream.");
return rAudioFormat;
}
rAudioFormat = AudioFormat(pFormatCtx_->streams[audioStream_]->codecpar->sample_rate,
pFormatCtx_->streams[audioStream_]->codecpar->ch_layout.nb_channels,
static_cast<AVSampleFormat>(
pFormatCtx_->streams[audioStream_]->codecpar->format));
return rAudioFormat;
}
void
ExampleAudioSubscriber::setIRAVFrame() // Feed data into the reverb filter.
{
AVCodecContext* pCodecCtx;
const AVCodec* pCodec = avcodec_find_decoder(pFormatCtx_->streams[audioStream_]->codecpar->codec_id);
if (pCodec == NULL) {
Plog::log(Plog::LogPriority::INFO, TAG, "Codec not found.");
return;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if (avcodec_parameters_to_context(pCodecCtx, pFormatCtx_->streams[audioStream_]->codecpar) < 0) {
Plog::log(Plog::LogPriority::INFO, __FILE__, "Failed to copy decoder parameters to decoder context.");
return;
}
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
Plog::log(Plog::LogPriority::INFO, TAG, "Could not open codec.");
return;
}
AVPacket* packet = av_packet_alloc();
AVFrame* pFrame = av_frame_alloc();
int idx = 0;
while (av_read_frame(pFormatCtx_, packet) == 0 && idx < 40) { // Limit for filter coefficients
idx++;
av_frame_unref(pFrame);
av_frame_free(&pFrame);
pFrame = av_frame_alloc();
if (avcodec_send_packet(pCodecCtx, packet) < 0) {
Plog::log(Plog::LogPriority::INFO, __FILE__, "Error submitting the packet to the decoder");
break;
}
// Read frames from decoder
while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {
reverbExample_.feedInput(pFrame, "ir0");
}
av_packet_unref(packet);
}
reverbExample_.feedEOF("ir0");
av_frame_unref(pFrame);
av_frame_free(&pFrame);
av_packet_unref(packet);
av_packet_free(&packet);
avcodec_close(pCodecCtx);
avcodec_free_context(&pCodecCtx);
avformat_close_input(&pFormatCtx_);
avformat_free_context(pFormatCtx_);
}
void
ExampleAudioSubscriber::update(Observable<AVFrame*>*, AVFrame* const& pluginFrame) // Edit each audio frame with the application of the filter.
{
if (!pluginFrame)
return;
if (firstRun) { // If this is the first run, initialize.
setExampleDescription(pluginFrame->sample_rate, pluginFrame->nb_samples, pluginFrame->format);
AudioFormat afmt_ = AudioFormat(pluginFrame->sample_rate,
pluginFrame->ch_layout.nb_channels,
static_cast<AVSampleFormat>(pluginFrame->format));
AudioFormat irfmt_ = getIRAVFrameInfos();
MediaStream ms_ = MediaStream("input", afmt_);
MediaStream irms_ = MediaStream("ir0", irfmt_);
reverbExample_.initialize(filterDescription_, {ms_, irms_});
setIRAVFrame();
firstRun = false;
}
if (!reverbExample_.initialized_)
return;
if (reverbExample_.feedInput(pluginFrame, "input") == 0) {
AVFrame* filteredFrame = reverbExample_.readOutput();
if (filteredFrame && filteredFrame->nb_samples == pluginFrame->nb_samples) {
moveFrom(pluginFrame, filteredFrame); // Edit the frame, applying reverb.
av_frame_unref(filteredFrame);
av_frame_free(&filteredFrame);
}
}
}
void
ExampleAudioSubscriber::attached(Observable<AVFrame*>* observable)
{
std::ostringstream oss;
oss << "::Attached ! " << std::endl;
Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
observable_ = observable;
}
void
ExampleAudioSubscriber::detached(Observable<AVFrame*>*)
{
reverbExample_.clean();
firstRun = true;
observable_ = nullptr;
std::ostringstream oss;
oss << "::Detached()" << std::endl;
Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
}
void
ExampleAudioSubscriber::detach() // Detach from the audio stream.
{
if (observable_) {
reverbExample_.clean();
firstRun = true;
std::ostringstream oss;
oss << "::Calling detach()" << std::endl;
Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
observable_->detach(this);
}
}
} // namespace jami
/**
* Copyright (C) 2023-2024 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ExampleMediaHandler.h"
#include "PluginPreferenceHandler.h"
#include "pluglog.h"
#include <string_view>
const char sep = separator();
const std::string TAG = "Example";
#define NAME "Example"
namespace jami {
ExampleMediaHandler::ExampleMediaHandler(std::string&& dataPath, // Construct the handler.
PluginPreferenceHandler* prefHandler)
: datapath_ {dataPath}
{
aph_ = prefHandler;
setId(datapath_);
mediaSubscriber_ = std::make_shared<ExampleVideoSubscriber>(datapath_);
setParameters("default");
}
void
ExampleMediaHandler::notifyAVFrameSubject(const StreamData& data, jami::avSubjectPtr subject) // When a new video stream is detected, check the direction, and attach a subscriber to it if conditions match.
{
std::ostringstream oss;
std::string_view direction = data.direction ? "Receive" : "Preview";
oss << "NEW SUBJECT: [" << data.id << "," << direction << "]" << std::endl;
accountId_ = data.source;
auto preferences = aph_->getPreferences(accountId_);
bool preferredStreamDirection {false}; // false for output; true for input
auto it = preferences.find("videostream");
if (it != preferences.end()) {
preferredStreamDirection = it->second == "1";
}
oss << "preferredStreamDirection " << preferredStreamDirection << std::endl;
if (data.type == StreamType::video && !data.direction
&& data.direction == preferredStreamDirection) {
if (attached_ == "1")
detach();
setParameters(data.source);
subject->attach(mediaSubscriber_.get()); // your image
oss << "got my sent image attached" << std::endl;
attached_ = "1";
} else if (data.type == StreamType::video && data.direction
&& data.direction == preferredStreamDirection) {
if (attached_ == "1")
detach();
setParameters(data.source);
subject->attach(mediaSubscriber_.get()); // the image you receive from others on the call
oss << "got received image attached" << std::endl;
attached_ = "1";
}
Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
}
void
ExampleMediaHandler::setParameters(const std::string& accountId) // Set the plugin preferences.
{
if (!accountId.empty() && accountId != accountId_)
return;
auto preferences = aph_->getPreferences(accountId_);
try {
mediaSubscriber_->setParameter(preferences["fontsize"], Parameter::FONTSIZE);
mediaSubscriber_->setParameter(preferences["logosize"], Parameter::LOGOSIZE);
mediaSubscriber_->setParameter(preferences["markbackground"], Parameter::LOGOBACKGROUND);
mediaSubscriber_->setParameter(preferences["showinfos"], Parameter::SHOWINFOS);
mediaSubscriber_->setParameter(preferences["showlogo"], Parameter::SHOWLOGO);
mediaSubscriber_->setParameter(preferences["mark"], Parameter::LOGOPATH);
mediaSubscriber_->setParameter(preferences["date"], Parameter::DATE);
mediaSubscriber_->setParameter(preferences["dateformat"], Parameter::DATEFORMAT);
mediaSubscriber_->setParameter(preferences["time"], Parameter::TIME);
mediaSubscriber_->setParameter(preferences["timezone"], Parameter::TIMEZONE);
mediaSubscriber_->setParameter(preferences["timeformat"], Parameter::TIMEFORMAT);
mediaSubscriber_->setParameter(preferences["location"], Parameter::LOCATION);
mediaSubscriber_->setParameter(preferences["infosposition"], Parameter::INFOSPOSITION);
mediaSubscriber_->setParameter(preferences["logoposition"], Parameter::LOGOPOSITION);
} catch (std::exception& e) {
Plog::log(Plog::LogPriority::ERR, TAG, e.what());
}
}
std::map<std::string, std::string>
ExampleMediaHandler::getCallMediaHandlerDetails()
{
return {{"name", NAME},
{"iconPath", datapath_ + sep + "icon.svg"},
{"pluginId", id()},
{"attached", attached_},
{"dataType", "1"}};
}
void
ExampleMediaHandler::detach() // Detach a subscriber.
{
attached_ = "0";
mediaSubscriber_->detach();
}
ExampleMediaHandler::~ExampleMediaHandler() // Deconstruct the handler.
{
Plog::log(Plog::LogPriority::INFO, TAG, "~ExampleMediaHandler from WaterMark Plugin");
detach();
}
} // namespace jami
/**
* Copyright (C) 2023-2024 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ExampleVideoSubscriber.h"
extern "C" {
#include <libavutil/display.h>
}
#include <accel.h>
#include <frameScaler.h>
#include <common.h>
#include <pluglog.h>
#include <algorithm>
#include <ctime>
#include <clocale>
#include <iostream>
const std::string TAG = "Example";
const char sep = separator();
namespace jami {
ExampleVideoSubscriber::ExampleVideoSubscriber(const std::string& dataPath) // Construct the video subscriber.
{
if (std::setlocale(LC_TIME, std::locale("").name().c_str()) == NULL) {
Plog::log(Plog::LogPriority::INFO, TAG, "error while setting locale");
}
std::setlocale(LC_NUMERIC, "C");
fontFile_ = string_utils::ffmpegFormatString(dataPath + sep + "Muli-Light.ttf");
}
ExampleVideoSubscriber::~ExampleVideoSubscriber() // Deconstruct the video subscriber.
{
validLogo_ = false;
logoFilter_.clean();
detach();
std::lock_guard<std::mutex> lk(mtx_);
Plog::log(Plog::LogPriority::INFO, TAG, "~ExampleMediaProcessor");
}
MediaStream
ExampleVideoSubscriber::getLogoAVFrameInfos() // Open an image logo and a video stream, so that the logo can be applied to the stream later on.
{
AVFormatContext* ctx = avformat_alloc_context();
// Open
if (avformat_open_input(&ctx, logoPath_.c_str(), NULL, NULL) != 0) {
avformat_free_context(ctx);
Plog::log(Plog::LogPriority::INFO, TAG, "Couldn't open input stream.");
validLogo_ = false;
return {};
}
pFormatCtx_.reset(ctx);
// Retrieve stream information
if (avformat_find_stream_info(pFormatCtx_.get(), NULL) < 0) {
Plog::log(Plog::LogPriority::INFO, TAG, "Couldn't find stream information.");
validLogo_ = false;
return {};
}
// Dump valid information onto standard error
av_dump_format(pFormatCtx_.get(), 0, logoPath_.c_str(), false);
// Find the video stream
for (int i = 0; i < static_cast<int>(pFormatCtx_->nb_streams); i++)
if (pFormatCtx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream_ = i;
break;
}
if (videoStream_ == -1) {
Plog::log(Plog::LogPriority::INFO, TAG, "Didn't find a video stream.");
validLogo_ = false;
return {};
}
rational<int> fr = pFormatCtx_->streams[videoStream_]->r_frame_rate;
return MediaStream("logo",
pFormatCtx_->streams[videoStream_]->codecpar->format,
1 / fr,
pFormatCtx_->streams[videoStream_]->codecpar->width,
pFormatCtx_->streams[videoStream_]->codecpar->height,
0,
fr);
}
void
ExampleVideoSubscriber::loadMarkLogo() // Load a logo, altering the image data so it can be safely placed onto the video stream.
{
if (logoPath_.empty())
return;
logoFilter_.clean();
logoDescription_ = "[logo]scale=" + logoSize_ + "*" + std::to_string(pluginFrameSize_.first)
+ ":" + logoSize_ + "*" + std::to_string(pluginFrameSize_.second)
+ ":force_original_aspect_ratio='decrease',format=yuva444p,"
"split=2[bg][fg],[bg]drawbox=c='"
+ backgroundColor_
+ "':replace=1:t=fill[bg],"
"[bg][fg]overlay=format=auto";
Plog::log(Plog::LogPriority::INFO, TAG, logoDescription_);
logoStream_ = getLogoAVFrameInfos();
logoFilter_.initialize(logoDescription_, {logoStream_});
AVCodecContext* pCodecCtx;
const AVCodec* pCodec = avcodec_find_decoder(
pFormatCtx_->streams[videoStream_]->codecpar->codec_id);
if (pCodec == nullptr) {
pFormatCtx_.reset();
Plog::log(Plog::LogPriority::INFO, TAG, "Codec not found.");
validLogo_ = false;
return;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
pFormatCtx_.reset();
Plog::log(Plog::LogPriority::INFO, TAG, "Could not open codec.");
validLogo_ = false;
return;
}
AVPacket* packet = av_packet_alloc();
if (av_read_frame(pFormatCtx_.get(), packet) < 0) {
avcodec_close(pCodecCtx);
avcodec_free_context(&pCodecCtx);
av_packet_unref(packet);
av_packet_free(&packet);
pFormatCtx_.reset();
Plog::log(Plog::LogPriority::INFO, TAG, "Could not read packet from context.");
validLogo_ = false;
return;
}
if (avcodec_send_packet(pCodecCtx, packet) < 0) {
avcodec_close(pCodecCtx);
avcodec_free_context(&pCodecCtx);
av_packet_unref(packet);
av_packet_free(&packet);
pFormatCtx_.reset();
Plog::log(Plog::LogPriority::INFO, TAG, "Could not send packet no codec.");
validLogo_ = false;
return;
}
uniqueFramePtr logoImage = {av_frame_alloc(), frameFree};
if (avcodec_receive_frame(pCodecCtx, logoImage.get()) < 0) {
avcodec_close(pCodecCtx);
avcodec_free_context(&pCodecCtx);
av_packet_unref(packet);
av_packet_free(&packet);
pFormatCtx_.reset();
Plog::log(Plog::LogPriority::INFO, TAG, "Could not read packet from codec.");
validLogo_ = false;
return;
}
logoFilter_.feedInput(logoImage.get(), "logo");
logoFilter_.feedEOF("logo");
avcodec_close(pCodecCtx);
avcodec_free_context(&pCodecCtx);
av_packet_unref(packet);
av_packet_free(&packet);
pFormatCtx_.reset();
mark_.reset(logoFilter_.readOutput());
mark_->pts = 0;
mark_->best_effort_timestamp = 0;
validLogo_ = mark_->width && mark_->height;
}
void
ExampleVideoSubscriber::setParameter(std::string& parameter, Parameter type) // Apply the parameters set by the user.
{
switch (type) {
case (Parameter::LOGOSIZE):
logoSize_ = parameter;
break;
case (Parameter::TIMEZONE):
timeZone_ = parameter == "1";
return;
case (Parameter::FONTSIZE):
fontSize_ = std::stoi(parameter);
break;
case (Parameter::LOGOBACKGROUND):
backgroundColor_ = parameter;
if (backgroundColor_.find("black") == std::string::npos) {
fontColor_ = "black";
fontBackground_ = "white@0.5";
} else {
fontColor_ = "white";
fontBackground_ = "black@0.5";
}
break;
case (Parameter::SHOWLOGO):
showLogo_ = parameter == "1";
break;
case (Parameter::SHOWINFOS):
showInfos_ = parameter == "1";
break;
case (Parameter::LOGOPATH):
logoPath_ = parameter;
break;
case (Parameter::TIME):
time_ = parameter == "1";
break;
case (Parameter::DATE):
date_ = parameter == "1";
break;
case (Parameter::TIMEFORMAT):
timeFormat_ = "%{localtime\\:'" + parameter + "'}";
if (timeZone_)
timeFormat_ = "%{localtime\\:'" + parameter + " %Z'}";
break;
case (Parameter::DATEFORMAT):
dateFormat_ = "%{localtime\\:'" + parameter + "'}";
break;
case (Parameter::LOCATION):
location_ = parameter;
break;
case (Parameter::INFOSPOSITION):
infosposition_ = parameter;
break;
case (Parameter::LOGOPOSITION):
logoposition_ = parameter;
break;
default:
return;
}
firstRun = true;
}
void
ExampleVideoSubscriber::setFilterDescription() // Prepare the logo to be processed by ffmpeg.
{
loadMarkLogo();
std::string infoSep = ", ";
if (pluginFrameSize_.first < pluginFrameSize_.second)
infoSep = ",\n";
std::vector<std::string> infos;
infosSize_ = 0;
if (!location_.empty()) {
infosSize_++;
infos.emplace_back(location_);
}
if (date_) {
infosSize_++;
infos.emplace_back(dateFormat_);
}
if (time_) {
infosSize_++;
infos.emplace_back(timeFormat_);
}
infosString.clear();
for (int i = 0; i < infosSize_ - 1; i++)
infosString += infos[i] + infoSep;
if (infosSize_ > 0)
infosString += infos.back();
setMarkPosition();
std::string rotateSides = "";
if (std::abs(angle_) == 90)
rotateSides = ":out_w=ih:out_h=iw";
std::string formatedPath = string_utils::ffmpegFormatString(logoPath_);
auto gifDescription = "movie='" + formatedPath + "':loop=0,setpts=N/(FR*TB)[logo],"
+ logoDescription_ + "[loop],";
if (angle_ != 0)
pluginFilterDescription_ = gifDescription
+ "[input]rotate=" + rotation[angle_] + rotateSides
+ "[rot],[rot][loop]overlay=" + std::to_string(points_[0].first)
+ ":" + std::to_string(points_[0].second)
+ ",rotate=" + rotation[-angle_] + rotateSides;
else
pluginFilterDescription_ = gifDescription
+ "[input][loop]overlay="
+ std::to_string(points_[0].first) + ":"
+ std::to_string(points_[0].second);
std::string baseInfosDescription = "[input]rotate=" + rotation[angle_] + rotateSides
+ ",drawtext=fontfile='" + fontFile_ + "':text='"
+ infosString + "':fontcolor=" + fontColor_
+ ":fontsize=" + std::to_string(fontSize_)
+ ":line_spacing=" + std::to_string(lineSpacing_)
+ ":box=1:boxcolor=" + fontBackground_ + ":boxborderw=5:x=";
if (infosposition_ == "1")
infosDescription_ = baseInfosDescription + std::to_string(points_[1].first)
+ "-text_w:y=" + std::to_string(points_[1].second);
else if (infosposition_ == "2")
infosDescription_ = baseInfosDescription + std::to_string(points_[1].first)
+ ":y=" + std::to_string(points_[1].second);
else if (infosposition_ == "3")
infosDescription_ = baseInfosDescription + std::to_string(points_[1].first)
+ ":y=" + std::to_string(points_[1].second) + "-text_h";
else if (infosposition_ == "4")
infosDescription_ = baseInfosDescription + std::to_string(points_[1].first)
+ "-text_w:y=" + std::to_string(points_[1].second) + "-text_h";
infosDescription_ += ",rotate=" + rotation[-angle_] + rotateSides + ",format=yuv420p";
Plog::log(Plog::LogPriority::INFO, TAG, infosDescription_);
Plog::log(Plog::LogPriority::INFO, TAG, pluginFilterDescription_);
}
void
ExampleVideoSubscriber::setMarkPosition() // Set the position where the logo will be applied on the video stream.
{
if (!validLogo_)
return;
// 1, 2, 3, and 4 are cartesian positions
int margin = 10;
int markWidth = showLogo_ ? mark_->width : 0;
int markHeight = showLogo_ ? mark_->height : 0;
int infoHeight = (std::abs(angle_) == 90) ? (fontSize_ + lineSpacing_) * infosSize_
: lineSpacing_ * 2 + fontSize_;
if (pluginFrameSize_.first == 0 || pluginFrameSize_.second == 0)
return;
if (infosposition_ == "1") {
points_[1] = {pluginFrameSize_.first - margin, margin};
} else if (infosposition_ == "2") {
points_[1] = {margin, margin};
} else if (infosposition_ == "3") {
points_[1] = {margin, pluginFrameSize_.second - margin};
} else if (infosposition_ == "4") {
points_[1] = {pluginFrameSize_.first - margin, pluginFrameSize_.second - margin};
}
if (logoposition_ == "1") {
points_[0] = {pluginFrameSize_.first - mark_->width - margin / 2, margin};
} else if (logoposition_ == "2") {
points_[0] = {margin / 2, margin};
} else if (logoposition_ == "3") {
points_[0] = {margin / 2, pluginFrameSize_.second - markHeight - margin};
} else if (logoposition_ == "4") {
points_[0] = {pluginFrameSize_.first - markWidth - margin / 2,
pluginFrameSize_.second - markHeight - margin};
}
if (infosposition_ == logoposition_ && showInfos_ && showLogo_) {
if (logoposition_ == "1" || logoposition_ == "2") {
points_[0].second += infoHeight;
} else if (logoposition_ == "3" || logoposition_ == "4") {
points_[0].second -= infoHeight;
}
}
}
void
ExampleVideoSubscriber::update(jami::Observable<AVFrame*>*, AVFrame* const& pluginFrame) // Get a video frame, and apply the logo onto the video frame.
{
if (!observable_ || !pluginFrame || (showLogo_ && !validLogo_))
return;
AVFrameSideData* side_data = av_frame_get_side_data(pluginFrame, AV_FRAME_DATA_DISPLAYMATRIX);
int newAngle {0};
if (side_data) {
auto matrix_rotation = reinterpret_cast<int32_t*>(side_data->data);
newAngle = static_cast<int>(av_display_rotation_get(matrix_rotation));
}
if (newAngle != angle_) {
angle_ = newAngle;
firstRun = true;
}
//======================================================================================
// GET RAW FRAME
uniqueFramePtr rgbFrame = {transferToMainMemory(pluginFrame, AV_PIX_FMT_NV12), frameFree};
rgbFrame.reset(FrameScaler::convertFormat(rgbFrame.get(), AV_PIX_FMT_YUV420P));
if (!rgbFrame.get())
return;
if (sourceTimeBase_.num != pluginFrame->time_base.num || sourceTimeBase_.den != pluginFrame->time_base.den)
firstRun = true;
rgbFrame->pts = pluginFrame->pts;
rgbFrame->time_base = pluginFrame->time_base;
sourceTimeBase_ = pluginFrame->time_base;
if (firstRun) {
pluginFilter_.clean();
infosFilter_.clean();
pluginFrameSize_ = {rgbFrame->width, rgbFrame->height};
if (std::abs(angle_) == 90)
pluginFrameSize_ = {rgbFrame->height, rgbFrame->width};
setFilterDescription();
rational<int> fr(sourceTimeBase_.den, sourceTimeBase_.num);
pluginstream_ = MediaStream("input",
rgbFrame->format,
1 / fr,
rgbFrame->width,
rgbFrame->height,
0,
fr);
if (showLogo_ && validLogo_) {
pluginFilter_.initialize(pluginFilterDescription_, {pluginstream_});
}
infosFilter_.initialize(infosDescription_, {pluginstream_});
firstRun = false;
}
if (!infosFilter_.initialized_ && !pluginFilter_.initialized_)
return;
if (showLogo_ && validLogo_) {
if (pluginFilter_.feedInput(rgbFrame.get(), "input") == 0) {
uniqueFramePtr filteredFrame = {pluginFilter_.readOutput(), frameFree};
if (filteredFrame.get())
moveFrom(rgbFrame.get(), filteredFrame.get());
}
}
if (showInfos_) {
if (infosFilter_.feedInput(rgbFrame.get(), "input") == 0) {
uniqueFramePtr filteredFrame = {infosFilter_.readOutput(), frameFree};
if (filteredFrame.get())
moveFrom(rgbFrame.get(), filteredFrame.get());
}
}
if (showInfos_ || showLogo_) {
moveFrom(pluginFrame, rgbFrame.get());
}
}
void
ExampleVideoSubscriber::attached(jami::Observable<AVFrame*>* observable)
{
Plog::log(Plog::LogPriority::INFO, TAG, "Attached!");
observable_ = observable;
}
void
ExampleVideoSubscriber::detached(jami::Observable<AVFrame*>*)
{
pluginFilter_.clean();
infosFilter_.clean();
firstRun = true;
observable_ = nullptr;
Plog::log(Plog::LogPriority::INFO, TAG, "Detached!");
mtx_.unlock();
}
void
ExampleVideoSubscriber::detach() // Detach from the video stream.
{
if (observable_) {
mtx_.lock();
firstRun = true;
observable_->detach(this);
}
}
} // namespace jami
{
"name": "Example",
"description_summary": "An example plugin.",
}
/**
* Copyright (C) 2023-2024 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <iostream>
#include <string.h>
#include <thread>
#include <memory>
#include <map>
#include "plugin/jamiplugin.h"
#include "BotChatHandler.h"
#ifdef __DEBUG__
#include <common.h>
#include <assert.h>
#include <yaml-cpp/yaml.h>
#include <fstream>
#endif
#ifdef WIN32
#define EXPORT_PLUGIN __declspec(dllexport)
#else
#define EXPORT_PLUGIN
#endif
#define Example_VERSION_MAJOR 2
#define Example_VERSION_MINOR 0
#define Example_VERSION_PATCH 0
extern "C" {
void
pluginExit(void)
{}
EXPORT_PLUGIN JAMI_PluginExitFunc
JAMI_dynPluginInit(const JAMI_PluginAPI* api)
{
// Print the plugin name and version.
std::cout << "*****************" << std::endl;
std::cout << "** Example **" << std::endl;
std::cout << "*****************" << std::endl << std::endl;
std::cout << "Version " << Example_VERSION_MAJOR << "." << Example_VERSION_MINOR << "."
<< Example_VERSION_PATCH << std::endl;
// If invokeService doesn't return an error
if (api) { // Ensure the API version is up to date.
if (api->version.api < JAMI_PLUGIN_API_VERSION)
return nullptr;
std::map<std::string, std::map<std::string, std::string>> preferences;
api->invokeService(api, "getPluginAccPreferences", &preferences); // Retrieve plugin preferences...
std::string dataPath;
api->invokeService(api, "getPluginDataPath", &dataPath); // And the assets, like the icon and background.
auto fmpPluginPreferenceHandler
= std::make_unique<jami::PluginPreferenceHandler>(api, std::move(preferences), dataPath); // Create a preference handler.
auto fmpBotChatHandler
= std::make_unique<jami::BotChatHandler>(api, // Create a chat handler, which will dispatch bots to subscribe to chat streams.
std::move(dataPath),
fmpPluginPreferenceHandler.get());
if (api->manageComponent(api, "ChatHandlerManager", fmpBotChatHandler.release())) {
return nullptr;
}
if (api->manageComponent(api,
"PreferenceHandlerManager",
fmpPluginPreferenceHandler.release())) {
return nullptr;
}
}
return pluginExit;
}
}
#ifdef __DEBUG__
int
main ()
{
std::cout << "*****************************" << std::endl; // A debug feature for plugins is also available.
std::cout << "** Example Debug Version **" << std::endl;
std::cout << "*****************************" << std::endl;
std::cout << "Version " << Example_VERSION_MAJOR << "." << Example_VERSION_MINOR << "."
<< Example_VERSION_PATCH << std::endl << std::endl;
std::ifstream file;
file_utils::openStream(file, "testPreferences.yml");
assert(file.is_open());
YAML::Node node = YAML::Load(file);
assert(node.IsMap());
std::map<std::string, std::map<std::string, std::string>> preferences;
preferences["default"] = {};
for (const auto& kv : node) {
preferences["default"][kv.first.as<std::string>()] = kv.second.as<std::string>();
std::cout << "Key: " << kv.first.as<std::string>() << "; Value: " << kv.second.as<std::string>() << std::endl;
}
std::string dataPath = "tester";
// Simulate the function of the plugin by creating bots...
auto fmpPluginPreferenceHandler
= std::make_unique<jami::PluginPreferenceHandler>(nullptr, std::move(preferences), dataPath);
auto fmpBotChatHandler
= std::make_unique<jami::BotChatHandler>(nullptr,
std::move(dataPath),
fmpPluginPreferenceHandler.get());
// ...and having these bots subscribe to streams.
auto subject = std::make_shared<jami::PublishObservable<jami::pluginMessagePtr>>();
std::pair<std::string, std::string> subjectConnection("origin", "destiny");
fmpBotChatHandler->notifyChatSubject(subjectConnection, subject);
// Only test for swarm
// Valid Sender, Receiver, direction and message
std::cout << "Test 1" << std::endl << "Should print input/output" << std::endl;
std::map<std::string, std::string> sendMsg = {{"type", "text/plain"}, {"body", preferences["default"]["inText"]}};
jami::pluginMessagePtr jamiMsg = std::make_shared<JamiMessage>("origin",
"destiny",
true,
sendMsg,
false);
jamiMsg->isSwarm = true;
subject->publish(jamiMsg);
// Valid Sender, Receiver and message but not direction
std::cout << "Test 2" << std::endl << "Should NOT print input/output" << std::endl;
jamiMsg.reset(new JamiMessage("origin",
"destiny",
false,
sendMsg,
false));
jamiMsg->isSwarm = true;
subject->publish(jamiMsg);
// Invalid Sender, Receiver, direction and message
std::cout << "Test 3" << std::endl << "Should NOT print input/output" << std::endl;
sendMsg["body"] = preferences["default"]["invalid"];
jamiMsg.reset(new JamiMessage("destiny",
"origin",
true,
sendMsg,
false));
jamiMsg->isSwarm = true;
subject->publish(jamiMsg);
return 0;
}
#endif
{
"id": "Example",
"name": "{{name}}",
"description": "{{description_summary}}",
"version": "1.0.0",
"iconPath": "icon.svg",
"backgroundPath": "background.jpg"
}
\ No newline at end of file
...@@ -6,6 +6,9 @@ set (Version MANIFESTVERSION) ...@@ -6,6 +6,9 @@ set (Version MANIFESTVERSION)
project(${ProjectName} VERSION ${Version}) project(${ProjectName} VERSION ${Version})
option(VIDEO_FFMPEG "Include Video FFMPEG Dependency" OFF)
option(AUDIO_FFMPEG "Include Audio FFMPEG Dependency" OFF)
set (DAEMON ${PROJECT_SOURCE_DIR}/../daemon) set (DAEMON ${PROJECT_SOURCE_DIR}/../daemon)
set (JPL_FILE_NAME ${ProjectName}.jpl) set (JPL_FILE_NAME ${ProjectName}.jpl)
set (DAEMON_SRC ${DAEMON}/src) set (DAEMON_SRC ${DAEMON}/src)
...@@ -17,13 +20,10 @@ set (LIBS_DIR ${PROJECT_SOURCE_DIR}/../contrib/Libs) ...@@ -17,13 +20,10 @@ set (LIBS_DIR ${PROJECT_SOURCE_DIR}/../contrib/Libs)
# Detect the operating system # Detect the operating system
if(WIN32) if(WIN32)
set(OS_NAME "WINDOWS") set(OS_NAME "WINDOWS")
---set(FFMPEG_PATH "${CONTRIB_PATH}/build/ffmpeg/Build/windows/x64")---
elseif(ANDROID) elseif(ANDROID)
set(OS_NAME "ANDROID") set(OS_NAME "ANDROID")
---set(FFMPEG_PATH "${CONTRIB_PATH}/build/ffmpeg/Build/android")---
else() else()
set(OS_NAME "UNIX") set(OS_NAME "UNIX")
---set(FFMPEG_PATH "${CONTRIB_PATH}/build/ffmpeg/Build/unix")---
endif() endif()
# Detect the architecture # Detect the architecture
...@@ -48,27 +48,20 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) ...@@ -48,27 +48,20 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /DMSGPACK_NO_BOOST /MT") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /DMSGPACK_NO_BOOST /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DMSGPACK_NO_BOOST /MTd") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DMSGPACK_NO_BOOST /MTd")
# Android-specific flags set(SOURCES "")
if(ANDROID) file(GLOB CPP_SOURCES "*.cpp")
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a) # Set the desired ABI list(APPEND SOURCES ${CPP_SOURCES})
set(CMAKE_ANDROID_STL_TYPE c++_shared) # Use C++ shared library
set(CMAKE_ANDROID_NDK_TOOLCHAIN_FILE ${CONTRIB_PATH}/build/cmake/android.toolchain.cmake)
set(CMAKE_ANDROID_STL_INCLUDE_DIR ${CONTRIB_PATH}/sysroot/usr/include)
set(CMAKE_ANDROID_STL_LIBRARIES ${CONTRIB_PATH}/sysroot/usr/lib)
endif()
set(plugin_SRC ---CPPFILENAME if(VIDEO_FFMPEG)
---FFMPEGCPP list(APPEND SOURCES "./../lib/accel.cpp")
---) list(APPEND SOURCES "./../lib/frameUtils.cpp")
endif()
set(plugin_HDR ---HFILENAME if(AUDIO_FFMPEG)
---FFMPEGH list(APPEND SOURCES "./../lib/frameUtils.cpp")
---./../lib/pluglog.h endif()
)
add_library(${ProjectName} SHARED ${plugin_SRC} add_library(${ProjectName} SHARED ${SOURCES})
${plugin_HDR}
)
target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR} target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR}
${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}
......
...@@ -41,7 +41,7 @@ UBUNTU_DISTRIBUTION_NAME = "ubuntu" ...@@ -41,7 +41,7 @@ UBUNTU_DISTRIBUTION_NAME = "ubuntu"
def parse(): def parse():
parser = argparse.ArgumentParser(description='Builds Plugins projects') parser = argparse.ArgumentParser(description='Builds Plugins projects')
parser.add_argument('--projects', type=str, parser.add_argument('--projects', type=str,
help='Select plugins to be build.') help='Select plugins to be built.')
parser.add_argument('--distribution') parser.add_argument('--distribution')
parser.add_argument('--processor', type=str, default="GPU", parser.add_argument('--processor', type=str, default="GPU",
help='Runtime plugin CPU/GPU setting.') help='Runtime plugin CPU/GPU setting.')
......
build.md 0 → 100644
1. Copy and rename the Example directory.
```
cp -R Example YourPluginName
sed -i 's/Example/YourPluginName/g' manifest.json
```
2. Change the version to fit the appropriate version of your plugin. You must change `manifest.json`, `main.cpp` and `CMakeLists.txt`. For example, for version 1.0.1:
```cpp
#define Example_VERSION_MAJOR 1
#define Example_VERSION_MINOR 0
#define Example_VERSION_PATCH 1
```
```json
"version": "1.0.1",
```
```
set (VERSION 1.0.1) # {FILL} Replace 0.0.0 with the version of your plugin.
```
3. Edit the CMakeLists.txt of your project directory. Each comment containing {FILL} should be adjusted depending on the associated instructions.
In order, that means:
- Line 2: Set PROJECT_NAME as your plugin name.
- Line 3: Set VERSION as your plugin version.
- Line 8: Turn VIDEO_FFMPEG ON if you'd like to subscribe to or modify Jami's video stream.
- Line 9: Turn AUDIO_FFMPEG ON if you'd like to subscribe to or modify Jami's audio stream.
- Line 62: Add all further `.cpp` imports to SOURCES
**NOTE: All files ending with `.cpp` in your plugin directory will be automatically picked up by `CMake`.**
Header files ending with `.h` which are included in your C++ files will be automatically added as dependencies.
4. Program the functionality of your plugin, taking the `Example` plugin's Video, Audio and Chat features as inspiration.
5. To add a new language, rename `Example_en.json`, replacing `Example` with your plugin name, and edit the contents to reflect your translations.
**NOTE: The language code is ISO 639.**
6. Build your plugin:
```
cd YourPluginName
mkdir -p build-local
cd build-local
cmake ..
cmake --build .
```
By default, you may find the resulting `.jpl` file inside the `build` directory. You may set the `ARCHIVE_JPL` variable to `OFF` in `CMakeLists.txt` to disable the creation of the `.jpl` archive.
7. By default, the `cmake` script will generate a developer certificate/key, and a plugin-specific certificate/key, in `plugins/.cert`. Once you have built your plugin on all platforms of interest and merged them together with `python3 SDK/pluginMainSDK.py`, then by typing `merge` in the SDK, you will be able to sign your final `.jpl` file. Call:
`python3 ./SDK/certKey.py --plugin sign --issuer <path-to-your-developer-certificate> --path <path-to-your-.jpl-file-to-sign> <path-to-save-the-final-.jpl-signed-output>`
...@@ -109,25 +109,11 @@ CONTRIB_SYSROOT=${DAEMON_DIR}/contrib/${TARGET} ...@@ -109,25 +109,11 @@ CONTRIB_SYSROOT=${DAEMON_DIR}/contrib/${TARGET}
mkdir -p ${CONTRIB_DIR} mkdir -p ${CONTRIB_DIR}
mkdir -p ${CONTRIB_SYSROOT}/lib/pkgconfig mkdir -p ${CONTRIB_SYSROOT}/lib/pkgconfig
echo "copying files"
cp -r ${CURRENTDIR}/freetype ${DAEMON_DIR}/contrib/src
cd ${CONTRIB_DIR} cd ${CONTRIB_DIR}
../bootstrap --host=${TARGET} --disable-x264 --enable-ffmpeg \ ../bootstrap --host=${TARGET}
--disable-webrtc-audio-processing --disable-argon2 \
--disable-asio --enable-fmt --disable-gcrypt --disable-gmp \
--disable-gnutls --disable-gpg-error --disable-gsm \
--disable-http_parser --disable-jack --disable-jsoncpp \
--disable-libarchive --disable-libressl --enable-msgpack \
--disable-natpmp --disable-nettle --enable-opencv --enable-opendht \
--disable-pjproject --disable-portaudio --disable-restinio \
--disable-secp256k1 --disable-speex --disable-speexdsp --disable-upnp \
--disable-uuid --disable-yaml-cpp --enable-onnx --disable-dhtnet --enable-opus \
--enable-freetype
make list make list
make fetch make fetch
export PATH="$PATH:$CONTRIB_SYSROOT/bin" export PATH="$PATH:$CONTRIB_SYSROOT/bin"
make $MAKEFLAGS make $MAKEFLAGS .ffmpeg .fmt .opencv .onnx .freetype
daemon @ 90766f95
Subproject commit 072fef2e67cf551c062ec738f1715e2aa7e1e07e Subproject commit 90766f95a36e030e36f89c05e7781cb3d6b09fc6
...@@ -30,7 +30,7 @@ RUN apt-get clean && \ ...@@ -30,7 +30,7 @@ RUN apt-get clean && \
libcanberra-gtk3-dev \ libcanberra-gtk3-dev \
libclutter-gtk-1.0-dev \ libclutter-gtk-1.0-dev \
libclutter-1.0-dev \ libclutter-1.0-dev \
libfreetype6-dev \ libfreetype-dev \
libglib2.0-dev \ libglib2.0-dev \
libgtk-3-dev \ libgtk-3-dev \
libnotify-dev \ libnotify-dev \
...@@ -80,6 +80,7 @@ RUN apt-get clean && \ ...@@ -80,6 +80,7 @@ RUN apt-get clean && \
ninja-build \ ninja-build \
libsystemd-dev libsystemd-dev
ENV DISABLE_PIPEWIRE=true
RUN apt-get install -y python3 python3-pip python3-setuptools \ RUN apt-get install -y python3 python3-pip python3-setuptools \
python3-wheel python3-wheel
......
...@@ -125,6 +125,7 @@ pipeline { ...@@ -125,6 +125,7 @@ pipeline {
"arm-linux-android", "arm-linux-android",
"native-arm-linux-android" "native-arm-linux-android"
] ]
sh(script: "mkdir -p ${hostBaseDir}", returnStatus: true).with { sh(script: "mkdir -p ${hostBaseDir}", returnStatus: true).with {
if (it!= 0) { if (it!= 0) {
error("Failed to create base directory: ${hostBaseDir}") error("Failed to create base directory: ${hostBaseDir}")
...@@ -138,6 +139,7 @@ pipeline { ...@@ -138,6 +139,7 @@ pipeline {
} else { } else {
println "Successfully created directory: ${hostBaseDir}/${subdir}" println "Successfully created directory: ${hostBaseDir}/${subdir}"
} }
println "Inside container, ensure this directory exists: ${containerBaseDir}/${subdir}" println "Inside container, ensure this directory exists: ${containerBaseDir}/${subdir}"
} }
docker.image('plugins-android').withRun('-t \ docker.image('plugins-android').withRun('-t \
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment