├── .clang-format ├── .github └── workflows │ ├── clang-format.yml │ └── main.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ci ├── ci_includes.cmd.in ├── ci_includes.sh.in ├── forum-update-info.json ├── macos │ ├── change-rpath.sh │ └── install-packagesbuild.sh └── windows │ └── package-windows.cmd ├── cmake ├── ObsPluginHelpers.cmake └── bundle │ └── macos │ ├── Plugin-Info.plist.in │ └── entitlements.plist ├── data └── locale │ └── en-US.ini ├── doc └── async-source-duplication.svg ├── installer ├── installer-Windows.iss.in └── installer-macOS.pkgproj.in └── src ├── async-source-duplication-filter.c ├── async-source-duplication-source.c ├── plugin-macros.h.in └── plugin-main.c /.clang-format: -------------------------------------------------------------------------------- 1 | # please use clang-format version 8 or later 2 | 3 | Standard: Cpp11 4 | AccessModifierOffset: -8 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | #AllowAllArgumentsOnNextLine: false # requires clang-format 9 12 | #AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: false 18 | #AllowShortLambdasOnASingleLine: Inline # requires clang-format 9 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterDefinitionReturnType: None 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: false 24 | BinPackArguments: true 25 | BinPackParameters: true 26 | BraceWrapping: 27 | AfterClass: false 28 | AfterControlStatement: false 29 | AfterEnum: false 30 | AfterFunction: true 31 | AfterNamespace: false 32 | AfterObjCDeclaration: false 33 | AfterStruct: true 34 | AfterUnion: false 35 | AfterExternBlock: false 36 | BeforeCatch: false 37 | BeforeElse: true 38 | IndentBraces: false 39 | SplitEmptyFunction: true 40 | SplitEmptyRecord: true 41 | SplitEmptyNamespace: true 42 | BreakBeforeBinaryOperators: None 43 | BreakBeforeBraces: Custom 44 | BreakBeforeTernaryOperators: true 45 | BreakConstructorInitializers: BeforeColon 46 | BreakStringLiterals: false # apparently unpredictable 47 | ColumnLimit: 120 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 8 51 | ContinuationIndentWidth: 8 52 | Cpp11BracedListStyle: true 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | FixNamespaceComments: false 56 | ForEachMacros: 57 | - 'json_object_foreach' 58 | - 'json_object_foreach_safe' 59 | - 'json_array_foreach' 60 | IncludeBlocks: Preserve 61 | IndentCaseLabels: false 62 | IndentPPDirectives: None 63 | IndentWidth: 8 64 | IndentWrappedFunctionNames: false 65 | KeepEmptyLinesAtTheStartOfBlocks: true 66 | MaxEmptyLinesToKeep: 1 67 | NamespaceIndentation: None 68 | #ObjCBinPackProtocolList: Auto # requires clang-format 7 69 | ObjCBlockIndentWidth: 8 70 | ObjCSpaceAfterProperty: true 71 | ObjCSpaceBeforeProtocolList: true 72 | 73 | PenaltyBreakAssignment: 10 74 | PenaltyBreakBeforeFirstCallParameter: 30 75 | PenaltyBreakComment: 10 76 | PenaltyBreakFirstLessLess: 0 77 | PenaltyBreakString: 10 78 | PenaltyExcessCharacter: 100 79 | PenaltyReturnTypeOnItsOwnLine: 60 80 | 81 | PointerAlignment: Right 82 | ReflowComments: false 83 | SortIncludes: false 84 | SortUsingDeclarations: false 85 | SpaceAfterCStyleCast: false 86 | #SpaceAfterLogicalNot: false # requires clang-format 9 87 | SpaceAfterTemplateKeyword: false 88 | SpaceBeforeAssignmentOperators: true 89 | #SpaceBeforeCtorInitializerColon: true # requires clang-format 7 90 | #SpaceBeforeInheritanceColon: true # requires clang-format 7 91 | SpaceBeforeParens: ControlStatements 92 | #SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7 93 | SpaceInEmptyParentheses: false 94 | SpacesBeforeTrailingComments: 1 95 | SpacesInAngles: false 96 | SpacesInCStyleCastParentheses: false 97 | SpacesInContainerLiterals: false 98 | SpacesInParentheses: false 99 | SpacesInSquareBrackets: false 100 | #StatementMacros: # requires clang-format 8 101 | # - 'Q_OBJECT' 102 | TabWidth: 8 103 | #TypenameMacros: # requires clang-format 9 104 | # - 'DARRAY' 105 | UseTab: ForContinuationAndIndentation 106 | --- 107 | Language: ObjC 108 | -------------------------------------------------------------------------------- /.github/workflows/clang-format.yml: -------------------------------------------------------------------------------- 1 | name: Clang Format Check 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | clang: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Clang 17 | run: | 18 | sudo apt-get install -y clang-format-12 19 | clang-format -i -fallback-style=none $(git ls-files | grep '\.\(c\|h\)$') 20 | 21 | - name: Check 22 | # Build your program with the given configuration 23 | run: | 24 | dirty=$(git ls-files --modified) 25 | set +x 26 | if [[ $dirty ]]; then 27 | git diff 28 | echo "Error: File(s) are not properly formatted." 29 | echo "$dirty" 30 | exit 1 31 | fi 32 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Plugin Build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | branches: 8 | - main 9 | tags: 10 | - '*' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | branches: 15 | - main 16 | 17 | env: 18 | artifactName: ${{ contains(github.ref_name, '/') && 'artifact' || github.ref_name }} 19 | qt: false 20 | 21 | jobs: 22 | linux_build: 23 | runs-on: ${{ matrix.ubuntu }} 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | obs: [27, 28] 28 | ubuntu: ['ubuntu-20.04'] 29 | defaults: 30 | run: 31 | shell: bash 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v3 35 | with: 36 | submodules: recursive 37 | 38 | - name: Download obs-studio development environment 39 | id: obsdeps 40 | uses: norihiro/obs-studio-devel-action@v1-beta 41 | with: 42 | obs: ${{ matrix.obs }} 43 | verbose: true 44 | qt: ${{ env.qt }} 45 | 46 | - name: Build plugin 47 | run: | 48 | OBS_QT_VERSION_MAJOR=${{ steps.obsdeps.outputs.OBS_QT_VERSION_MAJOR }} 49 | mkdir build 50 | cd build 51 | case ${{ matrix.obs }} in 52 | 27) 53 | cmake_opt=( 54 | -D CMAKE_INSTALL_LIBDIR=/usr/lib/ 55 | -D CPACK_DEBIAN_PACKAGE_DEPENDS='obs-studio (>= 27), obs-studio (<< 28)' 56 | ) 57 | ;; 58 | 28) 59 | cmake_opt=( 60 | -D CPACK_DEBIAN_PACKAGE_DEPENDS='obs-studio (>= 28)' 61 | ) 62 | ;; 63 | esac 64 | cmake .. \ 65 | -D QT_VERSION=$OBS_QT_VERSION_MAJOR \ 66 | -D CMAKE_INSTALL_PREFIX=/usr \ 67 | -D CMAKE_BUILD_TYPE=RelWithDebInfo \ 68 | -D LINUX_PORTABLE=OFF \ 69 | -D CPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON \ 70 | -D PKG_SUFFIX=-obs${{ matrix.obs }}-${{ matrix.ubuntu }}-x86_64 \ 71 | "${cmake_opt[@]}" 72 | make -j4 73 | make package 74 | echo "FILE_NAME=$(find $PWD -name '*.deb' | head -n 1)" >> $GITHUB_ENV 75 | - name: Upload build artifact 76 | uses: actions/upload-artifact@v3 77 | with: 78 | name: ${{ env.artifactName }} 79 | path: '${{ env.FILE_NAME }}' 80 | - name: Check package 81 | run: | 82 | . ci/ci_includes.generated.sh 83 | set -ex 84 | sudo apt install '${{ env.FILE_NAME }}' 85 | case ${{ matrix.obs }} in 86 | 27) plugins_dir=/usr/lib/obs-plugins ;; 87 | 28) plugins_dir=/usr/lib/x86_64-linux-gnu/obs-plugins ;; 88 | esac 89 | ldd $plugins_dir/${PLUGIN_NAME}.so > ldd.out 90 | if grep not.found ldd.out ; then 91 | echo "Error: unresolved shared object." >&2 92 | exit 1 93 | fi 94 | ls /usr/share/obs/obs-plugins/${PLUGIN_NAME}/ 95 | 96 | macos_build: 97 | runs-on: macos-12 98 | strategy: 99 | fail-fast: false 100 | matrix: 101 | include: 102 | - obs: 27 103 | arch: x86_64 104 | - obs: 28 105 | arch: universal 106 | defaults: 107 | run: 108 | shell: bash 109 | steps: 110 | - name: Checkout 111 | uses: actions/checkout@v3 112 | with: 113 | submodules: recursive 114 | 115 | - name: Setup Environment 116 | id: setup 117 | run: | 118 | set -e 119 | echo '::group::Set up code signing' 120 | if [[ '${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}' != '' && \ 121 | '${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}' != '' && \ 122 | '${{ secrets.MACOS_SIGNING_CERT }}' != '' ]]; then 123 | echo '::set-output name=haveCodesignIdent::true' 124 | else 125 | echo '::set-output name=haveCodesignIdent::false' 126 | fi 127 | if [[ '${{ secrets.MACOS_NOTARIZATION_USERNAME }}' != '' && \ 128 | '${{ secrets.MACOS_NOTARIZATION_PASSWORD }}' != '' ]]; then 129 | echo '::set-output name=haveNotarizationUser::true' 130 | else 131 | echo '::set-output name=haveNotarizationUser::false' 132 | fi 133 | echo '::endgroup::' 134 | 135 | - name: Install Apple Developer Certificate 136 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 137 | uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071 138 | with: 139 | keychain-password: ${{ github.run_id }} 140 | p12-file-base64: ${{ secrets.MACOS_SIGNING_CERT }} 141 | p12-password: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }} 142 | 143 | - name: Set Signing Identity 144 | if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 145 | run: | 146 | set -e 147 | TEAM_ID=$(echo "${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" | sed 's/.*(\([A-Za-z0-9]*\))$/\1/') 148 | xcrun notarytool store-credentials AC_PASSWORD \ 149 | --apple-id "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \ 150 | --team-id "$TEAM_ID" \ 151 | --password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" 152 | 153 | - name: Download obs-studio development environment 154 | id: obsdeps 155 | uses: norihiro/obs-studio-devel-action@v1-beta 156 | with: 157 | path: /tmp/deps-${{ matrix.obs }}-${{ matrix.arch }} 158 | arch: ${{ matrix.arch }} 159 | obs: ${{ matrix.obs }} 160 | verbose: true 161 | qt: ${{ env.qt }} 162 | 163 | - name: Build plugin 164 | run: | 165 | arch=${{ matrix.arch }} 166 | deps=/tmp/deps-${{ matrix.obs }}-${{ matrix.arch }} 167 | MACOSX_DEPLOYMENT_TARGET=${{ steps.obsdeps.outputs.MACOSX_DEPLOYMENT_TARGET }} 168 | OBS_QT_VERSION_MAJOR=${{ steps.obsdeps.outputs.OBS_QT_VERSION_MAJOR }} 169 | GIT_TAG=$(git describe --tags --always) 170 | PKG_SUFFIX=-${GIT_TAG}-obs${{ matrix.obs }}-macos-${{ matrix.arch }} 171 | set -e 172 | case "${{ matrix.obs }}" in 173 | 27) 174 | cmake_opt=() 175 | ;; 176 | 28) 177 | cmake_opt=( 178 | -D MACOSX_PLUGIN_BUNDLE_TYPE=BNDL 179 | -D OBS_BUNDLE_CODESIGN_IDENTITY='${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}' 180 | ) 181 | ;; 182 | esac 183 | cmake -S . -B build -G Ninja \ 184 | -D QT_VERSION=$OBS_QT_VERSION_MAJOR \ 185 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \ 186 | -DCMAKE_PREFIX_PATH="$PWD/release/" \ 187 | -DCMAKE_OSX_ARCHITECTURES=${arch/#universal/x86_64;arm64} \ 188 | -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \ 189 | -DCMAKE_FRAMEWORK_PATH="$deps/Frameworks;$deps/lib/cmake;$deps" \ 190 | -D PKG_SUFFIX=$PKG_SUFFIX \ 191 | "${cmake_opt[@]}" 192 | cmake --build build --config RelWithDebInfo 193 | echo "PKG_SUFFIX='$PKG_SUFFIX'" >> ci/ci_includes.generated.sh 194 | 195 | - name: Prepare package 196 | run: | 197 | set -ex 198 | . ci/ci_includes.generated.sh 199 | cmake --install build --config RelWithDebInfo --prefix=release 200 | case ${{ matrix.obs }} in 201 | 27) 202 | (cd release/${PLUGIN_NAME} && ../../ci/macos/change-rpath.sh -obs ${{ matrix.obs }} -lib lib/ bin/${PLUGIN_NAME}.so) 203 | cp LICENSE release/${PLUGIN_NAME}/data/LICENSE-$PLUGIN_NAME 204 | ;; 205 | 28) 206 | (cd release/${PLUGIN_NAME}.plugin/Contents && ../../../ci/macos/change-rpath.sh -obs 28 -lib lib/ MacOS/${PLUGIN_NAME}) 207 | cp LICENSE release/${PLUGIN_NAME}.plugin/Contents/Resources/LICENSE-$PLUGIN_NAME 208 | ;; 209 | esac 210 | 211 | - name: Codesign 212 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 213 | run: | 214 | . ci/ci_includes.generated.sh 215 | set -ex 216 | case ${{ matrix.obs }} in 217 | 27) 218 | files=( 219 | $(find release/${PLUGIN_NAME}/ -name '*.dylib') 220 | release/${PLUGIN_NAME}/bin/${PLUGIN_NAME}.so 221 | ) 222 | ;; 223 | 28) 224 | files=( 225 | $(find release/${PLUGIN_NAME}.plugin/ -name '*.dylib') 226 | release/${PLUGIN_NAME}.plugin/Contents/MacOS/${PLUGIN_NAME} 227 | ) 228 | ;; 229 | esac 230 | for dylib in "${files[@]}"; do 231 | codesign --force --sign "${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" "$dylib" 232 | done 233 | for dylib in "${files[@]}"; do 234 | codesign -vvv --deep --strict "$dylib" 235 | done 236 | 237 | - name: Package 238 | run: | 239 | . ci/ci_includes.generated.sh 240 | set -ex 241 | zipfile=$PWD/package/${PLUGIN_NAME}${PKG_SUFFIX}.zip 242 | mkdir package 243 | case ${{ matrix.obs }} in 244 | 27) (cd release/ && zip -r $zipfile ${PLUGIN_NAME}) ;; 245 | 28) (cd release/ && zip -r $zipfile ${PLUGIN_NAME}.plugin) ;; 246 | esac 247 | ci/macos/install-packagesbuild.sh 248 | packagesbuild \ 249 | --build-folder $PWD/package/ \ 250 | build/installer-macOS.generated.pkgproj 251 | 252 | - name: Productsign 253 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 254 | run: | 255 | . ci/ci_includes.generated.sh 256 | pkgfile=package/${PLUGIN_NAME}${PKG_SUFFIX}.pkg 257 | set -e 258 | . ci/ci_includes.generated.sh 259 | productsign --sign "${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}" $pkgfile package/${PLUGIN_NAME}-signed.pkg 260 | mv package/${PLUGIN_NAME}-signed.pkg $pkgfile 261 | 262 | - name: Notarize 263 | if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 264 | uses: norihiro/macos-notarize-action@v1 265 | with: 266 | path: package/* 267 | keychainProfile: AC_PASSWORD 268 | verbose: true 269 | 270 | - name: Upload build artifact 271 | uses: actions/upload-artifact@v3 272 | with: 273 | name: ${{ env.artifactName }} 274 | path: package/* 275 | 276 | windows_build: 277 | runs-on: windows-2022 278 | strategy: 279 | fail-fast: false 280 | matrix: 281 | obs: [27] 282 | arch: [x64] 283 | env: 284 | visualStudio: 'Visual Studio 17 2022' 285 | Configuration: 'RelWithDebInfo' 286 | defaults: 287 | run: 288 | shell: pwsh 289 | steps: 290 | - name: Checkout 291 | uses: actions/checkout@v3 292 | with: 293 | submodules: recursive 294 | - name: Download obs-studio 295 | id: obsdeps 296 | uses: norihiro/obs-studio-devel-action@v1-beta 297 | with: 298 | obs: ${{ matrix.obs }} 299 | qt: ${{ env.qt }} 300 | 301 | - name: Build plugin 302 | run: | 303 | $CmakeArgs = @( 304 | '-G', "${{ env.visualStudio }}" 305 | '-DQT_VERSION=${{ steps.obsdeps.outputs.OBS_QT_VERSION_MAJOR }}' 306 | '-DCMAKE_SYSTEM_VERSION=10.0.18363.657' 307 | "-DCMAKE_INSTALL_PREFIX=$(Resolve-Path -Path "./obs-build-dependencies/plugin-deps-${{ matrix.arch }}")" 308 | "-DCMAKE_PREFIX_PATH=$(Resolve-Path -Path "./obs-build-dependencies/plugin-deps-${{ matrix.arch }}")" 309 | ) 310 | cmake -S . -B build @CmakeArgs 311 | cmake --build build --config RelWithDebInfo -j 4 312 | cmake --install build --config RelWithDebInfo --prefix "$(Resolve-Path -Path .)/release" 313 | - name: Package plugin 314 | run: ci/windows/package-windows.cmd ${{ matrix.obs }} 315 | - name: Upload build artifact 316 | uses: actions/upload-artifact@v3 317 | with: 318 | name: ${{ env.artifactName }} 319 | path: package/* 320 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | /build/ 4 | /build32/ 5 | /build64/ 6 | /release/ 7 | /installer/Output/ 8 | 9 | .vscode 10 | .idea 11 | 12 | # ignore generated files 13 | *.generated.* 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(obs-async-source-duplication VERSION 0.4.1) 4 | 5 | set(PLUGIN_AUTHOR "Norihiro Kamae") 6 | 7 | set(MACOS_BUNDLEID "net.nagater.obs-async-source-duplication") 8 | set(ID_PREFIX "net.nagater.obs-async-source-duplication.") 9 | set(MACOS_PACKAGE_UUID "F3377825-5F69-443D-83E3-26ADC52DB3B9") 10 | set(MACOS_INSTALLER_UUID "D425E4A7-7F86-4DA7-B82B-3E597E9DCA5B") 11 | set(PLUGIN_URL "https://obsproject.com/forum/resources/asynchronous-source-duplication.1483/") 12 | 13 | set(LINUX_MAINTAINER_EMAIL "norihiro@nagater.net") 14 | 15 | # In case you need C++ 16 | set(CMAKE_CXX_STANDARD 11) 17 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 18 | 19 | find_package(libobs REQUIRED) 20 | include(cmake/ObsPluginHelpers.cmake) 21 | 22 | configure_file( 23 | src/plugin-macros.h.in 24 | ../src/plugin-macros.generated.h 25 | ) 26 | configure_file( 27 | installer/installer-Windows.iss.in 28 | ../installer/installer-Windows.generated.iss 29 | ) 30 | 31 | configure_file( 32 | ci/ci_includes.sh.in 33 | ../ci/ci_includes.generated.sh 34 | ) 35 | configure_file( 36 | ci/ci_includes.cmd.in 37 | ../ci/ci_includes.generated.cmd 38 | ) 39 | 40 | set(PLUGIN_SOURCES 41 | src/plugin-main.c 42 | src/async-source-duplication-filter.c 43 | src/async-source-duplication-source.c 44 | ) 45 | 46 | add_library(${CMAKE_PROJECT_NAME} MODULE ${PLUGIN_SOURCES}) 47 | 48 | target_link_libraries(${CMAKE_PROJECT_NAME} 49 | OBS::libobs 50 | ) 51 | 52 | if(OS_WINDOWS) 53 | # Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL when building with VS2019) 54 | if (MSVC) 55 | add_definitions(/MP /d2FH4-) 56 | add_definitions("-D_USE_MATH_DEFINES") 57 | endif() 58 | 59 | target_link_libraries(${CMAKE_PROJECT_NAME} OBS::w32-pthreads) 60 | endif() 61 | 62 | if(OS_LINUX) 63 | target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wall -Wextra) 64 | endif() 65 | 66 | if(APPLE) 67 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default") 68 | 69 | set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES PREFIX "") 70 | set(MACOSX_PLUGIN_GUI_IDENTIFIER "${MACOS_BUNDLEID}") 71 | set(MACOSX_PLUGIN_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION}") 72 | set(MACOSX_PLUGIN_SHORT_VERSION_STRING "1") 73 | endif() 74 | 75 | setup_plugin_target(${CMAKE_PROJECT_NAME}) 76 | 77 | configure_file(installer/installer-macOS.pkgproj.in installer-macOS.generated.pkgproj) 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asynchronous Source Duplication Plugin for OBS Studio 2 | 3 | ## Introduction 4 | 5 | This plugin provides a duplicated source of an asynchronous source. 6 | 7 | Asynchronous filters cannot be applied to a scene or a group. 8 | That means you cannot have one source as two scene-items with and without the asynchronous filter. 9 | 10 | For example, you want to have two sources; 11 | one is a video camera input and the other is the same video camera input but having a `Video Delay (Async)` filter to delay. 12 | However, you cannot have two separated sources that access the same video capture device. 13 | 14 | In such case, this plugin help you to duplicate the asynchronous source. 15 | 16 | ## Usage 17 | 18 | To start to use, 19 | 1. Select the scene you want to have the duplicated source. 20 | 2. Click `+` at the bottom of `Sources` list. 21 | 3. Click `Asynchronous Source Duplicator`. 22 | 4. Select the name of the source that you want to duplicate. 23 | 5. Click `Insert Filter to the Source` button. 24 | This step will add a filter to the source you selected above. 25 | 26 | To remove the filter, 27 | 1. Open filter dialog for the source. 28 | 2. Select `Asynchronous Source Duplication Filter` in the bottom of `Audio/Video Filters` list. 29 | 2. Click `-` at the bottom of `Audio/Video Filters` list. 30 | 31 | Note that the filter `Asynchronous Source Duplication Filter` should be located at the top so that other filters won't affect the duplicated source. 32 | 33 | ## Properties 34 | 35 | ### Source Name 36 | 37 | Choose the source you want to duplicate. 38 | 39 | ### Enable Buffering 40 | *Deprecated* 41 | 42 | Enable buffering of video frames in libobs. Default is disabled. 43 | 44 | ## Build and install 45 | ### Linux 46 | Use cmake to build on Linux. After checkout, run these commands. 47 | ``` 48 | sed -i 's;${CMAKE_INSTALL_FULL_LIBDIR};/usr/lib;' CMakeLists.txt 49 | mkdir build && cd build 50 | cmake -DCMAKE_INSTALL_PREFIX=/usr .. 51 | make 52 | sudo make install 53 | ``` 54 | 55 | ### macOS 56 | Use cmake to build on Linux. After checkout, run these commands. 57 | ``` 58 | mkdir build && cd build 59 | cmake .. 60 | make 61 | ``` 62 | -------------------------------------------------------------------------------- /ci/ci_includes.cmd.in: -------------------------------------------------------------------------------- 1 | set PluginName=@CMAKE_PROJECT_NAME@ 2 | set PluginVersion=@CMAKE_PROJECT_VERSION@ 3 | -------------------------------------------------------------------------------- /ci/ci_includes.sh.in: -------------------------------------------------------------------------------- 1 | PLUGIN_NAME="@CMAKE_PROJECT_NAME@" 2 | PLUGIN_VERSION="@CMAKE_PROJECT_VERSION@" 3 | MACOS_BUNDLEID="@MACOS_BUNDLEID@" 4 | LINUX_MAINTAINER_EMAIL="@LINUX_MAINTAINER_EMAIL@" -------------------------------------------------------------------------------- /ci/forum-update-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin_url": "https://obsproject.com/forum/resources/asynchronous-source-duplication.1483/" 3 | } 4 | -------------------------------------------------------------------------------- /ci/macos/change-rpath.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | libdir='' 4 | obsver='' 5 | 6 | while (($# > 0)); do 7 | case "$1" in 8 | -lib) 9 | libdir="$2" 10 | shift 2;; 11 | -obs) 12 | obsver="$2" 13 | shift 2;; 14 | *) 15 | break ;; 16 | esac 17 | done 18 | 19 | set -e 20 | 21 | function copy_local_dylib 22 | { 23 | local dylib 24 | t=$(mktemp) 25 | otool -L $1 > $t 26 | awk '/^ \/usr\/local\/(opt|Cellar)\/.*\.dylib/{print $1}' $t | 27 | while read -r dylib; do 28 | echo "Changing dependency $1 -> $dylib" 29 | local b=$(basename $dylib) 30 | if test ! -e $libdir/$b; then 31 | mkdir -p $libdir 32 | cp $dylib $libdir 33 | chmod +rwx $libdir/$b 34 | install_name_tool -id "@loader_path/$b" $libdir/$b 35 | copy_local_dylib $libdir/$b 36 | fi 37 | install_name_tool -change "$dylib" "@loader_path/../$libdir/$b" $1 38 | done 39 | rm -f "$t" 40 | } 41 | 42 | function change_obs27_libs 43 | { 44 | # obs-frontend-api: 45 | # OBS 27.2 provides only `libobs-frontend-api.dylib`. 46 | # OBS 28.0 will provide `libobs-frontend-api.1.dylib` and `libobs-frontend-api.dylib`. 47 | # libobs: 48 | # Both OBS 27.2 and 28.0 provides `libobs.dylib`, `libobs.0.dylib`, `libobs.framework/Versions/A/libobs`. 49 | 50 | install_name_tool \ 51 | -change @rpath/QtWidgets.framework/Versions/5/QtWidgets \ 52 | @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \ 53 | -change @rpath/QtGui.framework/Versions/5/QtGui \ 54 | @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \ 55 | -change @rpath/QtCore.framework/Versions/5/QtCore \ 56 | @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \ 57 | -change @rpath/libobs.framework/Versions/A/libobs \ 58 | @rpath/libobs.0.dylib \ 59 | -change @rpath/libobs-frontend-api.0.dylib \ 60 | @rpath/libobs-frontend-api.dylib \ 61 | "$1" 62 | } 63 | 64 | for i in "$@"; do 65 | case "$obsver" in 66 | 27 | 27.*) 67 | change_obs27_libs "$i" 68 | ;; 69 | 28 | 28.*) 70 | : # Not necessary to change dylib paths for OBS 28 71 | ;; 72 | esac 73 | copy_local_dylib "$i" 74 | done 75 | -------------------------------------------------------------------------------- /ci/macos/install-packagesbuild.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | if which packagesbuild; then 6 | exit 0 7 | fi 8 | 9 | packages_url='http://www.nagater.net/obs-studio/Packages.dmg' 10 | packages_hash='6afdd25386295974dad8f078b8f1e41cabebd08e72d970bf92f707c7e48b16c9' 11 | 12 | for ((retry=5; retry>0; retry--)); do 13 | curl -o Packages.dmg $packages_url 14 | sha256sum -c <<<"$packages_hash Packages.dmg" && break 15 | done 16 | 17 | hdiutil attach -noverify Packages.dmg 18 | packages_volume="$(hdiutil info -plist | grep '/Volumes/Packages' | sed 's/.*\(\/Volumes\/[^<]*\)<\/string>/\1/')" 19 | 20 | sudo installer -pkg "${packages_volume}/packages/Packages.pkg" -target / 21 | hdiutil detach "${packages_volume}" 22 | -------------------------------------------------------------------------------- /ci/windows/package-windows.cmd: -------------------------------------------------------------------------------- 1 | call "%~dp0..\ci_includes.generated.cmd" 2 | 3 | mkdir package 4 | cd package 5 | 6 | git describe --tags --always > package-version.txt 7 | set /p PackageVersion= 262 | DESTINATION $/${OBS_PLUGIN_DESTINATION} 263 | COMPONENT obs_rundir 264 | EXCLUDE_FROM_ALL) 265 | 266 | if(OS_WINDOWS) 267 | install( 268 | FILES $ 269 | CONFIGURATIONS "RelWithDebInfo" "Debug" 270 | DESTINATION ${OBS_PLUGIN_DESTINATION} 271 | COMPONENT ${target}_Runtime 272 | OPTIONAL) 273 | 274 | install( 275 | FILES $ 276 | CONFIGURATIONS "RelWithDebInfo" "Debug" 277 | DESTINATION $/${OBS_PLUGIN_DESTINATION} 278 | COMPONENT obs_rundir 279 | OPTIONAL EXCLUDE_FROM_ALL) 280 | endif() 281 | 282 | if(MSVC) 283 | target_link_options( 284 | ${target} 285 | PRIVATE 286 | "LINKER:/OPT:REF" 287 | "$<$>:LINKER\:/SAFESEH\:NO>" 288 | "$<$:LINKER\:/INCREMENTAL:NO>" 289 | "$<$:LINKER\:/INCREMENTAL:NO>") 290 | endif() 291 | 292 | setup_target_resources(${target} obs-plugins/${target}) 293 | 294 | if(OS_WINDOWS) 295 | add_custom_command( 296 | TARGET ${target} 297 | POST_BUILD 298 | COMMAND 299 | "${CMAKE_COMMAND}" -DCMAKE_INSTALL_PREFIX=${OBS_OUTPUT_DIR} 300 | -DCMAKE_INSTALL_COMPONENT=obs_rundir 301 | -DCMAKE_INSTALL_CONFIG_NAME=$ -P 302 | ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake 303 | COMMENT "Installing to plugin rundir" 304 | VERBATIM) 305 | endif() 306 | endfunction() 307 | 308 | function(setup_target_resources target destination) 309 | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) 310 | install( 311 | DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ 312 | DESTINATION ${OBS_DATA_DESTINATION}/${destination} 313 | USE_SOURCE_PERMISSIONS 314 | COMPONENT obs_plugins) 315 | 316 | install( 317 | DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data 318 | DESTINATION $/${OBS_DATA_DESTINATION}/${destination} 319 | USE_SOURCE_PERMISSIONS 320 | COMPONENT obs_rundir 321 | EXCLUDE_FROM_ALL) 322 | endif() 323 | endfunction() 324 | endif() 325 | -------------------------------------------------------------------------------- /cmake/bundle/macos/Plugin-Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleName 6 | ${MACOSX_PLUGIN_BUNDLE_NAME} 7 | CFBundleIdentifier 8 | ${MACOSX_PLUGIN_GUI_IDENTIFIER} 9 | CFBundleVersion 10 | ${MACOSX_PLUGIN_BUNDLE_VERSION} 11 | CFBundleShortVersionString 12 | ${MACOSX_PLUGIN_SHORT_VERSION_STRING} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleExecutable 16 | ${MACOSX_PLUGIN_EXECUTABLE_NAME} 17 | CFBundlePackageType 18 | ${MACOSX_PLUGIN_BUNDLE_TYPE} 19 | CFBundleSupportedPlatforms 20 | 21 | MacOSX 22 | 23 | LSMinimumSystemVersion 24 | 10.13 25 | 26 | 27 | -------------------------------------------------------------------------------- /cmake/bundle/macos/entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.device.camera 8 | 9 | com.apple.security.device.audio-input 10 | 11 | com.apple.security.cs.disable-library-validation 12 | 13 | 14 | com.apple.security.cs.allow-dyld-environment-variables 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/locale/en-US.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/norihiro/obs-async-source-duplication/1036e341189d8abbcd1716ed317984f614daaffd/data/locale/en-US.ini -------------------------------------------------------------------------------- /doc/async-source-duplication.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 41 | 46 | 47 | 49 | 57 | 62 | 63 | 71 | 76 | 77 | 85 | 90 | 91 | 99 | 104 | 105 | 113 | 118 | 119 | 127 | 132 | 133 | 141 | 146 | 147 | 155 | 160 | 161 | 169 | 174 | 175 | 183 | 188 | 189 | 197 | 202 | 203 | 211 | 216 | 217 | 225 | 230 | 231 | 239 | 244 | 245 | 253 | 258 | 259 | 260 | 264 | 271 | 278 | Source 289 | Asynchronous SourceDuplication Filter 305 | more filtersif you want 321 | 328 | 335 | Asynchronous SourceDuplicator 351 | Scene 362 | 369 | send to GPU 380 | 387 | Async filter chain 398 | 405 | 410 | 415 | 420 | 425 | Effect filter chain 436 | 443 | more filtersif you want 459 | 466 | Scene 477 | 484 | send to GPU 495 | 502 | Async filter chain 513 | 520 | 525 | 530 | 535 | 540 | Effect filter chain 551 | 558 | 563 | 564 | 565 | -------------------------------------------------------------------------------- /installer/installer-Windows.iss.in: -------------------------------------------------------------------------------- 1 | #define MyAppName "@CMAKE_PROJECT_NAME@" 2 | #define MyAppVersion "@CMAKE_PROJECT_VERSION@" 3 | #define MyAppPublisher "@PLUGIN_AUTHOR@" 4 | #define MyAppURL "@PLUGIN_URL@" 5 | 6 | [Setup] 7 | ; NOTE: The value of AppId uniquely identifies this application. 8 | ; Do not use the same AppId value in installers for other applications. 9 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 10 | AppId={{CD703FE5-1F2C-4837-BD3D-DD840D83C3E3} 11 | AppName={#MyAppName} 12 | AppVersion={#MyAppVersion} 13 | AppPublisher={#MyAppPublisher} 14 | AppPublisherURL={#MyAppURL} 15 | AppSupportURL={#MyAppURL} 16 | AppUpdatesURL={#MyAppURL} 17 | DefaultDirName={code:GetDirName} 18 | DefaultGroupName={#MyAppName} 19 | OutputBaseFilename={#MyAppName}-{#MyAppVersion}-Windows-Installer 20 | Compression=lzma 21 | SolidCompression=yes 22 | 23 | [Languages] 24 | Name: "english"; MessagesFile: "compiler:Default.isl" 25 | 26 | [Files] 27 | Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 28 | Source: "..\LICENSE"; Flags: dontcopy 29 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 30 | 31 | [Icons] 32 | Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}" 33 | Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" 34 | 35 | [Code] 36 | procedure InitializeWizard(); 37 | var 38 | GPLText: AnsiString; 39 | Page: TOutputMsgMemoWizardPage; 40 | begin 41 | ExtractTemporaryFile('LICENSE'); 42 | LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText); 43 | Page := CreateOutputMsgMemoPage(wpWelcome, 44 | 'License Information', 'Please review the license terms before installing {#MyAppName}', 45 | 'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.', 46 | String(GPLText) 47 | ); 48 | end; 49 | 50 | // credit where it's due : 51 | // following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45 52 | function GetDirName(Value: string): string; 53 | var 54 | InstallPath: string; 55 | begin 56 | // initialize default path, which will be returned when the following registry 57 | // key queries fail due to missing keys or for some different reason 58 | Result := '{pf}\obs-studio'; 59 | // query the first registry value; if this succeeds, return the obtained value 60 | if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then 61 | Result := InstallPath 62 | end; 63 | 64 | -------------------------------------------------------------------------------- /installer/installer-macOS.pkgproj.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PACKAGES 6 | 7 | 8 | MUST-CLOSE-APPLICATION-ITEMS 9 | 10 | MUST-CLOSE-APPLICATIONS 11 | 12 | PACKAGE_FILES 13 | 14 | DEFAULT_INSTALL_LOCATION 15 | / 16 | HIERARCHY 17 | 18 | CHILDREN 19 | 20 | 21 | CHILDREN 22 | 23 | GID 24 | 80 25 | PATH 26 | Applications 27 | PATH_TYPE 28 | 0 29 | PERMISSIONS 30 | 509 31 | TYPE 32 | 1 33 | UID 34 | 0 35 | 36 | 37 | CHILDREN 38 | 39 | 40 | CHILDREN 41 | 42 | 43 | CHILDREN 44 | 45 | 46 | CHILDREN 47 | 48 | 49 | BUNDLE_CAN_DOWNGRADE 50 | 51 | BUNDLE_POSTINSTALL_PATH 52 | 53 | PATH_TYPE 54 | 0 55 | 56 | BUNDLE_PREINSTALL_PATH 57 | 58 | PATH_TYPE 59 | 0 60 | 61 | CHILDREN 62 | 63 | GID 64 | 80 65 | PATH 66 | ../@RELATIVE_INSTALL_PATH@/@CMAKE_PROJECT_NAME@@FIRST_DIR_SUFFIX@ 67 | PATH_TYPE 68 | 1 69 | PERMISSIONS 70 | 493 71 | TYPE 72 | 3 73 | UID 74 | 0 75 | 76 | 77 | GID 78 | 80 79 | PATH 80 | plugins 81 | PATH_TYPE 82 | 2 83 | PERMISSIONS 84 | 509 85 | TYPE 86 | 2 87 | UID 88 | 0 89 | 90 | 91 | GID 92 | 80 93 | PATH 94 | obs-studio 95 | PATH_TYPE 96 | 2 97 | PERMISSIONS 98 | 509 99 | TYPE 100 | 2 101 | UID 102 | 0 103 | 104 | 105 | GID 106 | 80 107 | PATH 108 | Application Support 109 | PATH_TYPE 110 | 0 111 | PERMISSIONS 112 | 493 113 | TYPE 114 | 1 115 | UID 116 | 0 117 | 118 | 119 | CHILDREN 120 | 121 | GID 122 | 0 123 | PATH 124 | Automator 125 | PATH_TYPE 126 | 0 127 | PERMISSIONS 128 | 493 129 | TYPE 130 | 1 131 | UID 132 | 0 133 | 134 | 135 | CHILDREN 136 | 137 | GID 138 | 0 139 | PATH 140 | Documentation 141 | PATH_TYPE 142 | 0 143 | PERMISSIONS 144 | 493 145 | TYPE 146 | 1 147 | UID 148 | 0 149 | 150 | 151 | CHILDREN 152 | 153 | GID 154 | 0 155 | PATH 156 | Extensions 157 | PATH_TYPE 158 | 0 159 | PERMISSIONS 160 | 493 161 | TYPE 162 | 1 163 | UID 164 | 0 165 | 166 | 167 | CHILDREN 168 | 169 | GID 170 | 0 171 | PATH 172 | Filesystems 173 | PATH_TYPE 174 | 0 175 | PERMISSIONS 176 | 493 177 | TYPE 178 | 1 179 | UID 180 | 0 181 | 182 | 183 | CHILDREN 184 | 185 | GID 186 | 0 187 | PATH 188 | Frameworks 189 | PATH_TYPE 190 | 0 191 | PERMISSIONS 192 | 493 193 | TYPE 194 | 1 195 | UID 196 | 0 197 | 198 | 199 | CHILDREN 200 | 201 | GID 202 | 0 203 | PATH 204 | Input Methods 205 | PATH_TYPE 206 | 0 207 | PERMISSIONS 208 | 493 209 | TYPE 210 | 1 211 | UID 212 | 0 213 | 214 | 215 | CHILDREN 216 | 217 | GID 218 | 0 219 | PATH 220 | Internet Plug-Ins 221 | PATH_TYPE 222 | 0 223 | PERMISSIONS 224 | 493 225 | TYPE 226 | 1 227 | UID 228 | 0 229 | 230 | 231 | CHILDREN 232 | 233 | GID 234 | 0 235 | PATH 236 | LaunchAgents 237 | PATH_TYPE 238 | 0 239 | PERMISSIONS 240 | 493 241 | TYPE 242 | 1 243 | UID 244 | 0 245 | 246 | 247 | CHILDREN 248 | 249 | GID 250 | 0 251 | PATH 252 | LaunchDaemons 253 | PATH_TYPE 254 | 0 255 | PERMISSIONS 256 | 493 257 | TYPE 258 | 1 259 | UID 260 | 0 261 | 262 | 263 | CHILDREN 264 | 265 | GID 266 | 0 267 | PATH 268 | PreferencePanes 269 | PATH_TYPE 270 | 0 271 | PERMISSIONS 272 | 493 273 | TYPE 274 | 1 275 | UID 276 | 0 277 | 278 | 279 | CHILDREN 280 | 281 | GID 282 | 0 283 | PATH 284 | Preferences 285 | PATH_TYPE 286 | 0 287 | PERMISSIONS 288 | 493 289 | TYPE 290 | 1 291 | UID 292 | 0 293 | 294 | 295 | CHILDREN 296 | 297 | GID 298 | 80 299 | PATH 300 | Printers 301 | PATH_TYPE 302 | 0 303 | PERMISSIONS 304 | 493 305 | TYPE 306 | 1 307 | UID 308 | 0 309 | 310 | 311 | CHILDREN 312 | 313 | GID 314 | 0 315 | PATH 316 | PrivilegedHelperTools 317 | PATH_TYPE 318 | 0 319 | PERMISSIONS 320 | 1005 321 | TYPE 322 | 1 323 | UID 324 | 0 325 | 326 | 327 | CHILDREN 328 | 329 | GID 330 | 0 331 | PATH 332 | QuickLook 333 | PATH_TYPE 334 | 0 335 | PERMISSIONS 336 | 493 337 | TYPE 338 | 1 339 | UID 340 | 0 341 | 342 | 343 | CHILDREN 344 | 345 | GID 346 | 0 347 | PATH 348 | QuickTime 349 | PATH_TYPE 350 | 0 351 | PERMISSIONS 352 | 493 353 | TYPE 354 | 1 355 | UID 356 | 0 357 | 358 | 359 | CHILDREN 360 | 361 | GID 362 | 0 363 | PATH 364 | Screen Savers 365 | PATH_TYPE 366 | 0 367 | PERMISSIONS 368 | 493 369 | TYPE 370 | 1 371 | UID 372 | 0 373 | 374 | 375 | CHILDREN 376 | 377 | GID 378 | 0 379 | PATH 380 | Scripts 381 | PATH_TYPE 382 | 0 383 | PERMISSIONS 384 | 493 385 | TYPE 386 | 1 387 | UID 388 | 0 389 | 390 | 391 | CHILDREN 392 | 393 | GID 394 | 0 395 | PATH 396 | Services 397 | PATH_TYPE 398 | 0 399 | PERMISSIONS 400 | 493 401 | TYPE 402 | 1 403 | UID 404 | 0 405 | 406 | 407 | CHILDREN 408 | 409 | GID 410 | 0 411 | PATH 412 | Widgets 413 | PATH_TYPE 414 | 0 415 | PERMISSIONS 416 | 493 417 | TYPE 418 | 1 419 | UID 420 | 0 421 | 422 | 423 | GID 424 | 0 425 | PATH 426 | Library 427 | PATH_TYPE 428 | 0 429 | PERMISSIONS 430 | 493 431 | TYPE 432 | 1 433 | UID 434 | 0 435 | 436 | 437 | CHILDREN 438 | 439 | 440 | CHILDREN 441 | 442 | GID 443 | 0 444 | PATH 445 | Shared 446 | PATH_TYPE 447 | 0 448 | PERMISSIONS 449 | 1023 450 | TYPE 451 | 1 452 | UID 453 | 0 454 | 455 | 456 | GID 457 | 80 458 | PATH 459 | Users 460 | PATH_TYPE 461 | 0 462 | PERMISSIONS 463 | 493 464 | TYPE 465 | 1 466 | UID 467 | 0 468 | 469 | 470 | GID 471 | 0 472 | PATH 473 | / 474 | PATH_TYPE 475 | 0 476 | PERMISSIONS 477 | 493 478 | TYPE 479 | 1 480 | UID 481 | 0 482 | 483 | PAYLOAD_TYPE 484 | 0 485 | PRESERVE_EXTENDED_ATTRIBUTES 486 | 487 | SHOW_INVISIBLE 488 | 489 | SPLIT_FORKS 490 | 491 | TREAT_MISSING_FILES_AS_WARNING 492 | 493 | VERSION 494 | 5 495 | 496 | PACKAGE_SCRIPTS 497 | 498 | POSTINSTALL_PATH 499 | 500 | PATH_TYPE 501 | 0 502 | 503 | PREINSTALL_PATH 504 | 505 | PATH_TYPE 506 | 0 507 | 508 | RESOURCES 509 | 510 | 511 | PACKAGE_SETTINGS 512 | 513 | AUTHENTICATION 514 | 0 515 | CONCLUSION_ACTION 516 | 0 517 | FOLLOW_SYMBOLIC_LINKS 518 | 519 | IDENTIFIER 520 | @MACOS_BUNDLEID@ 521 | LOCATION 522 | 0 523 | NAME 524 | @CMAKE_PROJECT_NAME@ 525 | OVERWRITE_PERMISSIONS 526 | 527 | PAYLOAD_SIZE 528 | -1 529 | REFERENCE_PATH 530 | 531 | RELOCATABLE 532 | 533 | USE_HFS+_COMPRESSION 534 | 535 | VERSION 536 | @CMAKE_PROJECT_VERSION@ 537 | 538 | TYPE 539 | 0 540 | UUID 541 | @MACOS_PACKAGE_UUID@ 542 | 543 | 544 | PROJECT 545 | 546 | PROJECT_COMMENTS 547 | 548 | NOTES 549 | 550 | 551 | 552 | PROJECT_PRESENTATION 553 | 554 | BACKGROUND 555 | 556 | APPAREANCES 557 | 558 | DARK_AQUA 559 | 560 | LIGHT_AQUA 561 | 562 | 563 | SHARED_SETTINGS_FOR_ALL_APPAREANCES 564 | 565 | 566 | INSTALLATION TYPE 567 | 568 | HIERARCHIES 569 | 570 | INSTALLER 571 | 572 | LIST 573 | 574 | 575 | CHILDREN 576 | 577 | DESCRIPTION 578 | 579 | OPTIONS 580 | 581 | HIDDEN 582 | 583 | STATE 584 | 1 585 | 586 | PACKAGE_UUID 587 | @MACOS_PACKAGE_UUID@ 588 | TITLE 589 | 590 | TYPE 591 | 0 592 | UUID 593 | @MACOS_INSTALLER_UUID@ 594 | 595 | 596 | REMOVED 597 | 598 | 599 | 600 | MODE 601 | 0 602 | 603 | INSTALLATION_STEPS 604 | 605 | 606 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 607 | ICPresentationViewIntroductionController 608 | INSTALLER_PLUGIN 609 | Introduction 610 | LIST_TITLE_KEY 611 | InstallerSectionTitle 612 | 613 | 614 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 615 | ICPresentationViewReadMeController 616 | INSTALLER_PLUGIN 617 | ReadMe 618 | LIST_TITLE_KEY 619 | InstallerSectionTitle 620 | 621 | 622 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 623 | ICPresentationViewLicenseController 624 | INSTALLER_PLUGIN 625 | License 626 | LIST_TITLE_KEY 627 | InstallerSectionTitle 628 | 629 | 630 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 631 | ICPresentationViewDestinationSelectController 632 | INSTALLER_PLUGIN 633 | TargetSelect 634 | LIST_TITLE_KEY 635 | InstallerSectionTitle 636 | 637 | 638 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 639 | ICPresentationViewInstallationTypeController 640 | INSTALLER_PLUGIN 641 | PackageSelection 642 | LIST_TITLE_KEY 643 | InstallerSectionTitle 644 | 645 | 646 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 647 | ICPresentationViewInstallationController 648 | INSTALLER_PLUGIN 649 | Install 650 | LIST_TITLE_KEY 651 | InstallerSectionTitle 652 | 653 | 654 | ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS 655 | ICPresentationViewSummaryController 656 | INSTALLER_PLUGIN 657 | Summary 658 | LIST_TITLE_KEY 659 | InstallerSectionTitle 660 | 661 | 662 | INTRODUCTION 663 | 664 | LOCALIZATIONS 665 | 666 | 667 | LICENSE 668 | 669 | LOCALIZATIONS 670 | 671 | MODE 672 | 0 673 | 674 | README 675 | 676 | LOCALIZATIONS 677 | 678 | 679 | SUMMARY 680 | 681 | LOCALIZATIONS 682 | 683 | 684 | TITLE 685 | 686 | LOCALIZATIONS 687 | 688 | 689 | 690 | PROJECT_REQUIREMENTS 691 | 692 | LIST 693 | 694 | 695 | BEHAVIOR 696 | 3 697 | DICTIONARY 698 | 699 | IC_REQUIREMENT_OS_DISK_TYPE 700 | 1 701 | IC_REQUIREMENT_OS_DISTRIBUTION_TYPE 702 | 0 703 | IC_REQUIREMENT_OS_MINIMUM_VERSION 704 | 101300 705 | 706 | IC_REQUIREMENT_CHECK_TYPE 707 | 0 708 | IDENTIFIER 709 | fr.whitebox.Packages.requirement.os 710 | MESSAGE 711 | 712 | NAME 713 | Operating System 714 | STATE 715 | 716 | 717 | 718 | RESOURCES 719 | 720 | ROOT_VOLUME_ONLY 721 | 722 | 723 | PROJECT_SETTINGS 724 | 725 | ADVANCED_OPTIONS 726 | 727 | installer-script.domains:enable_currentUserHome 728 | 1 729 | 730 | BUILD_FORMAT 731 | 0 732 | BUILD_PATH 733 | 734 | PATH 735 | ../@RELATIVE_BUILD_PATH@ 736 | PATH_TYPE 737 | 1 738 | 739 | EXCLUDED_FILES 740 | 741 | 742 | PATTERNS_ARRAY 743 | 744 | 745 | REGULAR_EXPRESSION 746 | 747 | STRING 748 | .DS_Store 749 | TYPE 750 | 0 751 | 752 | 753 | PROTECTED 754 | 755 | PROXY_NAME 756 | Remove .DS_Store files 757 | PROXY_TOOLTIP 758 | Remove ".DS_Store" files created by the Finder. 759 | STATE 760 | 761 | 762 | 763 | PATTERNS_ARRAY 764 | 765 | 766 | REGULAR_EXPRESSION 767 | 768 | STRING 769 | .pbdevelopment 770 | TYPE 771 | 0 772 | 773 | 774 | PROTECTED 775 | 776 | PROXY_NAME 777 | Remove .pbdevelopment files 778 | PROXY_TOOLTIP 779 | Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. 780 | STATE 781 | 782 | 783 | 784 | PATTERNS_ARRAY 785 | 786 | 787 | REGULAR_EXPRESSION 788 | 789 | STRING 790 | CVS 791 | TYPE 792 | 1 793 | 794 | 795 | REGULAR_EXPRESSION 796 | 797 | STRING 798 | .cvsignore 799 | TYPE 800 | 0 801 | 802 | 803 | REGULAR_EXPRESSION 804 | 805 | STRING 806 | .cvspass 807 | TYPE 808 | 0 809 | 810 | 811 | REGULAR_EXPRESSION 812 | 813 | STRING 814 | .svn 815 | TYPE 816 | 1 817 | 818 | 819 | REGULAR_EXPRESSION 820 | 821 | STRING 822 | .git 823 | TYPE 824 | 1 825 | 826 | 827 | REGULAR_EXPRESSION 828 | 829 | STRING 830 | .gitignore 831 | TYPE 832 | 0 833 | 834 | 835 | PROTECTED 836 | 837 | PROXY_NAME 838 | Remove SCM metadata 839 | PROXY_TOOLTIP 840 | Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. 841 | STATE 842 | 843 | 844 | 845 | PATTERNS_ARRAY 846 | 847 | 848 | REGULAR_EXPRESSION 849 | 850 | STRING 851 | classes.nib 852 | TYPE 853 | 0 854 | 855 | 856 | REGULAR_EXPRESSION 857 | 858 | STRING 859 | designable.db 860 | TYPE 861 | 0 862 | 863 | 864 | REGULAR_EXPRESSION 865 | 866 | STRING 867 | info.nib 868 | TYPE 869 | 0 870 | 871 | 872 | PROTECTED 873 | 874 | PROXY_NAME 875 | Optimize nib files 876 | PROXY_TOOLTIP 877 | Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. 878 | STATE 879 | 880 | 881 | 882 | PATTERNS_ARRAY 883 | 884 | 885 | REGULAR_EXPRESSION 886 | 887 | STRING 888 | Resources Disabled 889 | TYPE 890 | 1 891 | 892 | 893 | PROTECTED 894 | 895 | PROXY_NAME 896 | Remove Resources Disabled folders 897 | PROXY_TOOLTIP 898 | Remove "Resources Disabled" folders. 899 | STATE 900 | 901 | 902 | 903 | SEPARATOR 904 | 905 | 906 | 907 | NAME 908 | @CMAKE_PROJECT_NAME@@PKG_SUFFIX@ 909 | PAYLOAD_ONLY 910 | 911 | TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING 912 | 913 | 914 | 915 | TYPE 916 | 0 917 | VERSION 918 | 2 919 | 920 | 921 | -------------------------------------------------------------------------------- /src/async-source-duplication-filter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "plugin-macros.generated.h" 5 | 6 | static const char *signals[] = { 7 | "void output_video(ptr frame)", 8 | "void output_audio(ptr audio)", 9 | NULL, 10 | }; 11 | 12 | struct filter_s 13 | { 14 | obs_source_t *context; 15 | 16 | pthread_mutex_t video_mutex; 17 | pthread_mutex_t audio_mutex; 18 | bool in_video; 19 | bool in_audio; 20 | 21 | volatile long show_refs; 22 | volatile long active_refs; 23 | 24 | gs_texrender_t *texrender; 25 | }; 26 | 27 | static const char *get_name(void *type_data) 28 | { 29 | UNUSED_PARAMETER(type_data); 30 | return obs_module_text("Asynchronous Source Duplication Filter"); 31 | } 32 | 33 | static void async_filter_video_internal(struct filter_s *s, struct obs_source_frame *frame) 34 | { 35 | struct calldata data; 36 | uint8_t stack[128]; 37 | calldata_init_fixed(&data, stack, sizeof(stack)); 38 | calldata_set_ptr(&data, "frame", frame); 39 | 40 | signal_handler_signal(obs_source_get_signal_handler(s->context), "output_video", &data); 41 | } 42 | 43 | static void async_filter_audio_internal(struct filter_s *s, struct obs_audio_data *audio) 44 | { 45 | const struct audio_output_info *obs_info = audio_output_get_info(obs_get_audio()); 46 | if (!obs_info) 47 | return; 48 | 49 | struct obs_source_audio srcaudio = { 50 | .frames = audio->frames, 51 | .timestamp = audio->timestamp, 52 | .speakers = obs_info->speakers, 53 | .format = obs_info->format, 54 | .samples_per_sec = obs_info->samples_per_sec, 55 | }; 56 | for (int i = 0; i < MAX_AV_PLANES; i++) 57 | srcaudio.data[i] = audio->data[i]; 58 | 59 | struct calldata data; 60 | uint8_t stack[128]; 61 | calldata_init_fixed(&data, stack, sizeof(stack)); 62 | calldata_set_ptr(&data, "audio", &srcaudio); 63 | 64 | signal_handler_signal(obs_source_get_signal_handler(s->context), "output_audio", &data); 65 | } 66 | 67 | static struct obs_source_frame *async_filter_video(void *data, struct obs_source_frame *frame) 68 | { 69 | struct filter_s *s = data; 70 | 71 | pthread_mutex_lock(&s->video_mutex); 72 | if (!s->in_video) { 73 | /* `in_video` will be true if a loop exists. */ 74 | s->in_video = true; 75 | async_filter_video_internal(s, frame); 76 | s->in_video = false; 77 | } 78 | pthread_mutex_unlock(&s->video_mutex); 79 | 80 | return frame; 81 | } 82 | 83 | static struct obs_audio_data *async_filter_audio(void *data, struct obs_audio_data *audio) 84 | { 85 | struct filter_s *s = data; 86 | 87 | pthread_mutex_lock(&s->audio_mutex); 88 | if (!s->in_audio) { 89 | /* `in_audio` will be true if a loop exists. */ 90 | s->in_audio = true; 91 | async_filter_audio_internal(s, audio); 92 | s->in_audio = false; 93 | } 94 | pthread_mutex_unlock(&s->audio_mutex); 95 | 96 | return audio; 97 | } 98 | 99 | #if LIBOBS_API_VER <= MAKE_SEMANTIC_VERSION(27, 0, 1) 100 | static inline int pthread_mutex_init_recursive(pthread_mutex_t *mutex) 101 | { 102 | pthread_mutexattr_t attr; 103 | int ret = pthread_mutexattr_init(&attr); 104 | if (ret == 0) { 105 | ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 106 | if (ret == 0) { 107 | ret = pthread_mutex_init(mutex, &attr); 108 | } 109 | 110 | pthread_mutexattr_destroy(&attr); 111 | } 112 | 113 | return ret; 114 | } 115 | #endif 116 | 117 | static void inc_showing(void *data, calldata_t *cd) 118 | { 119 | UNUSED_PARAMETER(cd); 120 | struct filter_s *s = data; 121 | 122 | if (os_atomic_inc_long(&s->show_refs) == 1) 123 | obs_source_inc_showing(obs_filter_get_parent(s->context)); 124 | } 125 | 126 | static void dec_showing(void *data, calldata_t *cd) 127 | { 128 | UNUSED_PARAMETER(cd); 129 | struct filter_s *s = data; 130 | 131 | if (os_atomic_dec_long(&s->show_refs) == 0) 132 | obs_source_dec_showing(obs_filter_get_parent(s->context)); 133 | } 134 | 135 | static void inc_active(void *data, calldata_t *cd) 136 | { 137 | UNUSED_PARAMETER(cd); 138 | struct filter_s *s = data; 139 | 140 | if (os_atomic_inc_long(&s->active_refs) == 1) 141 | obs_source_inc_active(obs_filter_get_parent(s->context)); 142 | } 143 | 144 | static void dec_active(void *data, calldata_t *cd) 145 | { 146 | UNUSED_PARAMETER(cd); 147 | struct filter_s *s = data; 148 | 149 | if (os_atomic_dec_long(&s->active_refs) == 0) 150 | obs_source_dec_active(obs_filter_get_parent(s->context)); 151 | } 152 | 153 | static void offscreen_render_cb(void *data, uint32_t cx, uint32_t cy) 154 | { 155 | struct filter_s *s = data; 156 | 157 | if (os_atomic_load_long(&s->show_refs) <= 0) 158 | return; 159 | 160 | obs_source_t *parent = obs_filter_get_parent(s->context); 161 | if (!parent) 162 | return; 163 | 164 | gs_texrender_reset(s->texrender); 165 | if (!gs_texrender_begin(s->texrender, 1, 1)) 166 | return; 167 | 168 | obs_source_video_render(parent); 169 | 170 | gs_texrender_end(s->texrender); 171 | } 172 | 173 | static void *create(obs_data_t *settings, obs_source_t *source) 174 | { 175 | struct filter_s *s = bzalloc(sizeof(struct filter_s)); 176 | s->context = source; 177 | 178 | pthread_mutex_init_recursive(&s->video_mutex); 179 | pthread_mutex_init_recursive(&s->audio_mutex); 180 | 181 | s->texrender = gs_texrender_create(GS_BGRA, GS_ZS_NONE); 182 | 183 | // update(s, settings); // no properties in this filter. 184 | 185 | signal_handler_add_array(obs_source_get_signal_handler(source), signals); 186 | proc_handler_t *ph = obs_source_get_proc_handler(source); 187 | proc_handler_add(ph, "void inc_showing()", inc_showing, s); 188 | proc_handler_add(ph, "void dec_showing()", dec_showing, s); 189 | proc_handler_add(ph, "void inc_active()", inc_active, s); 190 | proc_handler_add(ph, "void dec_active()", dec_active, s); 191 | 192 | obs_add_main_render_callback(offscreen_render_cb, s); 193 | 194 | return s; 195 | } 196 | 197 | static void filter_remove(void *data, obs_source_t *source) 198 | { 199 | UNUSED_PARAMETER(source); 200 | struct filter_s *s = data; 201 | 202 | while (os_atomic_load_long(&s->active_refs) > 0) 203 | dec_active(s, NULL); 204 | while (os_atomic_load_long(&s->show_refs) > 0) 205 | dec_showing(s, NULL); 206 | } 207 | 208 | static void destroy(void *data) 209 | { 210 | struct filter_s *s = data; 211 | 212 | obs_remove_main_render_callback(offscreen_render_cb, s); 213 | 214 | pthread_mutex_destroy(&s->audio_mutex); 215 | pthread_mutex_destroy(&s->video_mutex); 216 | 217 | obs_enter_graphics(); 218 | gs_texrender_destroy(s->texrender); 219 | obs_leave_graphics(); 220 | 221 | bfree(s); 222 | } 223 | 224 | const struct obs_source_info async_srcdup_filter = { 225 | .id = ID_PREFIX "filter", 226 | .type = OBS_SOURCE_TYPE_FILTER, 227 | .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO, 228 | .get_name = get_name, 229 | .create = create, 230 | .destroy = destroy, 231 | .filter_remove = filter_remove, 232 | .filter_video = async_filter_video, 233 | .filter_audio = async_filter_audio, 234 | }; 235 | -------------------------------------------------------------------------------- /src/async-source-duplication-source.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "plugin-macros.generated.h" 5 | 6 | struct source_s 7 | { 8 | obs_source_t *context; 9 | 10 | // properties 11 | char *target_source_name; 12 | 13 | pthread_mutex_t target_update_mutex; 14 | obs_weak_source_t *target_weak; 15 | float target_check; 16 | 17 | bool shown; 18 | bool activated; 19 | }; 20 | 21 | static const char *get_name(void *type_data) 22 | { 23 | UNUSED_PARAMETER(type_data); 24 | return obs_module_text("Asynchronous Source Duplicator"); 25 | } 26 | 27 | struct target_prop_info 28 | { 29 | obs_property_t *prop; 30 | struct source_s *s; 31 | }; 32 | 33 | static bool add_target_sources_cb(void *data, obs_source_t *source) 34 | { 35 | struct target_prop_info *info = data; 36 | 37 | if (info->s && source == info->s->context) 38 | return true; 39 | 40 | uint32_t caps = obs_source_get_output_flags(source); 41 | bool is_async_video = (caps & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; 42 | bool is_audio = !!(caps & OBS_SOURCE_AUDIO); 43 | if (!is_async_video && !is_audio) 44 | return true; 45 | 46 | const char *name = obs_source_get_name(source); 47 | obs_property_list_add_string(info->prop, name, name); 48 | 49 | return true; 50 | } 51 | 52 | static inline obs_source_t *source_to_filter(obs_source_t *src); 53 | 54 | static bool target_source_modified_cb(obs_properties_t *props, obs_property_t *p, obs_data_t *settings) 55 | { 56 | bool ret = false; 57 | 58 | bool no_filter = false; 59 | const char *target_source_name = obs_data_get_string(settings, "target_source_name"); 60 | obs_source_t *src = obs_get_source_by_name(target_source_name); 61 | if (src) { 62 | obs_source_t *target = source_to_filter(src); 63 | if (!target) 64 | no_filter = true; 65 | obs_source_release(target); 66 | } 67 | obs_source_release(src); 68 | 69 | obs_property_t *add_filter_button = obs_properties_get(props, "target_source_add_filter"); 70 | if (obs_property_visible(add_filter_button) != no_filter) { 71 | obs_property_set_visible(add_filter_button, no_filter); 72 | ret = true; 73 | } 74 | 75 | return ret; 76 | } 77 | 78 | static void add_filter(obs_source_t *src) 79 | { 80 | const char *display_name = obs_source_get_display_name(ID_PREFIX "filter"); 81 | struct dstr name; 82 | dstr_init_copy(&name, display_name); 83 | obs_source_t *found; 84 | int ix = 0; 85 | while ((found = obs_source_get_filter_by_name(src, name.array))) { 86 | obs_source_release(found); 87 | dstr_printf(&name, "%s (%d)", display_name, ++ix); 88 | } 89 | 90 | obs_source_t *filter = obs_source_create_private(ID_PREFIX "filter", name.array, NULL); 91 | obs_source_filter_add(src, filter); 92 | blog(LOG_INFO, "added filter '%s' (%p) to source '%s'", name.array, filter, obs_source_get_name(src)); 93 | obs_source_release(filter); 94 | dstr_free(&name); 95 | } 96 | 97 | static bool add_filter_cb(obs_properties_t *props, obs_property_t *property, void *data) 98 | { 99 | if (!data) 100 | return false; 101 | struct source_s *s = data; 102 | 103 | obs_source_t *src = obs_get_source_by_name(s->target_source_name); 104 | if (src) { 105 | add_filter(src); 106 | s->target_check = 0.0f; 107 | obs_property_t *add_filter_button = obs_properties_get(props, "target_source_add_filter"); 108 | obs_property_set_visible(add_filter_button, false); 109 | obs_source_release(src); 110 | return true; 111 | } 112 | 113 | return false; 114 | } 115 | 116 | static obs_properties_t *get_properties(void *data) 117 | { 118 | obs_properties_t *props = obs_properties_create(); 119 | obs_property_t *prop; 120 | obs_property_t *target_source_name; 121 | 122 | target_source_name = obs_properties_add_list(props, "target_source_name", obs_module_text("Source Name"), 123 | OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); 124 | 125 | prop = obs_properties_add_button2(props, "target_source_add_filter", 126 | obs_module_text("Insert Filter to the Source"), add_filter_cb, data); 127 | obs_property_set_visible(prop, false); 128 | 129 | obs_property_set_modified_callback(target_source_name, target_source_modified_cb); 130 | struct target_prop_info info = {target_source_name, data}; 131 | obs_enum_sources(add_target_sources_cb, &info); 132 | 133 | obs_properties_add_bool(props, "buffered", obs_module_text("Enable Buffering")); 134 | 135 | return props; 136 | } 137 | 138 | static void output_video(void *data, calldata_t *cd) 139 | { 140 | struct source_s *s = data; 141 | 142 | struct obs_source_frame *frame = calldata_ptr(cd, "frame"); 143 | obs_source_output_video(s->context, frame); 144 | } 145 | 146 | static void output_audio(void *data, calldata_t *cd) 147 | { 148 | struct source_s *s = data; 149 | 150 | struct obs_source_audio *audio = calldata_ptr(cd, "audio"); 151 | obs_source_output_audio(s->context, audio); 152 | } 153 | 154 | static void target_inc_showing(obs_source_t *target) 155 | { 156 | proc_handler_t *ph = obs_source_get_proc_handler(target); 157 | proc_handler_call(ph, "inc_showing", NULL); 158 | } 159 | 160 | static void target_dec_showing(obs_source_t *target) 161 | { 162 | proc_handler_t *ph = obs_source_get_proc_handler(target); 163 | proc_handler_call(ph, "dec_showing", NULL); 164 | } 165 | 166 | static void target_inc_active(obs_source_t *target) 167 | { 168 | proc_handler_t *ph = obs_source_get_proc_handler(target); 169 | proc_handler_call(ph, "inc_active", NULL); 170 | } 171 | 172 | static void target_dec_active(obs_source_t *target) 173 | { 174 | proc_handler_t *ph = obs_source_get_proc_handler(target); 175 | proc_handler_call(ph, "dec_active", NULL); 176 | } 177 | 178 | static void release_weak_target(struct source_s *s) 179 | { 180 | if (!s->target_weak) 181 | return; 182 | 183 | obs_source_t *target = obs_weak_source_get_source(s->target_weak); 184 | if (target) { 185 | signal_handler_t *sh = obs_source_get_signal_handler(target); 186 | signal_handler_disconnect(sh, "output_video", output_video, s); 187 | signal_handler_disconnect(sh, "output_audio", output_audio, s); 188 | 189 | if (s->shown) 190 | target_dec_showing(target); 191 | if (s->activated) 192 | target_dec_active(target); 193 | 194 | obs_source_release(target); 195 | } 196 | 197 | obs_weak_source_release(s->target_weak); 198 | s->target_weak = NULL; 199 | } 200 | 201 | static void set_weak_target(struct source_s *s, obs_source_t *target) 202 | { 203 | if (s->target_weak) 204 | release_weak_target(s); 205 | s->target_weak = obs_source_get_weak_source(target); 206 | s->target_check = 3.0f; 207 | 208 | signal_handler_t *sh = obs_source_get_signal_handler(target); 209 | signal_handler_connect(sh, "output_video", output_video, s); 210 | signal_handler_connect(sh, "output_audio", output_audio, s); 211 | 212 | if (s->shown) 213 | target_inc_showing(target); 214 | if (s->activated) 215 | target_inc_active(target); 216 | } 217 | 218 | static void find_filter(obs_source_t *parent, obs_source_t *child, void *param) 219 | { 220 | obs_source_t **target = param; 221 | if (*target) 222 | return; 223 | const char *id = obs_source_get_id(child); 224 | if (strcmp(id, ID_PREFIX "filter") == 0) 225 | *target = obs_source_get_ref(child); 226 | } 227 | 228 | static inline obs_source_t *source_to_filter(obs_source_t *src) 229 | { 230 | obs_source_t *target = NULL; 231 | obs_source_enum_filters(src, find_filter, &target); 232 | return target; 233 | } 234 | 235 | static obs_source_t *get_filter_by_target_source_name(const char *target_source_name) 236 | { 237 | obs_source_t *src = obs_get_source_by_name(target_source_name); 238 | if (!src) 239 | return NULL; 240 | obs_source_t *target = source_to_filter(src); 241 | obs_source_release(src); 242 | return target; 243 | } 244 | 245 | static inline void set_weak_target_by_name(struct source_s *s, const char *target_source_name) 246 | { 247 | obs_source_t *target = get_filter_by_target_source_name(target_source_name); 248 | if (target) { 249 | set_weak_target(s, target); 250 | obs_source_release(target); 251 | } 252 | } 253 | 254 | static void update(void *data, obs_data_t *settings) 255 | { 256 | struct source_s *s = data; 257 | 258 | const char *target_source_name = obs_data_get_string(settings, "target_source_name"); 259 | pthread_mutex_lock(&s->target_update_mutex); 260 | if (target_source_name && (!s->target_source_name || strcmp(target_source_name, s->target_source_name))) { 261 | bfree(s->target_source_name); 262 | s->target_source_name = bstrdup(target_source_name); 263 | release_weak_target(s); 264 | set_weak_target_by_name(s, target_source_name); 265 | } 266 | pthread_mutex_unlock(&s->target_update_mutex); 267 | 268 | bool buffered = obs_data_get_bool(settings, "buffered"); 269 | obs_source_set_async_unbuffered(s->context, !buffered); 270 | } 271 | 272 | static void tick(void *data, float seconds) 273 | { 274 | struct source_s *s = data; 275 | 276 | pthread_mutex_lock(&s->target_update_mutex); 277 | if ((s->target_check -= seconds) < 0.0f) { 278 | obs_source_t *target_by_name = get_filter_by_target_source_name(s->target_source_name); 279 | obs_source_t *target_by_weak = obs_weak_source_get_source(s->target_weak); 280 | if (target_by_name != target_by_weak) { 281 | blog(LOG_INFO, "updating target from %p to %p", target_by_weak, target_by_name); 282 | set_weak_target(s, target_by_name); 283 | } 284 | obs_source_release(target_by_weak); 285 | obs_source_release(target_by_name); 286 | s->target_check = 3.0f; 287 | } 288 | pthread_mutex_unlock(&s->target_update_mutex); 289 | } 290 | 291 | static void *create(obs_data_t *settings, obs_source_t *source) 292 | { 293 | struct source_s *s = bzalloc(sizeof(struct source_s)); 294 | s->context = source; 295 | pthread_mutex_init(&s->target_update_mutex, NULL); 296 | 297 | update(s, settings); 298 | 299 | return s; 300 | } 301 | 302 | static void destroy(void *data) 303 | { 304 | struct source_s *s = data; 305 | 306 | release_weak_target(s); 307 | pthread_mutex_destroy(&s->target_update_mutex); 308 | bfree(s->target_source_name); 309 | 310 | bfree(s); 311 | } 312 | 313 | static void show(void *data) 314 | { 315 | struct source_s *s = data; 316 | 317 | pthread_mutex_lock(&s->target_update_mutex); 318 | obs_source_t *target = obs_weak_source_get_source(s->target_weak); 319 | if (target && !s->shown) 320 | target_inc_showing(target); 321 | s->shown = true; 322 | pthread_mutex_unlock(&s->target_update_mutex); 323 | obs_source_release(target); 324 | } 325 | 326 | static void hide(void *data) 327 | { 328 | struct source_s *s = data; 329 | 330 | pthread_mutex_lock(&s->target_update_mutex); 331 | obs_source_t *target = obs_weak_source_get_source(s->target_weak); 332 | if (target && s->shown) 333 | target_dec_showing(target); 334 | s->shown = false; 335 | pthread_mutex_unlock(&s->target_update_mutex); 336 | obs_source_release(target); 337 | } 338 | 339 | static void activate(void *data) 340 | { 341 | struct source_s *s = data; 342 | 343 | pthread_mutex_lock(&s->target_update_mutex); 344 | obs_source_t *target = obs_weak_source_get_source(s->target_weak); 345 | if (target && !s->activated) 346 | target_inc_active(target); 347 | s->activated = true; 348 | pthread_mutex_unlock(&s->target_update_mutex); 349 | obs_source_release(target); 350 | } 351 | 352 | static void deactivate(void *data) 353 | { 354 | struct source_s *s = data; 355 | 356 | pthread_mutex_lock(&s->target_update_mutex); 357 | obs_source_t *target = obs_weak_source_get_source(s->target_weak); 358 | if (target && s->activated) 359 | target_dec_active(target); 360 | s->activated = false; 361 | pthread_mutex_unlock(&s->target_update_mutex); 362 | obs_source_release(target); 363 | } 364 | 365 | const struct obs_source_info async_srcdup_source = { 366 | .id = ID_PREFIX "source", 367 | .type = OBS_SOURCE_TYPE_INPUT, 368 | .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO, 369 | .get_name = get_name, 370 | .create = create, 371 | .destroy = destroy, 372 | .update = update, 373 | .video_tick = tick, 374 | .get_properties = get_properties, 375 | .show = show, 376 | .hide = hide, 377 | .activate = activate, 378 | .deactivate = deactivate, 379 | }; 380 | -------------------------------------------------------------------------------- /src/plugin-macros.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | OBS Asynchronous Source Duplication Plugin 3 | Copyright (C) 2022 Norihiro Kamae 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program; if not, write to the Free Software Foundation, Inc., 17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | 20 | #ifndef PLUGINNAME_H 21 | #define PLUGINNAME_H 22 | 23 | #define PLUGIN_NAME "@CMAKE_PROJECT_NAME@" 24 | #define PLUGIN_VERSION "@CMAKE_PROJECT_VERSION@" 25 | #define ID_PREFIX "@ID_PREFIX@" 26 | 27 | #define blog(level, msg, ...) blog(level, "[" PLUGIN_NAME "] " msg, ##__VA_ARGS__) 28 | 29 | #endif // PLUGINNAME_H 30 | -------------------------------------------------------------------------------- /src/plugin-main.c: -------------------------------------------------------------------------------- 1 | /* 2 | OBS Asynchronous Source Duplication Plugin 3 | Copyright (C) 2022 Norihiro Kamae 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program; if not, write to the Free Software Foundation, Inc., 17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | */ 19 | 20 | #include 21 | 22 | #include "plugin-macros.generated.h" 23 | 24 | OBS_DECLARE_MODULE() 25 | OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") 26 | 27 | extern const struct obs_source_info async_srcdup_filter; 28 | extern const struct obs_source_info async_srcdup_source; 29 | 30 | bool obs_module_load(void) 31 | { 32 | obs_register_source(&async_srcdup_filter); 33 | obs_register_source(&async_srcdup_source); 34 | blog(LOG_INFO, "plugin loaded (version %s)", PLUGIN_VERSION); 35 | return true; 36 | } 37 | 38 | void obs_module_unload() 39 | { 40 | blog(LOG_INFO, "plugin unloaded"); 41 | } 42 | --------------------------------------------------------------------------------