├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── NDIlib └── __init__.py ├── README.md ├── cmake └── Modules │ └── FindNDI.cmake ├── example ├── find.py ├── recv.py ├── recv_audio_16bpp.py ├── recv_audio_sd.py ├── recv_av.py ├── recv_cv.py ├── recv_framesync.py ├── recv_framesync_resend.py ├── requirements.txt ├── routing.py ├── send_audio.py ├── send_audio_16bpp.py ├── send_capture.py ├── send_png.py └── send_video.py ├── setup.py └── src └── main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: Never 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: All 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: false 30 | AfterControlStatement: false 31 | AfterEnum: false 32 | AfterFunction: false 33 | AfterNamespace: false 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: false 37 | AfterExternBlock: false 38 | BeforeCatch: false 39 | BeforeElse: false 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Attach 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 80 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: false 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Preserve 70 | IncludeCategories: 71 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 75 | Priority: 3 76 | SortPriority: 0 77 | - Regex: '.*' 78 | Priority: 1 79 | SortPriority: 0 80 | IncludeIsMainRegex: '(Test)?$' 81 | IncludeIsMainSourceRegex: '' 82 | IndentCaseLabels: false 83 | IndentGotoLabels: true 84 | IndentPPDirectives: None 85 | IndentWidth: 2 86 | IndentWrappedFunctionNames: false 87 | JavaScriptQuotes: Leave 88 | JavaScriptWrapImports: true 89 | KeepEmptyLinesAtTheStartOfBlocks: true 90 | MacroBlockBegin: '' 91 | MacroBlockEnd: '' 92 | MaxEmptyLinesToKeep: 1 93 | NamespaceIndentation: None 94 | ObjCBinPackProtocolList: Auto 95 | ObjCBlockIndentWidth: 2 96 | ObjCSpaceAfterProperty: false 97 | ObjCSpaceBeforeProtocolList: true 98 | PenaltyBreakAssignment: 2 99 | PenaltyBreakBeforeFirstCallParameter: 19 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | PenaltyBreakTemplateDeclaration: 10 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 60 106 | PointerAlignment: Right 107 | ReflowComments: true 108 | SortIncludes: true 109 | SortUsingDeclarations: true 110 | SpaceAfterCStyleCast: false 111 | SpaceAfterLogicalNot: false 112 | SpaceAfterTemplateKeyword: true 113 | SpaceBeforeAssignmentOperators: true 114 | SpaceBeforeCpp11BracedList: false 115 | SpaceBeforeCtorInitializerColon: true 116 | SpaceBeforeInheritanceColon: true 117 | SpaceBeforeParens: ControlStatements 118 | SpaceBeforeRangeBasedForLoopColon: true 119 | SpaceInEmptyBlock: false 120 | SpaceInEmptyParentheses: false 121 | SpacesBeforeTrailingComments: 1 122 | SpacesInAngles: false 123 | SpacesInConditionalStatement: false 124 | SpacesInContainerLiterals: true 125 | SpacesInCStyleCastParentheses: false 126 | SpacesInParentheses: false 127 | SpacesInSquareBrackets: false 128 | SpaceBeforeSquareBrackets: false 129 | Standard: Latest 130 | StatementMacros: 131 | - Q_UNUSED 132 | - QT_REQUIRE_VERSION 133 | TabWidth: 8 134 | UseCRLF: false 135 | UseTab: Never 136 | ... 137 | 138 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/c++,code,cmake,python 2 | # Edit at https://www.gitignore.io/?templates=c++,code,cmake,python 3 | 4 | ### C++ ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | *.obj 13 | 14 | # Precompiled Headers 15 | *.gch 16 | *.pch 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | 38 | ### CMake ### 39 | CMakeLists.txt.user 40 | CMakeCache.txt 41 | CMakeFiles 42 | CMakeScripts 43 | Testing 44 | Makefile 45 | cmake_install.cmake 46 | install_manifest.txt 47 | compile_commands.json 48 | CTestTestfile.cmake 49 | _deps 50 | 51 | ### CMake Patch ### 52 | # External projects 53 | *-prefix/ 54 | 55 | ### Code ### 56 | .vscode/* 57 | !.vscode/settings.json 58 | !.vscode/tasks.json 59 | !.vscode/launch.json 60 | !.vscode/extensions.json 61 | 62 | ### Python ### 63 | # Byte-compiled / optimized / DLL files 64 | __pycache__/ 65 | *.py[cod] 66 | *$py.class 67 | 68 | # C extensions 69 | 70 | # Distribution / packaging 71 | .Python 72 | build/ 73 | develop-eggs/ 74 | dist/ 75 | downloads/ 76 | eggs/ 77 | .eggs/ 78 | lib/ 79 | lib64/ 80 | parts/ 81 | sdist/ 82 | var/ 83 | wheels/ 84 | pip-wheel-metadata/ 85 | share/python-wheels/ 86 | *.egg-info/ 87 | .installed.cfg 88 | *.egg 89 | MANIFEST 90 | 91 | # PyInstaller 92 | # Usually these files are written by a python script from a template 93 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 94 | *.manifest 95 | *.spec 96 | 97 | # Installer logs 98 | pip-log.txt 99 | pip-delete-this-directory.txt 100 | 101 | # Unit test / coverage reports 102 | htmlcov/ 103 | .tox/ 104 | .nox/ 105 | .coverage 106 | .coverage.* 107 | .cache 108 | nosetests.xml 109 | coverage.xml 110 | *.cover 111 | .hypothesis/ 112 | .pytest_cache/ 113 | 114 | # Translations 115 | *.mo 116 | *.pot 117 | 118 | # Scrapy stuff: 119 | .scrapy 120 | 121 | # Sphinx documentation 122 | docs/_build/ 123 | 124 | # PyBuilder 125 | target/ 126 | 127 | # pyenv 128 | .python-version 129 | 130 | # pipenv 131 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 132 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 133 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 134 | # install all needed dependencies. 135 | #Pipfile.lock 136 | 137 | # celery beat schedule file 138 | celerybeat-schedule 139 | 140 | # SageMath parsed files 141 | *.sage.py 142 | 143 | # Spyder project settings 144 | .spyderproject 145 | .spyproject 146 | 147 | # Rope project settings 148 | .ropeproject 149 | 150 | # Mr Developer 151 | .mr.developer.cfg 152 | .project 153 | .pydevproject 154 | 155 | # mkdocs documentation 156 | /site 157 | 158 | # mypy 159 | .mypy_cache/ 160 | .dmypy.json 161 | dmypy.json 162 | 163 | # Pyre type checker 164 | .pyre/ 165 | 166 | # End of https://www.gitignore.io/api/c++,code,cmake,python 167 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/pybind11"] 2 | path = lib/pybind11 3 | url = https://github.com/pybind/pybind11.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | project(NDIlib VERSION 5.1.1) 4 | 5 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) 6 | 7 | set(CMAKE_CXX_STANDARD 17) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12") 10 | set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") 11 | 12 | add_subdirectory("lib/pybind11") 13 | 14 | find_package(NDI REQUIRED) 15 | 16 | file(GLOB INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp) 17 | file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) 18 | 19 | source_group("" FILES ${SOURCES} ${INCLUDES}) 20 | 21 | pybind11_add_module(NDIlib SHARED ${INCLUDES} ${SOURCES}) 22 | 23 | target_include_directories(NDIlib PRIVATE ${NDI_INCLUDE_DIR}) 24 | target_link_directories(NDIlib PRIVATE ${NDI_LIBRARY_DIR}) 25 | target_link_libraries(NDIlib PRIVATE pybind11::module ${NDI_LIBS}) 26 | 27 | set_target_properties(NDIlib PROPERTIES SKIP_RPATH TRUE) 28 | set_target_properties(NDIlib PROPERTIES BUILD_RPATH_USE_ORIGIN TRUE) 29 | set_target_properties(NDIlib PROPERTIES SKIP_BUILD_RPATH FALSE) 30 | set_target_properties(NDIlib PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) 31 | set_target_properties(NDIlib PROPERTIES INSTALL_RPATH_USE_LINK_PATH FALSE) 32 | if(APPLE) 33 | set_target_properties(NDIlib PROPERTIES INSTALL_RPATH "@loader_path") 34 | elseif(UNIX) 35 | set_target_properties(NDIlib PROPERTIES INSTALL_RPATH "$ORIGIN") 36 | endif() 37 | 38 | # install 39 | install( 40 | TARGETS NDIlib 41 | RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} 42 | LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} 43 | ) 44 | if(WIN32) 45 | install( 46 | FILES 47 | "${NDI_DIR}/Bin/${NDI_ARCH}/Processing.NDI.Lib.${NDI_ARCH}.dll" 48 | "${NDI_LICENSE_DIR}/Processing.NDI.Lib.Licenses.txt" 49 | DESTINATION ${CMAKE_INSTALL_PREFIX} 50 | ) 51 | elseif(APPLE) 52 | install( 53 | FILES 54 | "${NDI_LIBRARY_DIR}/libndi.dylib" 55 | "${NDI_LICENSE_DIR}/libndi_licenses.txt" 56 | DESTINATION ${CMAKE_INSTALL_PREFIX} 57 | ) 58 | elseif(UNIX) 59 | file(GLOB DLL "${NDI_LIBRARY_DIR}/libndi.so*") 60 | install( 61 | FILES 62 | ${DLL} 63 | "${NDI_LICENSE_DIR}/libndi_licenses.txt" 64 | DESTINATION ${CMAKE_INSTALL_PREFIX} 65 | ) 66 | endif() 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2022 Naoto Kondo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NDIlib/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | if os.name == 'nt' and sys.version_info.major >= 3 and sys.version_info.minor >= 8: 5 | os.add_dll_directory(os.path.dirname(__file__)) 6 | 7 | from .NDIlib import * 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ndi-python 2 | NewTek NDI Python wrapper 3 | 4 | ## Installation 5 | ndi-python is support pypi package now. https://pypi.org/project/ndi-python/ 6 | So you can install it using pip if it is a supported environment. 7 | ``` 8 | pip install ndi-python 9 | ``` 10 | The supported environment is as follows. 11 | - Windows x64 Python(3.7-3.10) 12 | - macOS x64(>=10.12),arm64(>=11.0) Python(3.7-3.10) 13 | - Linux x64,aarch64 Python(3.7-3.10) 14 | 15 | ## Setup Avahi 16 | Linux requires Avahi to search for NDI sources. 17 | Please install according to your environment. 18 | 19 | ### Ubuntu 20 | ``` 21 | sudo apt install avahi-daemon 22 | sudo systemctl enable --now avahi-daemon 23 | ``` 24 | 25 | ### Arch Linux 26 | ``` 27 | sudo pacman -S avahi 28 | sudo systemctl enable --now avahi-daemon 29 | ``` 30 | 31 | ## Run examples 32 | I've ported some examples to python. 33 | You can try it by running the ndi-python examples. 34 | ``` 35 | git clone --recursive https://github.com/buresu/ndi-python.git 36 | cd ndi-python/example 37 | pip install -r requirements.txt 38 | python find.py 39 | ``` 40 | 41 | ## Development 42 | 43 | ### Install NDI SDK 44 | NDI SDK is required for development. 45 | You should install according to your environment. 46 | 47 | ### Windows, Mac, Ubuntu 48 | Donwload and install NDI SDK. 49 | https://ndi.tv/sdk/ 50 | 51 | ### Arch Linux 52 | ``` 53 | yay -S ndi-sdk 54 | ``` 55 | 56 | ## Build 57 | ### Build with setup.py 58 | ``` 59 | git clone --recursive https://github.com/buresu/ndi-python.git 60 | cd ndi-python 61 | python setup.py build 62 | ``` 63 | For ubuntu you need to set the SDK directory in NDI_SDK_DIR as cmake options. 64 | ``` 65 | env CMAKE_ARGS="-DNDI_SDK_DIR=/path/to/ndisdk" python setup.py build 66 | ``` 67 | You can also specify the python version. 68 | ``` 69 | env CMAKE_ARGS="-DNDI_SDK_DIR=/path/to/ndisdk -DPYTHON_EXECUTABLE=/path/to/python3.8 -DPYBIND11_PYTHON_VERSION=3.8" /path/to/python3.8 setup.py build 70 | ``` 71 | 72 | ### Build for Python package 73 | ``` 74 | python setup.py bdist_wheel 75 | ``` 76 | 77 | ### Build only CMake 78 | ``` 79 | git clone --recursive https://github.com/buresu/ndi-python.git 80 | cd /path/to/build 81 | cmake /path/to/project 82 | cmake --build /path/to/build --config Release 83 | ``` 84 | 85 | For ubuntu you need to set the SDK directory in NDI_SDK_DIR. 86 | And build as follows. 87 | ``` 88 | cmake /path/to/project -DNDI_SDK_DIR=/path/to/ndisdk 89 | cmake --build /path/to/build --config Release 90 | ``` 91 | After build copy ndi-python binary and NDI binary to execute directory. 92 | 93 | ## License 94 | ndi-python is MIT License 95 | NDI follows NDI's license 96 | -------------------------------------------------------------------------------- /cmake/Modules/FindNDI.cmake: -------------------------------------------------------------------------------- 1 | if(WIN32) 2 | if(DEFINED ENV{NDI_SDK_DIR}) 3 | set(NDI_FOUND TRUE) 4 | set(NDI_DIR $ENV{NDI_SDK_DIR}) 5 | string(REPLACE "\\" "/" NDI_DIR "${NDI_DIR}") 6 | if(CMAKE_SIZEOF_VOID_P EQUAL 4) 7 | set(NDI_ARCH "x86") 8 | else() 9 | set(NDI_ARCH "x64") 10 | endif() 11 | set(NDI_INCLUDE_DIR "${NDI_DIR}/Include") 12 | set(NDI_LIBRARY_DIR "${NDI_DIR}/Lib/${NDI_ARCH}") 13 | set(NDI_LICENSE_DIR "${NDI_DIR}/Bin/${NDI_ARCH}") 14 | set(NDI_LIBS "Processing.NDI.Lib.${NDI_ARCH}") 15 | else() 16 | set(NDI_FOUND FALSE) 17 | endif() 18 | elseif(APPLE) 19 | if(EXISTS "/Library/NDI SDK for Apple/include/Processing.NDI.Lib.h") 20 | set(NDI_FOUND TRUE) 21 | set(NDI_DIR "/Library/NDI SDK for Apple") 22 | set(NDI_INCLUDE_DIR "${NDI_DIR}/include") 23 | set(NDI_LIBRARY_DIR "${NDI_DIR}/lib/macOS") 24 | set(NDI_LICENSE_DIR "${NDI_DIR}/licenses") 25 | file(GLOB NDI_LIBS "${NDI_LIBRARY_DIR}/*.dylib") 26 | else() 27 | set(NDI_FOUND FALSE) 28 | endif() 29 | elseif(UNIX) 30 | if(EXISTS "${NDI_SDK_DIR}/include/Processing.NDI.Lib.h") 31 | set(NDI_FOUND TRUE) 32 | set(NDI_DIR ${NDI_SDK_DIR}) 33 | if(NOT NDI_ARCH) 34 | if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") 35 | set(NDI_ARCH "aarch64-rpi4-linux-gnueabi") 36 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) 37 | set(NDI_ARCH "i686-linux-gnu") 38 | else() 39 | set(NDI_ARCH "x86_64-linux-gnu") 40 | endif() 41 | endif() 42 | set(NDI_INCLUDE_DIR "${NDI_DIR}/include") 43 | set(NDI_LIBRARY_DIR "${NDI_DIR}/lib/${NDI_ARCH}") 44 | set(NDI_LICENSE_DIR "${NDI_DIR}/licenses") 45 | set(NDI_LIBS "ndi") 46 | elseif(EXISTS "/usr/include/Processing.NDI.Lib.h") 47 | set(NDI_FOUND TRUE) 48 | set(NDI_DIR "/usr") 49 | set(NDI_INCLUDE_DIR "${NDI_DIR}/include") 50 | set(NDI_LIBRARY_DIR "${NDI_DIR}/lib") 51 | set(NDI_LICENSE_DIR "${NDI_DIR}/share/licenses/ndi-sdk") 52 | set(NDI_LIBS "ndi") 53 | else() 54 | set(NDI_FOUND FALSE) 55 | endif() 56 | endif() 57 | 58 | include(FindPackageHandleStandardArgs) 59 | find_package_handle_standard_args(NDI DEFAULT_MSG NDI_DIR ${NDI_FOUND}) 60 | -------------------------------------------------------------------------------- /example/find.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import NDIlib as ndi 4 | 5 | 6 | def main(): 7 | 8 | if not ndi.initialize(): 9 | return 0 10 | 11 | ndi_find = ndi.find_create_v2() 12 | 13 | if ndi_find is None: 14 | return 0 15 | 16 | t = time.time() 17 | while time.time() - t < 1.0 * 60: 18 | if not ndi.find_wait_for_sources(ndi_find, 5000): 19 | print('No change to the sources found.') 20 | continue 21 | sources = ndi.find_get_current_sources(ndi_find) 22 | print('Network sources (%s found).' % len(sources)) 23 | for i, s in enumerate(sources): 24 | print('%s. %s' % (i + 1, s.ndi_name)) 25 | 26 | ndi.find_destroy(ndi_find) 27 | 28 | ndi.destroy() 29 | 30 | return 0 31 | 32 | 33 | if __name__ == "__main__": 34 | sys.exit(main()) 35 | -------------------------------------------------------------------------------- /example/recv.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | import NDIlib as ndi 4 | 5 | 6 | def main(): 7 | 8 | if not ndi.initialize(): 9 | return 0 10 | 11 | ndi_find = ndi.find_create_v2() 12 | 13 | if ndi_find is None: 14 | return 0 15 | 16 | sources = [] 17 | while not len(sources) > 0: 18 | print('Looking for sources ...') 19 | ndi.find_wait_for_sources(ndi_find, 1000) 20 | sources = ndi.find_get_current_sources(ndi_find) 21 | 22 | ndi_recv_create = ndi.RecvCreateV3() 23 | ndi_recv_create.color_format = ndi.RECV_COLOR_FORMAT_BGRX_BGRA 24 | 25 | ndi_recv = ndi.recv_create_v3(ndi_recv_create) 26 | 27 | if ndi_recv is None: 28 | return 0 29 | 30 | ndi.recv_connect(ndi_recv, sources[0]) 31 | 32 | ndi.find_destroy(ndi_find) 33 | 34 | while True: 35 | t, v, a, _ = ndi.recv_capture_v2(ndi_recv, 5000) 36 | 37 | if t == ndi.FRAME_TYPE_NONE: 38 | print('No data received.') 39 | continue 40 | 41 | if t == ndi.FRAME_TYPE_VIDEO: 42 | print('Video data received (%dx%d).' % (v.xres, v.yres)) 43 | ndi.recv_free_video_v2(ndi_recv, v) 44 | continue 45 | 46 | if t == ndi.FRAME_TYPE_AUDIO: 47 | print('Audio data received (%d samples).' % a.no_samples) 48 | ndi.recv_free_audio_v2(ndi_recv, a) 49 | continue 50 | 51 | ndi.recv_destroy(ndi_recv) 52 | 53 | ndi.destroy() 54 | 55 | return 0 56 | 57 | 58 | if __name__ == "__main__": 59 | sys.exit(main()) 60 | -------------------------------------------------------------------------------- /example/recv_audio_16bpp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | import NDIlib as ndi 4 | 5 | 6 | def main(): 7 | 8 | if not ndi.initialize(): 9 | print('Cannot run NDI.') 10 | return 0 11 | 12 | find_create_desc = ndi.FindCreate() 13 | ndi_find = ndi.find_create_v2(find_create_desc) 14 | 15 | if ndi_find is None: 16 | return 0 17 | 18 | sources = [] 19 | while not len(sources) > 0: 20 | print('Looking for sources ...') 21 | ndi.find_wait_for_sources(ndi_find, 1000) 22 | sources = ndi.find_get_current_sources(ndi_find) 23 | 24 | if len(sources) < 1: 25 | return 0 26 | 27 | recv_create_desc = ndi.RecvCreateV3() 28 | recv_create_desc.source_to_connect_to = sources[0] 29 | recv_create_desc.ndi_recv_name = 'Example Audio Converter Receiver' 30 | 31 | ndi_recv = ndi.recv_create_v3(recv_create_desc) 32 | 33 | if ndi_recv is None: 34 | ndi.find_destroy(ndi_find) 35 | return 0 36 | 37 | ndi.find_destroy(ndi_find) 38 | 39 | while True: 40 | t, v, a, m = ndi.recv_capture_v2(ndi_recv, 1000) 41 | 42 | if t == ndi.FRAME_TYPE_NONE: 43 | print('No data received.') 44 | continue 45 | 46 | if t == ndi.FRAME_TYPE_VIDEO: 47 | print('Video data received (%dx%d).' % (v.xres, v.yres)) 48 | ndi.recv_free_video_v2(ndi_recv, v) 49 | continue 50 | 51 | if t == ndi.FRAME_TYPE_AUDIO: 52 | print('Audio data received (%d samples).' % a.no_samples) 53 | data = np.zeros((a.no_channels, a.no_samples), np.int16) 54 | audio_frame_16bpp_interleaved = ndi.AudioFrameInterleaved16s() 55 | audio_frame_16bpp_interleaved.reference_level = 20 56 | audio_frame_16bpp_interleaved.data = data 57 | ndi.util_audio_to_interleaved_16s_v2( 58 | a, audio_frame_16bpp_interleaved) 59 | ndi.recv_free_audio_v2(ndi_recv, a) 60 | continue 61 | 62 | if t == ndi.FRAME_TYPE_METADATA: 63 | print('Meta data received.') 64 | ndi.recv_free_metadata(ndi_recv, m) 65 | continue 66 | 67 | if t == ndi.FRANE_TYPE_STATUS_CHANGE: 68 | print('Receiver connection status changed.') 69 | continue 70 | 71 | ndi.recv_destroy(ndi_recv) 72 | 73 | ndi.destroy() 74 | 75 | return 0 76 | 77 | 78 | if __name__ == "__main__": 79 | sys.exit(main()) 80 | -------------------------------------------------------------------------------- /example/recv_audio_sd.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import queue 3 | import numpy as np 4 | import sounddevice as sd 5 | import NDIlib as ndi 6 | 7 | 8 | def main(): 9 | 10 | if not ndi.initialize(): 11 | print('Cannot run NDI.') 12 | return 0 13 | 14 | find_create_desc = ndi.FindCreate() 15 | ndi_find = ndi.find_create_v2(find_create_desc) 16 | 17 | if ndi_find is None: 18 | return 0 19 | 20 | sources = [] 21 | while not len(sources) > 0: 22 | print('Looking for sources ...') 23 | ndi.find_wait_for_sources(ndi_find, 1000) 24 | sources = ndi.find_get_current_sources(ndi_find) 25 | 26 | if len(sources) < 1: 27 | return 0 28 | 29 | recv_create_desc = ndi.RecvCreateV3() 30 | recv_create_desc.source_to_connect_to = sources[0] 31 | recv_create_desc.ndi_recv_name = 'Example Audio Converter Receiver' 32 | 33 | ndi_recv = ndi.recv_create_v3(recv_create_desc) 34 | 35 | if ndi_recv is None: 36 | ndi.find_destroy(ndi_find) 37 | return 0 38 | 39 | ndi.find_destroy(ndi_find) 40 | 41 | q = queue.Queue(maxsize=20) 42 | 43 | def audio_callback(outdata, frames, time, status): 44 | try: 45 | data = q.get_nowait() 46 | if not status.output_underflow: 47 | outdata[:] = data 48 | except queue.Empty as e: 49 | return 50 | 51 | stream = sd.RawOutputStream( 52 | samplerate=48000, blocksize=1024, channels=2, dtype='int16', callback=audio_callback) 53 | 54 | with stream: 55 | while True: 56 | t, v, a, m = ndi.recv_capture_v2(ndi_recv, 1000) 57 | 58 | if t == ndi.FRAME_TYPE_NONE: 59 | print('No data received.') 60 | continue 61 | 62 | if t == ndi.FRAME_TYPE_VIDEO: 63 | print('Video data received (%dx%d).' % (v.xres, v.yres)) 64 | ndi.recv_free_video_v2(ndi_recv, v) 65 | continue 66 | 67 | if t == ndi.FRAME_TYPE_AUDIO: 68 | print('Audio data received (%d samples).' % a.no_samples) 69 | data = np.zeros((a.no_channels, a.no_samples), np.int16) 70 | interleaved = ndi.AudioFrameInterleaved16s() 71 | interleaved.data = data 72 | ndi.util_audio_to_interleaved_16s_v2(a, interleaved) 73 | timeout = interleaved.no_samples * 20 / interleaved.sample_rate 74 | q.put(data, timeout=timeout) 75 | ndi.recv_free_audio_v2(ndi_recv, a) 76 | continue 77 | 78 | if t == ndi.FRAME_TYPE_METADATA: 79 | print('Meta data received.') 80 | ndi.recv_free_metadata(ndi_recv, m) 81 | continue 82 | 83 | if t == ndi.FRANE_TYPE_STATUS_CHANGE: 84 | print('Receiver connection status changed.') 85 | continue 86 | 87 | ndi.recv_destroy(ndi_recv) 88 | 89 | ndi.destroy() 90 | 91 | return 0 92 | 93 | 94 | if __name__ == "__main__": 95 | sys.exit(main()) 96 | -------------------------------------------------------------------------------- /example/recv_av.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import numpy as np 4 | import av 5 | from fractions import Fraction 6 | import NDIlib as ndi 7 | 8 | 9 | def main(): 10 | 11 | if not ndi.initialize(): 12 | return 0 13 | 14 | ndi_find = ndi.find_create_v2() 15 | 16 | if ndi_find is None: 17 | return 0 18 | 19 | sources = [] 20 | while not len(sources) > 0: 21 | print('Looking for sources ...') 22 | ndi.find_wait_for_sources(ndi_find, 1000) 23 | sources = ndi.find_get_current_sources(ndi_find) 24 | 25 | recv_create_desc = ndi.RecvCreateV3() 26 | recv_create_desc.color_format = ndi.RECV_COLOR_FORMAT_BGRX_BGRA 27 | 28 | ndi_recv = ndi.recv_create_v3(recv_create_desc) 29 | 30 | if ndi_recv is None: 31 | return 0 32 | 33 | ndi.recv_connect(ndi_recv, sources[0]) 34 | 35 | ndi.find_destroy(ndi_find) 36 | 37 | fps = 30 38 | output = av.open('output.mov', mode='w') 39 | stream = output.add_stream('mpeg4', rate=fps) 40 | stream.width = 1920 41 | stream.height = 1080 42 | stream.pix_fmt = 'yuv420p' 43 | stream.bit_rate = 8e+6 44 | stream.bit_rate_tolerance = 12e+6 45 | stream.codec_context.time_base = Fraction(1, fps) 46 | 47 | start = time.time() 48 | while time.time() - start < 1.0 * 30: 49 | t, v, _, _ = ndi.recv_capture_v2(ndi_recv, 5000) 50 | 51 | if t == ndi.FRAME_TYPE_VIDEO: 52 | print('Video data received (%dx%d).' % (v.xres, v.yres)) 53 | frame_time = time.time() - start 54 | try: 55 | frame = av.VideoFrame.from_ndarray(v.data, format='bgra') 56 | frame.pts = int( 57 | round(frame_time / stream.codec_context.time_base)) 58 | for packet in stream.encode(frame): 59 | output.mux(packet) 60 | except Exception as e: 61 | print(e) 62 | ndi.recv_free_video_v2(ndi_recv, v) 63 | 64 | for packet in stream.encode(): 65 | output.mux(packet) 66 | 67 | output.close() 68 | 69 | ndi.recv_destroy(ndi_recv) 70 | ndi.destroy() 71 | 72 | return 0 73 | 74 | 75 | if __name__ == "__main__": 76 | sys.exit(main()) 77 | -------------------------------------------------------------------------------- /example/recv_cv.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | import cv2 as cv 4 | import NDIlib as ndi 5 | 6 | 7 | def main(): 8 | 9 | if not ndi.initialize(): 10 | return 0 11 | 12 | ndi_find = ndi.find_create_v2() 13 | 14 | if ndi_find is None: 15 | return 0 16 | 17 | sources = [] 18 | while not len(sources) > 0: 19 | print('Looking for sources ...') 20 | ndi.find_wait_for_sources(ndi_find, 1000) 21 | sources = ndi.find_get_current_sources(ndi_find) 22 | 23 | ndi_recv_create = ndi.RecvCreateV3() 24 | ndi_recv_create.color_format = ndi.RECV_COLOR_FORMAT_BGRX_BGRA 25 | 26 | ndi_recv = ndi.recv_create_v3(ndi_recv_create) 27 | 28 | if ndi_recv is None: 29 | return 0 30 | 31 | ndi.recv_connect(ndi_recv, sources[0]) 32 | 33 | ndi.find_destroy(ndi_find) 34 | 35 | cv.startWindowThread() 36 | 37 | while True: 38 | t, v, _, _ = ndi.recv_capture_v2(ndi_recv, 5000) 39 | 40 | if t == ndi.FRAME_TYPE_VIDEO: 41 | print('Video data received (%dx%d).' % (v.xres, v.yres)) 42 | frame = np.copy(v.data) 43 | cv.imshow('ndi image', frame) 44 | ndi.recv_free_video_v2(ndi_recv, v) 45 | 46 | if cv.waitKey(1) & 0xff == 27: 47 | break 48 | 49 | ndi.recv_destroy(ndi_recv) 50 | ndi.destroy() 51 | cv.destroyAllWindows() 52 | 53 | return 0 54 | 55 | 56 | if __name__ == "__main__": 57 | sys.exit(main()) 58 | -------------------------------------------------------------------------------- /example/recv_framesync.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import numpy as np 4 | import NDIlib as ndi 5 | 6 | 7 | def main(): 8 | 9 | if not ndi.initialize(): 10 | return 0 11 | 12 | ndi_find = ndi.find_create_v2() 13 | 14 | if ndi_find is None: 15 | return 0 16 | 17 | sources = [] 18 | while not len(sources) > 0: 19 | print('Looking for sources ...') 20 | ndi.find_wait_for_sources(ndi_find, 1000) 21 | sources = ndi.find_get_current_sources(ndi_find) 22 | 23 | ndi_recv = ndi.recv_create_v3() 24 | 25 | if ndi_recv is None: 26 | return 0 27 | 28 | ndi.recv_connect(ndi_recv, sources[0]) 29 | 30 | ndi_framesync = ndi.framesync_create(ndi_recv) 31 | 32 | ndi.find_destroy(ndi_find) 33 | 34 | t = time.time() 35 | while time.time() - t < 5.0 * 60: 36 | v = ndi.framesync_capture_video(ndi_framesync) 37 | ndi.framesync_free_video(ndi_framesync, v) 38 | a = ndi.framesync_capture_audio(ndi_framesync, 48000, 4, 1600) 39 | ndi.framesync_free_audio(ndi_framesync, a) 40 | time.sleep(33/1000) 41 | 42 | ndi.framesync_destoroy(ndi_framesync) 43 | 44 | ndi.recv_destroy(ndi_recv) 45 | 46 | ndi.destroy() 47 | 48 | return 0 49 | 50 | 51 | if __name__ == "__main__": 52 | sys.exit(main()) 53 | -------------------------------------------------------------------------------- /example/recv_framesync_resend.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import numpy as np 4 | import NDIlib as ndi 5 | 6 | 7 | def main(): 8 | 9 | if not ndi.initialize(): 10 | return 0 11 | 12 | ndi_find = ndi.find_create_v2() 13 | 14 | if ndi_find is None: 15 | return 0 16 | 17 | sources = [] 18 | while not len(sources) > 0: 19 | print('Looking for sources ...') 20 | ndi.find_wait_for_sources(ndi_find, 1000) 21 | sources = ndi.find_get_current_sources(ndi_find) 22 | 23 | ndi_recv = ndi.recv_create_v3() 24 | 25 | if ndi_recv is None: 26 | return 0 27 | 28 | ndi.recv_connect(ndi_recv, sources[0]) 29 | 30 | ndi_framesync = ndi.framesync_create(ndi_recv) 31 | 32 | ndi.find_destroy(ndi_find) 33 | 34 | create_params = ndi.SendCreate() 35 | create_params.clock_video = True 36 | create_params.clock_audio = False 37 | 38 | ndi_send = ndi.send_create(create_params) 39 | 40 | current_time = 0 41 | t = time.time() 42 | while time.time() - t < 5.0 * 60: 43 | v = ndi.framesync_capture_video(ndi_framesync) 44 | if v.data.size != 0: 45 | ndi.send_send_video_v2(ndi_send, v) 46 | frame_start = current_time 47 | current_time += (v.frame_rate_D * 480000) / v.frame_rate_N 48 | ndi.framesync_free_video(ndi_framesync, v) 49 | no_audio_samples = (current_time + 5) / 10 - (frame_start + 5) / 10 50 | a = ndi.framesync_capture_audio( 51 | ndi_framesync, 48000, 4, int(no_audio_samples)) 52 | ndi.send_send_audio_v2(ndi_send, a) 53 | ndi.framesync_free_audio(ndi_framesync, a) 54 | else: 55 | time.sleep(33/1000) 56 | 57 | ndi.framesync_destoroy(ndi_framesync) 58 | 59 | ndi.recv_destroy(ndi_recv) 60 | 61 | ndi.destroy() 62 | 63 | return 0 64 | 65 | 66 | if __name__ == "__main__": 67 | sys.exit(main()) 68 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | opencv-python 3 | sounddevice 4 | av -------------------------------------------------------------------------------- /example/routing.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import random 4 | import NDIlib as ndi 5 | 6 | def main(): 7 | 8 | if not ndi.initialize(): 9 | print('Cannot run NDI.') 10 | return 0 11 | 12 | send_create_desc = ndi.RoutingCreate() 13 | send_create_desc.ndi_name = 'Routing' 14 | 15 | ndi_routing = ndi.routing_create(send_create_desc) 16 | 17 | if ndi_routing is None: 18 | return 0 19 | 20 | find_create_desc = ndi.FindCreate() 21 | ndi_find = ndi.find_create_v2(find_create_desc) 22 | 23 | if ndi_find is None: 24 | return 0 25 | 26 | for i in range(1000): 27 | sources = ndi.find_get_current_sources(ndi_find) 28 | if len(sources) > 0: 29 | new_source = random.choice(sources) 30 | print('routing to %s' % new_source.ndi_name) 31 | ndi.routing_change(ndi_routing, new_source) 32 | else: 33 | ndi.routing_clear(ndi_routing) 34 | time.sleep(15) 35 | 36 | ndi.find_destroy(ndi_find) 37 | ndi.routing_destroy(ndi_routing) 38 | 39 | ndi.destroy() 40 | 41 | return 0 42 | 43 | if __name__ == "__main__": 44 | sys.exit(main()) 45 | -------------------------------------------------------------------------------- /example/send_audio.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | import NDIlib as ndi 4 | 5 | 6 | def main(): 7 | 8 | if not ndi.initialize(): 9 | print('Cannot run NDI.') 10 | return 0 11 | 12 | send_create_desc = ndi.SendCreate() 13 | send_create_desc.ndi_name = 'My Audio' 14 | send_create_desc.clock_audio = True 15 | 16 | ndi_send = ndi.send_create(send_create_desc) 17 | 18 | if ndi_send is None: 19 | return 0 20 | 21 | audio_frame = ndi.AudioFrameV2() 22 | audio_frame.sample_rate = 48000 23 | audio_frame.no_channels = 4 24 | audio_frame.no_samples = 1920 25 | 26 | for i in range(1000): 27 | audio_frame.data = np.zeros((4, 1920), dtype=np.float) 28 | ndi.send_send_audio_v2(ndi_send, audio_frame) 29 | print('Frame number %d sent.' % i) 30 | 31 | ndi.send_destroy(ndi_send) 32 | 33 | ndi.destroy() 34 | 35 | return 0 36 | 37 | 38 | if __name__ == "__main__": 39 | sys.exit(main()) 40 | -------------------------------------------------------------------------------- /example/send_audio_16bpp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | import NDIlib as ndi 4 | 5 | 6 | def main(): 7 | 8 | if not ndi.initialize(): 9 | print('Cannot run NDI.') 10 | return 0 11 | 12 | send_create_desc = ndi.SendCreate() 13 | send_create_desc.ndi_name = 'My 16bpp Audio' 14 | send_create_desc.clock_audio = True 15 | 16 | ndi_send = ndi.send_create(send_create_desc) 17 | 18 | if ndi_send is None: 19 | return 0 20 | 21 | audio_frame = ndi.AudioFrameInterleaved16s() 22 | audio_frame.sample_rate = 48000 23 | audio_frame.no_channels = 4 24 | audio_frame.no_samples = 1920 25 | 26 | for i in range(1000): 27 | audio_frame.data = np.zeros((4, 1920), dtype=np.int16) 28 | ndi.util_send_send_audio_interleaved_16s(ndi_send, audio_frame) 29 | print('Frame number %d sent.' % i) 30 | 31 | ndi.send_destroy(ndi_send) 32 | 33 | ndi.destroy() 34 | 35 | return 0 36 | 37 | 38 | if __name__ == "__main__": 39 | sys.exit(main()) 40 | -------------------------------------------------------------------------------- /example/send_capture.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import numpy as np 4 | import cv2 as cv 5 | import NDIlib as ndi 6 | 7 | def main(): 8 | 9 | if not ndi.initialize(): 10 | return 0 11 | 12 | cap = cv.VideoCapture(0) 13 | 14 | send_settings = ndi.SendCreate() 15 | send_settings.ndi_name = 'ndi-python' 16 | 17 | ndi_send = ndi.send_create(send_settings) 18 | 19 | video_frame = ndi.VideoFrameV2() 20 | 21 | start = time.time() 22 | while time.time() - start < 60 * 5: 23 | start_send = time.time() 24 | 25 | for _ in reversed(range(200)): 26 | 27 | ret, img = cap.read() 28 | 29 | if ret: 30 | img = cv.cvtColor(img, cv.COLOR_BGR2BGRA) 31 | 32 | video_frame.data = img 33 | video_frame.FourCC = ndi.FOURCC_VIDEO_TYPE_BGRX 34 | 35 | ndi.send_send_video_v2(ndi_send, video_frame) 36 | 37 | print('200 frames sent, at %1.2ffps' % (200.0 / (time.time() - start_send))) 38 | 39 | ndi.send_destroy(ndi_send) 40 | 41 | ndi.destroy() 42 | 43 | return 0 44 | 45 | if __name__ == "__main__": 46 | sys.exit(main()) 47 | -------------------------------------------------------------------------------- /example/send_png.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import numpy as np 4 | import cv2 as cv 5 | import NDIlib as ndi 6 | 7 | def main(): 8 | 9 | if not ndi.initialize(): 10 | return 0 11 | 12 | img = cv.imread('image/test2.png', cv.IMREAD_ANYCOLOR) 13 | img = cv.cvtColor(img, cv.COLOR_BGR2RGBA) 14 | 15 | send_settings = ndi.SendCreate() 16 | send_settings.ndi_name = 'ndi-python' 17 | 18 | ndi_send = ndi.send_create(send_settings) 19 | 20 | video_frame = ndi.VideoFrameV2() 21 | 22 | video_frame.data = img 23 | video_frame.FourCC = ndi.FOURCC_VIDEO_TYPE_RGBX 24 | 25 | start = time.time() 26 | while time.time() - start < 60 * 5: 27 | start_send = time.time() 28 | 29 | for _ in reversed(range(200)): 30 | ndi.send_send_video_v2(ndi_send, video_frame) 31 | 32 | print('200 frames sent, at %1.2ffps' % (200.0 / (time.time() - start_send))) 33 | 34 | ndi.send_destroy(ndi_send) 35 | 36 | ndi.destroy() 37 | 38 | return 0 39 | 40 | if __name__ == "__main__": 41 | sys.exit(main()) 42 | -------------------------------------------------------------------------------- /example/send_video.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import numpy as np 4 | import NDIlib as ndi 5 | 6 | def main(): 7 | 8 | if not ndi.initialize(): 9 | return 0 10 | 11 | ndi_send = ndi.send_create() 12 | 13 | if ndi_send is None: 14 | return 0 15 | 16 | img = np.zeros((1080, 1920, 4), dtype=np.uint8) 17 | 18 | video_frame = ndi.VideoFrameV2() 19 | 20 | video_frame.data = img 21 | video_frame.FourCC = ndi.FOURCC_VIDEO_TYPE_BGRX 22 | 23 | start = time.time() 24 | while time.time() - start < 60 * 5: 25 | start_send = time.time() 26 | 27 | for idx in reversed(range(200)): 28 | img.fill(255 if idx % 2 else 0) 29 | ndi.send_send_video_v2(ndi_send, video_frame) 30 | 31 | print('200 frames sent, at %1.2ffps' % (200.0 / (time.time() - start_send))) 32 | 33 | ndi.send_destroy(ndi_send) 34 | 35 | ndi.destroy() 36 | 37 | return 0 38 | 39 | if __name__ == "__main__": 40 | sys.exit(main()) 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from pathlib import Path 4 | from setuptools import setup, Extension 5 | from setuptools.command.build_ext import build_ext 6 | 7 | 8 | class CMakeExtension(Extension): 9 | def __init__(self, name): 10 | super().__init__(name, sources=[]) 11 | 12 | 13 | class CMakeBuild(build_ext): 14 | def run(self): 15 | # build and install 16 | cwd = os.getcwd() 17 | build_dir = os.path.join(cwd, 'build') 18 | os.makedirs(build_dir, exist_ok=True) 19 | os.chdir(build_dir) 20 | install_dir = os.path.join(build_dir, 'install') 21 | if not self.dry_run: 22 | cmake_args = ['cmake', '..', 23 | '-DCMAKE_INSTALL_PREFIX=%s' % install_dir] 24 | if "CMAKE_ARGS" in os.environ: 25 | cmake_args += [ 26 | item for item in os.environ["CMAKE_ARGS"].split(" ") if item] 27 | self.spawn(cmake_args) 28 | if self.debug: 29 | self.spawn(['cmake', '--build', '.', '--config', 30 | 'Debug', '--target', 'install']) 31 | else: 32 | self.spawn(['cmake', '--build', '.', '--config', 33 | 'Release', '--target', 'install']) 34 | os.chdir(cwd) 35 | # move 36 | for ext in self.extensions: 37 | dst_dir = os.path.dirname(self.get_ext_fullpath(ext.name)) 38 | lib_dir = os.path.join(dst_dir, 'NDIlib') 39 | os.makedirs(lib_dir, exist_ok=True) 40 | files = os.listdir(install_dir) 41 | for filename in files: 42 | filepath = os.path.join(install_dir, filename) 43 | if os.path.isfile(filepath): 44 | shutil.copy(filepath, lib_dir) 45 | 46 | 47 | # read description 48 | this_directory = Path(__file__).parent 49 | long_description = (this_directory / "README.md").read_text() 50 | 51 | # setup 52 | setup( 53 | name='ndi-python', 54 | version='5.1.1.5', 55 | description='Wrapper package for NDI SDK python bindings.', 56 | long_description=long_description, 57 | long_description_content_type='text/markdown', 58 | author='Naoto Kondo ', 59 | url='https://github.com/buresu/ndi-python', 60 | license="MIT", 61 | python_requires='>=3.7', 62 | install_requires=['numpy'], 63 | ext_modules=[CMakeExtension('NDIlib')], 64 | cmdclass={'build_ext': CMakeBuild}, 65 | packages=['NDIlib'], 66 | package_data={'NDIlib': ['*.so*', '*.pyd', '*.dll', '*.dylib', '*.txt']}, 67 | zip_safe=False, 68 | keywords=['NDI', 'NewTek', 'Video Production'], 69 | classifiers=[ 70 | 'Intended Audience :: Developers', 71 | 'License :: OSI Approved :: MIT License', 72 | 'Operating System :: MacOS', 73 | 'Operating System :: Microsoft :: Windows', 74 | 'Operating System :: POSIX :: Linux', 75 | 'Programming Language :: C++', 76 | 'Programming Language :: Python', 77 | 'Programming Language :: Python :: 3', 78 | 'Programming Language :: Python :: 3 :: Only', 79 | 'Programming Language :: Python :: 3.7', 80 | 'Programming Language :: Python :: 3.8', 81 | 'Programming Language :: Python :: 3.9', 82 | 'Programming Language :: Python :: 3.10', 83 | 'Programming Language :: Python :: Implementation :: CPython', 84 | 'Topic :: Software Development', 85 | 'Topic :: Software Development :: Libraries', 86 | 'Topic :: Multimedia', 87 | 'Topic :: Multimedia :: Graphics', 88 | 'Topic :: Multimedia :: Sound/Audio', 89 | 'Topic :: Multimedia :: Video', 90 | 'Topic :: System :: Networking', 91 | ], 92 | ) 93 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace py = pybind11; 7 | 8 | PYBIND11_MODULE(NDIlib, m) { 9 | 10 | m.doc() = "NDI SDK for Python"; 11 | 12 | // Processing.NDI.structs 13 | py::enum_(m, "FrameType", py::arithmetic()) 14 | .value("FRAME_TYPE_NONE", NDIlib_frame_type_none) 15 | .value("FRAME_TYPE_VIDEO", NDIlib_frame_type_video) 16 | .value("FRAME_TYPE_AUDIO", NDIlib_frame_type_audio) 17 | .value("FRAME_TYPE_METADATA", NDIlib_frame_type_metadata) 18 | .value("FRAME_TYPE_ERROR", NDIlib_frame_type_error) 19 | .value("FRANE_TYPE_STATUS_CHANGE", NDIlib_frame_type_status_change) 20 | .value("FRAME_TYPE_MAX", NDIlib_frame_type_max) 21 | .export_values(); 22 | 23 | py::enum_(m, "FourCCVideoType", py::arithmetic()) 24 | .value("FOURCC_VIDEO_TYPE_UYVY", NDIlib_FourCC_video_type_UYVY) 25 | .value("FOURCC_VIDEO_TYPE_UYVA", NDIlib_FourCC_video_type_UYVA) 26 | .value("FOURCC_VIDEO_TYPE_P216", NDIlib_FourCC_video_type_P216) 27 | .value("FOURCC_VIDEO_TYPE_PA16", NDIlib_FourCC_video_type_PA16) 28 | .value("FOURCC_VIDEO_TYPE_YV12", NDIlib_FourCC_video_type_YV12) 29 | .value("FOURCC_VIDEO_TYPE_I420", NDIlib_FourCC_video_type_I420) 30 | .value("FOURCC_VIDEO_TYPE_NV12", NDIlib_FourCC_video_type_NV12) 31 | .value("FOURCC_VIDEO_TYPE_BGRA", NDIlib_FourCC_video_type_BGRA) 32 | .value("FOURCC_VIDEO_TYPE_BGRX", NDIlib_FourCC_video_type_BGRX) 33 | .value("FOURCC_VIDEO_TYPE_RGBA", NDIlib_FourCC_video_type_RGBA) 34 | .value("FOURCC_VIDEO_TYPE_RGBX", NDIlib_FourCC_video_type_RGBX) 35 | .value("FOURCC_VIDEO_TYPE_MAX", NDIlib_FourCC_video_type_max) 36 | .export_values(); 37 | 38 | py::enum_(m, "FourCCAudioType", py::arithmetic()) 39 | .value("FOURCC_AUDIO_TYPE_FLTP", NDIlib_FourCC_audio_type_FLTP) 40 | .value("FOURCC_AUDIO_TYPE_MAX", NDIlib_FourCC_audio_type_max) 41 | .export_values(); 42 | 43 | py::enum_(m, "FrameFormatType", py::arithmetic()) 44 | .value("FRAME_FORMAT_TYPE_PROGRESSIVE", 45 | NDIlib_frame_format_type_progressive) 46 | .value("FRAME_FORMAT_TYPE_INTERLEAVED", 47 | NDIlib_frame_format_type_interleaved) 48 | .value("FRAME_FORMAT_TYPE_FIELD_0", NDIlib_frame_format_type_field_0) 49 | .value("FRAME_FORMAT_TYPE_FIELD_1", NDIlib_frame_format_type_field_1) 50 | .value("FRAME_FORMAT_TYPE_MAX", NDIlib_frame_format_type_max) 51 | .export_values(); 52 | 53 | m.attr("SEND_TIMECODE_SYNTHESIZE") = py::int_(INT64_MAX); 54 | 55 | m.attr("RECV_TIMESTAMP_UNDEFINED") = py::int_(INT64_MAX); 56 | 57 | py::class_(m, "Source") 58 | .def(py::init(), 59 | py::arg("p_ndi_name") = nullptr, py::arg("p_url_address") = nullptr) 60 | .def_property( 61 | "ndi_name", 62 | [](const NDIlib_source_t &self) { 63 | if (!self.p_ndi_name) 64 | return py::str(); 65 | auto ustr = PyUnicode_DecodeLocale(self.p_ndi_name, nullptr); 66 | return py::reinterpret_steal(ustr); 67 | }, 68 | [](NDIlib_source_t &self, const std::string &name) { 69 | static std::unordered_map strs; 70 | strs[&self] = py::str(name); 71 | self.p_ndi_name = strs[&self].c_str(); 72 | }) 73 | .def_property( 74 | "url_address", 75 | [](const NDIlib_source_t &self) { 76 | if (!self.p_url_address) 77 | return py::str(); 78 | auto ustr = PyUnicode_DecodeLocale(self.p_url_address, nullptr); 79 | return py::reinterpret_steal(ustr); 80 | }, 81 | [](NDIlib_source_t &self, const std::string &address) { 82 | static std::unordered_map strs; 83 | strs[&self] = py::str(address); 84 | self.p_url_address = strs[&self].c_str(); 85 | }); 86 | 87 | py::class_(m, "VideoFrameV2") 88 | .def(py::init(), 91 | py::arg("xres") = 0, py::arg("yres") = 0, 92 | py::arg("FourCC") = NDIlib_FourCC_video_type_UYVY, 93 | py::arg("frame_rate_N") = 30000, py::arg("frame_rate_D") = 1001, 94 | py::arg("picture_aspect_ratio") = 0.0f, 95 | py::arg("frame_format_type") = NDIlib_frame_format_type_progressive, 96 | py::arg("timecode") = 0, py::arg("p_data") = 0, 97 | py::arg("line_stride_in_bytes") = 0, py::arg("p_metadata") = nullptr, 98 | py::arg("timestamp") = 0) 99 | .def_readwrite("xres", &NDIlib_video_frame_v2_t::xres) 100 | .def_readwrite("yres", &NDIlib_video_frame_v2_t::yres) 101 | .def_readwrite("FourCC", &NDIlib_video_frame_v2_t::FourCC) 102 | .def_readwrite("frame_rate_N", &NDIlib_video_frame_v2_t::frame_rate_N) 103 | .def_readwrite("frame_rate_D", &NDIlib_video_frame_v2_t::frame_rate_D) 104 | .def_readwrite("picture_aspect_ratio", 105 | &NDIlib_video_frame_v2_t::picture_aspect_ratio) 106 | .def_readwrite("frame_format_type", 107 | &NDIlib_video_frame_v2_t::frame_format_type) 108 | .def_readwrite("timecode", &NDIlib_video_frame_v2_t::timecode) 109 | .def_property( 110 | "data", 111 | [](const NDIlib_video_frame_v2_t &self) { 112 | int r = self.yres; 113 | int c = self.xres; 114 | size_t b1 = self.line_stride_in_bytes; 115 | size_t b2 = c > 0 ? b1 / c : 0; 116 | size_t b3 = sizeof(uint8_t); 117 | auto buffer_info = py::buffer_info( 118 | self.p_data, b3, py::format_descriptor::format(), 3, 119 | {r, c, int(b2)}, {b1, b2, b3}); 120 | return py::array(buffer_info); 121 | }, 122 | [](NDIlib_video_frame_v2_t &self, const py::array_t &array) { 123 | auto info = array.request(); 124 | self.p_data = static_cast(info.ptr); 125 | self.picture_aspect_ratio = info.shape[1] / float(info.shape[0]); 126 | self.xres = info.shape[1]; 127 | self.yres = info.shape[0]; 128 | self.line_stride_in_bytes = info.strides[0]; 129 | }) 130 | .def_readwrite("line_stride_in_bytes", 131 | &NDIlib_video_frame_v2_t::line_stride_in_bytes) 132 | .def_property( 133 | "metadata", 134 | [](const NDIlib_video_frame_v2_t &self) { 135 | if (!self.p_metadata) 136 | return py::str(); 137 | auto ustr = PyUnicode_DecodeLocale(self.p_metadata, nullptr); 138 | return py::reinterpret_steal(ustr); 139 | }, 140 | [](NDIlib_video_frame_v2_t &self, const std::string &data) { 141 | static std::unordered_map 142 | strs; 143 | strs[&self] = py::str(data); 144 | self.p_metadata = strs[&self].c_str(); 145 | }) 146 | .def_readwrite("timestamp", &NDIlib_video_frame_v2_t::timestamp); 147 | 148 | py::class_(m, "AudioFrameV2") 149 | .def(py::init(), 151 | py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, 152 | py::arg("no_samples") = 0, 153 | py::arg("timecode") = NDIlib_send_timecode_synthesize, 154 | py::arg("p_data") = 0, py::arg("channel_stride_in_bytes") = 0, 155 | py::arg("p_metadata") = nullptr, py::arg("timestamp") = 0) 156 | .def_readwrite("sample_rate", &NDIlib_audio_frame_v2_t::sample_rate) 157 | .def_readwrite("no_channels", &NDIlib_audio_frame_v2_t::no_channels) 158 | .def_readwrite("no_samples", &NDIlib_audio_frame_v2_t::no_samples) 159 | .def_readwrite("timecode", &NDIlib_audio_frame_v2_t::timecode) 160 | .def_property( 161 | "data", 162 | [](const NDIlib_audio_frame_v2_t &self) { 163 | size_t col = self.no_samples; 164 | size_t row = self.no_channels; 165 | size_t size = sizeof(float); 166 | auto buffer_info = py::buffer_info( 167 | self.p_data, size, py::format_descriptor::format(), 2, 168 | {row, col}, {col * size, size}); 169 | return py::array(buffer_info); 170 | }, 171 | [](NDIlib_audio_frame_v2_t &self, py::array_t &array) { 172 | auto info = array.request(); 173 | self.p_data = static_cast(info.ptr); 174 | self.no_channels = info.shape[0]; 175 | self.no_samples = info.shape[1]; 176 | self.channel_stride_in_bytes = info.strides[0]; 177 | }) 178 | .def_readwrite("channel_stride_in_bytes", 179 | &NDIlib_audio_frame_v2_t::channel_stride_in_bytes) 180 | .def_property( 181 | "metadata", 182 | [](const NDIlib_audio_frame_v2_t &self) { 183 | if (!self.p_metadata) 184 | return py::str(); 185 | auto ustr = PyUnicode_DecodeLocale(self.p_metadata, nullptr); 186 | return py::reinterpret_steal(ustr); 187 | }, 188 | [](NDIlib_audio_frame_v2_t &self, const std::string &data) { 189 | static std::unordered_map 190 | strs; 191 | strs[&self] = py::str(data); 192 | self.p_metadata = strs[&self].c_str(); 193 | }) 194 | .def_readwrite("timestamp", &NDIlib_audio_frame_v2_t::timestamp); 195 | 196 | py::class_(m, "AudioFrameV3") 197 | .def(py::init(), 199 | py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, 200 | py::arg("no_samples") = 0, 201 | py::arg("timecode") = NDIlib_send_timecode_synthesize, 202 | py::arg("FourCC") = NDIlib_FourCC_audio_type_FLTP, 203 | py::arg("p_data") = 0, py::arg("channel_stride_in_bytes") = 0, 204 | py::arg("p_metadata") = nullptr, py::arg("timestamp") = 0) 205 | .def_readwrite("sample_rate", &NDIlib_audio_frame_v3_t::sample_rate) 206 | .def_readwrite("no_channels", &NDIlib_audio_frame_v3_t::no_channels) 207 | .def_readwrite("no_samples", &NDIlib_audio_frame_v3_t::no_samples) 208 | .def_readwrite("timecode", &NDIlib_audio_frame_v3_t::timecode) 209 | .def_readwrite("FourCC", &NDIlib_audio_frame_v3_t::FourCC) 210 | .def_property( 211 | "data", 212 | [](const NDIlib_audio_frame_v3_t &self) { 213 | size_t col = self.no_samples; 214 | size_t row = self.no_channels; 215 | size_t size = sizeof(uint8_t); 216 | auto buffer_info = py::buffer_info( 217 | self.p_data, size, py::format_descriptor::format(), 2, 218 | {row, col}, {col * size * 4, size}); 219 | return py::array(buffer_info); 220 | }, 221 | [](NDIlib_audio_frame_v3_t &self, py::array_t &array) { 222 | auto info = array.request(); 223 | self.p_data = static_cast(info.ptr); 224 | self.no_channels = info.shape[0]; 225 | self.no_samples = info.shape[1]; 226 | self.channel_stride_in_bytes = info.strides[0]; 227 | }) 228 | .def_readwrite("channel_stride_in_bytes", 229 | &NDIlib_audio_frame_v3_t::channel_stride_in_bytes) 230 | .def_property( 231 | "metadata", 232 | [](const NDIlib_audio_frame_v3_t &self) { 233 | if (!self.p_metadata) 234 | return py::str(); 235 | auto ustr = PyUnicode_DecodeLocale(self.p_metadata, nullptr); 236 | return py::reinterpret_steal(ustr); 237 | }, 238 | [](NDIlib_audio_frame_v3_t &self, const std::string &data) { 239 | static std::unordered_map 240 | strs; 241 | strs[&self] = py::str(data); 242 | self.p_metadata = strs[&self].c_str(); 243 | }) 244 | .def_readwrite("timestamp", &NDIlib_audio_frame_v3_t::timestamp); 245 | 246 | py::class_(m, "MetadataFrame") 247 | .def(py::init(), py::arg("length") = 0, 248 | py::arg("timecode") = NDIlib_send_timecode_synthesize, 249 | py::arg("p_data") = nullptr) 250 | .def_readwrite("length", &NDIlib_metadata_frame_t::length) 251 | .def_readwrite("timecode", &NDIlib_metadata_frame_t::timecode) 252 | .def_property( 253 | "data", 254 | [](const NDIlib_metadata_frame_t &self) { 255 | if (!self.p_data) 256 | return py::str(); 257 | auto ustr = PyUnicode_DecodeLocale(self.p_data, nullptr); 258 | return py::reinterpret_steal(ustr); 259 | }, 260 | [](NDIlib_metadata_frame_t &self, const std::string &data) { 261 | static std::unordered_map 262 | strs; 263 | strs[&self] = py::str(data); 264 | self.p_data = &strs[&self][0]; 265 | }); 266 | 267 | py::class_(m, "Tally") 268 | .def(py::init(), py::arg("on_program") = false, 269 | py::arg("on_preview") = false) 270 | .def_readwrite("on_program", &NDIlib_tally_t::on_program) 271 | .def_readwrite("on_preview", &NDIlib_tally_t::on_preview); 272 | 273 | // Processing.NDI.Lib 274 | m.def("initialize", &NDIlib_initialize); 275 | 276 | m.def("destroy", &NDIlib_destroy); 277 | 278 | m.def("version", &NDIlib_version); 279 | 280 | m.def("is_supported_CPU", &NDIlib_is_supported_CPU); 281 | 282 | // Processing.NDI.Find 283 | py::class_(m, "FindCreate") 284 | .def(py::init(), 285 | py::arg("show_local_sources") = true, py::arg("p_groups") = nullptr, 286 | py::arg("p_extra_ips") = nullptr) 287 | .def_readwrite("show_local_sources", 288 | &NDIlib_find_create_t::show_local_sources) 289 | .def_property( 290 | "groups", 291 | [](const NDIlib_find_create_t &self) { 292 | if (!self.p_groups) 293 | return py::str(); 294 | auto ustr = PyUnicode_DecodeLocale(self.p_groups, nullptr); 295 | return py::reinterpret_steal(ustr); 296 | }, 297 | [](NDIlib_find_create_t &self, const std::string &groups) { 298 | static std::unordered_map strs; 299 | strs[&self] = py::str(groups); 300 | self.p_groups = strs[&self].c_str(); 301 | }) 302 | .def_property( 303 | "extra_ips", 304 | [](const NDIlib_find_create_t &self) { 305 | if (!self.p_extra_ips) 306 | return py::str(); 307 | auto ustr = PyUnicode_DecodeLocale(self.p_extra_ips, nullptr); 308 | return py::reinterpret_steal(ustr); 309 | }, 310 | [](NDIlib_find_create_t &self, const std::string &extra_ips) { 311 | static std::unordered_map strs; 312 | strs[&self] = py::str(extra_ips); 313 | self.p_extra_ips = strs[&self].c_str(); 314 | }); 315 | 316 | m.def( 317 | "find_create_v2", 318 | [](const NDIlib_find_create_t *p_create_settings) { 319 | auto p_instance = NDIlib_find_create_v2(p_create_settings); 320 | return py::capsule(p_instance, "FindInstance"); 321 | }, 322 | py::arg("create_settings") = nullptr); 323 | 324 | m.def( 325 | "find_destroy", 326 | [](py::capsule instance) { 327 | auto p_instance = 328 | static_cast(instance.get_pointer()); 329 | NDIlib_find_destroy(p_instance); 330 | }, 331 | py::arg("instance")); 332 | 333 | m.def( 334 | "find_get_current_sources", 335 | [](py::capsule instance) { 336 | auto p_instance = 337 | static_cast(instance.get_pointer()); 338 | uint32_t count = 0; 339 | auto sources = NDIlib_find_get_current_sources(p_instance, &count); 340 | py::list out; 341 | for (uint32_t i = 0; i < count; ++i) 342 | out.append(sources + i); 343 | return out; 344 | }, 345 | py::arg("instance")); 346 | 347 | m.def( 348 | "find_wait_for_sources", 349 | [](py::capsule instance, uint32_t timeout_in_ms) { 350 | auto p_instance = 351 | static_cast(instance.get_pointer()); 352 | return NDIlib_find_wait_for_sources(p_instance, timeout_in_ms); 353 | }, 354 | py::arg("instance"), py::arg("timeout_in_ms")); 355 | 356 | // Processing.NDI.Recv 357 | py::enum_(m, "RecvBandwidth", py::arithmetic()) 358 | .value("RECV_BANDWIDTH_METADATA_ONLY", 359 | NDIlib_recv_bandwidth_metadata_only) 360 | .value("RECV_BANDWIDTH_AUDIO_ONLY", NDIlib_recv_bandwidth_audio_only) 361 | .value("RECV_BANDWIDTH_LOWEST", NDIlib_recv_bandwidth_lowest) 362 | .value("RECV_BANDWIDTH_HIGHEST", NDIlib_recv_bandwidth_highest) 363 | .value("RECV_BANDWIDTH_MAX", NDIlib_recv_bandwidth_max) 364 | .export_values(); 365 | 366 | py::enum_(m, "RecvColorFormat", py::arithmetic()) 367 | .value("RECV_COLOR_FORMAT_BGRX_BGRA", NDIlib_recv_color_format_BGRX_BGRA) 368 | .value("RECV_COLOR_FORMAT_UYVY_BGRA", NDIlib_recv_color_format_UYVY_BGRA) 369 | .value("RECV_COLOR_FORMAT_RGBX_RGBA", NDIlib_recv_color_format_RGBX_RGBA) 370 | .value("RECV_COLOR_FORMAT_UYVY_RGBA", NDIlib_recv_color_format_UYVY_RGBA) 371 | .value("RECV_COLOR_FORMAT_FASTEST", NDIlib_recv_color_format_fastest) 372 | .value("RECV_COLOR_FORMAT_BEST", NDIlib_recv_color_format_best) 373 | .value("RECV_COLOR_FORMAT_E_BGRX_BGRA", 374 | NDIlib_recv_color_format_e_BGRX_BGRA) 375 | .value("RECV_COLOR_FORMAT_E_UYVY_BGRA", 376 | NDIlib_recv_color_format_e_UYVY_BGRA) 377 | .value("RECV_COLOR_FORMAT_E_RGBX_RGBA", 378 | NDIlib_recv_color_format_e_RGBX_RGBA) 379 | .value("RECV_COLOR_FORMAT_E_UYVY_RGBA", 380 | NDIlib_recv_color_format_e_UYVY_RGBA) 381 | #ifdef _WIN32 382 | .value("RECV_COLOR_FORMAT_BGRX_BGRA_FLIPPED", 383 | NDIlib_recv_color_format_BGRX_BGRA_flipped) 384 | #endif 385 | .value("RECV_COLOR_FORMAT_MAX", NDIlib_recv_color_format_max) 386 | .export_values(); 387 | 388 | py::class_(m, "RecvCreateV3") 389 | .def(py::init(), 391 | py::arg("source_to_connect_to") = NDIlib_source_t(), 392 | py::arg("color_format") = NDIlib_recv_color_format_UYVY_BGRA, 393 | py::arg("bandwidth") = NDIlib_recv_bandwidth_highest, 394 | py::arg("allow_video_fields") = true, 395 | py::arg("p_ndi_recv_name") = nullptr) 396 | .def_readwrite("source_to_connect_to", 397 | &NDIlib_recv_create_v3_t::source_to_connect_to) 398 | .def_readwrite("color_format", &NDIlib_recv_create_v3_t::color_format) 399 | .def_readwrite("bandwidth", &NDIlib_recv_create_v3_t::bandwidth) 400 | .def_readwrite("allow_video_fields", 401 | &NDIlib_recv_create_v3_t::allow_video_fields) 402 | .def_property( 403 | "ndi_recv_name", 404 | [](const NDIlib_recv_create_v3_t &self) { 405 | if (!self.p_ndi_recv_name) 406 | return py::str(); 407 | auto ustr = PyUnicode_DecodeLocale(self.p_ndi_recv_name, nullptr); 408 | return py::reinterpret_steal(ustr); 409 | }, 410 | [](NDIlib_recv_create_v3_t &self, const std::string &ndi_recv_name) { 411 | static std::unordered_map 412 | strs; 413 | strs[&self] = py::str(ndi_recv_name); 414 | self.p_ndi_recv_name = strs[&self].c_str(); 415 | }); 416 | 417 | py::class_(m, "RecvPerformance") 418 | .def(py::init<>()) 419 | .def_readwrite("video_frames", &NDIlib_recv_performance_t::video_frames) 420 | .def_readwrite("audio_frames", &NDIlib_recv_performance_t::audio_frames) 421 | .def_readwrite("metadata_frames", 422 | &NDIlib_recv_performance_t::metadata_frames); 423 | 424 | py::class_(m, "RecvQueue") 425 | .def(py::init<>()) 426 | .def_readwrite("video_frames", &NDIlib_recv_queue_t::video_frames) 427 | .def_readwrite("audio_frames", &NDIlib_recv_queue_t::audio_frames) 428 | .def_readwrite("metadata_frames", &NDIlib_recv_queue_t::metadata_frames); 429 | 430 | m.def( 431 | "recv_create_v3", 432 | [](const NDIlib_recv_create_v3_t *p_create_settings) { 433 | auto p_instance = NDIlib_recv_create_v3(p_create_settings); 434 | return py::capsule(p_instance, "RecvInstance"); 435 | }, 436 | py::arg("create_settings") = nullptr); 437 | 438 | m.def( 439 | "recv_destroy", 440 | [](py::capsule instance) { 441 | auto p_instance = 442 | static_cast(instance.get_pointer()); 443 | NDIlib_recv_destroy(p_instance); 444 | }, 445 | py::arg("instance")); 446 | 447 | m.def( 448 | "recv_connect", 449 | [](py::capsule instance, const NDIlib_source_t *p_src) { 450 | auto p_instance = 451 | static_cast(instance.get_pointer()); 452 | NDIlib_recv_connect(p_instance, p_src); 453 | }, 454 | py::arg("instance"), py::arg("source") = nullptr); 455 | 456 | m.def( 457 | "recv_capture_v2", 458 | [](py::capsule instance, uint32_t timeout_in_ms) { 459 | auto p_instance = 460 | static_cast(instance.get_pointer()); 461 | NDIlib_video_frame_v2_t video_frame; 462 | NDIlib_audio_frame_v2_t audio_frame; 463 | NDIlib_metadata_frame_t metadata_frame; 464 | auto type = 465 | NDIlib_recv_capture_v2(p_instance, &video_frame, &audio_frame, 466 | &metadata_frame, timeout_in_ms); 467 | return std::tuple( 469 | type, video_frame, audio_frame, metadata_frame); 470 | }, 471 | py::arg("instance"), py::arg("timeout_in_ms")); 472 | 473 | m.def( 474 | "recv_capture_v3", 475 | [](py::capsule instance, uint32_t timeout_in_ms) { 476 | auto p_instance = 477 | static_cast(instance.get_pointer()); 478 | NDIlib_video_frame_v2_t video_frame; 479 | NDIlib_audio_frame_v3_t audio_frame; 480 | NDIlib_metadata_frame_t metadata_frame; 481 | auto type = 482 | NDIlib_recv_capture_v3(p_instance, &video_frame, &audio_frame, 483 | &metadata_frame, timeout_in_ms); 484 | return std::tuple( 486 | type, video_frame, audio_frame, metadata_frame); 487 | }, 488 | py::arg("instance"), py::arg("timeout_in_ms")); 489 | 490 | m.def( 491 | "recv_free_video_v2", 492 | [](py::capsule instance, const NDIlib_video_frame_v2_t *p_video_data) { 493 | auto p_instance = 494 | static_cast(instance.get_pointer()); 495 | NDIlib_recv_free_video_v2(p_instance, p_video_data); 496 | }, 497 | py::arg("instance"), py::arg("video_data") = nullptr); 498 | 499 | m.def( 500 | "recv_free_audio_v2", 501 | [](py::capsule instance, const NDIlib_audio_frame_v2_t *p_audio_data) { 502 | auto p_instance = 503 | static_cast(instance.get_pointer()); 504 | NDIlib_recv_free_audio_v2(p_instance, p_audio_data); 505 | }, 506 | py::arg("instance"), py::arg("audio_data") = nullptr); 507 | 508 | m.def( 509 | "recv_free_audio_v3", 510 | [](py::capsule instance, const NDIlib_audio_frame_v3_t *p_audio_data) { 511 | auto p_instance = 512 | static_cast(instance.get_pointer()); 513 | NDIlib_recv_free_audio_v3(p_instance, p_audio_data); 514 | }, 515 | py::arg("instance"), py::arg("audio_data") = nullptr); 516 | 517 | m.def( 518 | "recv_free_metadata", 519 | [](py::capsule instance, const NDIlib_metadata_frame_t *p_metadata) { 520 | auto p_instance = 521 | static_cast(instance.get_pointer()); 522 | NDIlib_recv_free_metadata(p_instance, p_metadata); 523 | }, 524 | py::arg("instance"), py::arg("metadata") = nullptr); 525 | 526 | m.def( 527 | "recv_free_string", 528 | [](py::capsule instance, const char *p_string) { 529 | auto p_instance = 530 | static_cast(instance.get_pointer()); 531 | NDIlib_recv_free_string(p_instance, p_string); 532 | }, 533 | py::arg("instance"), py::arg("string") = nullptr); 534 | 535 | m.def( 536 | "recv_send_metadata", 537 | [](py::capsule instance, const NDIlib_metadata_frame_t *p_metadata) { 538 | auto p_instance = 539 | static_cast(instance.get_pointer()); 540 | return NDIlib_recv_send_metadata(p_instance, p_metadata); 541 | }, 542 | py::arg("instance"), py::arg("metadata_frame")); 543 | 544 | m.def( 545 | "recv_set_tally", 546 | [](py::capsule instance, const NDIlib_tally_t *p_tally) { 547 | auto p_instance = 548 | static_cast(instance.get_pointer()); 549 | return NDIlib_recv_set_tally(p_instance, p_tally); 550 | }, 551 | py::arg("instance"), py::arg("tally")); 552 | 553 | m.def( 554 | "recv_get_performance", 555 | [](py::capsule instance) { 556 | auto p_instance = 557 | static_cast(instance.get_pointer()); 558 | NDIlib_recv_performance_t total; 559 | NDIlib_recv_performance_t dropped; 560 | NDIlib_recv_get_performance(p_instance, &total, &dropped); 561 | return std::tuple( 562 | total, dropped); 563 | }, 564 | py::arg("instance")); 565 | 566 | m.def( 567 | "recv_get_queue", 568 | [](py::capsule instance) { 569 | auto p_instance = 570 | static_cast(instance.get_pointer()); 571 | NDIlib_recv_queue_t total; 572 | NDIlib_recv_get_queue(p_instance, &total); 573 | return total; 574 | }, 575 | py::arg("instance")); 576 | 577 | m.def( 578 | "recv_clear_connection_metadata", 579 | [](py::capsule instance) { 580 | auto p_instance = 581 | static_cast(instance.get_pointer()); 582 | NDIlib_recv_clear_connection_metadata(p_instance); 583 | }, 584 | py::arg("instance")); 585 | 586 | m.def( 587 | "recv_add_connection_metadata", 588 | [](py::capsule instance, const NDIlib_metadata_frame_t *p_metadata) { 589 | auto p_instance = 590 | static_cast(instance.get_pointer()); 591 | NDIlib_recv_add_connection_metadata(p_instance, p_metadata); 592 | }, 593 | py::arg("instance"), py::arg("metadata")); 594 | 595 | m.def( 596 | "recv_get_no_connections", 597 | [](py::capsule instance) { 598 | auto p_instance = 599 | static_cast(instance.get_pointer()); 600 | return NDIlib_recv_get_no_connections(p_instance); 601 | }, 602 | py::arg("instance")); 603 | 604 | m.def( 605 | "recv_get_web_control", 606 | [](py::capsule instance) { 607 | auto p_instance = 608 | static_cast(instance.get_pointer()); 609 | auto str = NDIlib_recv_get_web_control(p_instance); 610 | auto ustr = PyUnicode_DecodeLocale(str, nullptr); 611 | return py::reinterpret_steal(ustr); 612 | }, 613 | py::arg("instance")); 614 | 615 | // Processing.NDI.Recv.ex 616 | m.def( 617 | "recv_ptz_is_supported", 618 | [](py::capsule instance) { 619 | auto p_instance = 620 | static_cast(instance.get_pointer()); 621 | return NDIlib_recv_ptz_is_supported(p_instance); 622 | }, 623 | py::arg("instance")); 624 | 625 | m.def( 626 | "recv_recording_is_supported", 627 | [](py::capsule instance) { 628 | auto p_instance = 629 | static_cast(instance.get_pointer()); 630 | return NDIlib_recv_recording_is_supported(p_instance); 631 | }, 632 | py::arg("instance")); 633 | 634 | m.def( 635 | "recv_ptz_zoom", 636 | [](py::capsule instance, const float zoom_value) { 637 | auto p_instance = 638 | static_cast(instance.get_pointer()); 639 | return NDIlib_recv_ptz_zoom(p_instance, zoom_value); 640 | }, 641 | py::arg("instance"), py::arg("zoom_value")); 642 | 643 | m.def( 644 | "recv_ptz_zoom_speed", 645 | [](py::capsule instance, const float zoom_speed) { 646 | auto p_instance = 647 | static_cast(instance.get_pointer()); 648 | return NDIlib_recv_ptz_zoom_speed(p_instance, zoom_speed); 649 | }, 650 | py::arg("instance"), py::arg("zoom_speed")); 651 | 652 | m.def( 653 | "recv_ptz_pan_tilt", 654 | [](py::capsule instance, const float pan_value, const float tilt_value) { 655 | auto p_instance = 656 | static_cast(instance.get_pointer()); 657 | return NDIlib_recv_ptz_pan_tilt(p_instance, pan_value, tilt_value); 658 | }, 659 | py::arg("instance"), py::arg("pan_value"), py::arg("tilt_value")); 660 | 661 | m.def( 662 | "recv_ptz_pan_tilt_speed", 663 | [](py::capsule instance, const float pan_speed, const float tilt_speed) { 664 | auto p_instance = 665 | static_cast(instance.get_pointer()); 666 | return NDIlib_recv_ptz_pan_tilt_speed(p_instance, pan_speed, 667 | tilt_speed); 668 | }, 669 | py::arg("instance"), py::arg("pan_speed"), py::arg("tilt_speed")); 670 | 671 | m.def( 672 | "recv_ptz_store_preset", 673 | [](py::capsule instance, const int preset_no) { 674 | auto p_instance = 675 | static_cast(instance.get_pointer()); 676 | return NDIlib_recv_ptz_store_preset(p_instance, preset_no); 677 | }, 678 | py::arg("instance"), py::arg("preset_no")); 679 | 680 | m.def( 681 | "recv_ptz_recall_preset", 682 | [](py::capsule instance, const int preset_no, const float speed) { 683 | auto p_instance = 684 | static_cast(instance.get_pointer()); 685 | return NDIlib_recv_ptz_recall_preset(p_instance, preset_no, speed); 686 | }, 687 | py::arg("instance"), py::arg("preset_no"), py::arg("speed")); 688 | 689 | m.def( 690 | "recv_ptz_auto_focus", 691 | [](py::capsule instance) { 692 | auto p_instance = 693 | static_cast(instance.get_pointer()); 694 | return NDIlib_recv_ptz_auto_focus(p_instance); 695 | }, 696 | py::arg("instance")); 697 | 698 | m.def( 699 | "recv_ptz_focus", 700 | [](py::capsule instance, const float focus_value) { 701 | auto p_instance = 702 | static_cast(instance.get_pointer()); 703 | return NDIlib_recv_ptz_focus(p_instance, focus_value); 704 | }, 705 | py::arg("instance"), py::arg("focus_value")); 706 | 707 | m.def( 708 | "recv_ptz_focus_speed", 709 | [](py::capsule instance, const float focus_speed) { 710 | auto p_instance = 711 | static_cast(instance.get_pointer()); 712 | return NDIlib_recv_ptz_focus_speed(p_instance, focus_speed); 713 | }, 714 | py::arg("instance"), py::arg("focus_speed")); 715 | 716 | m.def( 717 | "recv_ptz_white_balance_auto", 718 | [](py::capsule instance) { 719 | auto p_instance = 720 | static_cast(instance.get_pointer()); 721 | return NDIlib_recv_ptz_white_balance_auto(p_instance); 722 | }, 723 | py::arg("instance")); 724 | 725 | m.def( 726 | "recv_ptz_white_balance_indoor", 727 | [](py::capsule instance) { 728 | auto p_instance = 729 | static_cast(instance.get_pointer()); 730 | return NDIlib_recv_ptz_white_balance_indoor(p_instance); 731 | }, 732 | py::arg("instance")); 733 | 734 | m.def( 735 | "recv_ptz_white_balance_outdoor", 736 | [](py::capsule instance) { 737 | auto p_instance = 738 | static_cast(instance.get_pointer()); 739 | return NDIlib_recv_ptz_white_balance_outdoor(p_instance); 740 | }, 741 | py::arg("instance")); 742 | 743 | m.def( 744 | "recv_ptz_white_balance_oneshot", 745 | [](py::capsule instance) { 746 | auto p_instance = 747 | static_cast(instance.get_pointer()); 748 | return NDIlib_recv_ptz_white_balance_oneshot(p_instance); 749 | }, 750 | py::arg("instance")); 751 | 752 | m.def( 753 | "recv_ptz_white_balance_manual", 754 | [](py::capsule instance, const float red, const float blue) { 755 | auto p_instance = 756 | static_cast(instance.get_pointer()); 757 | return NDIlib_recv_ptz_white_balance_manual(p_instance, red, blue); 758 | }, 759 | py::arg("instance"), py::arg("red"), py::arg("blue")); 760 | 761 | m.def( 762 | "recv_ptz_exposure_auto", 763 | [](py::capsule instance) { 764 | auto p_instance = 765 | static_cast(instance.get_pointer()); 766 | return NDIlib_recv_ptz_exposure_auto(p_instance); 767 | }, 768 | py::arg("instance")); 769 | 770 | m.def( 771 | "recv_ptz_exposure_manual", 772 | [](py::capsule instance, const float exposure_level) { 773 | auto p_instance = 774 | static_cast(instance.get_pointer()); 775 | return NDIlib_recv_ptz_exposure_manual(p_instance, exposure_level); 776 | }, 777 | py::arg("instance"), py::arg("exposure_level")); 778 | 779 | m.def( 780 | "recv_ptz_exposure_manual_v2", 781 | [](py::capsule instance, const float iris, const float gain, 782 | const float shutter_speed) { 783 | auto p_instance = 784 | static_cast(instance.get_pointer()); 785 | return NDIlib_recv_ptz_exposure_manual_v2(p_instance, iris, gain, 786 | shutter_speed); 787 | }, 788 | py::arg("instance"), py::arg("iris"), py::arg("gain"), 789 | py::arg("shutter_speed")); 790 | 791 | m.def( 792 | "recv_recording_start", 793 | [](py::capsule instance, const char *p_filename_hint) { 794 | auto p_instance = 795 | static_cast(instance.get_pointer()); 796 | return NDIlib_recv_recording_start(p_instance, p_filename_hint); 797 | }, 798 | py::arg("instance"), py::arg("filename_hint")); 799 | 800 | m.def( 801 | "recv_recording_stop", 802 | [](py::capsule instance) { 803 | auto p_instance = 804 | static_cast(instance.get_pointer()); 805 | return NDIlib_recv_recording_stop(p_instance); 806 | }, 807 | py::arg("instance")); 808 | 809 | m.def( 810 | "recv_recording_set_audio_level", 811 | [](py::capsule instance, const float level_dB) { 812 | auto p_instance = 813 | static_cast(instance.get_pointer()); 814 | return NDIlib_recv_recording_set_audio_level(p_instance, level_dB); 815 | }, 816 | py::arg("instance"), py::arg("level_dB")); 817 | 818 | m.def( 819 | "recv_recording_is_recording", 820 | [](py::capsule instance) { 821 | auto p_instance = 822 | static_cast(instance.get_pointer()); 823 | return NDIlib_recv_recording_is_recording(p_instance); 824 | }, 825 | py::arg("instance")); 826 | 827 | m.def( 828 | "recv_recording_get_filename", 829 | [](py::capsule instance) { 830 | auto p_instance = 831 | static_cast(instance.get_pointer()); 832 | auto str = NDIlib_recv_recording_get_filename(p_instance); 833 | auto ustr = PyUnicode_DecodeLocale(str, nullptr); 834 | return py::reinterpret_steal(ustr); 835 | }, 836 | py::arg("instance")); 837 | 838 | m.def( 839 | "recv_recording_get_error", 840 | [](py::capsule instance) { 841 | auto p_instance = 842 | static_cast(instance.get_pointer()); 843 | auto str = NDIlib_recv_recording_get_error(p_instance); 844 | auto ustr = PyUnicode_DecodeLocale(str, nullptr); 845 | return py::reinterpret_steal(ustr); 846 | }, 847 | py::arg("instance")); 848 | 849 | py::class_(m, "RecvRecordingTime") 850 | .def(py::init<>()) 851 | .def_readwrite("no_frames", &NDIlib_recv_recording_time_t::no_frames) 852 | .def_readwrite("start_time", &NDIlib_recv_recording_time_t::start_time) 853 | .def_readwrite("last_time", &NDIlib_recv_recording_time_t::last_time); 854 | 855 | m.def( 856 | "recv_recording_get_times", 857 | [](py::capsule instance, NDIlib_recv_recording_time_t *p_times) { 858 | auto p_instance = 859 | static_cast(instance.get_pointer()); 860 | return NDIlib_recv_recording_get_times(p_instance, p_times); 861 | }, 862 | py::arg("instance"), py::arg("times")); 863 | 864 | // Processing.NDI.Send 865 | py::class_(m, "SendCreate") 866 | .def(py::init(), 867 | py::arg("p_ndi_name") = nullptr, py::arg("p_groups") = nullptr, 868 | py::arg("clock_video") = true, py::arg("clock_audio") = true) 869 | .def_property( 870 | "ndi_name", 871 | [](const NDIlib_send_create_t &self) { 872 | if (!self.p_ndi_name) 873 | return py::str(); 874 | auto ustr = PyUnicode_DecodeLocale(self.p_ndi_name, nullptr); 875 | return py::reinterpret_steal(ustr); 876 | }, 877 | [](NDIlib_send_create_t &self, const char *name) { 878 | static std::unordered_map strs; 879 | strs[&self] = py::str(name); 880 | self.p_ndi_name = strs[&self].c_str(); 881 | }) 882 | .def_property( 883 | "groups", 884 | [](const NDIlib_send_create_t &self) { 885 | if (!self.p_groups) 886 | return py::str(); 887 | auto ustr = PyUnicode_DecodeLocale(self.p_groups, nullptr); 888 | return py::reinterpret_steal(ustr); 889 | }, 890 | [](NDIlib_send_create_t &self, const std::string &groups) { 891 | static std::unordered_map strs; 892 | strs[&self] = py::str(groups); 893 | self.p_groups = strs[&self].c_str(); 894 | }) 895 | .def_readwrite("clock_video", &NDIlib_send_create_t::clock_video) 896 | .def_readwrite("clock_audio", &NDIlib_send_create_t::clock_audio); 897 | 898 | m.def( 899 | "send_create", 900 | [](const NDIlib_send_create_t *p_create_settings) { 901 | auto p_instance = NDIlib_send_create(p_create_settings); 902 | return py::capsule(p_instance, "SendInstance"); 903 | }, 904 | py::arg("create_settings") = nullptr); 905 | 906 | m.def( 907 | "send_destroy", 908 | [](py::capsule instance) { 909 | auto p_instance = 910 | static_cast(instance.get_pointer()); 911 | NDIlib_send_destroy(p_instance); 912 | }, 913 | py::arg("instance")); 914 | 915 | m.def( 916 | "send_send_video_v2", 917 | [](py::capsule instance, const NDIlib_video_frame_v2_t *p_video_data) { 918 | auto p_instance = 919 | static_cast(instance.get_pointer()); 920 | NDIlib_send_send_video_v2(p_instance, p_video_data); 921 | }, 922 | py::arg("instance"), py::arg("video_data")); 923 | 924 | m.def( 925 | "send_send_video_async_v2", 926 | [](py::capsule instance, const NDIlib_video_frame_v2_t *p_video_data) { 927 | auto p_instance = 928 | static_cast(instance.get_pointer()); 929 | NDIlib_send_send_video_async_v2(p_instance, p_video_data); 930 | }, 931 | py::arg("instance"), py::arg("video_data")); 932 | 933 | m.def( 934 | "send_send_audio_v2", 935 | [](py::capsule instance, const NDIlib_audio_frame_v2_t *p_audio_data) { 936 | auto p_instance = 937 | static_cast(instance.get_pointer()); 938 | NDIlib_send_send_audio_v2(p_instance, p_audio_data); 939 | }, 940 | py::arg("instance"), py::arg("audio_data")); 941 | 942 | m.def( 943 | "send_send_audio_v3", 944 | [](py::capsule instance, const NDIlib_audio_frame_v3_t *p_audio_data) { 945 | auto p_instance = 946 | static_cast(instance.get_pointer()); 947 | NDIlib_send_send_audio_v3(p_instance, p_audio_data); 948 | }, 949 | py::arg("instance"), py::arg("audio_data")); 950 | 951 | m.def( 952 | "send_send_metadata", 953 | [](py::capsule instance, const NDIlib_metadata_frame_t *p_metadata) { 954 | auto p_instance = 955 | static_cast(instance.get_pointer()); 956 | NDIlib_send_send_metadata(p_instance, p_metadata); 957 | }, 958 | py::arg("instance"), py::arg("metadata")); 959 | 960 | m.def( 961 | "send_capture", 962 | [](py::capsule instance, NDIlib_metadata_frame_t *p_metadata, 963 | uint32_t timeout_in_ms) { 964 | auto p_instance = 965 | static_cast(instance.get_pointer()); 966 | NDIlib_send_capture(p_instance, p_metadata, timeout_in_ms); 967 | }, 968 | py::arg("instance"), py::arg("metadata"), py::arg("timeout_in_ms")); 969 | 970 | m.def( 971 | "send_free_metadata", 972 | [](py::capsule instance, const NDIlib_metadata_frame_t *p_metadata) { 973 | auto p_instance = 974 | static_cast(instance.get_pointer()); 975 | NDIlib_send_free_metadata(p_instance, p_metadata); 976 | }, 977 | py::arg("instance"), py::arg("metadata")); 978 | 979 | m.def( 980 | "send_get_tally", 981 | [](py::capsule instance, NDIlib_tally_t *p_tally, 982 | uint32_t timeout_in_ms) { 983 | auto p_instance = 984 | static_cast(instance.get_pointer()); 985 | return NDIlib_send_get_tally(p_instance, p_tally, timeout_in_ms); 986 | }, 987 | py::arg("instance"), py::arg("tally"), py::arg("timeout_in_ms")); 988 | 989 | m.def( 990 | "send_get_no_connections", 991 | [](py::capsule instance, uint32_t timeout_in_ms) { 992 | auto p_instance = 993 | static_cast(instance.get_pointer()); 994 | return NDIlib_send_get_no_connections(p_instance, timeout_in_ms); 995 | }, 996 | py::arg("instance"), py::arg("timeout_in_ms")); 997 | 998 | m.def( 999 | "send_clear_connection_metadata", 1000 | [](py::capsule instance) { 1001 | auto p_instance = 1002 | static_cast(instance.get_pointer()); 1003 | NDIlib_send_clear_connection_metadata(p_instance); 1004 | }, 1005 | py::arg("instance")); 1006 | 1007 | m.def( 1008 | "send_add_connection_metadata", 1009 | [](py::capsule instance, const NDIlib_metadata_frame_t *p_metadata) { 1010 | auto p_instance = 1011 | static_cast(instance.get_pointer()); 1012 | NDIlib_send_add_connection_metadata(p_instance, p_metadata); 1013 | }, 1014 | py::arg("instance"), py::arg("metadata")); 1015 | 1016 | m.def( 1017 | "send_set_failover", 1018 | [](py::capsule instance, const NDIlib_source_t *p_failover_source) { 1019 | auto p_instance = 1020 | static_cast(instance.get_pointer()); 1021 | NDIlib_send_set_failover(p_instance, p_failover_source); 1022 | }, 1023 | py::arg("instance"), py::arg("failover_source")); 1024 | 1025 | m.def( 1026 | "send_get_source_name", 1027 | [](py::capsule instance) { 1028 | auto p_instance = 1029 | static_cast(instance.get_pointer()); 1030 | return NDIlib_send_get_source_name(p_instance); 1031 | }, 1032 | py::arg("instance")); 1033 | 1034 | // Processing.NDI.Routing 1035 | py::class_(m, "RoutingCreate") 1036 | .def(py::init(), 1037 | py::arg("p_ndi_name") = nullptr, py::arg("p_groups") = nullptr) 1038 | .def_property( 1039 | "ndi_name", 1040 | [](const NDIlib_routing_create_t &self) { 1041 | if (!self.p_ndi_name) 1042 | return py::str(); 1043 | auto ustr = PyUnicode_DecodeLocale(self.p_ndi_name, nullptr); 1044 | return py::reinterpret_steal(ustr); 1045 | }, 1046 | [](NDIlib_routing_create_t &self, const char *name) { 1047 | static std::unordered_map 1048 | strs; 1049 | strs[&self] = py::str(name); 1050 | self.p_ndi_name = strs[&self].c_str(); 1051 | }) 1052 | .def_property( 1053 | "groups", 1054 | [](const NDIlib_routing_create_t &self) { 1055 | if (!self.p_groups) 1056 | return py::str(); 1057 | auto ustr = PyUnicode_DecodeLocale(self.p_groups, nullptr); 1058 | return py::reinterpret_steal(ustr); 1059 | }, 1060 | [](NDIlib_routing_create_t &self, const std::string &groups) { 1061 | static std::unordered_map 1062 | strs; 1063 | strs[&self] = py::str(groups); 1064 | self.p_groups = strs[&self].c_str(); 1065 | }); 1066 | 1067 | m.def( 1068 | "routing_create", 1069 | [](const NDIlib_routing_create_t *p_create_settings) { 1070 | auto p_instance = NDIlib_routing_create(p_create_settings); 1071 | return py::capsule(p_instance, "RoutingInstance"); 1072 | }, 1073 | py::arg("create_settings")); 1074 | 1075 | m.def( 1076 | "routing_destroy", 1077 | [](py::capsule instance) { 1078 | auto p_instance = 1079 | static_cast(instance.get_pointer()); 1080 | NDIlib_routing_destroy(p_instance); 1081 | }, 1082 | py::arg("instance")); 1083 | 1084 | m.def( 1085 | "routing_change", 1086 | [](py::capsule instance, const NDIlib_source_t *p_source) { 1087 | auto p_instance = 1088 | static_cast(instance.get_pointer()); 1089 | return NDIlib_routing_change(p_instance, p_source); 1090 | }, 1091 | py::arg("instance"), py::arg("source")); 1092 | 1093 | m.def( 1094 | "routing_clear", 1095 | [](py::capsule instance) { 1096 | auto p_instance = 1097 | static_cast(instance.get_pointer()); 1098 | NDIlib_routing_clear(p_instance); 1099 | }, 1100 | py::arg("instance")); 1101 | 1102 | m.def( 1103 | "routing_get_no_connections", 1104 | [](py::capsule instance, uint32_t timeout_in_ms) { 1105 | auto p_instance = 1106 | static_cast(instance.get_pointer()); 1107 | return NDIlib_routing_get_no_connections(p_instance, timeout_in_ms); 1108 | }, 1109 | py::arg("instance"), py::arg("timeout_in_ms")); 1110 | 1111 | m.def( 1112 | "routing_get_source_name(", 1113 | [](py::capsule instance) { 1114 | auto p_instance = 1115 | static_cast(instance.get_pointer()); 1116 | return NDIlib_routing_get_source_name(p_instance); 1117 | }, 1118 | py::arg("instance")); 1119 | 1120 | // Processing.NDI.utilities 1121 | py::class_(m, 1122 | "AudioFrameInterleaved16s") 1123 | .def(py::init(), 1124 | py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, 1125 | py::arg("no_samples") = 0, 1126 | py::arg("timecode") = NDIlib_send_timecode_synthesize, 1127 | py::arg("reference_level") = 0, py::arg("p_data") = 0) 1128 | .def_readwrite("sample_rate", 1129 | &NDIlib_audio_frame_interleaved_16s_t::sample_rate) 1130 | .def_readwrite("no_channels", 1131 | &NDIlib_audio_frame_interleaved_16s_t::no_channels) 1132 | .def_readwrite("no_samples", 1133 | &NDIlib_audio_frame_interleaved_16s_t::no_samples) 1134 | .def_readwrite("timecode", 1135 | &NDIlib_audio_frame_interleaved_16s_t::timecode) 1136 | .def_readwrite("reference_level", 1137 | &NDIlib_audio_frame_interleaved_16s_t::reference_level) 1138 | .def_property( 1139 | "data", 1140 | [](const NDIlib_audio_frame_interleaved_16s_t &self) { 1141 | size_t col = self.no_samples; 1142 | size_t row = self.no_channels; 1143 | size_t size = sizeof(int16_t); 1144 | auto buffer_info = py::buffer_info( 1145 | self.p_data, size, py::format_descriptor::format(), 2, 1146 | {row, col}, {col * size, size}); 1147 | return py::array(buffer_info); 1148 | }, 1149 | [](NDIlib_audio_frame_interleaved_16s_t &self, 1150 | py::array_t &array) { 1151 | auto info = array.request(); 1152 | self.p_data = static_cast(info.ptr); 1153 | self.no_channels = info.shape[0]; 1154 | self.no_samples = info.shape[1]; 1155 | }); 1156 | 1157 | py::class_(m, 1158 | "AudioFrameInterleaved32s") 1159 | .def(py::init(), 1160 | py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, 1161 | py::arg("no_samples") = 0, 1162 | py::arg("timecode") = NDIlib_send_timecode_synthesize, 1163 | py::arg("reference_level") = 0, py::arg("p_data") = 0) 1164 | .def_readwrite("sample_rate", 1165 | &NDIlib_audio_frame_interleaved_32s_t::sample_rate) 1166 | .def_readwrite("no_channels", 1167 | &NDIlib_audio_frame_interleaved_32s_t::no_channels) 1168 | .def_readwrite("no_samples", 1169 | &NDIlib_audio_frame_interleaved_32s_t::no_samples) 1170 | .def_readwrite("timecode", 1171 | &NDIlib_audio_frame_interleaved_32s_t::timecode) 1172 | .def_readwrite("reference_level", 1173 | &NDIlib_audio_frame_interleaved_32s_t::reference_level) 1174 | .def_property( 1175 | "data", 1176 | [](const NDIlib_audio_frame_interleaved_32s_t &self) { 1177 | size_t col = self.no_samples; 1178 | size_t row = self.no_channels; 1179 | size_t size = sizeof(int32_t); 1180 | auto buffer_info = py::buffer_info( 1181 | self.p_data, size, py::format_descriptor::format(), 2, 1182 | {row, col}, {col * size, size}); 1183 | return py::array(buffer_info); 1184 | }, 1185 | [](NDIlib_audio_frame_interleaved_32s_t &self, 1186 | py::array_t &array) { 1187 | auto info = array.request(); 1188 | self.p_data = static_cast(info.ptr); 1189 | self.no_channels = info.shape[0]; 1190 | self.no_samples = info.shape[1]; 1191 | }); 1192 | 1193 | py::class_(m, 1194 | "AudioFrameInterleaved32f") 1195 | .def(py::init(), 1196 | py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, 1197 | py::arg("no_samples") = 0, 1198 | py::arg("timecode") = NDIlib_send_timecode_synthesize, 1199 | py::arg("p_data") = 0) 1200 | .def_readwrite("sample_rate", 1201 | &NDIlib_audio_frame_interleaved_32f_t::sample_rate) 1202 | .def_readwrite("no_channels", 1203 | &NDIlib_audio_frame_interleaved_32f_t::no_channels) 1204 | .def_readwrite("no_samples", 1205 | &NDIlib_audio_frame_interleaved_32f_t::no_samples) 1206 | .def_readwrite("timecode", 1207 | &NDIlib_audio_frame_interleaved_32f_t::timecode) 1208 | .def_property( 1209 | "data", 1210 | [](const NDIlib_audio_frame_interleaved_32f_t &self) { 1211 | size_t col = self.no_samples; 1212 | size_t row = self.no_channels; 1213 | size_t size = sizeof(float); 1214 | auto buffer_info = py::buffer_info( 1215 | self.p_data, size, py::format_descriptor::format(), 2, 1216 | {row, col}, {col * size, size}); 1217 | return py::array(buffer_info); 1218 | }, 1219 | [](NDIlib_audio_frame_interleaved_32f_t &self, 1220 | py::array_t &array) { 1221 | auto info = array.request(); 1222 | self.p_data = static_cast(info.ptr); 1223 | self.no_channels = info.shape[0]; 1224 | self.no_samples = info.shape[1]; 1225 | }); 1226 | 1227 | m.def( 1228 | "util_send_send_audio_interleaved_16s", 1229 | [](py::capsule instance, 1230 | const NDIlib_audio_frame_interleaved_16s_t *p_audio_data) { 1231 | auto p_instance = 1232 | static_cast(instance.get_pointer()); 1233 | NDIlib_util_send_send_audio_interleaved_16s(p_instance, p_audio_data); 1234 | }, 1235 | py::arg("instance"), py::arg("audio_data")); 1236 | 1237 | m.def( 1238 | "util_send_send_audio_interleaved_32s", 1239 | [](py::capsule instance, 1240 | const NDIlib_audio_frame_interleaved_32s_t *p_audio_data) { 1241 | auto p_instance = 1242 | static_cast(instance.get_pointer()); 1243 | NDIlib_util_send_send_audio_interleaved_32s(p_instance, p_audio_data); 1244 | }, 1245 | py::arg("instance"), py::arg("audio_data")); 1246 | 1247 | m.def( 1248 | "util_send_send_audio_interleaved_32f", 1249 | [](py::capsule instance, 1250 | const NDIlib_audio_frame_interleaved_32f_t *p_audio_data) { 1251 | auto p_instance = 1252 | static_cast(instance.get_pointer()); 1253 | NDIlib_util_send_send_audio_interleaved_32f(p_instance, p_audio_data); 1254 | }, 1255 | py::arg("instance"), py::arg("audio_data")); 1256 | 1257 | m.def("util_audio_to_interleaved_16s_v2", 1258 | &NDIlib_util_audio_to_interleaved_16s_v2, py::arg("src"), 1259 | py::arg("dst")); 1260 | 1261 | m.def("util_audio_from_interleaved_16s_v2", 1262 | &NDIlib_util_audio_from_interleaved_16s_v2, py::arg("src"), 1263 | py::arg("dst")); 1264 | 1265 | m.def("util_audio_to_interleaved_32s_v2", 1266 | &NDIlib_util_audio_to_interleaved_32s_v2, py::arg("src"), 1267 | py::arg("dst")); 1268 | 1269 | m.def("util_audio_from_interleaved_32s_v2", 1270 | &NDIlib_util_audio_from_interleaved_32s_v2, py::arg("src"), 1271 | py::arg("dst")); 1272 | 1273 | m.def("util_audio_to_interleaved_32f_v2", 1274 | &NDIlib_util_audio_to_interleaved_32f_v2, py::arg("src"), 1275 | py::arg("dst")); 1276 | 1277 | m.def("util_audio_from_interleaved_32f_v2", 1278 | &NDIlib_util_audio_from_interleaved_32f_v2, py::arg("src"), 1279 | py::arg("dst")); 1280 | 1281 | m.def("util_V210_to_P216", &NDIlib_util_V210_to_P216, py::arg("src_v210"), 1282 | py::arg("dst_p216")); 1283 | 1284 | m.def("util_P216_to_V210", &NDIlib_util_P216_to_V210, py::arg("src_p216"), 1285 | py::arg("dst_v210")); 1286 | 1287 | // Processing.NDI.deprecated 1288 | // TODO 1289 | 1290 | // Processing.NDI.FrameSync 1291 | m.def( 1292 | "framesync_create", 1293 | [](py::capsule instance) { 1294 | auto p_recv_instance = 1295 | static_cast(instance.get_pointer()); 1296 | auto p_instance = NDIlib_framesync_create(p_recv_instance); 1297 | return py::capsule(p_instance, "FrameSyncInstance"); 1298 | }, 1299 | py::arg("receiver")); 1300 | 1301 | m.def( 1302 | "framesync_destroy", 1303 | [](py::capsule instance) { 1304 | auto p_instance = static_cast( 1305 | instance.get_pointer()); 1306 | NDIlib_framesync_destroy(p_instance); 1307 | }, 1308 | py::arg("instance")); 1309 | 1310 | m.def( 1311 | "framesync_capture_audio", 1312 | [](py::capsule instance, int sample_rate, int no_channels, 1313 | int no_samples) { 1314 | auto p_instance = static_cast( 1315 | instance.get_pointer()); 1316 | NDIlib_audio_frame_v2_t audio_frame; 1317 | NDIlib_framesync_capture_audio(p_instance, &audio_frame, sample_rate, 1318 | no_channels, no_samples); 1319 | return audio_frame; 1320 | }, 1321 | py::arg("instance"), py::arg("sample_rate"), py::arg("no_channels"), 1322 | py::arg("no_samples")); 1323 | 1324 | m.def( 1325 | "framesync_capture_audio_v2", 1326 | [](py::capsule instance, int sample_rate, int no_channels, 1327 | int no_samples) { 1328 | auto p_instance = static_cast( 1329 | instance.get_pointer()); 1330 | NDIlib_audio_frame_v3_t audio_frame; 1331 | NDIlib_framesync_capture_audio_v2(p_instance, &audio_frame, sample_rate, 1332 | no_channels, no_samples); 1333 | return audio_frame; 1334 | }, 1335 | py::arg("instance"), py::arg("sample_rate"), py::arg("no_channels"), 1336 | py::arg("no_samples")); 1337 | 1338 | m.def( 1339 | "framesync_free_audio", 1340 | [](py::capsule instance, NDIlib_audio_frame_v2_t *p_audio_data) { 1341 | auto p_instance = static_cast( 1342 | instance.get_pointer()); 1343 | NDIlib_framesync_free_audio(p_instance, p_audio_data); 1344 | }, 1345 | py::arg("instance"), py::arg("audio_data")); 1346 | 1347 | m.def( 1348 | "framesync_free_audio_v2", 1349 | [](py::capsule instance, NDIlib_audio_frame_v3_t *p_audio_data) { 1350 | auto p_instance = static_cast( 1351 | instance.get_pointer()); 1352 | NDIlib_framesync_free_audio_v2(p_instance, p_audio_data); 1353 | }, 1354 | py::arg("instance"), py::arg("audio_data")); 1355 | 1356 | m.def( 1357 | "framesync_audio_queue_depth", 1358 | [](py::capsule instance) { 1359 | auto p_instance = static_cast( 1360 | instance.get_pointer()); 1361 | return NDIlib_framesync_audio_queue_depth(p_instance); 1362 | }, 1363 | py::arg("instance")); 1364 | 1365 | m.def( 1366 | "framesync_capture_video", 1367 | [](py::capsule instance, NDIlib_frame_format_type_e field_type) { 1368 | auto p_instance = static_cast( 1369 | instance.get_pointer()); 1370 | NDIlib_video_frame_v2_t video_frame; 1371 | NDIlib_framesync_capture_video(p_instance, &video_frame, field_type); 1372 | return video_frame; 1373 | }, 1374 | py::arg("instance"), 1375 | py::arg("field_type") = NDIlib_frame_format_type_progressive); 1376 | 1377 | m.def( 1378 | "framesync_free_video", 1379 | [](py::capsule instance, NDIlib_video_frame_v2_t *p_video_data) { 1380 | auto p_instance = static_cast( 1381 | instance.get_pointer()); 1382 | NDIlib_framesync_free_video(p_instance, p_video_data); 1383 | }, 1384 | py::arg("instance"), py::arg("video_data")); 1385 | } 1386 | --------------------------------------------------------------------------------