From 95f9ea1b366fbf9c46ae0b5947709feeaac9758d Mon Sep 17 00:00:00 2001 From: Clayton Groeneveld Date: Wed, 28 Apr 2021 07:07:24 -0500 Subject: [PATCH] Version 1.0.0 --- .clang-format | 107 ++++ .github/FUNDING.yml | 1 + .github/workflows/main.yml | 362 +++++++++++++ .gitignore | 13 + CMakeLists.txt | 190 +++++++ LICENSE | 10 +- README.md | 3 + ci/ci_includes.cmd.in | 2 + ci/ci_includes.sh.in | 4 + data/locale/en-US.ini | 1 + external/FindLibObs.cmake | 107 ++++ formatcode.sh | 40 ++ installer/installer-Windows.iss.in | 63 +++ installer/installer-macOS.pkgproj.in | 726 +++++++++++++++++++++++++++ src/plugin-macros.h.in | 9 + src/plugin-main.c | 264 ++++++++++ 16 files changed, 1897 insertions(+), 5 deletions(-) create mode 100644 .clang-format create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 ci/ci_includes.cmd.in create mode 100644 ci/ci_includes.sh.in create mode 100644 data/locale/en-US.ini create mode 100644 external/FindLibObs.cmake create mode 100755 formatcode.sh create mode 100644 installer/installer-Windows.iss.in create mode 100644 installer/installer-macOS.pkgproj.in create mode 100644 src/plugin-macros.h.in create mode 100644 src/plugin-main.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c9dfc48 --- /dev/null +++ b/.clang-format @@ -0,0 +1,107 @@ +# please use clang-format version 8 or later + +Standard: Cpp11 +AccessModifierOffset: -8 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +#AllowAllArgumentsOnNextLine: false # requires clang-format 9 +#AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9 +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +#AllowShortLambdasOnASingleLine: Inline # requires clang-format 9 +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: false # apparently unpredictable +ColumnLimit: 80 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +FixNamespaceComments: false +ForEachMacros: + - 'json_object_foreach' + - 'json_object_foreach_safe' + - 'json_array_foreach' +IncludeBlocks: Preserve +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +#ObjCBinPackProtocolList: Auto # requires clang-format 7 +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +#SpaceAfterLogicalNot: false # requires clang-format 9 +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +#SpaceBeforeCtorInitializerColon: true # requires clang-format 7 +#SpaceBeforeInheritanceColon: true # requires clang-format 7 +SpaceBeforeParens: ControlStatements +#SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7 +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +#StatementMacros: # requires clang-format 8 +# - 'Q_OBJECT' +TabWidth: 8 +#TypenameMacros: # requires clang-format 9 +# - 'DARRAY' +UseTab: ForContinuationAndIndentation +--- +Language: ObjC diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..27e77f3 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ['https://paypal.me/claytong2121'] diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..84761fe --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,362 @@ +name: 'CI Plugin Build' + +on: + push: + paths-ignore: + - '**.md' + branches: + - main + tags: + - '*' + +env: + MACOS_SIGN_PLUGIN: false + OBS_VERSION: "26.1.2" + QT_VERSION: "5.15.2" + PLUGIN_NAME: "obs-decklink-output-filter" + PLUGIN_VERSION: "1.0.0" + +jobs: + ubuntu64: + name: 'Linux/Ubuntu 64-bit' + runs-on: [ubuntu-latest] + steps: + - name: 'Checkout' + uses: actions/checkout@v2.3.3 + with: + submodules: 'recursive' + - name: Install dependencies + if: success() + shell: bash + run: | + echo "NPROC=$(($(nproc)+1))" >> $GITHUB_ENV + sudo add-apt-repository -y ppa:obsproject/obs-studio + sudo apt-get -qq update + sudo apt-get install -y \ + libc-dev-bin \ + libc6-dev git \ + build-essential \ + checkinstall \ + cmake \ + obs-studio \ + qtbase5-dev + sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/${{ env.OBS_VERSION }}/UI/obs-frontend-api/obs-frontend-api.h + sudo ldconfig + - name: Build Plugin + if: success() + shell: bash + run: | + mkdir build && cd build + cmake -DCMAKE_INSTALL_PREFIX=/usr .. + make -j${NPROC:-4} + - name: Package Plugin + if: success() + shell: bash + run: | + cd build + sudo checkinstall -y --type=debian --fstrans=no --nodoc \ + --backup=no \ + --deldoc=yes \ + --install=no \ + --pkgname="${{ env.PLUGIN_NAME }}" \ + --pkgversion="${{ env.PLUGIN_VERSION }}" \ + --pkglicense="GPLv2.0" \ + --pkggroup="video" \ + --requires="obs-studio \(\>= 25.0.7\), libqt5core5a, libqt5widgets5, qt5-image-formats-plugins" \ + --pakdir="${{ github.workspace }}/package" + sudo chmod ao+r ${{ github.workspace }}/package/* + - name: 'Publish artifact' + if: success() + uses: actions/upload-artifact@v2.2.0 + with: + name: 'ubuntu-artifact' + path: './package' + macos64: + name: 'macOS 64-bit' + runs-on: [macos-latest] + env: + MACOS_DEPS_VERSION: "2021-03-25" + MIN_MACOS_VERSION: '10.13' + MACOS_BUNDLEID: "com.obs-plugin.decklink-output-filter" + CODE_SIGNING_IDENTITY: ${{ secrets.macOScodeSigningIdentity }} + INSTALLER_SIGNING_IDENTITY: ${{ secrets.macOSinstallerSigningIdentity }} + AC_USERNAME: ${{ secrets.macOSnotarizationusername }} + AC_PASSWORD: ${{ secrets.macOSnotarizationpassword }} + AC_PROVIDER_SHORTNAME: ${{ secrets.macOSnotarizationproviderShortName }} + steps: + - name: Get current setup + shell: bash + id: get_arch + run: | + echo "CURRENT_ARCH=$(uname -m)" >> $GITHUB_ENV + echo "NPROC=$(sysctl -n hw.ncpu)" >> $GITHUB_ENV + - name: 'Checkout' + uses: actions/checkout@v2.3.3 + with: + submodules: 'recursive' + - name: 'Install prerequisite: Pre-built dependencies' + if: steps.deps-cache.outputs.cache-hit != 'true' + shell: bash + run: | + curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-${{ env.CURRENT_ARCH }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz + tar -xf ./macos-deps-${{ env.CURRENT_ARCH }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp" + - name: 'Install prerequisite: Pre-built dependency Qt' + if: steps.deps-qt-cache.outputs.cache-hit != 'true' + shell: bash + run: | + curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-qt-${{ env.QT_VERSION }}-${{ env.CURRENT_ARCH }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz + tar -xf ./macos-qt-${{ env.QT_VERSION }}-${{ env.CURRENT_ARCH }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp" + xattr -r -d com.apple.quarantine /tmp/obsdeps + - name: Build OBS + if: success() + shell: bash + run: | + git clone https://github.com/obsproject/obs-studio + cd obs-studio + git checkout ${{ env.OBS_VERSION }} + mkdir build && cd build + cmake .. \ + -DDepsPath="/tmp/obsdeps" \ + -DQTDIR="/tmp/obsdeps" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ env.MIN_MACOS_VERSION }} \ + -DDISABLE_PLUGINS=true \ + -DENABLE_SCRIPTING=0 + make -j${NPROC:-4} + - name: Build plugin + if: success() + shell: bash + run: | + mkdir build && cd build + cmake .. \ + -DQTDIR="/tmp/obsdeps" \ + -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}/obs-studio/libobs" \ + -DLIBOBS_LIB="${{ github.workspace }}/obs-studio/libobs" \ + -DOBS_FRONTEND_LIB="${{ github.workspace }}/obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX="/usr" + make -j${NPROC:-4} + - name: 'Install Apple Developer Certificate' + if: success() && github.event_name == 'push' && contains(github.ref, 'refs/tags/') && env.MACOS_SIGN_PLUGIN == 'true' + uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071 + with: + p12-file-base64: ${{ secrets.MACOS_SIGNING_CERT }} + p12-password: ${{ secrets.macOScertificatesImportPassword }} + - name: Preparing plugin for packaging + if: success() + shell: bash + run: | + install_name_tool \ + -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets \ + @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \ + -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui \ + @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \ + -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore \ + @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \ + ./build/${{ env.PLUGIN_NAME }}.so + otool -L ./build/${{ env.PLUGIN_NAME }}.so + - name: Codesigning plugin + if: success() && github.event_name == 'push' && contains(github.ref, 'refs/tags/') && env.MACOS_SIGN_PLUGIN == 'true' + shell: bash + run: | + codesign --sign "${{ env.CODE_SIGNING_IDENTITY }}" ${{ github.workspace }}/build/${{ env.PLUGIN_NAME }}.so + - name: 'Cache DMGs' + id: dmgs-cache + uses: actions/cache@v2.1.2 + env: + CACHE_NAME: 'dmgs-cache' + with: + path: ${{ github.workspace }}/dmgs + key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-packages-dmg + - name: Download Packages + if: steps.dmgs-cache.outputs.cache-hit != 'true' + run: | + mkdir ./dmgs + curl -L -o ./dmgs/Packages.dmg http://s.sudre.free.fr/Software/files/Packages.dmg + - name: Install Packages + run: | + hdiutil attach ./dmgs/Packages.dmg + sudo installer -pkg /Volumes/Packages*/packages/Packages.pkg -target / + hdiutil detach /Volumes/Packages* + - name: Building package + if: success() + shell: bash + run: | + packagesbuild ./installer/installer-macOS.generated.pkgproj + mv ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}.pkg ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}-Unsigned.pkg + - name: Signing package + if: success() && github.event_name == 'push' && contains(github.ref, 'refs/tags/') && env.MACOS_SIGN_PLUGIN == 'true' + shell: bash + run: | + productsign \ + --sign "${{ env.INSTALLER_SIGNING_IDENTITY }}" \ + ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}-Unsigned.pkg \ + ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.pkg + rm ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}-Unsigned.pkg + zip -r ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.pkg.zip ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.pkg + UPLOAD_RESULT=$(xcrun altool \ + --notarize-app \ + --primary-bundle-id "${{ env.MACOS_BUNDLEID }}" \ + --username "${{ env.AC_USERNAME }}" \ + --password "${{ env.AC_PASSWORD }}" \ + --asc-provider "${{ env.AC_PROVIDER_SHORTNAME }}" \ + --file "${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.pkg.zip") + rm ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.pkg.zip + REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}') + echo "Request UUID: $REQUEST_UUID" + echo "=> Wait for notarization result" + while sleep 30 && date; do + CHECK_RESULT=$(xcrun altool \ + --notarization-info "$REQUEST_UUID" \ + --username "${{ env.AC_USERNAME }}" \ + --password "${{ env.AC_PASSWORD }}" \ + --asc-provider "${{ env.AC_PROVIDER_SHORTNAME }}") + echo $CHECK_RESULT + if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then + echo "=> Staple ticket to installer: ${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.pkg" + xcrun stapler staple ${{ github.workspace }}/release/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.pkg + break + fi + done + - name: 'Publish artifact' + if: success() + uses: actions/upload-artifact@v2.2.0 + with: + name: 'mac-artifact' + path: './release/*.pkg' + win64: + name: 'Windows 64-bit' + runs-on: [windows-latest] + env: + CMAKE_GENERATOR: "Visual Studio 16 2019" + CMAKE_SYSTEM_VERSION: "10.0.18363.657" + WINDOWS_DEPS_VERSION: '2019' + steps: + - name: 'Add msbuild to PATH' + uses: microsoft/setup-msbuild@v1.0.2 + - name: 'Checkout' + uses: actions/checkout@v2.3.3 + with: + submodules: 'recursive' + - name: 'Restore QT dependency from cache' + id: qt-cache + uses: actions/cache@v2.1.2 + env: + CACHE_NAME: 'windows-qt-cache' + with: + path: ${{ github.workspace }}/cmbuild/QT + key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.QT_VERSION }} + - name: 'Restore pre-built dependencies from cache' + id: deps-cache + uses: actions/cache@v2.1.2 + env: + CACHE_NAME: 'windows-deps-cache' + with: + path: ${{ github.workspace }}/cmbuild/deps + key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.WINDOWS_DEPS_VERSION }} + - name: 'Install prerequisite: QT' + if: success() && steps.qt-cache.outputs.cache-hit != 'true' + run: | + curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C - + 7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}/cmbuild/QT" + - name: 'Install prerequisite: Pre-built dependencies' + if: success() && steps.deps-cache.outputs.cache-hit != 'true' + run: | + curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C - + 7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}/cmbuild/deps" + - name: 'Configure OBS' + if: success() + shell: bash + run: | + git clone https://github.com/obsproject/obs-studio + cd obs-studio + git checkout ${{ env.OBS_VERSION }} + mkdir build + cd build + cmake .. \ + -G "${{ env.CMAKE_GENERATOR }}" \ + -A x64 \ + -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" \ + -DDepsPath="${{ github.workspace }}/cmbuild/deps/win64" \ + -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2019_64" \ + -DDISABLE_PLUGINS=TRUE \ + -DCOPIED_DEPENDENCIES=FALSE \ + -DCOPY_DEPENDENCIES=TRUE + - name: 'Build OBS' + if: success() + run: msbuild /m /p:Configuration=RelWithDebInfo ${{ github.workspace }}/obs-studio/build/obs-studio.sln + - name: 'Configure plugin' + if: success() + shell: bash + run: | + mkdir build + cd build + cmake .. \ + -G "${{ env.CMAKE_GENERATOR }}" \ + -A x64 \ + -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" \ + -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2019_64" \ + -DLibObs_DIR="${{ github.workspace }}/obs-studio/build/libobs" \ + -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}/obs-studio/libobs" \ + -DLIBOBS_LIB="${{ github.workspace }}/obs-studio/build/libobs/RelWithDebInfo/obs.lib" \ + -DOBS_FRONTEND_LIB="${{ github.workspace }}/obs-studio/build/UI/obs-frontend-api/RelWithDebInfo/obs-frontend-api.lib" + - name: 'Build plugin' + if: success() + run: msbuild /m /p:Configuration=RelWithDebInfo ${{ github.workspace }}/build/${{ env.PLUGIN_NAME }}.sln + - name: 'Package plugin' + if: success() + run: | + mkdir package + cd package + 7z a "${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}-win64.zip" "${{ github.workspace }}/release/*" + iscc ${{ github.workspace }}/installer/installer-Windows.generated.iss /O. /F"${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}-windows-installer" + - name: 'Publish artifact' + if: success() + uses: actions/upload-artifact@v2.2.0 + with: + name: 'windows-artifact' + path: './package' + make-release: + name: 'Create and upload release' + runs-on: [ubuntu-latest] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + needs: [win64, macos64, ubuntu64] + steps: + - name: 'Create Release' + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.PLUGIN_VERSION }} + release_name: OBS Decklink Output Filter ${{ env.PLUGIN_VERSION }} + draft: false + prerelease: false + - name: 'Download release artifacts' + uses: actions/download-artifact@v2-preview + - name: 'Upload win64 to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ github.workspace }}/windows-artifact/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}-windows-installer.exe + asset_name: ${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}-win64-installer.exe + asset_content_type: application/octet-stream + - name: 'Upload macOS to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ github.workspace }}/mac-artifact/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}-Unsigned.pkg + asset_name: ${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.pkg + asset_content_type: application/octet-stream + - name: 'Upload Ubuntu to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ github.workspace }}/ubuntu-artifact/${{ env.PLUGIN_NAME }}_${{ env.PLUGIN_VERSION }}-1_amd64.deb + asset_name: ${{ env.PLUGIN_NAME }}_${{ env.PLUGIN_VERSION }}-1_amd64.deb + asset_content_type: application/octet-stream diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1982bf8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*~ +.DS_Store +/build/ +/build32/ +/build64/ +/release/ +/installer/Output/ + +.vscode +.idea + +# ignore generated files +*.generated.* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a3acd15 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,190 @@ +cmake_minimum_required(VERSION 3.10) + +# Change obs-plugintemplate to your plugin's name in a machine-readable format +# (e.g.: obs-myawesomeplugin) and set +project(obs-decklink-output-filter VERSION 1.0.0) + +# Replace `Your Name Here` with the name (yours or your organization's) you want +# to see as the author of the plugin (in the plugin's metadata itself and in the installers) +set(PLUGIN_AUTHOR "cg2121") + +# Replace `com.example.obs-plugin-template` with a unique Bundle ID for macOS releases +# (used both in the installer and when submitting the installer for notarization) +set(MACOS_BUNDLEID "com.obs-plugins.decklink-output-filter") + +# TAKE NOTE: No need to edit things past this point + +set(CMAKE_PREFIX_PATH "${QTDIR}") +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +# In case you need C++ +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (WIN32 OR APPLE) +include(external/FindLibObs.cmake) +endif() + +find_package(LibObs REQUIRED) +find_package(Qt5 REQUIRED COMPONENTS Core Widgets) + +configure_file( + src/plugin-macros.h.in + ../src/plugin-macros.generated.h +) +configure_file( + installer/installer-macOS.pkgproj.in + ../installer/installer-macOS.generated.pkgproj +) +configure_file( + installer/installer-Windows.iss.in + ../installer/installer-Windows.generated.iss +) + +configure_file( + ci/ci_includes.sh.in + ../ci/ci_includes.generated.sh +) +configure_file( + ci/ci_includes.cmd.in + ../ci/ci_includes.generated.cmd +) + +set(PLUGIN_SOURCES + src/plugin-main.c) + +set(PLUGIN_HEADERS + src/plugin-macros.generated.h) + +# --- Platform-independent build settings --- +add_library(${CMAKE_PROJECT_NAME} MODULE ${PLUGIN_SOURCES} ${PLUGIN_HEADERS}) + +include_directories( + "${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" + ${Qt5Core_INCLUDES} + ${Qt5Widgets_INCLUDES} +) + +target_link_libraries(${CMAKE_PROJECT_NAME} + libobs + Qt5::Core + Qt5::Widgets +) + +# --- End of section --- + +# --- Windows-specific build settings and tasks --- +if(WIN32) + if(NOT DEFINED OBS_FRONTEND_LIB) + set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library") + message(FATAL_ERROR "Could not find OBS Frontend API library !") + endif() + + # Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL when building with VS2019) + if (MSVC) + add_definitions(/MP /d2FH4-) + endif() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ARCH_NAME "64bit") + set(OBS_BUILDDIR_ARCH "build64") + else() + set(ARCH_NAME "32bit") + set(OBS_BUILDDIR_ARCH "build32") + endif() + + include_directories( + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/UI" + ) + + target_link_libraries(${CMAKE_PROJECT_NAME} + "${OBS_FRONTEND_LIB}" + ) + + # --- Release package helper --- + # The "release" folder has a structure similar OBS' one on Windows + set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release") + + add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD + # If config is Release or RelWithDebInfo, package release files + COMMAND if $,$>==1 ( + "${CMAKE_COMMAND}" -E make_directory + "${RELEASE_DIR}/data/obs-plugins/${CMAKE_PROJECT_NAME}" + "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}" + ) + + COMMAND if $,$>==1 ( + "${CMAKE_COMMAND}" -E copy_directory + "${PROJECT_SOURCE_DIR}/data" + "${RELEASE_DIR}/data/obs-plugins/${CMAKE_PROJECT_NAME}" + ) + + COMMAND if $,$>==1 ( + "${CMAKE_COMMAND}" -E copy + "$" + "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}" + ) + + # If config is RelWithDebInfo, copy the pdb file + COMMAND if $==1 ( + "${CMAKE_COMMAND}" -E copy + "$" + "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}" + ) + + # Copy to obs-studio dev environment for immediate testing + COMMAND if $==1 ( + "${CMAKE_COMMAND}" -E copy + "$" + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}" + ) + + COMMAND if $==1 ( + "${CMAKE_COMMAND}" -E copy + "$" + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}" + ) + + COMMAND if $==1 ( + "${CMAKE_COMMAND}" -E make_directory + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/data/obs-plugins/${CMAKE_PROJECT_NAME}" + ) + + COMMAND if $==1 ( + "${CMAKE_COMMAND}" -E copy_directory + "${PROJECT_SOURCE_DIR}/data" + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/data/obs-plugins/${CMAKE_PROJECT_NAME}" + ) + ) + # --- End of sub-section --- + +endif() +# --- End of section --- + +# --- Linux-specific build settings and tasks --- +if(UNIX AND NOT APPLE) + include(GNUInstallDirs) + + set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES PREFIX "") + target_link_libraries(${CMAKE_PROJECT_NAME} obs-frontend-api) + + file(GLOB locale_files data/locale/*.ini) + + install(TARGETS ${CMAKE_PROJECT_NAME} + LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/obs-plugins") + + install(FILES ${locale_files} + DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/obs/obs-plugins/${CMAKE_PROJECT_NAME}/locale") +endif() +# --- End of section --- + +# -- OS X specific build settings and tasks -- +if(APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default") + + set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES PREFIX "") + target_link_libraries(${CMAKE_PROJECT_NAME} "${OBS_FRONTEND_LIB}") +endif() +# -- End of section -- diff --git a/LICENSE b/LICENSE index d159169..1b8a5cd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc., + Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -290,8 +290,8 @@ to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + {description} + Copyright (C) {year} {fullname} 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 @@ -329,11 +329,11 @@ necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. - , 1 April 1989 + {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. +Public License instead of this License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..032f562 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# OBS Decklink Output Filter + +OBS filter to output a specific scene or source to a Decklink card. diff --git a/ci/ci_includes.cmd.in b/ci/ci_includes.cmd.in new file mode 100644 index 0000000..e347729 --- /dev/null +++ b/ci/ci_includes.cmd.in @@ -0,0 +1,2 @@ +set PluginName=@CMAKE_PROJECT_NAME@ +set PluginVersion=@CMAKE_PROJECT_VERSION@ diff --git a/ci/ci_includes.sh.in b/ci/ci_includes.sh.in new file mode 100644 index 0000000..306b941 --- /dev/null +++ b/ci/ci_includes.sh.in @@ -0,0 +1,4 @@ +PLUGIN_NAME="@CMAKE_PROJECT_NAME@" +PLUGIN_VERSION="@CMAKE_PROJECT_VERSION@" +MACOS_BUNDLEID="@MACOS_BUNDLEID@" +LINUX_MAINTAINER_EMAIL="@LINUX_MAINTAINER_EMAIL@" \ No newline at end of file diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini new file mode 100644 index 0000000..7b67f88 --- /dev/null +++ b/data/locale/en-US.ini @@ -0,0 +1 @@ +DecklinkOutput="Decklink Output" diff --git a/external/FindLibObs.cmake b/external/FindLibObs.cmake new file mode 100644 index 0000000..ab0a3de --- /dev/null +++ b/external/FindLibObs.cmake @@ -0,0 +1,107 @@ +# 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/formatcode.sh b/formatcode.sh new file mode 100755 index 0000000..f776cf2 --- /dev/null +++ b/formatcode.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Original source https://github.com/Project-OSRM/osrm-backend/blob/master/scripts/format.sh + +set +x +set -o errexit +set -o pipefail +set -o nounset + +# Runs the Clang Formatter in parallel on the code base. +# Return codes: +# - 1 there are files to be formatted +# - 0 everything looks fine + +# Get CPU count +OS=$(uname) +NPROC=1 +if [[ $OS = "Linux" || $OS = "Darwin" ]] ; then + NPROC=$(getconf _NPROCESSORS_ONLN) +fi + +# Discover clang-format +if type clang-format-10 2> /dev/null ; then + CLANG_FORMAT=clang-format-10 +elif type clang-format-8 2> /dev/null ; then + CLANG_FORMAT=clang-format-8 +else + CLANG_FORMAT=clang-format +fi + +find . -type d \( -path ./deps \ +-o -path ./cmake \ +-o -path ./plugins/decklink/win/decklink-sdk \ +-o -path ./plugins/decklink/mac/decklink-sdk \ +-o -path ./plugins/decklink/linux/decklink-sdk \ +-o -path ./plugins/enc-amf \ +-o -path ./plugins/mac-syphon/syphon-framework \ +-o -path ./plugins/obs-outputs/ftl-sdk \ +-o -path ./plugins/obs-vst \ +-o -path ./build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \ +| xargs -L100 -P${NPROC} ${CLANG_FORMAT} -i -style=file -fallback-style=none diff --git a/installer/installer-Windows.iss.in b/installer/installer-Windows.iss.in new file mode 100644 index 0000000..323272b --- /dev/null +++ b/installer/installer-Windows.iss.in @@ -0,0 +1,63 @@ +#define MyAppName "@CMAKE_PROJECT_NAME@" +#define MyAppVersion "@CMAKE_PROJECT_VERSION@" +#define MyAppPublisher "@PLUGIN_AUTHOR@" +#define MyAppURL "http://www.mywebsite.com" + +[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/installer/installer-macOS.pkgproj.in b/installer/installer-macOS.pkgproj.in new file mode 100644 index 0000000..1b3793a --- /dev/null +++ b/installer/installer-macOS.pkgproj.in @@ -0,0 +1,726 @@ + + + + + PROJECT + + PACKAGE_FILES + + DEFAULT_INSTALL_LOCATION + / + HIERARCHY + + CHILDREN + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + ../build/@CMAKE_PROJECT_NAME@.so + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + GID + 80 + PATH + bin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 2 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + ../data + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + GID + 80 + PATH + @CMAKE_PROJECT_NAME@ + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 2 + UID + 0 + + + GID + 80 + PATH + plugins + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 2 + UID + 0 + + + GID + 80 + PATH + obs-studio + PATH_TYPE + 0 + PERMISSIONS + 493 + 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 + 493 + 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 + VERSION + 4 + + PACKAGE_SCRIPTS + + RESOURCES + + + PACKAGE_SETTINGS + + AUTHENTICATION + 1 + CONCLUSION_ACTION + 0 + IDENTIFIER + @MACOS_BUNDLEID@ + OVERWRITE_PERMISSIONS + + VERSION + @CMAKE_PROJECT_VERSION@ + + PROJECT_COMMENTS + + NOTES + + PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M + IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv + c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l + cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 + IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 + ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp + dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u + dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD + b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg + dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 + Pgo8L2JvZHk+CjwvaHRtbD4K + + + PROJECT_SETTINGS + + BUILD_PATH + + PATH + ../release + 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 + @CMAKE_PROJECT_NAME@ + + + TYPE + 1 + VERSION + 2 + + diff --git a/src/plugin-macros.h.in b/src/plugin-macros.h.in new file mode 100644 index 0000000..b2b2184 --- /dev/null +++ b/src/plugin-macros.h.in @@ -0,0 +1,9 @@ +#ifndef PLUGINNAME_H +#define PLUGINNAME_H + +#define PLUGIN_NAME "@CMAKE_PROJECT_NAME@" +#define PLUGIN_VERSION "@CMAKE_PROJECT_VERSION@" + +#define blog(level, msg, ...) blog(level, "[" PLUGIN_NAME "] " msg, ##__VA_ARGS__) + +#endif // PLUGINNAME_H diff --git a/src/plugin-main.c b/src/plugin-main.c new file mode 100644 index 0000000..0dcb765 --- /dev/null +++ b/src/plugin-main.c @@ -0,0 +1,264 @@ +#include +#include "plugin-macros.generated.h" +#include +#include +#include +#include + +struct decklink_output_filter_context { + obs_output_t *output; + obs_source_t *source; + + video_t *video_output; + gs_texrender_t *texrender; + gs_stagesurf_t *stagesurface; + + bool active; +}; + +static const char *decklink_output_filter_get_name(void *unused) +{ + UNUSED_PARAMETER(unused); + return "DecklinkOutput"; +} + +static void render_preview_source(void *data, uint32_t cx, uint32_t cy) +{ + UNUSED_PARAMETER(cx); + UNUSED_PARAMETER(cy); + + struct decklink_output_filter_context *filter = data; + + gs_texrender_reset(filter->texrender); + + uint32_t width = gs_stagesurface_get_width(filter->stagesurface); + uint32_t height = gs_stagesurface_get_height(filter->stagesurface); + + if (!gs_texrender_begin(filter->texrender, width, height)) + return; + + struct vec4 background; + vec4_zero(&background); + + gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0); + gs_ortho(0.0f, (float)width, 0.0f, (float)height, -100.0f, 100.0f); + + gs_blend_state_push(); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + + obs_source_skip_video_filter(filter->source); + + gs_blend_state_pop(); + gs_texrender_end(filter->texrender); + + struct video_frame output_frame; + if (!video_output_lock_frame(filter->video_output, &output_frame, 1, + obs_get_video_frame_time())) + return; + + gs_stage_texture(filter->stagesurface, + gs_texrender_get_texture(filter->texrender)); + + uint8_t *video_data; + uint32_t video_linesize; + if (!gs_stagesurface_map(filter->stagesurface, &video_data, + &video_linesize)) + return; + + uint32_t linesize = output_frame.linesize[0]; + + for (uint32_t i = 0; i < height; i++) { + uint32_t dst_offset = linesize * i; + uint32_t src_offset = video_linesize * i; + memcpy(output_frame.data[0] + dst_offset, + video_data + src_offset, linesize); + } + + gs_stagesurface_unmap(filter->stagesurface); + video_output_unlock_frame(filter->video_output); +} + +static void decklink_output_filter_stop(void *data) +{ + struct decklink_output_filter_context *filter = data; + + if (!filter->active) + return; + + obs_remove_main_render_callback(render_preview_source, filter); + + obs_output_stop(filter->output); + obs_output_release(filter->output); + video_output_stop(filter->video_output); + + obs_enter_graphics(); + gs_stagesurface_destroy(filter->stagesurface); + gs_texrender_destroy(filter->texrender); + obs_leave_graphics(); + + video_output_close(filter->video_output); + + filter->active = false; +} + +static void decklink_output_filter_start(void *data) +{ + struct decklink_output_filter_context *filter = data; + + if (filter->active) + return; + + filter->active = false; + + if (!obs_source_enabled(filter->source)) + return; + + obs_source_t *parent = obs_filter_get_target(filter->source); + uint32_t width = obs_source_get_base_width(parent); + uint32_t height = obs_source_get_base_height(parent); + + if (!width || !height) + return; + + obs_data_t *settings = obs_source_get_settings(filter->source); + const char *hash = obs_data_get_string(settings, "device_hash"); + int mode_id = obs_data_get_int(settings, "mode_id"); + obs_data_release(settings); + + if (!hash || !*hash || !mode_id) + return; + + filter->output = obs_output_create( + "decklink_output", "decklink_filter_output", settings, NULL); + + obs_enter_graphics(); + filter->texrender = gs_texrender_create(GS_BGRA, GS_ZS_NONE); + filter->stagesurface = gs_stagesurface_create(width, height, GS_BGRA); + obs_leave_graphics(); + + const struct video_output_info *main_voi = + video_output_get_info(obs_get_video()); + + struct video_output_info vi = {0}; + vi.format = VIDEO_FORMAT_BGRA; + vi.width = width; + vi.height = height; + vi.fps_den = main_voi->fps_den; + vi.fps_num = main_voi->fps_num; + vi.cache_size = 16; + vi.colorspace = main_voi->colorspace; + vi.range = main_voi->range; + vi.name = obs_source_get_name(filter->source); + + video_output_open(&filter->video_output, &vi); + obs_output_set_media(filter->output, filter->video_output, + obs_get_audio()); + + bool started = obs_output_start(filter->output); + filter->active = true; + + if (!started) + decklink_output_filter_stop(filter); + else + obs_add_main_render_callback(render_preview_source, filter); +} + +static void decklink_output_filter_update(void *data, obs_data_t *settings) +{ + struct decklink_output_filter_context *filter = data; + decklink_output_filter_stop(filter); + decklink_output_filter_start(filter); + + UNUSED_PARAMETER(settings); +} + +static void set_filter_enabled(void *data, calldata_t *calldata) +{ + struct decklink_output_filter_context *filter = data; + + bool enable = calldata_bool(calldata, "enabled"); + + if (enable) + decklink_output_filter_start(filter); + else + decklink_output_filter_stop(filter); +} + +static void frontend_event(enum obs_frontend_event event, void *data) +{ + struct decklink_output_filter_context *filter = data; + + switch (event) { + case OBS_FRONTEND_EVENT_FINISHED_LOADING: + decklink_output_filter_start(filter); + break; + default: + break; + } +} + +static void *decklink_output_filter_create(obs_data_t *settings, + obs_source_t *source) +{ + struct decklink_output_filter_context *filter = + bzalloc(sizeof(struct decklink_output_filter_context)); + filter->source = source; + filter->active = false; + + signal_handler_t *sh = obs_source_get_signal_handler(filter->source); + signal_handler_connect(sh, "enable", set_filter_enabled, filter); + + obs_frontend_add_event_callback(frontend_event, filter); + + return filter; +} + +static void decklink_output_filter_destroy(void *data) +{ + struct decklink_output_filter_context *filter = data; + obs_frontend_remove_event_callback(frontend_event, filter); + decklink_output_filter_stop(filter); + + bfree(filter); +} + +static obs_properties_t *decklink_output_filter_properties(void *data) +{ + obs_properties_t *props = obs_get_output_properties("decklink_output"); + obs_property_t *prop = obs_properties_get(props, "auto_start"); + obs_property_set_visible(prop, false); + + return props; +} + +void decklink_output_filter_render(void *data, gs_effect_t *effect) +{ + struct decklink_output_filter_context *filter = data; + obs_source_skip_video_filter(filter->source); + + UNUSED_PARAMETER(effect); +} + +struct obs_source_info decklink_output_filter = { + .id = "decklink_output_filter", + .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = decklink_output_filter_get_name, + .create = decklink_output_filter_create, + .destroy = decklink_output_filter_destroy, + .update = decklink_output_filter_update, + .get_properties = decklink_output_filter_properties, + .video_render = decklink_output_filter_render}; + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("decklink-output-filter", "en-US") +MODULE_EXPORT const char *obs_module_description(void) +{ + return "Decklink Output Filter"; +} + +bool obs_module_load(void) +{ + obs_register_source(&decklink_output_filter); + return true; +}