SDK: Scripts to generate plugin's skeleton code

done:
- create plugin folder structure
- manifest.json + icon.png
- copyright header
- main.cpp
- choose number of functionalities and the api to each one of them
- create src files for each functionality (APIs skeleton .h and .cpp)
- create preferences.json
- put colors into prints and clear outputs when needed
- modify src files to set preferences code
- create pakage.json
- add helper files
- use library Cmd
- reorganize functions into classes define inherits stack
- add GNU GPL to python files
- jpl merge function
- pre and post assembles
- default options plugin build
- windows build with build-plugin.py
- add build option for windows build
- generate base CMakeLists.txt and build.sh

Change-Id: Id8eb5a97fa7a51e99a0f9215835c3d5ffea630ad
GitLab: #2
diff --git a/SDK/Templates/CMakeLists.txt b/SDK/Templates/CMakeLists.txt
new file mode 100644
index 0000000..882dafc
--- /dev/null
+++ b/SDK/Templates/CMakeLists.txt
@@ -0,0 +1,81 @@
+cmake_minimum_required(VERSION 3.10)
+
+# set the project name
+set (ProjectName PLUGINNAME)
+set (Version MANIFESTVERSION)
+
+project(${ProjectName} VERSION ${Version})
+
+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)
+set (LIBS_DIR ${PROJECT_SOURCE_DIR}/../contrib/Libs)
+
+if(WIN32)
+    message(OS:\  WINDOWS\ ${CMAKE_SYSTEM_PROCESSOR})
+    if (NOT ${CMAKE_CL_64})
+        message( FATAL_ERROR "\nUse CMake only for x64 Windows" )
+    endif()
+    set (CONTRIB_PLATFORM_CURT x64)
+    set (CONTRIB_PLATFORM ${CONTRIB_PLATFORM_CURT}-windows)
+    set (LIBRARY_FILE_NAME ${ProjectName}.dll)
+    ---set (FFMPEG ${CONTRIB_PATH}/build/ffmpeg/Build/win32/x64)
+---else()
+    message( FATAL_ERROR "\nUse CMake only for Windows! For linux or Android (linux host), use our bash scripts." )
+endif()
+
+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})
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED True)
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
+
+set(plugin_SRC ---CPPFILENAME
+               ---../lib/accel.cppFFMPEGCPP
+               ---)
+
+set(plugin_HDR ---HFILENAME
+               ---../lib/accel.hFFMPEGH
+               ../lib/framescaler.h
+               ---../lib/pluglog.h
+               )
+
+add_library(${ProjectName} SHARED ${plugin_SRC}
+                                  ${plugin_HDR}
+                                  )
+
+target_include_directories(${ProjectName} PUBLIC ${PROJECT_BINARY_DIR}
+                                                 ${PROJECT_SOURCE_DIR}
+                                                 ${PLUGINS_LIB}
+                                                 ${DAEMON_SRC}
+                                                 ${CONTRIB_PATH}
+                                                 ---${FFMPEG}/include---
+                                                 )
+target_link_directories(${ProjectName} PUBLIC ${CONTRIB_PATH}
+                                        ---${FFMPEG}/bin---
+                                        )
+
+target_link_libraries(${ProjectName} PUBLIC ---swscale avutil---)
+
+add_custom_command(
+    TARGET ${ProjectName}
+    PRE_BUILD
+    COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --preassemble --plugin=${ProjectName}
+    COMMENT "Assembling Plugin files"
+)
+
+add_custom_command(
+    TARGET ${ProjectName}
+    POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/Release/${ProjectName}.lib ${JPL_DIRECTORY}/lib/${CONTRIB_PLATFORM}
+    COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/Release/${LIBRARY_FILE_NAME} ${JPL_DIRECTORY}/lib/${CONTRIB_PLATFORM}
+    COMMAND python3 ${PROJECT_SOURCE_DIR}/../SDK/jplManipulation.py --assemble --plugin=${ProjectName}
+    COMMENT "Generating JPL archive"
+)
diff --git a/SDK/Templates/build.sh b/SDK/Templates/build.sh
new file mode 100644
index 0000000..2452de4
--- /dev/null
+++ b/SDK/Templates/build.sh
@@ -0,0 +1,205 @@
+#! /bin/bash
+# Build the plugin for the project
+export OSTYPE
+ARCH=$(arch)
+EXTRAPATH=''
+# Flags:
+
+# -p: number of processors to use
+# -c: Runtime plugin cpu/gpu setting.
+# -t: target platform.
+
+
+if [ -z "${DAEMON}" ]; then
+    DAEMON="./../../daemon"
+    echo "DAEMON not provided, building with ${DAEMON}"
+fi
+
+PLUGIN_NAME="PLUGINNAME"
+JPL_FILE_NAME="${PLUGIN_NAME}.jpl"
+SO_FILE_NAME="lib${PLUGIN_NAME}.so"
+DAEMON_SRC="${DAEMON}/src"
+CONTRIB_PATH="${DAEMON}/contrib"
+PLUGINS_LIB="../lib"
+LIBS_DIR="./../contrib/Libs"
+
+if [ -z "${PLATFORM}" ]; then
+    PLATFORM="linux-gnu"
+    echo "PLATFORM not provided, building with ${PLATFORM}"
+    echo "Other options: redhat-linux"
+fi
+
+while getopts t:c:p OPT; do
+  case "$OPT" in
+    t)
+      PLATFORM="${OPTARG}"
+    ;;
+    c)
+      PROCESSOR="${OPTARG}"
+    ;;
+    p)
+    ;;
+    \?)
+      exit 1
+    ;;
+  esac
+done
+
+if [ "${PLATFORM}" = "linux-gnu" ] || [ "${PLATFORM}" = "redhat-linux" ]
+then
+    python3 ./../SDK/jplManipulation.py --preassemble --plugin=${PLUGIN_NAME}
+
+    CONTRIB_PLATFORM_CURT=${ARCH}
+    CONTRIB_PLATFORM=${CONTRIB_PLATFORM_CURT}-${PLATFORM}
+
+    # Compile
+    clang++ -std=c++17 -shared -fPIC \
+    -Wl,-Bsymbolic,-rpath,"\${ORIGIN}" \
+    -Wall -Wextra \
+    -Wno-unused-variable \
+    -Wno-unused-function \
+    -Wno-unused-parameter \
+    -I"." \
+    -I"${DAEMON_SRC}" \
+    -I"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/include" \
+    -I"${PLUGINS_LIB}" \
+    ---FFMPEGCPP./../lib/accel.cpp \
+    ---CPPFILENAME \
+    ----L"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/lib/" \
+    ----l:libswscale.a \
+    -l:libavutil.a \
+    -lva \
+    ----o "build-local/jpl/lib/${CONTRIB_PLATFORM_CURT}-linux-gnu/${SO_FILE_NAME}"
+
+elif [ "${PLATFORM}" = "android" ]
+then
+    python3 ./../SDK/jplManipulation.py --preassemble --plugin=${PLUGIN_NAME} --distribution=${PLATFORM}
+
+    if [ -z "$ANDROID_NDK" ]; then
+            ANDROID_NDK="/home/${USER}/Android/Sdk/ndk/21.1.6352462"
+        echo "ANDROID_NDK not provided, building with ${ANDROID_NDK}"
+    fi
+
+    #=========================================================
+    #    Check if the ANDROID_ABI was provided
+    #    if not, set default
+    #=========================================================
+    if [ -z "$ANDROID_ABI" ]; then
+        ANDROID_ABI="armeabi-v7a arm64-v8a x86_64"
+        echo "ANDROID_ABI not provided, building for ${ANDROID_ABI}"
+    fi
+
+    buildlib() {
+        echo "$CURRENT_ABI"
+
+        #=========================================================
+        #    ANDROID TOOLS
+        #=========================================================
+        export HOST_TAG=linux-x86_64
+        export TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG
+
+        if [ "$CURRENT_ABI" = armeabi-v7a ]
+        then
+        export AR=$TOOLCHAIN/bin/arm-linux-android-ar
+        export AS=$TOOLCHAIN/bin/arm-linux-android-as
+        export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang
+        export CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi21-clang++
+        export LD=$TOOLCHAIN/bin/arm-linux-android-ld
+        export RANLIB=$TOOLCHAIN/bin/arm-linux-android-ranlib
+        export STRIP=$TOOLCHAIN/bin/arm-linux-androideabi-strip
+        export ANDROID_SYSROOT=${DAEMON}/../client-android/android-toolchain-21-arm/sysroot
+
+        elif [ "$CURRENT_ABI" = arm64-v8a ]
+        then
+        export AR=$TOOLCHAIN/bin/aarch64-linux-android-ar
+        export AS=$TOOLCHAIN/bin/aarch64-linux-android-as
+        export CC=$TOOLCHAIN/bin/aarch64-linux-android21-clang
+        export CXX=$TOOLCHAIN/bin/aarch64-linux-android21-clang++
+        export LD=$TOOLCHAIN/bin/aarch64-linux-android-ld
+        export RANLIB=$TOOLCHAIN/bin/aarch64-linux-android-ranlib
+        export STRIP=$TOOLCHAIN/bin/aarch64-linux-android-strip
+        export ANDROID_SYSROOT=${DAEMON}/../client-android/android-toolchain-21-arm64/sysroot
+
+        elif [ "$CURRENT_ABI" = x86_64 ]
+        then
+        export AR=$TOOLCHAIN/bin/x86_64-linux-android-ar
+        export AS=$TOOLCHAIN/bin/x86_64-linux-android-as
+        export CC=$TOOLCHAIN/bin/x86_64-linux-android21-clang
+        export CXX=$TOOLCHAIN/bin/x86_64-linux-android21-clang++
+        export LD=$TOOLCHAIN/bin/x86_64-linux-android-ld
+        export RANLIB=$TOOLCHAIN/bin/x86_64-linux-android-ranlib
+        export STRIP=$TOOLCHAIN/bin/x86_64-linux-android-strip
+        export ANDROID_SYSROOT=${DAEMON}/../client-android/android-toolchain-21-x86_64/sysroot
+
+        else
+        echo "ABI NOT OK" >&2
+        exit 1
+        fi
+
+        #=========================================================
+        #    CONTRIBS
+        #=========================================================
+        if [ "$CURRENT_ABI" = armeabi-v7a ]
+        then
+        CONTRIB_PLATFORM=arm-linux-androideabi
+
+        elif [ "$CURRENT_ABI" = arm64-v8a ]
+        then
+        CONTRIB_PLATFORM=aarch64-linux-android
+
+        elif [ "$CURRENT_ABI" = x86_64 ]
+        then
+        CONTRIB_PLATFORM=x86_64-linux-android
+        fi
+
+        #NDK SOURCES FOR cpufeatures
+        NDK_SOURCES=${ANDROID_NDK}/sources/android
+
+        #=========================================================
+        #    LD_FLAGS
+        #=========================================================
+        if [ "$CURRENT_ABI" = armeabi-v7a ]
+        then
+        export EXTRA_LDFLAGS="${EXTRA_LDFLAGS} -L${ANDROID_SYSROOT}/usr/lib/arm-linux-androideabi -L${ANDROID_SYSROOT}/usr/lib/arm-linux-androideabi/21"
+        elif [ "$CURRENT_ABI" = arm64-v8a ]
+        then
+        export EXTRA_LDFLAGS="${EXTRA_LDFLAGS} -L${ANDROID_SYSROOT}/usr/lib/aarch64-linux-android -L${ANDROID_SYSROOT}/usr/lib/aarch64-linux-android/21"
+        elif [ "$CURRENT_ABI" = x86_64 ]
+        then
+        export EXTRA_LDFLAGS="${EXTRA_LDFLAGS} -L${ANDROID_SYSROOT}/usr/lib/x86_64-linux-android -L${ANDROID_SYSROOT}/usr/lib/x86_64-linux-android/21"
+        fi
+
+        #=========================================================
+        #    Compile the plugin
+        #=========================================================
+
+        # Create so destination folder
+        $CXX --std=c++14 -O3 -g -fPIC \
+        -Wl,-Bsymbolic,-rpath,"\${ORIGIN}" \
+        -shared \
+        -Wall -Wextra \
+        -Wno-unused-variable \
+        -Wno-unused-function \
+        -Wno-unused-parameter \
+        -I"." \
+        -I"${DAEMON_SRC}" \
+        -I"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/include" \
+        -I"${PLUGINS_LIB}" \
+        ---FFMPEGCPP./../lib/accel.cpp \
+        ---CPPFILENAME \
+        ----L"${CONTRIB_PATH}/${CONTRIB_PLATFORM}/lib/" \
+        ----lswscale \
+        -lavutil \
+        ----llog -lz \
+        --sysroot=$ANDROID_SYSROOT \
+        -o "build-local/jpl/lib/$CURRENT_ABI/${SO_FILE_NAME}"
+    }
+
+    # Build the so
+    for i in ${ANDROID_ABI}; do
+        CURRENT_ABI=$i
+        buildlib
+    done
+fi
+
+python3 ./../SDK/jplManipulation.py --assemble --plugin=${PLUGIN_NAME} --distribution=${PLATFORM} --extraPath=${EXTRAPATH}
diff --git a/SDK/Templates/copyright.txt b/SDK/Templates/copyright.txt
new file mode 100644
index 0000000..cf00a19
--- /dev/null
+++ b/SDK/Templates/copyright.txt
@@ -0,0 +1,19 @@
+/**
+ *  YEAR
+ *
+ *  Author: AUTHORNAME <AUTHORMAIL>
+ *
+ *  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.
+ */
\ No newline at end of file
diff --git a/SDK/Templates/defaultDependenciesStrings.json b/SDK/Templates/defaultDependenciesStrings.json
new file mode 100644
index 0000000..3488cb8
--- /dev/null
+++ b/SDK/Templates/defaultDependenciesStrings.json
@@ -0,0 +1,10 @@
+{
+    "package.json":
+    {
+        "MediaHandler": {
+            "deps": [
+                "ffmpeg"
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/SDK/Templates/genericConversationHandler.h b/SDK/Templates/genericConversationHandler.h
new file mode 100644
index 0000000..1906ce6
--- /dev/null
+++ b/SDK/Templates/genericConversationHandler.h
@@ -0,0 +1,23 @@
+HEADER
+
+#pragma once
+//Project
+#include "chatsubscriber.h"
+//Jami plugin
+#include "plugin/jamiplugin.h"
+#include "plugin/conversationhandler.h"
+
+class GENERICConversationHandler : public jami::ConversationHandler
+{
+public:
+    GENERICConversationHandler(const JAMI_PluginAPI * api, std::string &&dataPath);
+    ~GENERICConversationHandler();
+    void detach();
+    virtual void notifyStrMapSubject(const bool direction,
+                                     jami::strMapSubjectPtr subject) override;
+    const std::string& dataPath() const { return dataPath_; }
+
+private:
+    std::string dataPath_;
+    std::shared_ptr<ChatSubscriber> css;
+};
diff --git a/SDK/Templates/genericMediaHandler.cpp b/SDK/Templates/genericMediaHandler.cpp
new file mode 100644
index 0000000..f3bbb39
--- /dev/null
+++ b/SDK/Templates/genericMediaHandler.cpp
@@ -0,0 +1,84 @@
+HEADER
+
+#include "GENERICMediaHandler.h"
+// Logger
+#include "pluglog.h"
+const char sep = separator();
+const std::string TAG = "GENERIC";
+
+#define NAME "GENERIC"
+
+namespace jami {
+
+GENERICMediaHandler::GENERICMediaHandler(std::map<std::string, std::string>&& ppm,
+                                         std::string&& datapath)
+    : datapath_ {datapath}
+    , ppm_ {ppm}
+{
+    setId(datapath_);
+    mVS = std::make_shared<GENERICVideoSubscriber>(datapath_);
+}
+
+void
+GENERICMediaHandler::notifyAVFrameSubject(const StreamData& data, jami::avSubjectPtr subject)
+{
+    Plog::log(Plog::LogPriority::INFO, TAG, "IN AVFRAMESUBJECT");
+    std::ostringstream oss;
+    std::string direction = data.direction ? "Receive" : "Preview";
+    oss << "NEW SUBJECT: [" << data.id << "," << direction << "]" << std::endl;
+
+    bool preferredStreamDirection = false; // false for output; true for input
+    oss << "preferredStreamDirection " << preferredStreamDirection << std::endl;
+    if (data.type == StreamType::video && !data.direction
+        && data.direction == preferredStreamDirection) {
+        subject->attach(mVS.get()); // your image
+        oss << "got my sent image attached" << std::endl;
+    } else if (data.type == StreamType::video && data.direction
+               && data.direction == preferredStreamDirection) {
+        subject->attach(mVS.get()); // the image you receive from others on the call
+        oss << "got received image attached" << std::endl;
+    }
+
+    Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
+}
+
+std::map<std::string, std::string>
+GENERICMediaHandler::getCallMediaHandlerDetails()
+{
+    return {{"name", NAME}, {"iconPath", datapath_ + sep + "icon.png"}, {"pluginId", id()}};
+}
+
+void
+GENERICMediaHandler::setPreferenceAttribute(const std::string& key, const std::string& value)
+{
+    auto it = ppm_.find(key);
+    if (it != ppm_.end() && it->second != value) {
+        it->second = value;----------------
+        if (key == "PREFERENCE1") {
+            // use preference
+            return;
+        }----------------
+    }
+}
+
+bool
+GENERICMediaHandler::preferenceMapHasKey(const std::string& key)
+{----------------
+    if (key == "PREFERENCE2") { return true; }----------------
+    return false;
+}
+
+void
+GENERICMediaHandler::detach()
+{
+    mVS->detach();
+}
+
+GENERICMediaHandler::~GENERICMediaHandler()
+{
+    std::ostringstream oss;
+    oss << " ~GENERICMediaHandler from PLUGINNAME Plugin" << std::endl;
+    Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
+    detach();
+}
+} // namespace jami
diff --git a/SDK/Templates/genericMediaHandler.h b/SDK/Templates/genericMediaHandler.h
new file mode 100644
index 0000000..6b3a70d
--- /dev/null
+++ b/SDK/Templates/genericMediaHandler.h
@@ -0,0 +1,37 @@
+HEADER
+
+#pragma once
+
+// Project
+#include "GENERICVideoSubscriber.h"
+
+// Plugin
+#include "plugin/jamiplugin.h"
+#include "plugin/mediahandler.h"
+
+using avSubjectPtr = std::weak_ptr<jami::Observable<AVFrame*>>;
+
+namespace jami {
+
+class GENERICMediaHandler : public jami::CallMediaHandler
+{
+public:
+    GENERICMediaHandler(std::map<std::string, std::string>&& ppm, std::string&& dataPath);
+    ~GENERICMediaHandler();
+
+    virtual void notifyAVFrameSubject(const StreamData& data, avSubjectPtr subject) override;
+    virtual std::map<std::string, std::string> getCallMediaHandlerDetails() override;
+
+    virtual void detach() override;
+    virtual void setPreferenceAttribute(const std::string& key, const std::string& value) override;
+    virtual bool preferenceMapHasKey(const std::string& key) override;
+
+    std::shared_ptr<GENERICVideoSubscriber> mVS;
+
+    const std::string& dataPath() const { return datapath_; }
+
+private:
+    const std::string datapath_;
+    std::map<std::string, std::string> ppm_;
+};
+} // namespace jami
diff --git a/SDK/Templates/genericVideoSubscriber.cpp b/SDK/Templates/genericVideoSubscriber.cpp
new file mode 100644
index 0000000..13398cb
--- /dev/null
+++ b/SDK/Templates/genericVideoSubscriber.cpp
@@ -0,0 +1,119 @@
+HEADER
+
+#include "GENERICVideoSubscriber.h"
+
+extern "C" {
+#include <libavutil/display.h>
+}
+#include <accel.h>
+
+// LOGGING
+#include <pluglog.h>
+
+const std::string TAG = "GENERIC";
+const char sep = separator();
+
+namespace jami {
+
+GENERICVideoSubscriber::GENERICVideoSubscriber(const std::string& dataPath)
+    : path_ {dataPath}
+{}
+
+GENERICVideoSubscriber::~GENERICVideoSubscriber()
+{
+    std::ostringstream oss;
+    oss << "~GENERICMediaProcessor" << std::endl;
+    Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
+}
+
+void
+GENERICVideoSubscriber::update(jami::Observable<AVFrame*>*, AVFrame* const& iFrame)
+{
+    if (!iFrame)
+        return;
+    AVFrame* pluginFrame = const_cast<AVFrame*>(iFrame);
+
+    //======================================================================================
+    // GET FRAME ROTATION
+    AVFrameSideData* side_data = av_frame_get_side_data(iFrame, AV_FRAME_DATA_DISPLAYMATRIX);
+
+    int angle {0};
+    if (side_data) {
+        auto matrix_rotation = reinterpret_cast<int32_t*>(side_data->data);
+        angle = static_cast<int>(av_display_rotation_get(matrix_rotation));
+    }
+
+    //======================================================================================
+    // GET RAW FRAME
+    // Use a non-const Frame
+    // Convert input frame to RGB
+    int inputHeight = pluginFrame->height;
+    int inputWidth = pluginFrame->width;
+    FrameUniquePtr bgrFrame = scaler.convertFormat(transferToMainMemory(pluginFrame,
+                                                                        AV_PIX_FMT_NV12),
+                                                   AV_PIX_FMT_RGB24);
+
+    // transferToMainMemory USED TO COPY FRAME TO MAIN MEMORY IF HW ACCEL IS ENABLED
+    // NOT NEEDED TO BE USED IF ALL YOUR PROCESS WILL BE DONE WITHIN A
+    // HW ACCEL PLATFORM
+
+    if (firstRun) {
+        // IMPLEMENT CODE TO CONFIGURE
+        // VARIABLES UPON FIRST RUN
+        firstRun = false;
+    }
+
+    // IMPLEMENT PROCESS
+
+    //======================================================================================
+    // REPLACE AVFRAME DATA WITH FRAME DATA
+    if (bgrFrame && bgrFrame->data[0]) {
+        uint8_t* frameData = bgrFrame->data[0];
+        if (angle == 90 || angle == -90) {
+            std::memmove(frameData,
+                         frameData, // PUT HERE YOUR PROCESSED FRAME VARIABLE DATA!
+                         static_cast<size_t>(pluginFrame->width * pluginFrame->height * 3)
+                             * sizeof(uint8_t));
+        }
+    }
+    // Copy Frame meta data
+    if (bgrFrame && pluginFrame) {
+        av_frame_copy_props(bgrFrame.get(), pluginFrame);
+        scaler.moveFrom(pluginFrame, bgrFrame.get());
+    }
+
+    // Remove the pointer
+    pluginFrame = nullptr;
+}
+
+void
+GENERICVideoSubscriber::attached(jami::Observable<AVFrame*>* observable)
+{
+    std::ostringstream oss;
+    oss << "::Attached ! " << std::endl;
+    Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
+    observable_ = observable;
+}
+
+void
+GENERICVideoSubscriber::detached(jami::Observable<AVFrame*>*)
+{
+    firstRun = true;
+    observable_ = nullptr;
+    std::ostringstream oss;
+    oss << "::Detached()" << std::endl;
+    Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
+}
+
+void
+GENERICVideoSubscriber::detach()
+{
+    if (observable_) {
+        firstRun = true;
+        std::ostringstream oss;
+        oss << "::Calling detach()" << std::endl;
+        Plog::log(Plog::LogPriority::INFO, TAG, oss.str());
+        observable_->detach(this);
+    }
+}
+} // namespace jami
diff --git a/SDK/Templates/genericVideoSubscriber.h b/SDK/Templates/genericVideoSubscriber.h
new file mode 100644
index 0000000..eaa6649
--- /dev/null
+++ b/SDK/Templates/genericVideoSubscriber.h
@@ -0,0 +1,39 @@
+HEADER
+
+#pragma once
+
+// AvFrame
+extern "C" {
+#include <libavutil/frame.h>
+}
+#include <observer.h>
+
+// Frame Scaler
+#include <framescaler.h>
+
+namespace jami {
+
+class GENERICVideoSubscriber : public jami::Observer<AVFrame*>
+{
+public:
+    GENERICVideoSubscriber(const std::string& dataPath);
+    ~GENERICVideoSubscriber();
+
+    virtual void update(jami::Observable<AVFrame*>*, AVFrame* const&) override;
+    virtual void attached(jami::Observable<AVFrame*>*) override;
+    virtual void detached(jami::Observable<AVFrame*>*) override;
+
+    void detach();
+
+private:
+    // Observer pattern
+    Observable<AVFrame*>* observable_ = nullptr;
+
+    // Data
+    std::string path_;
+    FrameScaler scaler;
+
+    // Status variables of the processing
+    bool firstRun {true};
+};
+} // namespace jami
diff --git a/SDK/Templates/icon.png b/SDK/Templates/icon.png
new file mode 100644
index 0000000..2ce0aab
--- /dev/null
+++ b/SDK/Templates/icon.png
Binary files differ
diff --git a/SDK/Templates/main.cpp b/SDK/Templates/main.cpp
new file mode 100644
index 0000000..e97416a
--- /dev/null
+++ b/SDK/Templates/main.cpp
@@ -0,0 +1,48 @@
+HEADER
+#include <iostream>
+#include <string.h>
+#include <thread>
+#include <memory>
+#include <plugin/jamiplugin.h>
+----------------
+#include "INCLUDESAPI.h"----------------
+
+#ifdef WIN32
+#define EXPORT_PLUGIN __declspec(dllexport)
+#else
+#define EXPORT_PLUGIN
+#endif
+#define PLUGINNAME_VERSION_MAJOR PLUGINVERSIONMAJOR
+#define PLUGINNAME_VERSION_MINOR PLUGINVERSIONMINOR
+#define PLUGINNAME_VERSION_PATCH PLUGINVERSIONPATCH
+extern "C" {
+
+void
+pluginExit(void)
+{}
+
+EXPORT_PLUGIN JAMI_PluginExitFunc
+JAMI_dynPluginInit(const JAMI_PluginAPI* api)
+{
+    std::cout << "**************************" << std::endl << std::endl;
+    std::cout << "**  PLUGINNAME  **" << std::endl;
+    std::cout << "**************************" << std::endl << std::endl;
+    std::cout << " Version " << PLUGINNAME_VERSION_MAJOR << "." << PLUGINNAME_VERSION_MINOR << "."
+              << PLUGINNAME_VERSION_PATCH << std::endl;
+
+    // If invokeService doesn't return an error
+    if (api) {
+        std::map<std::string, std::string> ppm;
+        api->invokeService(api, "getPluginPreferences", &ppm);
+        std::string dataPath;
+        api->invokeService(api, "getPluginDataPath", &dataPath);
+----------------
+        auto fmpPLUGINAPI = std::make_unique<jami::PLUGINAPI>(std::move(ppm), std::move(dataPath));
+        if (api->manageComponent(api, "APIMANAGER", fmpPLUGINAPI.release())) {
+            return nullptr;
+        }
+----------------
+    }
+    return pluginExit;
+}
+}
diff --git a/SDK/Templates/manifest.json b/SDK/Templates/manifest.json
new file mode 100644
index 0000000..d5c8df6
--- /dev/null
+++ b/SDK/Templates/manifest.json
@@ -0,0 +1,5 @@
+{
+    "name": "pluginName",
+    "description": "plugin description",
+    "version": "version"
+}
\ No newline at end of file
diff --git a/SDK/Templates/package.json b/SDK/Templates/package.json
new file mode 100644
index 0000000..0bb6d39
--- /dev/null
+++ b/SDK/Templates/package.json
@@ -0,0 +1,16 @@
+{
+    "name": "",
+    "version": "",
+    "extractLibs": false,
+    "deps": [],
+    "defines": [],
+    "custom_scripts": {
+        "pre_build": [
+            "mkdir msvc"
+        ],
+        "build": [
+            "cmake --build ./msvc --config Release"
+        ],
+        "post_build": []
+    }
+}
diff --git a/SDK/Templates/preferences.json b/SDK/Templates/preferences.json
new file mode 100644
index 0000000..4caacdc
--- /dev/null
+++ b/SDK/Templates/preferences.json
@@ -0,0 +1,23 @@
+[
+    {
+        "category" : "genericList",
+        "type": "List",
+        "key": "keyName",
+        "title": "preference title",
+        "summary": "preference summary",
+        "defaultValue": "default value",
+        "scope": "plugin, Name of API implementation",
+        "entryValues": ["List of", "variables", "true", "values"],
+        "entries": ["List of", "UI", "variables", "names"]
+    },
+    {
+        "category" : "genericPath",
+        "type": "Path",
+        "key": "keyName",
+        "title": "preference title",
+        "summary": "preference summary",
+        "defaultValue": "default values",
+        "scope": "plugin, Name of API implementation",
+        "mimeType": "*/*"
+    }
+]