From 75821eb8226eab877df2e0f65f291df46406de1a Mon Sep 17 00:00:00 2001 From: L-Nafaryus Date: Tue, 25 Jun 2024 16:08:15 +0500 Subject: [PATCH] Add CMake build support for Windows and MacOS, formatting --- .clang-format | 5 + .gitignore | 3 + CMakeLists.txt | 63 +- README.md | 97 ++- cmake/FindLibObs.cmake | 107 --- cmake/ObsPluginHelpers.cmake | 328 ++++++++ cmake/bundle/macos/Plugin-Info.plist.in | 26 + cmake/bundle/macos/entitlements.plist | 17 + cmake/installer/installer-Windows.iss.in | 62 ++ cmake/installer/installer-macOS.pkgproj.in | 920 +++++++++++++++++++++ image-reaction.c | 526 ------------ src/image-reaction.c | 469 +++++++++++ 12 files changed, 1939 insertions(+), 684 deletions(-) create mode 100644 .clang-format create mode 100644 .gitignore delete mode 100644 cmake/FindLibObs.cmake create mode 100644 cmake/ObsPluginHelpers.cmake create mode 100644 cmake/bundle/macos/Plugin-Info.plist.in create mode 100644 cmake/bundle/macos/entitlements.plist create mode 100644 cmake/installer/installer-Windows.iss.in create mode 100644 cmake/installer/installer-macOS.pkgproj.in delete mode 100644 image-reaction.c create mode 100644 src/image-reaction.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f85a8e1 --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +Standard: Cpp11 +ColumnLimit: 120 +IndentWidth: 4 +PointerAlignment: Left +TabWidth: 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6cc3011 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/cmake-build* +/build +/result diff --git a/CMakeLists.txt b/CMakeLists.txt index 76f9381..66648ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,31 +1,48 @@ -cmake_minimum_required(VERSION 3.5) -project(image-reaction VERSION 1.0) -set(PLUGIN_AUTHOR "scaled") -set(PLUGIN_GIT image-reaction) -set(LINUX_MAINTAINER_EMAIL "scaled@scaledteam.ru") -set(MACOS_BUNDLEID "ru.scaledteam.image-reaction") -set(CMAKE_CXX_STANDARD 11) +cmake_minimum_required(VERSION 3.16) -if (WIN32 OR APPLE) - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") +project(image-reaction VERSION 1.2) + +set(PLUGIN_AUTHOR "scaled") +set(PLUGIN_GIT ${PROJECT_NAME}) +set(LINUX_MAINTAINER_EMAIL "scaled@scaledteam.ru") +set(PLUGIN_URL "https://obsproject.com/forum/resources/mute-filter.1478/") + +set(MACOS_BUNDLEID "ru.scaledteam.image-reaction") +set(MACOS_PACKAGE_UUID "AC34BEA6-6AAB-4CAE-AE0F-222F49F0503A") +set(MACOS_INSTALLER_UUID "4F2A848B-08BE-4EED-AD7F-824BD7B97A8A") + +find_package(libobs REQUIRED) +include(cmake/ObsPluginHelpers.cmake) + +add_library(image-reaction MODULE src/image-reaction.c) + +target_link_libraries(image-reaction OBS::libobs) + +if(OS_WINDOWS) + # Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL when building with VS2019) + if(MSVC) + add_definitions(/MP /d2FH4-) + endif() + + target_link_libraries(${PROJECT_NAME} OBS::w32-pthreads) + + configure_file(cmake/installer/installer-Windows.iss.in installer-Windows.generated.iss) endif() -if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") - add_definitions(-DLINUX=1) - add_definitions(-DUNIX=1) -endif () +if(OS_LINUX) + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) +endif() -find_package(LibObs REQUIRED) +if(APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default") -set(image-reaction_SOURCES image-reaction.c) + set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + set(MACOSX_PLUGIN_GUI_IDENTIFIER "${MACOS_BUNDLEID}") + set(MACOSX_PLUGIN_BUNDLE_VERSION "${PROJECT_VERSION}") + set(MACOSX_PLUGIN_SHORT_VERSION_STRING "1") -add_library(image-reaction MODULE - ${image-reaction_SOURCES}) + configure_file(cmake/installer/installer-macOS.pkgproj.in installer-macOS.generated.pkgproj) +endif() -target_link_libraries(image-reaction - ${LIBOBS_LIBRARIES} - ${OBS_FRONTEND_LIB}) +setup_plugin_target(${PROJECT_NAME}) -include_directories("${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" - ${LIBOBS_INCLUDE_DIR} -) diff --git a/README.md b/README.md index 9d6cf7b..612e799 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,77 @@ # OBS Image Reaction Plugin + Image that reacts to sound source. -## Installing binaries -Download binaries from [official releases](https://github.com/scaledteam/obs-image-reaction/releases/) or more user-friendly version from [ashmanix](https://github.com/ashmanix/obs-image-reaction/releases). - -For Windows, Move the contents of plugin into your obs installation directory. It usually installed into "C:\Program Files\obs-studio\". - -For GNU/Linux, put "libimage-reaction" folder into "~/.config/obs-studio/plugins/" folder. - -For Mac OS, try this port from ashmanix: https://github.com/ashmanix/obs-image-reaction . - ## Building and installing for GNU/Linux: -``` -git clone https://github.com/scaledteam/obs-image-reaction -cd obs-image-reaction -mkdir build -cd build -cmake .. -make -mkdir -p ~/.config/obs-studio/plugins/libimage-reaction/bin/64bit -cp libimage-reaction.so ~/.config/obs-studio/plugins/libimage-reaction/bin/64bit/ -cp -r ../data ~/.config/obs-studio/plugins/libimage-reaction/ + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release -S . +cmake --build build + +mkdir -p $XDG_CONFIG_HOME/obs-studio/plugins/image-reaction/bin/64bit +cp build/libimage-reaction.so $XDG_CONFIG_HOME/obs-studio/plugins/image-reaction/bin/64bit/ +cp -r data $XDG_CONFIG_HOME/obs-studio/plugins/image-reaction/ ``` -## Building for Windows from GNU/Linux: -You need to download MinGW, OBS Studio source code, Wine, install OBS Studio using wine. +## Building for Windows via MinGW: + +You need to complete several steps before actual building: +- Install MinGW +- Install Wine +- Install OBS Studio to created wine environment: https://github.com/obsproject/obs-studio/releases +- Download OBS Studio sources: https://github.com/obsproject/obs-studio + +```bash +# Debian example +cmake \ + -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SYSTEM_NAME=Windows \ + -DCMAKE_CXX_COMPILER=/usr/bin/x86_64-w64-mingw32-g++-win32 \ + -DCMAKE_C_COMPILER=/usr/bin/x86_64-w64-mingw32-gcc-win32 \ + -DLIBOBS_INCLUDE_DIR=~/git/obs-studio-27.0.1/libobs \ + -DLIBOBS_LIB=~/.wine/drive_c/Program\ Files/obs-studio/bin/64bit/obs.dll \ + -S . +cmake --build build ``` -git clone https://github.com/scaledteam/obs-image-reaction -cd obs-image-reaction -mkdir build-win -cd build-win -cmake .. -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_CXX_COMPILER=/usr/bin/x86_64-w64-mingw32-g++-win32 -DCMAKE_C_COMPILER=/usr/bin/x86_64-w64-mingw32-gcc-win32 -DLIBOBS_INCLUDE_DIR=~/git/obs-studio-27.0.1/libobs -DLIBOBS_LIB=~/.wine/drive_c/Program\ Files/obs-studio/bin/64bit/obs.dll -make + +Now move `libimage-reaction.dll` into OBS Plugin directory. + +## Installing on NixOS with Nix Flakes + +```nix +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + obs-image-reaction.url = "github:L-Nafaryus/obs-image-reaction"; + }; + outputs = { nixpkgs, ... } @ inputs: + { + nixosConfigurations.foo = nixpkgs.lib.nixosSystem rec { + system = "x86_64-linux"; + specialArgs = { inherit inputs; }; + modules = [ + ({ pkgs, inputs, ... }: + { + environment.systemPackages = with pkgs; [ + (pkgs.wrapOBS { + plugins = with pkgs.obs-studio-plugins; [ + inputs.obs-image-reaction.packages.${pkgs.system}.default + ]; + }) + ]; + }) + ]; + }; + }; +} ``` -Now move libimage-reaction.dll into OBS Plugin directory. + +## References + +- Original plugin sources: https://github.com/scaledteam/obs-image-reaction +- Fork with Windows/MacOS bundle support for CMake: https://github.com/ashmanix/obs-image-reaction + +## License + +Like [obs-studio](https://github.com/obsproject/obs-studio), `obs-image-reaction` is licensed under [GNU General Public License v2.0](./LICENSE). diff --git a/cmake/FindLibObs.cmake b/cmake/FindLibObs.cmake deleted file mode 100644 index ab0a3de..0000000 --- a/cmake/FindLibObs.cmake +++ /dev/null @@ -1,107 +0,0 @@ -# This module can be copied and used by external plugins for OBS -# -# Once done these will be defined: -# -# LIBOBS_FOUND -# LIBOBS_INCLUDE_DIRS -# LIBOBS_LIBRARIES - -find_package(PkgConfig QUIET) -if (PKG_CONFIG_FOUND) - pkg_check_modules(_OBS QUIET obs libobs) -endif() - -if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(_lib_suffix 64) -else() - set(_lib_suffix 32) -endif() - -if(DEFINED CMAKE_BUILD_TYPE) - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set(_build_type_base "debug") - else() - set(_build_type_base "release") - endif() -endif() - -find_path(LIBOBS_INCLUDE_DIR - NAMES obs.h - HINTS - ENV obsPath${_lib_suffix} - ENV obsPath - ${obsPath} - PATHS - /usr/include /usr/local/include /opt/local/include /sw/include - PATH_SUFFIXES - libobs - ) - -function(find_obs_lib base_name repo_build_path lib_name) - string(TOUPPER "${base_name}" base_name_u) - - if(DEFINED _build_type_base) - set(_build_type_${repo_build_path} "${_build_type_base}/${repo_build_path}") - set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}") - endif() - - find_library(${base_name_u}_LIB - NAMES ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} - HINTS - ENV obsPath${_lib_suffix} - ENV obsPath - ${obsPath} - ${_${base_name_u}_LIBRARY_DIRS} - PATHS - /usr/lib /usr/local/lib /opt/local/lib /sw/lib - PATH_SUFFIXES - lib${_lib_suffix} lib - libs${_lib_suffix} libs - bin${_lib_suffix} bin - ../lib${_lib_suffix} ../lib - ../libs${_lib_suffix} ../libs - ../bin${_lib_suffix} ../bin - # base repo non-msvc-specific search paths - ${_build_type_${repo_build_path}} - ${_build_type_${repo_build_path}${_lib_suffix}} - build/${repo_build_path} - build${_lib_suffix}/${repo_build_path} - # base repo msvc-specific search paths on windows - build${_lib_suffix}/${repo_build_path}/Debug - build${_lib_suffix}/${repo_build_path}/RelWithDebInfo - build/${repo_build_path}/Debug - build/${repo_build_path}/RelWithDebInfo - ) -endfunction() - -find_obs_lib(LIBOBS libobs obs) - -if(MSVC) - find_obs_lib(W32_PTHREADS deps/w32-pthreads w32-pthreads) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Libobs DEFAULT_MSG LIBOBS_LIB LIBOBS_INCLUDE_DIR) -mark_as_advanced(LIBOBS_INCLUDE_DIR LIBOBS_LIB) - -if(LIBOBS_FOUND) - if(MSVC) - if (NOT DEFINED W32_PTHREADS_LIB) - message(FATAL_ERROR "Could not find the w32-pthreads library" ) - endif() - - set(W32_PTHREADS_INCLUDE_DIR ${LIBOBS_INCLUDE_DIR}/../deps/w32-pthreads) - endif() - - set(LIBOBS_INCLUDE_DIRS ${LIBOBS_INCLUDE_DIR} ${W32_PTHREADS_INCLUDE_DIR}) - set(LIBOBS_LIBRARIES ${LIBOBS_LIB} ${W32_PTHREADS_LIB}) - include(${LIBOBS_INCLUDE_DIR}/../cmake/external/ObsPluginHelpers.cmake) - - # allows external plugins to easily use/share common dependencies that are often included with libobs (such as FFmpeg) - if(NOT DEFINED INCLUDED_LIBOBS_CMAKE_MODULES) - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LIBOBS_INCLUDE_DIR}/../cmake/Modules/") - set(INCLUDED_LIBOBS_CMAKE_MODULES true) - endif() -else() - message(FATAL_ERROR "Could not find the libobs library" ) -endif() diff --git a/cmake/ObsPluginHelpers.cmake b/cmake/ObsPluginHelpers.cmake new file mode 100644 index 0000000..7336ce1 --- /dev/null +++ b/cmake/ObsPluginHelpers.cmake @@ -0,0 +1,328 @@ +if(POLICY CMP0087) + cmake_policy(SET CMP0087 NEW) +endif() + +set(OBS_STANDALONE_PLUGIN_DIR ${CMAKE_SOURCE_DIR}/release) +set(INCLUDED_LIBOBS_CMAKE_MODULES ON) + +include(GNUInstallDirs) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(OS_MACOS ON) + set(OS_POSIX ON) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|OpenBSD") + set(OS_POSIX ON) + string(TOUPPER "${CMAKE_SYSTEM_NAME}" _SYSTEM_NAME_U) + set(OS_${_SYSTEM_NAME_U} ON) +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(OS_WINDOWS ON) + set(OS_POSIX OFF) +endif() + +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND (OS_WINDOWS OR OS_MACOS)) + set(CMAKE_INSTALL_PREFIX + ${OBS_STANDALONE_PLUGIN_DIR} + CACHE STRING "Directory to install OBS plugin after building" FORCE) +endif() + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE + "RelWithDebInfo" + CACHE STRING + "OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Release RelWithDebInfo + Debug MinSizeRel) +endif() + +if(NOT QT_VERSION) + set(QT_VERSION + "5" + CACHE STRING "OBS Qt version [5, 6]" FORCE) + set_property(CACHE QT_VERSION PROPERTY STRINGS 5 6) +endif() + +macro(find_qt) + set(oneValueArgs VERSION) + set(multiValueArgs COMPONENTS COMPONENTS_WIN COMPONENTS_MAC COMPONENTS_LINUX) + cmake_parse_arguments(FIND_QT "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(OS_WINDOWS) + find_package( + Qt${FIND_QT_VERSION} + COMPONENTS ${FIND_QT_COMPONENTS} ${FIND_QT_COMPONENTS_WIN} + REQUIRED) + elseif(OS_MACOS) + find_package( + Qt${FIND_QT_VERSION} + COMPONENTS ${FIND_QT_COMPONENTS} ${FIND_QT_COMPONENTS_MAC} + REQUIRED) + else() + find_package( + Qt${FIND_QT_VERSION} + COMPONENTS ${FIND_QT_COMPONENTS} ${FIND_QT_COMPONENTS_LINUX} + REQUIRED) + endif() + + if("Gui" IN_LIST FIND_QT_COMPONENTS) + list(APPEND FIND_QT_COMPONENTS "GuiPrivate") + endif() + + foreach(_COMPONENT IN LISTS FIND_QT_COMPONENTS FIND_QT_COMPONENTS_WIN + FIND_QT_COMPONENTS_MAC FIND_QT_COMPONENTS_LINUX) + if(NOT TARGET Qt::${_COMPONENT} AND TARGET + Qt${FIND_QT_VERSION}::${_COMPONENT}) + + add_library(Qt::${_COMPONENT} INTERFACE IMPORTED) + set_target_properties( + Qt::${_COMPONENT} PROPERTIES INTERFACE_LINK_LIBRARIES + "Qt${FIND_QT_VERSION}::${_COMPONENT}") + endif() + endforeach() +endmacro() + +file(RELATIVE_PATH RELATIVE_INSTALL_PATH ${CMAKE_SOURCE_DIR} ${CMAKE_INSTALL_PREFIX}) +file(RELATIVE_PATH RELATIVE_BUILD_PATH ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) + +if(OS_MACOS) + set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "OBS build architecture for macOS - x86_64 required at least") + set_property(CACHE CMAKE_OSX_ARCHITECTURES PROPERTY STRINGS x86_64 arm64 "x86_64;arm64") + + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "OBS deployment target for macOS - 10.13+ required") + set_property(CACHE CMAKE_OSX_DEPLOYMENT_TARGET PROPERTY STRINGS 10.15 11 12) + + set(OBS_BUNDLE_CODESIGN_IDENTITY "-" CACHE STRING "OBS code signing identity for macOS") + set(OBS_CODESIGN_LINKER ON + CACHE BOOL "Enable linker code-signing on macOS (macOS 11+ required)") + + if(XCODE) + # Tell Xcode to pretend the linker signed binaries so that editing with + # install_name_tool preserves ad-hoc signatures. This option is supported by + # codesign on macOS 11 or higher. See CMake Issue 21854: + # https://gitlab.kitware.com/cmake/cmake/-/issues/21854 + + set(CMAKE_XCODE_GENERATE_SCHEME ON) + endif() + + # Set default options for bundling on macOS + set(CMAKE_MACOSX_RPATH ON) + set(CMAKE_SKIP_BUILD_RPATH OFF) + set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF) + set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks/") + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF) + + function(setup_plugin_target target) + if(NOT DEFINED MACOSX_PLUGIN_GUI_IDENTIFIER) + message( + FATAL_ERROR + "No 'MACOSX_PLUGIN_GUI_IDENTIFIER' set, but is required to build plugin bundles on macOS - example: 'com.yourname.pluginname'" + ) + endif() + + if(NOT DEFINED MACOSX_PLUGIN_BUNDLE_VERSION) + message( + FATAL_ERROR + "No 'MACOSX_PLUGIN_BUNDLE_VERSION' set, but is required to build plugin bundles on macOS - example: '25'" + ) + endif() + + if(NOT DEFINED MACOSX_PLUGIN_SHORT_VERSION_STRING) + message( + FATAL_ERROR + "No 'MACOSX_PLUGIN_SHORT_VERSION_STRING' set, but is required to build plugin bundles on macOS - example: '1.0.2'" + ) + endif() + + set(MACOSX_PLUGIN_BUNDLE_NAME "${target}" PARENT_SCOPE) + set(MACOSX_PLUGIN_BUNDLE_VERSION "${MACOSX_BUNDLE_BUNDLE_VERSION}" PARENT_SCOPE) + set(MACOSX_PLUGIN_SHORT_VERSION_STRING "${MACOSX_BUNDLE_SHORT_VERSION_STRING}" PARENT_SCOPE) + set(MACOSX_PLUGIN_EXECUTABLE_NAME "${target}" PARENT_SCOPE) + + if("${MACOSX_PLUGIN_BUNDLE_TYPE}" STREQUAL "BNDL") + message(STATUS "Bundle type plugin") + + install( + TARGETS ${target} + LIBRARY DESTINATION "." + COMPONENT obs_plugins + NAMELINK_COMPONENT ${target}_Development) + + set_target_properties( + ${target} + PROPERTIES + BUNDLE ON + BUNDLE_EXTENSION "plugin" + OUTPUT_NAME ${target} + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bundle/macOS/Plugin-Info.plist.in" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${MACOSX_PLUGIN_GUI_IDENTIFIER}" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "${OBS_BUNDLE_CODESIGN_IDENTITY}" + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bundle/macOS/entitlements.plist") + + install_bundle_resources(${target}) + + set(FIRST_DIR_SUFFIX ".plugin" PARENT_SCOPE) + else() + message(STATUS "Old type plugin") + + install( + TARGETS ${target} + LIBRARY DESTINATION "${target}/bin/" + COMPONENT obs_plugins + NAMELINK_COMPONENT ${target}_Development) + + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) + install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ + DESTINATION "${target}/data/" + USE_SOURCE_PERMISSIONS + COMPONENT obs_plugins) + endif() + set(FIRST_DIR_SUFFIX "" PARENT_SCOPE) + endif() + + endfunction() + + function(install_bundle_resources target) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) + file(GLOB_RECURSE _DATA_FILES "${CMAKE_CURRENT_SOURCE_DIR}/data/*") + foreach(_DATA_FILE IN LISTS _DATA_FILES) + file(RELATIVE_PATH _RELATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/data/ + ${_DATA_FILE}) + get_filename_component(_RELATIVE_PATH ${_RELATIVE_PATH} PATH) + target_sources(${target} PRIVATE ${_DATA_FILE}) + set_source_files_properties( + ${_DATA_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION + Resources/${_RELATIVE_PATH}) + string(REPLACE "\\" "\\\\" _GROUP_NAME "${_RELATIVE_PATH}") + source_group("Resources\\${_GROUP_NAME}" FILES ${_DATA_FILE}) + endforeach() + endif() + endfunction() + +else() + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_ARCH_SUFFIX 64) + else() + set(_ARCH_SUFFIX 32) + endif() + set(OBS_OUTPUT_DIR ${CMAKE_BINARY_DIR}/rundir) + + if(OS_POSIX) + option(LINUX_PORTABLE "Build portable version (Linux)" ON) + option(LINUX_RPATH "Set runpath (Linux)" ON) + if(NOT LINUX_PORTABLE) + set(OBS_LIBRARY_DESTINATION ${CMAKE_INSTALL_LIBDIR}) + set(OBS_PLUGIN_DESTINATION ${OBS_LIBRARY_DESTINATION}/obs-plugins) + if (LINUX_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib) + endif() + set(OBS_DATA_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/obs) + else() + set(OBS_LIBRARY_DESTINATION bin/${_ARCH_SUFFIX}bit) + set(OBS_PLUGIN_DESTINATION obs-plugins/${_ARCH_SUFFIX}bit) + if (LINUX_RPATH) + set(CMAKE_INSTALL_RPATH "$ORIGIN/" "${CMAKE_INSTALL_PREFIX}/${OBS_LIBRARY_DESTINATION}") + endif() + set(OBS_DATA_DESTINATION "data") + endif() + + if(OS_LINUX) + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${LINUX_MAINTAINER_EMAIL}") + set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") + set(PKG_SUFFIX "-linux-x86_64" CACHE STRING "Suffix of package name") + set(CPACK_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}${PKG_SUFFIX}") + + set(CPACK_GENERATOR "DEB") + + if(NOT LINUX_PORTABLE) + set(CPACK_SET_DESTDIR ON) + endif() + include(CPack) + endif() + else() + set(OBS_LIBRARY_DESTINATION "bin/${_ARCH_SUFFIX}bit") + set(OBS_LIBRARY32_DESTINATION "bin/32bit") + set(OBS_LIBRARY64_DESTINATION "bin/64bit") + set(OBS_PLUGIN_DESTINATION "obs-plugins/${_ARCH_SUFFIX}bit") + set(OBS_PLUGIN32_DESTINATION "obs-plugins/32bit") + set(OBS_PLUGIN64_DESTINATION "obs-plugins/64bit") + + set(OBS_DATA_DESTINATION "data") + endif() + + function(setup_plugin_target target) + set_target_properties(${target} PROPERTIES PREFIX "") + + install( + TARGETS ${target} + RUNTIME DESTINATION "${OBS_PLUGIN_DESTINATION}" + COMPONENT ${target}_Runtime + LIBRARY DESTINATION "${OBS_PLUGIN_DESTINATION}" + COMPONENT ${target}_Runtime + NAMELINK_COMPONENT ${target}_Development) + + install( + FILES $ + DESTINATION $/${OBS_PLUGIN_DESTINATION} + COMPONENT obs_rundir + EXCLUDE_FROM_ALL) + + if(OS_WINDOWS) + install( + FILES $ + CONFIGURATIONS "RelWithDebInfo" "Debug" + DESTINATION ${OBS_PLUGIN_DESTINATION} + COMPONENT ${target}_Runtime + OPTIONAL) + + install( + FILES $ + CONFIGURATIONS "RelWithDebInfo" "Debug" + DESTINATION $/${OBS_PLUGIN_DESTINATION} + COMPONENT obs_rundir + OPTIONAL EXCLUDE_FROM_ALL) + endif() + + if(MSVC) + target_link_options( + ${target} + PRIVATE + "LINKER:/OPT:REF" + "$<$>:LINKER\:/SAFESEH\:NO>" + "$<$:LINKER\:/INCREMENTAL:NO>" + "$<$:LINKER\:/INCREMENTAL:NO>") + endif() + + setup_target_resources(${target} obs-plugins/${target}) + + if(OS_WINDOWS) + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND + "${CMAKE_COMMAND}" -DCMAKE_INSTALL_PREFIX=${OBS_OUTPUT_DIR} + -DCMAKE_INSTALL_COMPONENT=obs_rundir + -DCMAKE_INSTALL_CONFIG_NAME=$ -P + ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake + COMMENT "Installing to plugin rundir" + VERBATIM) + endif() + endfunction() + + function(setup_target_resources target destination) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) + install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ + DESTINATION ${OBS_DATA_DESTINATION}/${destination} + USE_SOURCE_PERMISSIONS + COMPONENT obs_plugins) + + install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data + DESTINATION $/${OBS_DATA_DESTINATION}/${destination} + USE_SOURCE_PERMISSIONS + COMPONENT obs_rundir + EXCLUDE_FROM_ALL) + endif() + endfunction() +endif() diff --git a/cmake/bundle/macos/Plugin-Info.plist.in b/cmake/bundle/macos/Plugin-Info.plist.in new file mode 100644 index 0000000..0bd299c --- /dev/null +++ b/cmake/bundle/macos/Plugin-Info.plist.in @@ -0,0 +1,26 @@ + + + + + CFBundleName + ${MACOSX_PLUGIN_BUNDLE_NAME} + CFBundleIdentifier + ${MACOSX_PLUGIN_GUI_IDENTIFIER} + CFBundleVersion + ${MACOSX_PLUGIN_BUNDLE_VERSION} + CFBundleShortVersionString + ${MACOSX_PLUGIN_SHORT_VERSION_STRING} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleExecutable + ${MACOSX_PLUGIN_EXECUTABLE_NAME} + CFBundlePackageType + ${MACOSX_PLUGIN_BUNDLE_TYPE} + CFBundleSupportedPlatforms + + MacOSX + + LSMinimumSystemVersion + 10.13 + + diff --git a/cmake/bundle/macos/entitlements.plist b/cmake/bundle/macos/entitlements.plist new file mode 100644 index 0000000..516d0d5 --- /dev/null +++ b/cmake/bundle/macos/entitlements.plist @@ -0,0 +1,17 @@ + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.device.camera + + com.apple.security.device.audio-input + + com.apple.security.cs.disable-library-validation + + + com.apple.security.cs.allow-dyld-environment-variables + + + diff --git a/cmake/installer/installer-Windows.iss.in b/cmake/installer/installer-Windows.iss.in new file mode 100644 index 0000000..9f6f9a1 --- /dev/null +++ b/cmake/installer/installer-Windows.iss.in @@ -0,0 +1,62 @@ +#define MyAppName "@PROJECT_NAME@" +#define MyAppVersion "@PROJECT_VERSION@" +#define MyAppPublisher "@PLUGIN_AUTHOR@" +#define MyAppURL "@PLUGIN_URL@" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{CD703FE5-1F2C-4837-BD3D-DD840D83C3E3} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={code:GetDirName} +DefaultGroupName={#MyAppName} +OutputBaseFilename={#MyAppName}-{#MyAppVersion}-Windows-Installer +Compression=lzma +SolidCompression=yes + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Files] +Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "..\LICENSE"; Flags: dontcopy +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}" +Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" + +[Code] +procedure InitializeWizard(); +var + GPLText: AnsiString; + Page: TOutputMsgMemoWizardPage; +begin + ExtractTemporaryFile('LICENSE'); + LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText); + Page := CreateOutputMsgMemoPage(wpWelcome, + 'License Information', 'Please review the license terms before installing {#MyAppName}', + 'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.', + String(GPLText) + ); +end; + +// credit where it's due : +// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45 +function GetDirName(Value: string): string; +var + InstallPath: string; +begin + // initialize default path, which will be returned when the following registry + // key queries fail due to missing keys or for some different reason + Result := '{pf}\obs-studio'; + // query the first registry value; if this succeeds, return the obtained value + if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then + Result := InstallPath +end; diff --git a/cmake/installer/installer-macOS.pkgproj.in b/cmake/installer/installer-macOS.pkgproj.in new file mode 100644 index 0000000..e4b64d7 --- /dev/null +++ b/cmake/installer/installer-macOS.pkgproj.in @@ -0,0 +1,920 @@ + + + + + PACKAGES + + + MUST-CLOSE-APPLICATION-ITEMS + + MUST-CLOSE-APPLICATIONS + + PACKAGE_FILES + + DEFAULT_INSTALL_LOCATION + / + HIERARCHY + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Applications + PATH_TYPE + 0 + PERMISSIONS + 509 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + BUNDLE_CAN_DOWNGRADE + + BUNDLE_POSTINSTALL_PATH + + PATH_TYPE + 0 + + BUNDLE_PREINSTALL_PATH + + PATH_TYPE + 0 + + CHILDREN + + GID + 80 + PATH + ../@RELATIVE_INSTALL_PATH@/@PROJECT_NAME@@FIRST_DIR_SUFFIX@ + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + GID + 80 + PATH + plugins + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + GID + 80 + PATH + obs-studio + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + GID + 80 + PATH + Application Support + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Automator + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Documentation + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Extensions + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Filesystems + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Frameworks + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Input Methods + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Internet Plug-Ins + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchAgents + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchDaemons + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PreferencePanes + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Preferences + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Printers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PrivilegedHelperTools + PATH_TYPE + 0 + PERMISSIONS + 1005 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickLook + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickTime + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Screen Savers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Scripts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Services + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Widgets + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Shared + PATH_TYPE + 0 + PERMISSIONS + 1023 + TYPE + 1 + UID + 0 + + + GID + 80 + PATH + Users + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + / + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + PAYLOAD_TYPE + 0 + PRESERVE_EXTENDED_ATTRIBUTES + + SHOW_INVISIBLE + + SPLIT_FORKS + + TREAT_MISSING_FILES_AS_WARNING + + VERSION + 5 + + PACKAGE_SCRIPTS + + POSTINSTALL_PATH + + PATH_TYPE + 0 + + PREINSTALL_PATH + + PATH_TYPE + 0 + + RESOURCES + + + PACKAGE_SETTINGS + + AUTHENTICATION + 0 + CONCLUSION_ACTION + 0 + FOLLOW_SYMBOLIC_LINKS + + IDENTIFIER + @MACOS_BUNDLEID@ + LOCATION + 0 + NAME + @PROJECT_NAME@ + OVERWRITE_PERMISSIONS + + PAYLOAD_SIZE + -1 + REFERENCE_PATH + + RELOCATABLE + + USE_HFS+_COMPRESSION + + VERSION + @PROJECT_VERSION@ + + TYPE + 0 + UUID + @MACOS_PACKAGE_UUID@ + + + PROJECT + + PROJECT_COMMENTS + + NOTES + + + + PROJECT_PRESENTATION + + BACKGROUND + + APPAREANCES + + DARK_AQUA + + LIGHT_AQUA + + + SHARED_SETTINGS_FOR_ALL_APPAREANCES + + + INSTALLATION TYPE + + HIERARCHIES + + INSTALLER + + LIST + + + CHILDREN + + DESCRIPTION + + OPTIONS + + HIDDEN + + STATE + 1 + + PACKAGE_UUID + @MACOS_PACKAGE_UUID@ + TITLE + + TYPE + 0 + UUID + @MACOS_INSTALLER_UUID@ + + + REMOVED + + + + MODE + 0 + + INSTALLATION_STEPS + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewIntroductionController + INSTALLER_PLUGIN + Introduction + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewReadMeController + INSTALLER_PLUGIN + ReadMe + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewLicenseController + INSTALLER_PLUGIN + License + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewDestinationSelectController + INSTALLER_PLUGIN + TargetSelect + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewInstallationTypeController + INSTALLER_PLUGIN + PackageSelection + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewInstallationController + INSTALLER_PLUGIN + Install + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewSummaryController + INSTALLER_PLUGIN + Summary + LIST_TITLE_KEY + InstallerSectionTitle + + + INTRODUCTION + + LOCALIZATIONS + + + LICENSE + + LOCALIZATIONS + + MODE + 0 + + README + + LOCALIZATIONS + + + SUMMARY + + LOCALIZATIONS + + + TITLE + + LOCALIZATIONS + + + + PROJECT_REQUIREMENTS + + LIST + + + BEHAVIOR + 3 + DICTIONARY + + IC_REQUIREMENT_OS_DISK_TYPE + 1 + IC_REQUIREMENT_OS_DISTRIBUTION_TYPE + 0 + IC_REQUIREMENT_OS_MINIMUM_VERSION + 101300 + + IC_REQUIREMENT_CHECK_TYPE + 0 + IDENTIFIER + fr.whitebox.Packages.requirement.os + MESSAGE + + NAME + Operating System + STATE + + + + RESOURCES + + ROOT_VOLUME_ONLY + + + PROJECT_SETTINGS + + ADVANCED_OPTIONS + + installer-script.domains:enable_currentUserHome + 1 + + BUILD_FORMAT + 0 + BUILD_PATH + + PATH + ../@RELATIVE_BUILD_PATH@ + PATH_TYPE + 1 + + EXCLUDED_FILES + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .DS_Store + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .DS_Store files + PROXY_TOOLTIP + Remove ".DS_Store" files created by the Finder. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .pbdevelopment + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .pbdevelopment files + PROXY_TOOLTIP + Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + CVS + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .cvsignore + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .cvspass + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .svn + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .git + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .gitignore + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove SCM metadata + PROXY_TOOLTIP + Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + classes.nib + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + designable.db + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + info.nib + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Optimize nib files + PROXY_TOOLTIP + Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + Resources Disabled + TYPE + 1 + + + PROTECTED + + PROXY_NAME + Remove Resources Disabled folders + PROXY_TOOLTIP + Remove "Resources Disabled" folders. + STATE + + + + SEPARATOR + + + + NAME + @PROJECT_NAME@@PKG_SUFFIX@ + PAYLOAD_ONLY + + TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING + + + + TYPE + 0 + VERSION + 2 + + diff --git a/image-reaction.c b/image-reaction.c deleted file mode 100644 index ba15f98..0000000 --- a/image-reaction.c +++ /dev/null @@ -1,526 +0,0 @@ -// -// Created by scaled -// -// Based on image-source.c from OBS Studio: https://github.com/obsproject/obs-studio -// Also included some code from Spectralizer plugin: https://github.com/univrsal/spectralizer -// - -#include -#include -#include -#include -#include -#include - -#define blog(log_level, format, ...) \ - blog(log_level, "[image_reaction_source: '%s'] " format, \ - obs_source_get_name(context->source), ##__VA_ARGS__) - -#define debug(format, ...) blog(LOG_DEBUG, format, ##__VA_ARGS__) -#define info(format, ...) blog(LOG_INFO, format, ##__VA_ARGS__) -#define warn(format, ...) blog(LOG_WARNING, format, ##__VA_ARGS__) - -struct image_reaction_source { - obs_source_t *source; - char source_name[255]; - - char *file1; - char *file2; - bool persistent; - bool linear_alpha; - bool active; - - gs_image_file3_t if31; - gs_image_file3_t if32; - - obs_weak_source_t *audio_source; - - bool loud; - float threshold; - float smoothness; - float average; - - uint64_t last_time; - uint64_t capture_check_time; - - bool animReset1; - bool animReset2; - bool loudOld; - bool animResetTrigger; -}; - -/*int MAX(int a, int b) { - return a > b ? a : b; -}*/ - -#define MIN(a,b) ((a)<(b) ? (a):(b)) -#define MAX(a,b) ((a)>(b) ? (a):(b)) - -static const char *image_reaction_source_get_name(void *unused) -{ - UNUSED_PARAMETER(unused); - return obs_module_text("ImageReactionSource"); -} - -static void image_reaction_source_load(struct image_reaction_source *context) -{ - for (int i = 0; i <=1; i++) { - char *file = i == 0 ? context->file1 : context->file2; - gs_image_file3_t *if3 = i == 0 ? &context->if31 : &context->if32; - - obs_enter_graphics(); - gs_image_file3_free(if3); - obs_leave_graphics(); - - if (file && *file) { - debug("loading texture '%s'", file); - gs_image_file3_init(if3, file, - context->linear_alpha - ? GS_IMAGE_ALPHA_PREMULTIPLY_SRGB - : GS_IMAGE_ALPHA_PREMULTIPLY); - - obs_enter_graphics(); - gs_image_file3_init_texture(if3); - obs_leave_graphics(); - - if (!if3->image2.image.loaded) - warn("failed to load texture '%s'", file); - } - } -} - -static void image_reaction_source_unload(struct image_reaction_source *context) -{ - obs_enter_graphics(); - gs_image_file3_free(&context->if31); - gs_image_file3_free(&context->if32); - obs_leave_graphics(); -} - -static void audio_capture(void *param, obs_source_t *src, const struct audio_data *data, bool muted) -{ - struct image_reaction_source *context = param; - - if (muted) { - context->average = 0; - } - else - { - uint32_t samplesCount = data->frames; - float* samples = (float*)data->data[0]; - - float averageLocal = 0.0f; - - for (uint32_t i = 0; i < samplesCount; i++) { - averageLocal += fabs(samples[i]) / samplesCount; - } - - context->average += context->smoothness * (averageLocal - context->average); - } - - context->loudOld = context->loud; - context->loud = context->average > context->threshold; - - if (context->loud != context->loudOld) - context->animResetTrigger = true; -} - -static void image_reaction_source_update(void *data, obs_data_t *settings) -{ - struct image_reaction_source *context = data; - const char *file1 = obs_data_get_string(settings, "file1"); - const char *file2 = obs_data_get_string(settings, "file2"); - const bool anim_reset_1 = obs_data_get_bool(settings, "anim_reset_1"); - const bool anim_reset_2 = obs_data_get_bool(settings, "anim_reset_2"); - const bool unload = obs_data_get_bool(settings, "unload"); - const bool linear_alpha = obs_data_get_bool(settings, "linear_alpha"); - const double threshold = obs_data_get_double(settings, "threshold"); - const double smoothness = obs_data_get_double(settings, "smoothness"); - - if (context->file1) - bfree(context->file1); - context->file1 = bstrdup(file1); - - if (context->file2) - bfree(context->file2); - context->file2 = bstrdup(file2); - - context->animReset1 = anim_reset_1; - context->animReset2 = anim_reset_2; - - context->persistent = !unload; - context->linear_alpha = linear_alpha; - context->threshold = db_to_mul(threshold); - context->smoothness = pow(0.1, smoothness); - - /* Load the image if the source is persistent or showing */ - if (context->persistent || obs_source_showing(context->source)) - image_reaction_source_load(data); - else - image_reaction_source_unload(data); - - const char* cfg_source_name = obs_data_get_string(settings, "audio_source"); - - obs_weak_source_t *old = NULL; - - if (cfg_source_name[0] == '\0') { - if (context->audio_source) { - old = context->audio_source; - context->audio_source = NULL; - } - context->source_name[0] = '\0'; - } - else { - if (context->source_name[0] == '\0' || strcmp(context->source_name, cfg_source_name) != 0) { - if (context->audio_source) { - old = context->audio_source; - context->audio_source = NULL; - } - strcpy(context->source_name, cfg_source_name); - context->capture_check_time = os_gettime_ns() - 3000000000; - } - } - - if (old) { - obs_source_t *old_source = obs_weak_source_get_source(old); - if (old_source) { - info("Removed audio capture from '%s'", obs_source_get_name(old_source)); - obs_source_remove_audio_capture_callback(old_source, audio_capture, context); - obs_source_release(old_source); - } - obs_weak_source_release(old); - } -} - -static void image_reaction_source_defaults(obs_data_t *settings) -{ - obs_data_set_default_bool(settings, "unload", false); - obs_data_set_default_bool(settings, "linear_alpha", false); - obs_data_set_default_string(settings, "audio_source", ""); - obs_data_set_default_double(settings, "threshold", -40.0f); - obs_data_set_default_double(settings, "smoothness", 1.0f); -} - -static void image_reaction_source_show(void *data) -{ - struct image_reaction_source *context = data; - - if (!context->persistent) - image_reaction_source_load(context); -} - -static void image_reaction_source_hide(void *data) -{ - struct image_reaction_source *context = data; - - if (!context->persistent) - image_reaction_source_unload(context); -} - -static void *image_reaction_source_create(obs_data_t *settings, obs_source_t *source) -{ - struct image_reaction_source *context = bzalloc(sizeof(struct image_reaction_source)); - context->source = source; - - context->source_name[0] = '\0'; - context->loud = false; - - image_reaction_source_update(context, settings); - return context; -} - -static void image_reaction_source_destroy(void *data) -{ - struct image_reaction_source *context = data; - - image_reaction_source_unload(context); - - if (context->file1) - bfree(context->file1); - - if (context->file2) - bfree(context->file2); - - /*if (context->audio_source) { - //obs_source_t *source = obs_weak_source_get_source(context->audio_source); - //if (source) { - info("Removed audio capture from '%s'", obs_source_get_name(context->audio_source)); - obs_source_remove_audio_capture_callback(context->audio_source, audio_capture, context); - //obs_source_release(source); - //} - //obs_weak_source_release(context->audio_source); - }*/ - if (context->audio_source) { - obs_source_t *source = obs_weak_source_get_source(context->audio_source); - if (source) { - info("Removed audio capture from '%s'", obs_source_get_name(source)); - obs_source_remove_audio_capture_callback(source, audio_capture, context); - obs_source_release(source); - } - obs_weak_source_release(context->audio_source); - } - - bfree(context); -} - -static uint32_t image_reaction_source_getwidth(void *data) -{ - struct image_reaction_source *context = data; - return MAX(context->if31.image2.image.cx, context->if32.image2.image.cx); -} - -static uint32_t image_reaction_source_getheight(void *data) -{ - struct image_reaction_source *context = data; - return MAX(context->if31.image2.image.cy, context->if32.image2.image.cy); -} - -static void image_reaction_source_render(void *data, gs_effect_t *effect) -{ - struct image_reaction_source *context = data; - - const bool previous = gs_framebuffer_srgb_enabled(); - gs_enable_framebuffer_srgb(true); - gs_blend_state_push(); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); - - gs_image_file3_t *if3 = context->loud ? &context->if32 : &context->if31; - if (if3->image2.image.texture) - { - gs_eparam_t *const param = gs_effect_get_param_by_name(effect, "image"); - gs_effect_set_texture_srgb(param, if3->image2.image.texture); - - gs_draw_sprite(if3->image2.image.texture, 0, - if3->image2.image.cx, - if3->image2.image.cy); - } - //context->loud = false; - - gs_blend_state_pop(); - - gs_enable_framebuffer_srgb(previous); -} - -static void image_reaction_tick(void *data, float seconds) -{ - struct image_reaction_source *context = data; - - - // Update / refresh audio capturing - char* new_name = NULL; - if (context->source_name[0] != '\0' && !context->audio_source) { - uint64_t t = os_gettime_ns(); - - if (t - context->capture_check_time > 3000000000) { - new_name = context->source_name; - context->capture_check_time = t; - } - } - - if (new_name != NULL) { - obs_source_t *capture = obs_get_source_by_name(new_name); - obs_weak_source_t *weak_capture = capture ? obs_source_get_weak_source(capture) : NULL; - - if (context->source_name[0] != '\0' && new_name == context->source_name) { - context->audio_source = weak_capture; - weak_capture = NULL; - } - - if (capture) { - info("Added audio capture to '%s'", obs_source_get_name(capture)); - obs_source_add_audio_capture_callback(capture, audio_capture, context); - obs_weak_source_release(weak_capture); - obs_source_release(capture); - } - } - - // update GIF's - uint64_t frame_time = obs_get_video_frame_time(); - if (obs_source_active(context->source)) { - if (!context->active) { - if (context->if31.image2.image.is_animated_gif || context->if32.image2.image.is_animated_gif) - context->last_time = frame_time; - context->active = true; - } - - } else { - if (context->active) { - for (int i = 0; i <=1; i++) { - gs_image_file3_t *if3 = i == 0 ? &context->if31 : &context->if32; - if (if3->image2.image.is_animated_gif) { - if3->image2.image.cur_frame = 0; - if3->image2.image.cur_loop = 0; - if3->image2.image.cur_time = 0; - - obs_enter_graphics(); - gs_image_file3_update_texture(if3); - obs_leave_graphics(); - } - } - - context->active = false; - } - } - - for (int i = 0; i <=1; i++) { - gs_image_file3_t *if3 = i == 0 ? &context->if31 : &context->if32; - bool animReset = i == 0 ? context->animReset1 : context->animReset2; - - - if (context->last_time && if3->image2.image.is_animated_gif) { - if (animReset && context->animResetTrigger) { - if3->image2.image.cur_frame = 0; - if3->image2.image.cur_loop = 0; - if3->image2.image.cur_time = 0; - - obs_enter_graphics(); - gs_image_file3_update_texture(if3); - obs_leave_graphics(); - } - else { - uint64_t elapsed = frame_time - context->last_time; - bool updated = gs_image_file3_tick(if3, elapsed); - - if (updated) { - obs_enter_graphics(); - gs_image_file3_update_texture(if3); - obs_leave_graphics(); - } - } - } - } - context->animResetTrigger = false; - - context->last_time = frame_time; -} - -static const char *image_filter = - "All formats (*.bmp *.tga *.png *.jpeg *.jpg *.gif *.psd *.webp);;" - "BMP Files (*.bmp);;" - "Targa Files (*.tga);;" - "PNG Files (*.png);;" - "JPEG Files (*.jpeg *.jpg);;" - "GIF Files (*.gif);;" - "PSD Files (*.psd);;" - "WebP Files (*.webp);;" - "All Files (*.*)"; - -static bool add_source(void* param, obs_source_t* src) -{ - obs_property_t *list = param; - - uint32_t caps = obs_source_get_output_flags(src); - - if ((caps & OBS_SOURCE_AUDIO) == 0) - return true; - const char *name = obs_source_get_name(src); - obs_property_list_add_string(list, name, name); - return true; -} - -static bool source_changed(obs_properties_t *props, obs_property_t * prop, obs_data_t *data) -{ - obs_data_get_string(data, "audio_source"); - return true; -} - -static obs_properties_t *image_reaction_source_properties(void *data) -{ - struct image_reaction_source *s = data; - struct dstr path = {0}; - - obs_properties_t *props = obs_properties_create(); - - if (s && s->file1 && *s->file1) { - const char *slash; - - dstr_copy(&path, s->file1); - dstr_replace(&path, "\\", "/"); - slash = strrchr(path.array, '/'); - if (slash) - dstr_resize(&path, slash - path.array + 1); - } - - obs_properties_add_path(props, "file1", obs_module_text("Reaction1"), - OBS_PATH_FILE, image_filter, path.array); - obs_properties_add_bool(props, "anim_reset_1", - obs_module_text("AnimReset1")); - obs_properties_add_path(props, "file2", obs_module_text("Reaction2"), - OBS_PATH_FILE, image_filter, path.array); - obs_properties_add_bool(props, "anim_reset_2", - obs_module_text("AnimReset2")); - dstr_free(&path); - - obs_properties_add_bool(props, "unload", - obs_module_text("UnloadWhenNotShowing")); - obs_properties_add_bool(props, "linear_alpha", - obs_module_text("LinearAlpha")); - obs_property_t* sources_list = obs_properties_add_list(props, "audio_source", - obs_module_text("AudioSource"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); - obs_property_list_add_string(sources_list, "", ""); - - obs_property_t *p = obs_properties_add_float_slider(props, "threshold", - obs_module_text("Threshold"), -60.0, 0.0, 0.1); - obs_property_float_set_suffix(p, " dB"); - - obs_properties_add_float_slider(props, "smoothness", - obs_module_text("Smoothness"), 0.0, 5.0, 0.1); - - //obs_property_set_modified_callback(src, source_changed); - obs_enum_sources(add_source, sources_list); - - return props; -} - -uint64_t image_reaction_source_get_memory_usage(void *data) -{ - struct image_reaction_source *s = data; - return s->if31.image2.mem_usage + s->if32.image2.mem_usage; -} - -static void missing_file_callback(void *src, const char *new_path, void *data) -{ - struct image_reaction_source *s = src; - - obs_source_t *source = s->source; - obs_data_t *settings = obs_source_get_settings(source); - obs_data_set_string(settings, "file", new_path); - obs_source_update(source, settings); - obs_data_release(settings); - - UNUSED_PARAMETER(data); -} - -static struct obs_source_info image_reaction_source_info = { - .id = "image_reaction_source", - .type = OBS_SOURCE_TYPE_INPUT, - .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB, - .get_name = image_reaction_source_get_name, - .create = image_reaction_source_create, - .destroy = image_reaction_source_destroy, - .update = image_reaction_source_update, - .get_defaults = image_reaction_source_defaults, - .show = image_reaction_source_show, - .hide = image_reaction_source_hide, - .get_width = image_reaction_source_getwidth, - .get_height = image_reaction_source_getheight, - .video_render = image_reaction_source_render, - .video_tick = image_reaction_tick, - .get_properties = image_reaction_source_properties, - .icon_type = OBS_ICON_TYPE_IMAGE, -}; - -OBS_DECLARE_MODULE() -OBS_MODULE_USE_DEFAULT_LOCALE("image-reaction", "en-US") -MODULE_EXPORT const char *obs_module_description(void) -{ - return "Image reaction source"; -} - -extern struct obs_source_info slideshow_info; - -bool obs_module_load(void) -{ - obs_register_source(&image_reaction_source_info); - return true; -} diff --git a/src/image-reaction.c b/src/image-reaction.c new file mode 100644 index 0000000..1819358 --- /dev/null +++ b/src/image-reaction.c @@ -0,0 +1,469 @@ +// +// Created by scaled +// +// Based on image-source.c from OBS Studio: +// https://github.com/obsproject/obs-studio Also included some code from +// Spectralizer plugin: https://github.com/univrsal/spectralizer +// + +#include +#include +#include +#include +#include +#include + +#define blog(log_level, format, ...) \ + blog(log_level, "[image_reaction_source: '%s'] " format, obs_source_get_name(context->source), ##__VA_ARGS__) + +#define debug(format, ...) blog(LOG_DEBUG, format, ##__VA_ARGS__) +#define info(format, ...) blog(LOG_INFO, format, ##__VA_ARGS__) +#define warn(format, ...) blog(LOG_WARNING, format, ##__VA_ARGS__) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +struct image_reaction_source { + obs_source_t* source; + char source_name[255]; + + char* file1; + char* file2; + bool persistent; + bool linear_alpha; + bool active; + + gs_image_file3_t if31; + gs_image_file3_t if32; + + obs_weak_source_t* audio_source; + + bool loud; + float threshold; + float smoothness; + float average; + + uint64_t last_time; + uint64_t capture_check_time; + + bool animReset1; + bool animReset2; + bool loudOld; + bool animResetTrigger; +}; + +static const char* image_reaction_source_get_name(void* unused) { + UNUSED_PARAMETER(unused); + return obs_module_text("ImageReactionSource"); +} + +static void image_reaction_source_load(struct image_reaction_source* context) { + for (int i = 0; i <= 1; i++) { + char* file = i == 0 ? context->file1 : context->file2; + gs_image_file3_t* if3 = i == 0 ? &context->if31 : &context->if32; + + obs_enter_graphics(); + gs_image_file3_free(if3); + obs_leave_graphics(); + + if (file && *file) { + debug("loading texture '%s'", file); + gs_image_file3_init(if3, file, + context->linear_alpha ? GS_IMAGE_ALPHA_PREMULTIPLY_SRGB : GS_IMAGE_ALPHA_PREMULTIPLY); + + obs_enter_graphics(); + gs_image_file3_init_texture(if3); + obs_leave_graphics(); + + if (!if3->image2.image.loaded) + warn("failed to load texture '%s'", file); + } + } +} + +static void image_reaction_source_unload(struct image_reaction_source* context) { + obs_enter_graphics(); + gs_image_file3_free(&context->if31); + gs_image_file3_free(&context->if32); + obs_leave_graphics(); +} + +static void audio_capture(void* param, obs_source_t* src, const struct audio_data* data, bool muted) { + struct image_reaction_source* context = param; + + if (muted) { + context->average = 0; + } else { + uint32_t samplesCount = data->frames; + float* samples = (float*)data->data[0]; + + float averageLocal = 0.0f; + + for (uint32_t i = 0; i < samplesCount; i++) { + averageLocal += fabs(samples[i]) / samplesCount; + } + + context->average += context->smoothness * (averageLocal - context->average); + } + + context->loudOld = context->loud; + context->loud = context->average > context->threshold; + + if (context->loud != context->loudOld) + context->animResetTrigger = true; +} + +static void image_reaction_source_update(void* data, obs_data_t* settings) { + struct image_reaction_source* context = data; + const char* file1 = obs_data_get_string(settings, "file1"); + const char* file2 = obs_data_get_string(settings, "file2"); + const bool anim_reset_1 = obs_data_get_bool(settings, "anim_reset_1"); + const bool anim_reset_2 = obs_data_get_bool(settings, "anim_reset_2"); + const bool unload = obs_data_get_bool(settings, "unload"); + const bool linear_alpha = obs_data_get_bool(settings, "linear_alpha"); + const double threshold = obs_data_get_double(settings, "threshold"); + const double smoothness = obs_data_get_double(settings, "smoothness"); + + if (context->file1) + bfree(context->file1); + context->file1 = bstrdup(file1); + + if (context->file2) + bfree(context->file2); + context->file2 = bstrdup(file2); + + context->animReset1 = anim_reset_1; + context->animReset2 = anim_reset_2; + + context->persistent = !unload; + context->linear_alpha = linear_alpha; + context->threshold = db_to_mul(threshold); + context->smoothness = pow(0.1, smoothness); + + /* Load the image if the source is persistent or showing */ + if (context->persistent || obs_source_showing(context->source)) + image_reaction_source_load(data); + else + image_reaction_source_unload(data); + + const char* cfg_source_name = obs_data_get_string(settings, "audio_source"); + + obs_weak_source_t* old = NULL; + + if (cfg_source_name[0] == '\0') { + if (context->audio_source) { + old = context->audio_source; + context->audio_source = NULL; + } + context->source_name[0] = '\0'; + } else { + if (context->source_name[0] == '\0' || strcmp(context->source_name, cfg_source_name) != 0) { + if (context->audio_source) { + old = context->audio_source; + context->audio_source = NULL; + } + strcpy(context->source_name, cfg_source_name); + context->capture_check_time = os_gettime_ns() - 3000000000; + } + } + + if (old) { + obs_source_t* old_source = obs_weak_source_get_source(old); + if (old_source) { + info("Removed audio capture from '%s'", obs_source_get_name(old_source)); + obs_source_remove_audio_capture_callback(old_source, audio_capture, context); + obs_source_release(old_source); + } + obs_weak_source_release(old); + } +} + +static void image_reaction_source_defaults(obs_data_t* settings) { + obs_data_set_default_bool(settings, "unload", false); + obs_data_set_default_bool(settings, "linear_alpha", false); + obs_data_set_default_string(settings, "audio_source", ""); + obs_data_set_default_double(settings, "threshold", -40.0f); + obs_data_set_default_double(settings, "smoothness", 1.0f); +} + +static void image_reaction_source_show(void* data) { + struct image_reaction_source* context = data; + + if (!context->persistent) + image_reaction_source_load(context); +} + +static void image_reaction_source_hide(void* data) { + struct image_reaction_source* context = data; + + if (!context->persistent) + image_reaction_source_unload(context); +} + +static void* image_reaction_source_create(obs_data_t* settings, obs_source_t* source) { + struct image_reaction_source* context = bzalloc(sizeof(struct image_reaction_source)); + context->source = source; + + context->source_name[0] = '\0'; + context->loud = false; + + image_reaction_source_update(context, settings); + return context; +} + +static void image_reaction_source_destroy(void* data) { + struct image_reaction_source* context = data; + + image_reaction_source_unload(context); + + if (context->file1) + bfree(context->file1); + + if (context->file2) + bfree(context->file2); + + if (context->audio_source) { + obs_source_t* source = obs_weak_source_get_source(context->audio_source); + if (source) { + info("Removed audio capture from '%s'", obs_source_get_name(source)); + obs_source_remove_audio_capture_callback(source, audio_capture, context); + obs_source_release(source); + } + obs_weak_source_release(context->audio_source); + } + + bfree(context); +} + +static uint32_t image_reaction_source_getwidth(void* data) { + struct image_reaction_source* context = data; + return MAX(context->if31.image2.image.cx, context->if32.image2.image.cx); +} + +static uint32_t image_reaction_source_getheight(void* data) { + struct image_reaction_source* context = data; + return MAX(context->if31.image2.image.cy, context->if32.image2.image.cy); +} + +static void image_reaction_source_render(void* data, gs_effect_t* effect) { + struct image_reaction_source* context = data; + + const bool previous = gs_framebuffer_srgb_enabled(); + gs_enable_framebuffer_srgb(true); + gs_blend_state_push(); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); + + gs_image_file3_t* if3 = context->loud ? &context->if32 : &context->if31; + if (if3->image2.image.texture) { + gs_eparam_t* const param = gs_effect_get_param_by_name(effect, "image"); + gs_effect_set_texture_srgb(param, if3->image2.image.texture); + + gs_draw_sprite(if3->image2.image.texture, 0, if3->image2.image.cx, if3->image2.image.cy); + } + + gs_blend_state_pop(); + + gs_enable_framebuffer_srgb(previous); +} + +static void image_reaction_tick(void* data, float seconds) { + struct image_reaction_source* context = data; + + // Update / refresh audio capturing + char* new_name = NULL; + if (context->source_name[0] != '\0' && !context->audio_source) { + uint64_t t = os_gettime_ns(); + + if (t - context->capture_check_time > 3000000000) { + new_name = context->source_name; + context->capture_check_time = t; + } + } + + if (new_name != NULL) { + obs_source_t* capture = obs_get_source_by_name(new_name); + obs_weak_source_t* weak_capture = capture ? obs_source_get_weak_source(capture) : NULL; + + if (context->source_name[0] != '\0' && new_name == context->source_name) { + context->audio_source = weak_capture; + weak_capture = NULL; + } + + if (capture) { + info("Added audio capture to '%s'", obs_source_get_name(capture)); + obs_source_add_audio_capture_callback(capture, audio_capture, context); + obs_weak_source_release(weak_capture); + obs_source_release(capture); + } + } + + // update GIF's + uint64_t frame_time = obs_get_video_frame_time(); + if (obs_source_active(context->source)) { + if (!context->active) { + if (context->if31.image2.image.is_animated_gif || context->if32.image2.image.is_animated_gif) + context->last_time = frame_time; + context->active = true; + } + } else { + if (context->active) { + for (int i = 0; i <= 1; i++) { + gs_image_file3_t* if3 = i == 0 ? &context->if31 : &context->if32; + if (if3->image2.image.is_animated_gif) { + if3->image2.image.cur_frame = 0; + if3->image2.image.cur_loop = 0; + if3->image2.image.cur_time = 0; + + obs_enter_graphics(); + gs_image_file3_update_texture(if3); + obs_leave_graphics(); + } + } + + context->active = false; + } + } + + for (int i = 0; i <= 1; i++) { + gs_image_file3_t* if3 = i == 0 ? &context->if31 : &context->if32; + bool animReset = i == 0 ? context->animReset1 : context->animReset2; + + if (context->last_time && if3->image2.image.is_animated_gif) { + if (animReset && context->animResetTrigger) { + if3->image2.image.cur_frame = 0; + if3->image2.image.cur_loop = 0; + if3->image2.image.cur_time = 0; + + obs_enter_graphics(); + gs_image_file3_update_texture(if3); + obs_leave_graphics(); + } else { + uint64_t elapsed = frame_time - context->last_time; + bool updated = gs_image_file3_tick(if3, elapsed); + + if (updated) { + obs_enter_graphics(); + gs_image_file3_update_texture(if3); + obs_leave_graphics(); + } + } + } + } + context->animResetTrigger = false; + + context->last_time = frame_time; +} + +static const char* image_filter = "All formats (*.bmp *.tga *.png *.jpeg *.jpg *.gif *.psd *.webp);;" + "BMP Files (*.bmp);;" + "Targa Files (*.tga);;" + "PNG Files (*.png);;" + "JPEG Files (*.jpeg *.jpg);;" + "GIF Files (*.gif);;" + "PSD Files (*.psd);;" + "WebP Files (*.webp);;" + "All Files (*.*)"; + +static bool add_source(void* param, obs_source_t* src) { + obs_property_t* list = param; + + uint32_t caps = obs_source_get_output_flags(src); + + if ((caps & OBS_SOURCE_AUDIO) == 0) + return true; + const char* name = obs_source_get_name(src); + obs_property_list_add_string(list, name, name); + return true; +} + +static bool source_changed(obs_properties_t* props, obs_property_t* prop, obs_data_t* data) { + obs_data_get_string(data, "audio_source"); + return true; +} + +static obs_properties_t* image_reaction_source_properties(void* data) { + struct image_reaction_source* s = data; + struct dstr path = {0}; + + obs_properties_t* props = obs_properties_create(); + + if (s && s->file1 && *s->file1) { + const char* slash; + + dstr_copy(&path, s->file1); + dstr_replace(&path, "\\", "/"); + slash = strrchr(path.array, '/'); + if (slash) + dstr_resize(&path, slash - path.array + 1); + } + + obs_properties_add_path(props, "file1", obs_module_text("Reaction1"), OBS_PATH_FILE, image_filter, path.array); + obs_properties_add_bool(props, "anim_reset_1", obs_module_text("AnimReset1")); + obs_properties_add_path(props, "file2", obs_module_text("Reaction2"), OBS_PATH_FILE, image_filter, path.array); + obs_properties_add_bool(props, "anim_reset_2", obs_module_text("AnimReset2")); + dstr_free(&path); + + obs_properties_add_bool(props, "unload", obs_module_text("UnloadWhenNotShowing")); + obs_properties_add_bool(props, "linear_alpha", obs_module_text("LinearAlpha")); + obs_property_t* sources_list = obs_properties_add_list(props, "audio_source", obs_module_text("AudioSource"), + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(sources_list, "", ""); + + obs_property_t* p = + obs_properties_add_float_slider(props, "threshold", obs_module_text("Threshold"), -60.0, 0.0, 0.1); + obs_property_float_set_suffix(p, " dB"); + + obs_properties_add_float_slider(props, "smoothness", obs_module_text("Smoothness"), 0.0, 5.0, 0.1); + + // obs_property_set_modified_callback(src, source_changed); + obs_enum_sources(add_source, sources_list); + + return props; +} + +uint64_t image_reaction_source_get_memory_usage(void* data) { + struct image_reaction_source* s = data; + return s->if31.image2.mem_usage + s->if32.image2.mem_usage; +} + +static void missing_file_callback(void* src, const char* new_path, void* data) { + struct image_reaction_source* s = src; + + obs_source_t* source = s->source; + obs_data_t* settings = obs_source_get_settings(source); + obs_data_set_string(settings, "file", new_path); + obs_source_update(source, settings); + obs_data_release(settings); + + UNUSED_PARAMETER(data); +} + +static struct obs_source_info image_reaction_source_info = { + .id = "image_reaction_source", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB, + .get_name = image_reaction_source_get_name, + .create = image_reaction_source_create, + .destroy = image_reaction_source_destroy, + .update = image_reaction_source_update, + .get_defaults = image_reaction_source_defaults, + .show = image_reaction_source_show, + .hide = image_reaction_source_hide, + .get_width = image_reaction_source_getwidth, + .get_height = image_reaction_source_getheight, + .video_render = image_reaction_source_render, + .video_tick = image_reaction_tick, + .get_properties = image_reaction_source_properties, + .icon_type = OBS_ICON_TYPE_IMAGE, +}; + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("image-reaction", "en-US") +MODULE_EXPORT const char* obs_module_description(void) { return "Image reaction source"; } + +extern struct obs_source_info slideshow_info; + +bool obs_module_load(void) { + obs_register_source(&image_reaction_source_info); + return true; +}