From b3b94e0e514622611ad2b06ac93238d519f3f218 Mon Sep 17 00:00:00 2001 From: Naoto Kondo Date: Tue, 16 Apr 2019 19:59:04 +0900 Subject: [PATCH] Initial commit --- .clang-format | 121 +++++++++ .gitignore | 186 +++++++++++++ .gitmodules | 3 + CMakeLists.txt | 11 + README.md | 1 + example/send_capture.py | 50 ++++ example/send_png.py | 45 ++++ example/send_video.py | 46 ++++ lib/pybind11 | 1 + src/main.cpp | 570 ++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1034 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 example/send_capture.py create mode 100644 example/send_png.py create mode 100644 example/send_video.py create mode 160000 lib/pybind11 create mode 100644 src/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d30c5da --- /dev/null +++ b/.clang-format @@ -0,0 +1,121 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2598baa --- /dev/null +++ b/.gitignore @@ -0,0 +1,186 @@ +# Created by https://www.gitignore.io/api/c++,code,cmake,python +# Edit at https://www.gitignore.io/?templates=c++,code,cmake,python + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +# External projects +*-prefix/ + +### Code ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/c++,code,cmake,python diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8372b7a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/pybind11"] + path = lib/pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7418bcb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.1) + +project(NDIlib VERSION 3.8.0) + +set(PYBIND11_CPP_STANDARD -std=c++14) + +add_subdirectory("lib/pybind11") + +pybind11_add_module(NDIlib SHARED ${CMAKE_SOURCE_DIR}/src/main.cpp) + +target_link_libraries(NDIlib PRIVATE pybind11::module ndi) diff --git a/README.md b/README.md new file mode 100644 index 0000000..861f47c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ndi-python \ No newline at end of file diff --git a/example/send_capture.py b/example/send_capture.py new file mode 100644 index 0000000..b756f12 --- /dev/null +++ b/example/send_capture.py @@ -0,0 +1,50 @@ +import sys +import time +import numpy as np +import cv2 +import NDIlib + +def main(): + + if not NDIlib.initialize(): + return 0 + + cap = cv2.VideoCapture(0) + + send_settings = NDIlib.SendCreate() + send_settings.p_ndi_name = b'ndi-python' + + ndi_send = NDIlib.send_create(send_settings) + + video_frame = NDIlib.VideoFrameV2() + + start = time.time() + while time.time() - start < 60 * 5: + start_send = time.time() + + for idx in reversed(range(200)): + + ret, img = cap.read() + + if ret: + h, w = img.shape[:2] + img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + + video_frame.xres = w + video_frame.yres = h + video_frame.p_data = img.ctypes.data_as(NDIlib.POINTER(NDIlib.c_uint8)) + video_frame.FourCC = NDIlib.FOURCC_TYPE_BGRX + video_frame.line_stride_in_bytes = w * 4 + + NDIlib.send_send_video_v2(ndi_send, video_frame) + + print('200 frames sent, at %1.2ffps' % (200.0 / (time.time() - start_send))) + + NDIlib.send_destroy(ndi_send) + + NDIlib.destroy() + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/example/send_png.py b/example/send_png.py new file mode 100644 index 0000000..976e3de --- /dev/null +++ b/example/send_png.py @@ -0,0 +1,45 @@ +import sys +import time +import numpy as np +import cv2 +import NDIlib + +def main(): + + if not NDIlib.initialize(): + return 0 + + img = cv2.imread('image/test2.png', cv2.IMREAD_ANYCOLOR) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) + + h, w = img.shape[:2] + + send_settings = NDIlib.SendCreate() + send_settings.p_ndi_name = b'ndi-python' + + ndi_send = NDIlib.send_create(send_settings) + + video_frame = NDIlib.VideoFrameV2() + video_frame.xres = w + video_frame.yres = h + video_frame.FourCC = NDIlib.FOURCC_TYPE_RGBA + video_frame.p_data = img.ctypes.data_as(NDIlib.POINTER(NDIlib.c_uint8)) + video_frame.line_stride_in_bytes = w * 4 + + start = time.time() + while time.time() - start < 60 * 5: + start_send = time.time() + + for idx in reversed(range(200)): + NDIlib.send_send_video_v2(ndi_send, video_frame) + + print('200 frames sent, at %1.2ffps' % (200.0 / (time.time() - start_send))) + + NDIlib.send_destroy(ndi_send) + + NDIlib.destroy() + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/example/send_video.py b/example/send_video.py new file mode 100644 index 0000000..9103129 --- /dev/null +++ b/example/send_video.py @@ -0,0 +1,46 @@ +import sys +import time +import numpy as np +import NDIlib +import ctypes + +def main(): + + if not NDIlib.initialize(): + return 0 + + ndi_send = NDIlib.send_create() + + if ndi_send is 0: + return 0 + + img = np.zeros((1080, 1920, 4), dtype=np.uint8) + h, w = img.shape[:2] + + video_frame = NDIlib.VideoFrameV2() + + video_frame.xres = w + video_frame.yres = h + video_frame.FourCC = NDIlib.FOURCC_TYPE_BGRX + #video_frame.data = img.ctypes.data_as(NDIlib.POINTER(NDIlib.c_uint8)) + #video_frame.data = img.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)) + print(type(video_frame.data)) + + start = time.time() + while time.time() - start < 60 * 5: + start_send = time.time() + + for idx in reversed(range(200)): + img.fill(255 if idx % 2 else 0) + NDIlib.send_send_video_v2(ndi_send, video_frame) + + print('200 frames sent, at %1.2ffps' % (200.0 / (time.time() - start_send))) + + NDIlib.send_destroy(ndi_send) + + NDIlib.destroy() + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/lib/pybind11 b/lib/pybind11 new file mode 160000 index 0000000..9bb3313 --- /dev/null +++ b/lib/pybind11 @@ -0,0 +1 @@ +Subproject commit 9bb3313162c0b856125e481ceece9d8faa567716 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a1472df --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,570 @@ +#include + +#include + +namespace py = pybind11; + +PYBIND11_MODULE(NDIlib, m) { + + m.doc() = "NDI Plugin"; + + // Processing.NDI.structs + enum FrameType { + FRAME_TYPE_NONE = NDIlib_frame_type_none, + FRAME_TYPE_VIDEO = NDIlib_frame_type_video, + FRAME_TYPE_AUDIO = NDIlib_frame_type_audio, + FRAME_TYPE_METADATA = NDIlib_frame_type_metadata, + FRAME_TYPE_ERROR = NDIlib_frame_type_error, + FRANE_TYPE_STATUS_CHANGE = NDIlib_frame_type_status_change + }; + + py::enum_(m, "FrameType", py::arithmetic()) + .value("FRAME_TYPE_NONE", FrameType::FRAME_TYPE_NONE) + .value("FRAME_TYPE_VIDEO", FrameType::FRAME_TYPE_VIDEO) + .value("FRAME_TYPE_AUDIO", FrameType::FRAME_TYPE_AUDIO) + .value("FRAME_TYPE_METADATA", FrameType::FRAME_TYPE_METADATA) + .value("FRAME_TYPE_ERROR", FrameType::FRAME_TYPE_ERROR) + .value("FRANE_TYPE_STATUS_CHANGE", FrameType::FRANE_TYPE_STATUS_CHANGE) + .export_values(); + + enum FourCCType { + FOURCC_TYPE_UYVY = NDIlib_FourCC_type_UYVY, + FOURCC_TYPE_YV12 = NDIlib_FourCC_type_YV12, + FOURCC_TYPE_NV12 = NDIlib_FourCC_type_NV12, + FOURCC_TYPE_I420 = NDIlib_FourCC_type_I420, + FOURCC_TYPE_BGRA = NDIlib_FourCC_type_BGRA, + FOURCC_TYPE_BGRX = NDIlib_FourCC_type_BGRX, + FOURCC_TYPE_RGBA = NDIlib_FourCC_type_RGBA, + FOURCC_TYPE_RGBX = NDIlib_FourCC_type_RGBX, + FOURCC_TYPE_UYVA = NDIlib_FourCC_type_UYVA, + }; + + py::enum_(m, "FourCCType", py::arithmetic()) + .value("FOURCC_TYPE_UYVY", FourCCType::FOURCC_TYPE_UYVY) + .value("FOURCC_TYPE_YV12", FourCCType::FOURCC_TYPE_YV12) + .value("FOURCC_TYPE_NV12", FourCCType::FOURCC_TYPE_NV12) + .value("FOURCC_TYPE_I420", FourCCType::FOURCC_TYPE_I420) + .value("FOURCC_TYPE_BGRA", FourCCType::FOURCC_TYPE_BGRA) + .value("FOURCC_TYPE_BGRX", FourCCType::FOURCC_TYPE_BGRX) + .value("FOURCC_TYPE_RGBA", FourCCType::FOURCC_TYPE_RGBA) + .value("FOURCC_TYPE_RGBX", FourCCType::FOURCC_TYPE_RGBX) + .value("FOURCC_TYPE_UYVA", FourCCType::FOURCC_TYPE_UYVA) + .export_values(); + + enum FrameFormatType { + FRAME_FORMAT_TYPE_PROGRESSIVE = NDIlib_frame_format_type_progressive, + FRAME_FORMAT_TYPE_INTERLEAVED = NDIlib_frame_format_type_interleaved, + FRAME_FORMAT_TYPE_FIELD_0 = NDIlib_frame_format_type_field_0, + FRAME_FORMAT_TYPE_FIELD_1 = NDIlib_frame_format_type_field_1 + }; + + py::enum_(m, "FrameFormatType", py::arithmetic()) + .value("FRAME_FORMAT_TYPE_PROGRESSIVE", + FrameFormatType::FRAME_FORMAT_TYPE_PROGRESSIVE) + .value("FRAME_FORMAT_TYPE_INTERLEAVED", + FrameFormatType::FRAME_FORMAT_TYPE_INTERLEAVED) + .value("FRAME_FORMAT_TYPE_FIELD_0", + FrameFormatType::FRAME_FORMAT_TYPE_FIELD_0) + .value("FRAME_FORMAT_TYPE_FIELD_1", + FrameFormatType::FRAME_FORMAT_TYPE_FIELD_1) + .export_values(); + + m.attr("SEND_TIMECODE_SYNTHESIZE") = py::int_(INT64_MAX); + + m.attr("RECV_TIMESTAMP_UNDEFINED") = py::int_(INT64_MAX); + + py::class_(m, "Source") + .def(py::init(), + py::arg("ndi_name") = nullptr, py::arg("url_address") = nullptr) + .def_readwrite("ndi_name", &NDIlib_source_t::p_ndi_name) + .def_readwrite("url_address", &NDIlib_source_t::p_url_address); + + py::class_(m, "VideoFrameV2") + .def(py::init(), + py::arg("xres") = 0, py::arg("yres") = 0, + py::arg("FourCC") = FOURCC_TYPE_UYVY, + py::arg("frame_rate_N") = 30000, py::arg("frame_rate_D") = 1001, + py::arg("picture_aspect_ratio") = 0.0f, + py::arg("frame_format_type") = FRAME_FORMAT_TYPE_PROGRESSIVE, + py::arg("timecode") = 0, py::arg("data") = NULL, + py::arg("line_stride_in_bytes") = 0, py::arg("metadata") = nullptr, + py::arg("timestamp") = 0) + .def_readwrite("xres", &NDIlib_video_frame_v2_t::xres) + .def_readwrite("yres", &NDIlib_video_frame_v2_t::yres) + .def_readwrite("FourCC", &NDIlib_video_frame_v2_t::FourCC) + .def_readwrite("frame_rate_N", &NDIlib_video_frame_v2_t::frame_rate_N) + .def_readwrite("frame_rate_D", &NDIlib_video_frame_v2_t::frame_rate_D) + .def_readwrite("picture_aspect_ratio", + &NDIlib_video_frame_v2_t::picture_aspect_ratio) + .def_readwrite("frame_format_type", + &NDIlib_video_frame_v2_t::frame_format_type) + .def_readwrite("timecode", &NDIlib_video_frame_v2_t::timecode) + .def_readwrite("data", &NDIlib_video_frame_v2_t::p_data) + .def_readwrite("line_stride_in_bytes", + &NDIlib_video_frame_v2_t::line_stride_in_bytes) + .def_readwrite("metadata", &NDIlib_video_frame_v2_t::p_metadata) + .def_readwrite("timestamp", &NDIlib_video_frame_v2_t::timestamp); + + py::class_(m, "AudioFrameV2") + .def(py::init(), + py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, + py::arg("no_samples") = 0, + py::arg("timecode") = NDIlib_send_timecode_synthesize, + py::arg("data") = NULL, py::arg("channel_stride_in_bytes") = 0, + py::arg("metadata") = nullptr, py::arg("timestamp") = 0) + .def_readwrite("sample_rate", &NDIlib_audio_frame_v2_t::sample_rate) + .def_readwrite("no_channels", &NDIlib_audio_frame_v2_t::no_channels) + .def_readwrite("no_samples", &NDIlib_audio_frame_v2_t::no_samples) + .def_readwrite("timecode", &NDIlib_audio_frame_v2_t::timecode) + .def_readwrite("data", &NDIlib_audio_frame_v2_t::p_data) + .def_readwrite("channel_stride_in_bytes", + &NDIlib_audio_frame_v2_t::channel_stride_in_bytes) + .def_readwrite("metadata", &NDIlib_audio_frame_v2_t::p_metadata) + .def_readwrite("timestamp", &NDIlib_audio_frame_v2_t::timestamp); + + py::class_(m, "MetadataFrame") + .def(py::init(), py::arg("length") = 0, + py::arg("timecode") = NDIlib_send_timecode_synthesize, + py::arg("data") = nullptr) + .def_readwrite("length", &NDIlib_metadata_frame_t::length) + .def_readwrite("timecode", &NDIlib_metadata_frame_t::timecode) + .def_readwrite("data", &NDIlib_metadata_frame_t::p_data); + + py::class_(m, "Tally") + .def(py::init(), py::arg("on_program") = false, + py::arg("on_preview") = false) + .def_readwrite("on_program", &NDIlib_tally_t::on_program) + .def_readwrite("on_preview", &NDIlib_tally_t::on_preview); + + // Processing.NDI.Lib + m.def("initialize", &NDIlib_initialize); + + m.def("destroy", &NDIlib_destroy); + + m.def("version", &NDIlib_version); + + m.def("is_supported_CPU", &NDIlib_is_supported_CPU); + + // Processing.NDI.Find + py::class_(m, "FindCreate") + .def(py::init(), + py::arg("show_local_sources") = true, py::arg("groups") = nullptr, + py::arg("extra_ips") = nullptr) + .def_readwrite("show_local_sources", + &NDIlib_find_create_t::show_local_sources) + .def_readwrite("groups", &NDIlib_find_create_t::p_groups) + .def_readwrite("extra_ips", &NDIlib_find_create_t::p_extra_ips); + + m.def("find_create_v2", &NDIlib_find_create_v2, + py::arg("create_settings") = nullptr); + + m.def("find_destroy", &NDIlib_find_destroy, py::arg("instance")); + + m.def("find_get_current_sources", &NDIlib_find_get_current_sources, + py::arg("instance"), py::arg("no_sources")); + + m.def("find_wait_for_sources", &NDIlib_find_wait_for_sources, + py::arg("instance"), py::arg("timeout_in_ms")); + + // Processing.NDI.Recv + enum RecvBandwidth { + RECV_BANDWIDTH_METADATA_ONLY = NDIlib_recv_bandwidth_metadata_only, + RECV_BANDWIDTH_AUDIO_ONLY = NDIlib_recv_bandwidth_audio_only, + RECV_BANDWIDTH_LOWEST = NDIlib_recv_bandwidth_lowest, + RECV_BANDWIDTH_HIGHEST = NDIlib_recv_bandwidth_highest + }; + + py::enum_(m, "RecvBandwidth", py::arithmetic()) + .value("RECV_BANDWIDTH_METADATA_ONLY", + RecvBandwidth::RECV_BANDWIDTH_METADATA_ONLY) + .value("RECV_BANDWIDTH_AUDIO_ONLY", + RecvBandwidth::RECV_BANDWIDTH_AUDIO_ONLY) + .value("RECV_BANDWIDTH_LOWEST", RecvBandwidth::RECV_BANDWIDTH_LOWEST) + .value("RECV_BANDWIDTH_HIGHEST", RecvBandwidth::RECV_BANDWIDTH_HIGHEST) + .export_values(); + + enum RecvColorFormat { + RECV_COLOR_FORMAT_BGRX_BGRA = NDIlib_recv_color_format_BGRX_BGRA, + RECV_COLOR_FORMAT_UYVY_BGRA = NDIlib_recv_color_format_UYVY_BGRA, + RECV_COLOR_FORMAT_RGBX_RGBA = NDIlib_recv_color_format_RGBX_RGBA, + RECV_COLOR_FORMAT_UYVY_RGBA = NDIlib_recv_color_format_UYVY_RGBA, +#ifdef _WIN32 + RECV_COLOR_FORMAT_BGRX_BGRA_FLIPPED = + NDIlib_recv_color_format_BGRX_BGRA_flipped, +#endif + RECV_COLOR_FORMAT_FASTEST = NDIlib_recv_color_format_fastest, + RECV_COLOR_FORMAT_E_BGRX_BGRA = NDIlib_recv_color_format_e_BGRX_BGRA, + RECV_COLOR_FORMAT_E_UYVY_BGRA = NDIlib_recv_color_format_e_UYVY_BGRA, + RECV_COLOR_FORMAT_E_RGBX_RGBA = NDIlib_recv_color_format_e_RGBX_RGBA, + RECV_COLOR_FORMAT_E_UYVY_RGBA = NDIlib_recv_color_format_e_UYVY_RGBA + }; + + py::enum_(m, "RecvColorFormat", py::arithmetic()) + .value("RECV_COLOR_FORMAT_BGRX_BGRA", + RecvColorFormat::RECV_COLOR_FORMAT_BGRX_BGRA) + .value("RECV_COLOR_FORMAT_UYVY_BGRA", + RecvColorFormat::RECV_COLOR_FORMAT_UYVY_BGRA) + .value("RECV_COLOR_FORMAT_RGBX_RGBA", + RecvColorFormat::RECV_COLOR_FORMAT_RGBX_RGBA) + .value("RECV_COLOR_FORMAT_UYVY_RGBA", + RecvColorFormat::RECV_COLOR_FORMAT_UYVY_RGBA) +#ifdef _WIN32 + .value("RECV_COLOR_FORMAT_BGRX_BGRA_FLIPPED", + RecvColorFormat::RECV_COLOR_FORMAT_BGRX_BGRA_FLIPPED) +#endif + .value("RECV_COLOR_FORMAT_FASTEST", + RecvColorFormat::RECV_COLOR_FORMAT_FASTEST) + .value("RECV_COLOR_FORMAT_E_BGRX_BGRA", + RecvColorFormat::RECV_COLOR_FORMAT_E_BGRX_BGRA) + .value("RECV_COLOR_FORMAT_E_UYVY_BGRA", + RecvColorFormat::RECV_COLOR_FORMAT_E_UYVY_BGRA) + .value("RECV_COLOR_FORMAT_E_RGBX_RGBA", + RecvColorFormat::RECV_COLOR_FORMAT_E_RGBX_RGBA) + .value("RECV_COLOR_FORMAT_E_UYVY_RGBA", + RecvColorFormat::RECV_COLOR_FORMAT_E_UYVY_RGBA) + .export_values(); + + py::class_(m, "RecvCreateV3") + .def(py::init(), + py::arg("source_to_connect_to") = NDIlib_source_t(), + py::arg("color_format") = RECV_COLOR_FORMAT_UYVY_BGRA, + py::arg("bandwidth") = RECV_BANDWIDTH_HIGHEST, + py::arg("allow_video_fields") = true, py::arg("ndi_name") = nullptr) + .def_readwrite("source_to_connect_to", + &NDIlib_recv_create_v3_t::source_to_connect_to) + .def_readwrite("color_format", &NDIlib_recv_create_v3_t::color_format) + .def_readwrite("bandwidth", &NDIlib_recv_create_v3_t::bandwidth) + .def_readwrite("allow_video_fields", + &NDIlib_recv_create_v3_t::allow_video_fields) + .def_readwrite("ndi_recv_name", + &NDIlib_recv_create_v3_t::p_ndi_recv_name); + + py::class_(m, "RecvPerformance") + .def(py::init<>()) + .def_readwrite("video_frames", &NDIlib_recv_performance_t::video_frames) + .def_readwrite("audio_frames", &NDIlib_recv_performance_t::audio_frames) + .def_readwrite("metadata_frames", + &NDIlib_recv_performance_t::metadata_frames); + + py::class_(m, "RecvQueue") + .def(py::init<>()) + .def_readwrite("video_frames", &NDIlib_recv_queue_t::video_frames) + .def_readwrite("audio_frames", &NDIlib_recv_queue_t::audio_frames) + .def_readwrite("metadata_frames", &NDIlib_recv_queue_t::metadata_frames); + + m.def("recv_create_v3", &NDIlib_recv_create_v3, + py::arg("instance") = nullptr); + + m.def("recv_destroy", &NDIlib_recv_destroy, py::arg("instance")); + + m.def("recv_connect", &NDIlib_recv_connect, py::arg("instance"), + py::arg("source") = nullptr); + + m.def("recv_capture_v2", &NDIlib_recv_capture_v2, py::arg("instance"), + py::arg("video_data"), py::arg("audio_data"), py::arg("metadata"), + py::arg("timeout_in_ms")); + + m.def("recv_free_video_v2", &NDIlib_recv_free_video_v2, py::arg("instance"), + py::arg("video_data")); + + m.def("recv_free_audio_v2", &NDIlib_recv_free_audio_v2, py::arg("instance"), + py::arg("audio_data")); + + m.def("recv_free_metadata", &NDIlib_recv_free_metadata, py::arg("instance"), + py::arg("metadata")); + + m.def("recv_free_string", &NDIlib_recv_free_string, py::arg("instance"), + py::arg("string")); + + m.def("recv_send_metadata", &NDIlib_recv_send_metadata, py::arg("instance"), + py::arg("metadata_frame")); + + m.def("recv_set_tally", &NDIlib_recv_set_tally, py::arg("instance"), + py::arg("tally")); + + m.def("recv_get_performance", &NDIlib_recv_get_performance, + py::arg("instance"), py::arg("total"), py::arg("dropped")); + + m.def("recv_get_queue", &NDIlib_recv_get_queue, py::arg("instance"), + py::arg("total")); + + m.def("recv_clear_connection_metadata", + &NDIlib_recv_clear_connection_metadata, py::arg("instance")); + + m.def("recv_add_connection_metadata", &NDIlib_recv_add_connection_metadata, + py::arg("instance"), py::arg("metadata")); + + m.def("recv_get_no_connections", &NDIlib_recv_get_no_connections, + py::arg("instance")); + + m.def("recv_get_web_control", &NDIlib_recv_get_web_control, + py::arg("instance")); + + // Processing.NDI.Recv.ex + m.def("recv_ptz_is_supported", &NDIlib_recv_ptz_is_supported, + py::arg("instance")); + + m.def("recv_recording_is_supported", &NDIlib_recv_recording_is_supported, + py::arg("instance")); + + m.def("recv_ptz_zoom", &NDIlib_recv_ptz_zoom, py::arg("instance"), + py::arg("zoom_value")); + + m.def("recv_ptz_zoom_speed", &NDIlib_recv_ptz_zoom_speed, py::arg("instance"), + py::arg("zoom_speed")); + + m.def("recv_ptz_pan_tilt", &NDIlib_recv_ptz_pan_tilt, py::arg("instance"), + py::arg("pan_value"), py::arg("tilt_value")); + + m.def("recv_ptz_pan_tilt_speed", &NDIlib_recv_ptz_pan_tilt_speed, + py::arg("instance"), py::arg("pan_speed"), py::arg("tilt_speed")); + + m.def("recv_ptz_store_preset", &NDIlib_recv_ptz_store_preset, + py::arg("instance"), py::arg("preset_no")); + + m.def("recv_ptz_recall_preset", &NDIlib_recv_ptz_recall_preset, + py::arg("instance"), py::arg("preset_no"), py::arg("speed")); + + m.def("recv_ptz_auto_focus", &NDIlib_recv_ptz_auto_focus, + py::arg("instance")); + + m.def("recv_ptz_focus", &NDIlib_recv_ptz_focus, py::arg("instance"), + py::arg("focus_value")); + + m.def("recv_ptz_focus_speed", &NDIlib_recv_ptz_focus_speed, + py::arg("instance"), py::arg("focus_speed")); + + m.def("recv_ptz_white_balance_auto", &NDIlib_recv_ptz_white_balance_auto, + py::arg("instance")); + + m.def("recv_ptz_white_balance_indoor", &NDIlib_recv_ptz_white_balance_indoor, + py::arg("instance")); + + m.def("recv_ptz_white_balance_outdoor", + &NDIlib_recv_ptz_white_balance_outdoor, py::arg("instance")); + + m.def("recv_ptz_white_balance_oneshot", + &NDIlib_recv_ptz_white_balance_oneshot, py::arg("instance")); + + m.def("recv_ptz_white_balance_manual", &NDIlib_recv_ptz_white_balance_manual, + py::arg("instance"), py::arg("red"), py::arg("blue")); + + m.def("recv_ptz_exposure_auto", &NDIlib_recv_ptz_exposure_auto, + py::arg("instance")); + + m.def("recv_ptz_exposure_manual", &NDIlib_recv_ptz_exposure_manual, + py::arg("instance"), py::arg("exposure_level")); + + m.def("recv_recording_start", &NDIlib_recv_recording_start, + py::arg("instance"), py::arg("filename_hint")); + + m.def("recv_recording_stop", &NDIlib_recv_recording_stop, + py::arg("instance")); + + m.def("recv_recording_set_audio_level", + &NDIlib_recv_recording_set_audio_level, py::arg("instance"), + py::arg("level_dB")); + + m.def("recv_recording_is_recording", &NDIlib_recv_recording_is_recording, + py::arg("instance")); + + m.def("recv_recording_get_filename", &NDIlib_recv_recording_get_filename, + py::arg("instance")); + + m.def("recv_recording_get_error", &NDIlib_recv_recording_get_error, + py::arg("instance")); + + py::class_(m, "RecvRecordingTime") + .def(py::init<>()) + .def_readwrite("no_frames", &NDIlib_recv_recording_time_t::no_frames) + .def_readwrite("start_time", &NDIlib_recv_recording_time_t::start_time) + .def_readwrite("last_time", &NDIlib_recv_recording_time_t::last_time); + + m.def("recv_recording_get_times", &NDIlib_recv_recording_get_times, + py::arg("instance"), py::arg("times")); + + // Processing.NDI.Send + py::class_(m, "SendCreate") + .def(py::init(), + py::arg("ndi_name") = nullptr, py::arg("groups") = nullptr, + py::arg("clock_video") = true, py::arg("clock_audio") = true) + .def_readwrite("ndi_name", &NDIlib_send_create_t::p_ndi_name) + .def_readwrite("groups", &NDIlib_send_create_t::p_groups) + .def_readwrite("clock_video", &NDIlib_send_create_t::clock_video) + .def_readwrite("clock_audio", &NDIlib_send_create_t::clock_audio); + + m.def("send_create", &NDIlib_send_create, + py::arg("create_settings") = nullptr); + + m.def("send_destroy", &NDIlib_send_destroy, py::arg("instance")); + + m.def("send_send_video_v2", &NDIlib_send_send_video_v2, py::arg("instance"), + py::arg("video_data")); + + m.def("send_send_video_async_v2", &NDIlib_send_send_video_async_v2, + py::arg("instance"), py::arg("video_data")); + + m.def("send_send_audio_v2", &NDIlib_send_send_audio_v2, py::arg("instance"), + py::arg("audio_data")); + + m.def("send_send_metadata", &NDIlib_send_send_metadata, py::arg("instance"), + py::arg("metadata")); + + m.def("send_capture", &NDIlib_send_capture, py::arg("instance"), + py::arg("metadata"), py::arg("timeout_in_ms")); + + m.def("send_free_metadata", &NDIlib_send_free_metadata, py::arg("instance"), + py::arg("metadata")); + + m.def("send_get_tally", &NDIlib_send_get_tally, py::arg("instance"), + py::arg("tally"), py::arg("timeout_in_ms")); + + m.def("send_get_no_connections", &NDIlib_send_get_no_connections, + py::arg("instance"), py::arg("timeout_in_ms")); + + m.def("send_clear_connection_metadata", + &NDIlib_send_clear_connection_metadata, py::arg("instance")); + + m.def("send_add_connection_metadata", &NDIlib_send_add_connection_metadata, + py::arg("instance"), py::arg("metadata")); + + m.def("send_set_failover", &NDIlib_send_set_failover, py::arg("instance"), + py::arg("failover_source")); + + m.def("send_get_source_name", &NDIlib_send_get_source_name, + py::arg("instance")); + + // Processing.NDI.Routing + py::class_(m, "RoutingCreate") + .def(py::init(), + py::arg("ndi_name") = nullptr, py::arg("groups") = nullptr) + .def_readwrite("ndi_name", &NDIlib_routing_create_t::p_ndi_name) + .def_readwrite("groups", &NDIlib_routing_create_t::p_groups); + + m.def("routing_create", &NDIlib_routing_create, py::arg("create_settings")); + + m.def("routing_destroy", &NDIlib_routing_destroy, py::arg("instance")); + + m.def("routing_change", &NDIlib_routing_change, py::arg("instance"), + py::arg("source")); + + m.def("routing_clear", &NDIlib_routing_clear, py::arg("instance")); + + // Processing.NDI.utilities + py::class_(m, + "AudioFrameInterleaved16s") + .def(py::init(), + py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, + py::arg("no_samples") = 0, + py::arg("timecode") = NDIlib_send_timecode_synthesize, + py::arg("reference_level") = 0, py::arg("data") = NULL) + .def_readwrite("sample_rate", + &NDIlib_audio_frame_interleaved_16s_t::sample_rate) + .def_readwrite("no_channels", + &NDIlib_audio_frame_interleaved_16s_t::no_channels) + .def_readwrite("no_samples", + &NDIlib_audio_frame_interleaved_16s_t::no_samples) + .def_readwrite("timecode", + &NDIlib_audio_frame_interleaved_16s_t::timecode) + .def_readwrite("reference_level", + &NDIlib_audio_frame_interleaved_16s_t::reference_level) + .def_readwrite("data", &NDIlib_audio_frame_interleaved_16s_t::p_data); + + py::class_(m, + "AudioFrameInterleaved32s") + .def(py::init(), + py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, + py::arg("no_samples") = 0, + py::arg("timecode") = NDIlib_send_timecode_synthesize, + py::arg("reference_level") = 0, py::arg("data") = NULL) + .def_readwrite("sample_rate", + &NDIlib_audio_frame_interleaved_32s_t::sample_rate) + .def_readwrite("no_channels", + &NDIlib_audio_frame_interleaved_32s_t::no_channels) + .def_readwrite("no_samples", + &NDIlib_audio_frame_interleaved_32s_t::no_samples) + .def_readwrite("timecode", + &NDIlib_audio_frame_interleaved_32s_t::timecode) + .def_readwrite("reference_level", + &NDIlib_audio_frame_interleaved_32s_t::reference_level) + .def_readwrite("data", &NDIlib_audio_frame_interleaved_32s_t::p_data); + + py::class_(m, + "AudioFrameInterleaved32f") + .def(py::init(), + py::arg("sample_rate") = 48000, py::arg("no_channels") = 2, + py::arg("no_samples") = 0, + py::arg("timecode") = NDIlib_send_timecode_synthesize, + py::arg("data") = NULL) + .def_readwrite("sample_rate", + &NDIlib_audio_frame_interleaved_32f_t::sample_rate) + .def_readwrite("no_channels", + &NDIlib_audio_frame_interleaved_32f_t::no_channels) + .def_readwrite("no_samples", + &NDIlib_audio_frame_interleaved_32f_t::no_samples) + .def_readwrite("timecode", + &NDIlib_audio_frame_interleaved_32f_t::timecode) + .def_readwrite("data", &NDIlib_audio_frame_interleaved_32f_t::p_data); + + m.def("util_send_send_audio_interleaved_16s", + &NDIlib_util_send_send_audio_interleaved_16s, py::arg("instance"), + py::arg("audio_data")); + + m.def("util_send_send_audio_interleaved_32s", + &NDIlib_util_send_send_audio_interleaved_32s, py::arg("instance"), + py::arg("audio_data")); + + m.def("util_send_send_audio_interleaved_32f", + &NDIlib_util_send_send_audio_interleaved_32f, py::arg("instance"), + py::arg("audio_data")); + + m.def("util_audio_to_interleaved_16s_v2", + &NDIlib_util_audio_to_interleaved_16s_v2, py::arg("src"), + py::arg("dst")); + + m.def("util_audio_from_interleaved_16s_v2", + &NDIlib_util_audio_from_interleaved_16s_v2, py::arg("src"), + py::arg("dst")); + + m.def("util_audio_to_interleaved_32s_v2", + &NDIlib_util_audio_to_interleaved_32s_v2, py::arg("src"), + py::arg("dst")); + + m.def("util_audio_from_interleaved_32s_v2", + &NDIlib_util_audio_from_interleaved_32s_v2, py::arg("src"), + py::arg("dst")); + + m.def("util_audio_to_interleaved_32f_v2", + &NDIlib_util_audio_to_interleaved_32f_v2, py::arg("src"), + py::arg("dst")); + + m.def("util_audio_from_interleaved_32f_v2", + &NDIlib_util_audio_from_interleaved_32f_v2, py::arg("src"), + py::arg("dst")); + + // Processing.NDI.deprecated + // TODO + + // Processing.NDI.FrameSync + m.def("framesync_create", &NDIlib_framesync_create, py::arg("receiver")); + + m.def("framesync_destroy", &NDIlib_framesync_destroy, py::arg("instance")); + + m.def("framesync_capture_audio", &NDIlib_framesync_capture_audio, + py::arg("instance"), py::arg("audio_data"), py::arg("sample_rate"), + py::arg("no_channels"), py::arg("no_samples")); + + m.def("framesync_free_audio", &NDIlib_framesync_free_audio, + py::arg("instance"), py::arg("audio_data")); + + m.def("framesync_capture_video", &NDIlib_framesync_capture_video, + py::arg("instance"), py::arg("video_data"), + py::arg("field_type") = FRAME_FORMAT_TYPE_PROGRESSIVE); + + m.def("framesync_free_video", &NDIlib_framesync_free_video, + py::arg("instance"), py::arg("video_data")); +}