diff --git a/NDI_SETUP_GUIDE.md b/NDI_SETUP_GUIDE.md new file mode 100644 index 0000000..e92f2e4 --- /dev/null +++ b/NDI_SETUP_GUIDE.md @@ -0,0 +1,145 @@ +# NDI Output for PNGTuber-Plus + +This guide explains how to use the NDI (Network Device Interface) output feature in PNGTuber-Plus to stream your avatar to OBS Studio or other NDI-compatible applications. + +## Requirements + +1. **NDI Runtime**: Download and install the NDI Runtime from [NDI website](http://ndi.link/NDIRedistV6) + - Windows: [NDI Runtime for Windows](http://ndi.link/NDIRedistV6) + - macOS: [NDI Runtime for macOS](http://ndi.link/NDIRedistV6Apple) + - Linux: [NDI Runtime for Linux](https://github.com/DistroAV/DistroAV/wiki/1.-Installation#linux) (Note: Flatpak incompatible) + +2. **Godot 4.4-stable or later**: This plugin requires Godot 4.4+ to function properly + +3. **NDI-compatible software**: Such as OBS Studio with NDI plugin, vMix, Wirecast, etc. + +## Features + +- **Real-time NDI streaming**: Stream your PNGTuber avatar over the network +- **Audio support**: Include system audio in the NDI stream (optional) +- **Customizable source name**: Set a custom name for your NDI source +- **Hotkey controls**: Quick toggle streaming with keyboard shortcuts +- **Settings UI**: Easy configuration through the settings menu + +## Getting Started + +### 1. Basic Setup + +The NDI output is automatically enabled when you start PNGTuber-Plus. The default settings are: +- **Source Name**: "PNGTuber-Plus" +- **Audio**: Enabled (Master bus) +- **Auto-start**: Enabled + +### 2. Settings Menu Configuration + +Access the NDI settings through the Settings Menu: + +1. Click the settings wheel in PNGTuber-Plus +2. Scroll down to find the "NDI streaming" section +3. Configure the following options: + - **NDI streaming**: Enable/disable NDI output + - **Source name**: Set a custom name for your NDI source (default: "PNGTuber-Plus") + - **Include audio**: Toggle audio streaming on/off + - **Status**: View current streaming status + - **Start/Stop NDI**: Manual control button + +### 3. Hotkey Controls + +Quick keyboard shortcuts for NDI control: +- **F9**: Toggle NDI streaming on/off +- **F10**: Restart NDI streaming (useful for applying new settings) + +### 4. OBS Studio Setup + +To capture the NDI stream in OBS Studio: + +1. **Install NDI Plugin for OBS**: + - Download from [OBS NDI Plugin](https://github.com/Palakis/obs-ndi/releases) + - Install following the provided instructions + +2. **Add NDI Source in OBS**: + - In OBS, click the "+" in Sources + - Select "NDI Source" + - Name your source (e.g., "PNGTuber Avatar") + - In the properties, select your NDI source from the dropdown + - Look for "PNGTuber-Plus" (or your custom name) + - Configure audio settings as needed + +3. **Position and Scale**: + - Resize and position the NDI source in your scene + - The stream will include the full PNGTuber viewport + +## Troubleshooting + +### NDI Source Not Appearing in OBS + +1. **Check NDI Runtime**: Ensure NDI Runtime is installed on both machines +2. **Network connectivity**: NDI works over network, ensure devices can communicate +3. **Firewall settings**: NDI uses specific ports, check firewall rules +4. **Restart applications**: Try restarting both PNGTuber-Plus and OBS +5. **Check source name**: Verify the NDI source name matches in settings + +### Audio Issues + +1. **Audio bus**: Ensure the correct audio bus is selected (default: "Master") +2. **System audio**: Check that PNGTuber-Plus is receiving audio input +3. **OBS audio settings**: Verify NDI source audio is not muted in OBS + +### Performance Issues + +1. **Network bandwidth**: NDI requires good network performance for quality streaming +2. **Local streaming**: For best performance, use NDI on the same machine +3. **Resolution**: Lower viewport resolution if experiencing lag +4. **Frame rate**: Adjust FPS settings in PNGTuber-Plus settings + +### NDI Not Working + +1. **Godot version**: Ensure you're using Godot 4.4-stable or later +2. **Plugin enabled**: Verify the godot-ndi plugin is properly installed +3. **System compatibility**: Check NDI Runtime compatibility with your OS +4. **Console errors**: Check Godot console for any NDI-related errors + +## Advanced Configuration + +### Custom Audio Bus + +You can route specific audio to NDI by: +1. Creating a custom audio bus in PNGTuber-Plus +2. Setting the NDI audio bus to your custom bus name +3. Routing desired audio sources to that bus + +### Network Performance + +For optimal network streaming: +- Use wired connections when possible +- Ensure sufficient bandwidth (>100 Mbps recommended for high quality) +- Keep NDI devices on the same network segment +- Consider NDI quality settings if available + +### Multiple Instances + +You can run multiple PNGTuber-Plus instances with different NDI source names: +1. Change the "Source name" in each instance +2. Each will appear as a separate NDI source +3. Useful for multiple avatars or backup streams + +## Technical Details + +- **Video Format**: NDI handles video format automatically based on viewport +- **Audio Format**: Uses the selected audio bus output +- **Compression**: NDI uses efficient compression for network streaming +- **Latency**: Typically very low latency (<1 frame) on local network + +## Legal Notice + +NDI® is a registered trademark of Vizrt NDI AB. Your application must comply with the [NDI SDK license](http://ndi.link/ndisdk_license) when using this feature. + +## Support + +For issues specific to NDI functionality: +1. Check the console output for error messages +2. Verify NDI Runtime installation +3. Test with other NDI applications to isolate issues +4. Check network connectivity for remote streaming + +For PNGTuber-Plus specific issues, refer to the main application documentation and support channels. diff --git a/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll b/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll deleted file mode 100644 index 60e5fe6..0000000 Binary files a/addons/godot-git-plugin/win64/~libgit_plugin.windows.editor.x86_64.dll and /dev/null differ diff --git a/addons/godot-ndi/LICENSE b/addons/godot-ndi/LICENSE new file mode 100644 index 0000000..94610df --- /dev/null +++ b/addons/godot-ndi/LICENSE @@ -0,0 +1,379 @@ + +https://github.com/unvermuthet/godot-ndi + + (C) 2025 Henry Muth - unvermuthet + + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/addons/godot-ndi/NOTICE.md b/addons/godot-ndi/NOTICE.md new file mode 100644 index 0000000..47d3e2f --- /dev/null +++ b/addons/godot-ndi/NOTICE.md @@ -0,0 +1,52 @@ +# Third-party software + +## NDI SDK + +- `ndi/*.h` + +### MIT License + +> The following MIT license applies to these files ONLY and not to the SDK as a whole. Please review the SDK documentation for the description of the full license terms, which are also provided in the file "NDI License Agreement.pdf" within the SDK or online at http://ndi.link/ndisdk_license. Your use of any part of this SDK is acknowledgment that you agree to the SDK license terms. The full NDI SDK may be downloaded at http://ndi.video/ + +Copyright (C) 2023-2024 Vizrt NDI AB. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files(the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions : + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## godot-cpp + +- `godot-cpp/**` + +### MIT License + +Copyright (c) 2017-present Godot Engine contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/godot-ndi/README.md b/addons/godot-ndi/README.md new file mode 100644 index 0000000..280dc0c --- /dev/null +++ b/addons/godot-ndi/README.md @@ -0,0 +1,62 @@ +# godot-ndi [![Made with Godot](https://img.shields.io/badge/Made%20with-Godot-478CBF?style=flat&logo=godot%20engine&logoColor=white)](https://godotengine.org) [![Build GDExtension](https://github.com/unvermuthet/godot-ndi/actions/workflows/builds.yml/badge.svg)](https://github.com/unvermuthet/godot-ndi/actions/workflows/builds.yml) + +> Integrates the NDI® SDK with Godot +> +> [ NDI® is a registered trademark of Vizrt NDI AB ] + +Adds a new [VideoStream](https://godot-ndi.readthedocs.io/stable/classes/class_videostreamndi.html) class to implement **NDI playback**. +For **outputting the Viewport** as an NDI source, add the [NDIOutput](https://godot-ndi.readthedocs.io/stable/classes/class_ndioutput.html) Node to the scene. +Check out the **demo scenes** included with all releases. + +This was born out of work on my DMX Lighting Control Software. I'm fascinated by Live Event Productions, Projection Mapping, TechArt, and Broadcasting. [So reach out](https://discord.com/users/203583245223198722) or [show off](https://github.com/unvermuthet/godot-ndi/discussions/categories/show-and-tell) what you made with Godot NDI! + + + +![Demo GIF](https://github.com/user-attachments/assets/159eb455-b0de-41bb-972f-be0817771455) + +> This GIF only shows local transmission, but imagine this over the network! + +## Install + +> [!IMPORTANT] +> - Only compatible with `Godot 4.4-stable` and later +> - Your application needs to comply with the [NDI SDK license](http://ndi.link/ndisdk_license). +> - The NDI Runtime is required ([Windows](http://ndi.link/NDIRedistV6), [MacOS](http://ndi.link/NDIRedistV6Apple), [Linux (Flatpak incompatible)](https://github.com/DistroAV/DistroAV/wiki/1.-Installation#linux)) + +Download the latest version under `Releases` and install by extracting the ZIP file in your Godot project. +Alternatively, you can install the extension from Godot's Asset Library. + +## Support + +For ***commercial*** support, exclusive alternative licensing, or [NDI Advanced SDK](https://ndi.video/for-developers/ndi-advanced/) integration, contact me at [godot-ndi@muth.pro](mailto:godot-ndi@muth.pro). + +In the production industries, software usually comes with exorbitant licensing fees. If this project has been useful to you and you want to show appreciation for my time and effort, consider a donation. + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I51A7ZC3) + +## Building from source + +To help you get going, I've configured a [Development Container](https://containers.dev/) with everything set up to target Windows or Linux. Just run `scons` or check `scons --help`! + +If you want to configure the environment yourself, follow [Godot's "Building from source" Guide](https://docs.godotengine.org/en/latest/contributing/development/compiling/). This project uses an identical build system. + +## License + +**(C) 2025-present Henry Muth - unvermuthet and Godot NDI contributors** + +### Software + +Subject to the terms of the [Mozilla Public License, v. 2.0](https://www.mozilla.org/en-US/MPL/2.0/) + +### Documentation + +Licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) + +### Attribution suggestion + +``` +godot-ndi + Licensed under MPL-2.0 + (C) 2025-present Henry Muth - unvermuthet and Godot NDI contributors + https://github.com/unvermuthet/godot-ndi +``` diff --git a/addons/godot-ndi/bin/linux/libgodot-ndi.linux.template_debug.arm64.so b/addons/godot-ndi/bin/linux/libgodot-ndi.linux.template_debug.arm64.so new file mode 100644 index 0000000..1cd6187 Binary files /dev/null and b/addons/godot-ndi/bin/linux/libgodot-ndi.linux.template_debug.arm64.so differ diff --git a/addons/godot-ndi/bin/linux/libgodot-ndi.linux.template_debug.x86_64.so b/addons/godot-ndi/bin/linux/libgodot-ndi.linux.template_debug.x86_64.so new file mode 100644 index 0000000..f874af1 Binary files /dev/null and b/addons/godot-ndi/bin/linux/libgodot-ndi.linux.template_debug.x86_64.so differ diff --git a/addons/godot-ndi/bin/macos/libgodot-ndi.macos.template_debug.universal.dylib b/addons/godot-ndi/bin/macos/libgodot-ndi.macos.template_debug.universal.dylib new file mode 100644 index 0000000..9d7933f Binary files /dev/null and b/addons/godot-ndi/bin/macos/libgodot-ndi.macos.template_debug.universal.dylib differ diff --git a/addons/godot-ndi/bin/windows/libgodot-ndi.windows.template_debug.x86_64.dll b/addons/godot-ndi/bin/windows/libgodot-ndi.windows.template_debug.x86_64.dll new file mode 100644 index 0000000..8212dde Binary files /dev/null and b/addons/godot-ndi/bin/windows/libgodot-ndi.windows.template_debug.x86_64.dll differ diff --git a/addons/godot-ndi/demo/3DOutput.tscn b/addons/godot-ndi/demo/3DOutput.tscn new file mode 100644 index 0000000..a534f3e --- /dev/null +++ b/addons/godot-ndi/demo/3DOutput.tscn @@ -0,0 +1,73 @@ +[gd_scene load_steps=4 format=3 uid="uid://deioml0m7ep1t"] + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_4r73q"] +emission_shape = 1 +emission_sphere_radius = 1.0 +direction = Vector3(0, 1, 0) +initial_velocity_min = 2.0 +initial_velocity_max = 6.0 + +[sub_resource type="SphereMesh" id="SphereMesh_jpqj6"] + +[sub_resource type="LabelSettings" id="LabelSettings_ln6fu"] +font_size = 24 +shadow_size = 5 +shadow_color = Color(0, 0, 0, 0.235294) + +[node name="3DOutput" type="SubViewportContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +stretch = true + +[node name="Node3D" type="Node3D" parent="."] + +[node name="GPUParticles3D" type="GPUParticles3D" parent="Node3D"] +amount = 32 +lifetime = 2.0 +process_material = SubResource("ParticleProcessMaterial_4r73q") +draw_pass_1 = SubResource("SphereMesh_jpqj6") + +[node name="SubViewportB" type="SubViewport" parent="."] +handle_input_locally = false +size = Vector2i(1152, 648) +render_target_update_mode = 4 + +[node name="NDIOutput" type="NDIOutput" parent="SubViewportB"] +name = "Godot Cam B" +enable_editor_output = true + +[node name="Camera3D" type="Camera3D" parent="SubViewportB"] +transform = Transform3D(1, 0, 0, 0, -0.0010647, 0.999999, 0, -0.999999, -0.0010647, 0, 6.41845, 0) +current = true + +[node name="SubViewportA" type="SubViewport" parent="."] +handle_input_locally = false +size = Vector2i(1152, 648) +render_target_update_mode = 4 + +[node name="NDIOutput" type="NDIOutput" parent="SubViewportA"] +name = "Godot Cam A" +enable_editor_output = true + +[node name="Label" type="Label" parent="SubViewportA"] +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 31.0 +offset_top = -357.0 +offset_right = 534.0 +offset_bottom = -27.0 +grow_vertical = 0 +text = "NDIOutput always transmits the Viewport closest up the Tree. When running in the Editor that's the 2D View. + +If you want to display a camera view, add NDIOutput to a SubViewport instead. You can skip the SubViewportContainer incase you don't need the Scene to be visible when running the Game. " +label_settings = SubResource("LabelSettings_ln6fu") +horizontal_alignment = 3 +autowrap_mode = 2 + +[node name="Camera3D" type="Camera3D" parent="SubViewportA"] +transform = Transform3D(0.99299, -0.0520139, 0.106136, 0, 0.897966, 0.440065, -0.118196, -0.43698, 0.891672, -2.28829, 1.75016, 5.03714) +current = true diff --git a/addons/godot-ndi/demo/AnimationOutput.tscn b/addons/godot-ndi/demo/AnimationOutput.tscn new file mode 100644 index 0000000..7a4bbdf --- /dev/null +++ b/addons/godot-ndi/demo/AnimationOutput.tscn @@ -0,0 +1,58 @@ +[gd_scene load_steps=5 format=3 uid="uid://p6ywmqtt7nke"] + +[ext_resource type="Texture2D" uid="uid://cfvxj5cdj3n45" path="res://addons/godot-ndi/demo/assets/icon.svg" id="1_ekn6m"] + +[sub_resource type="Animation" id="Animation_62e2m"] +length = 0.001 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Icon:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 0, +"values": [Vector2(1034, 527)] +} + +[sub_resource type="Animation" id="Animation_0fbet"] +resource_name = "new_animation" +length = 8.0 +loop_mode = 1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("Icon:position") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0, 3.9627, 7.9587), +"transitions": PackedFloat32Array(1, 1, 1), +"update": 0, +"values": [Vector2(116, 119), Vector2(1034, 527), Vector2(116, 119)] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_ee4bf"] +_data = { +&"RESET": SubResource("Animation_62e2m"), +&"new_animation": SubResource("Animation_0fbet") +} + +[node name="SubViewport" type="Node2D"] + +[node name="NDIOutput" type="NDIOutput" parent="."] +name = "Godot (Animation)" + +[node name="Icon" type="Sprite2D" parent="."] +position = Vector2(1034, 527) +scale = Vector2(1.463, 1.463) +texture = ExtResource("1_ekn6m") + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +libraries = { +&"": SubResource("AnimationLibrary_ee4bf") +} +autoplay = "new_animation" diff --git a/addons/godot-ndi/demo/Finder.tscn b/addons/godot-ndi/demo/Finder.tscn new file mode 100644 index 0000000..535116f --- /dev/null +++ b/addons/godot-ndi/demo/Finder.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=2 format=3 uid="uid://dydvtidqppffs"] + +[ext_resource type="Script" uid="uid://kbgdevvugj7k" path="res://addons/godot-ndi/demo/ndi_finder.gd" id="1_0rvup"] + +[node name="Canvas" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VideoStreamPlayer" type="VideoStreamPlayer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +autoplay = true +expand = true + +[node name="NDIFinder" type="NDIFinder" parent="."] +script = ExtResource("1_0rvup") + +[connection signal="sources_changed" from="NDIFinder" to="NDIFinder" method="_on_sources_changed"] +[connection signal="sources_found" from="NDIFinder" to="NDIFinder" method="_on_sources_found"] +[connection signal="sources_gone" from="NDIFinder" to="NDIFinder" method="_on_sources_gone"] diff --git a/addons/godot-ndi/demo/VideoInput.tscn b/addons/godot-ndi/demo/VideoInput.tscn new file mode 100644 index 0000000..655900b --- /dev/null +++ b/addons/godot-ndi/demo/VideoInput.tscn @@ -0,0 +1,22 @@ +[gd_scene load_steps=2 format=3 uid="uid://byblss3lhwvqm"] + +[sub_resource type="VideoStreamNDI" id="VideoStreamNDI_lv7vm"] + +[node name="Canvas" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VideoStreamPlayer" type="VideoStreamPlayer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +stream = SubResource("VideoStreamNDI_lv7vm") +autoplay = true +expand = true diff --git a/addons/godot-ndi/demo/VideoOutput.tscn b/addons/godot-ndi/demo/VideoOutput.tscn new file mode 100644 index 0000000..41c2e73 --- /dev/null +++ b/addons/godot-ndi/demo/VideoOutput.tscn @@ -0,0 +1,37 @@ +[gd_scene load_steps=5 format=3 uid="uid://ddr3hiaft61tl"] + +[ext_resource type="VideoStream" uid="uid://0whnog13yrlf" path="res://addons/godot-ndi/demo/assets/liftoff_space_shuttle_atlantis.ogv" id="1_utmvk"] +[ext_resource type="AudioStream" uid="uid://dyvxmok1n3vu1" path="res://addons/godot-ndi/demo/assets/sub-d-more-jazz-guitar.wav" id="2_51pmd"] +[ext_resource type="Script" uid="uid://d2sex4jisnkck" path="res://addons/godot-ndi/demo/video_stream_player.gd" id="2_w6vuq"] +[ext_resource type="VideoStream" path="res://addons/godot-ndi/demo/assets/twitch_sync_footage.ogv" id="3_ug8g3"] + +[node name="Canvas" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VideoStreamPlayer" type="VideoStreamPlayer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +volume_db = -15.0 +autoplay = true +script = ExtResource("2_w6vuq") +playlist = Array[VideoStream]([ExtResource("1_utmvk"), ExtResource("3_ug8g3")]) + +[node name="AudioStreamPlayer" type="AudioStreamPlayer2D" parent="."] +stream = ExtResource("2_51pmd") +autoplay = true +panning_strength = 0.0 + +[node name="NDIOutput" type="NDIOutput" parent="."] +name = "Godot (Video)" +audio_bus = &"Master" + +[connection signal="finished" from="VideoStreamPlayer" to="VideoStreamPlayer" method="_on_finished"] diff --git a/addons/godot-ndi/demo/assets/icon.svg b/addons/godot-ndi/demo/assets/icon.svg new file mode 100644 index 0000000..9d8b7fa --- /dev/null +++ b/addons/godot-ndi/demo/assets/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/godot-ndi/demo/assets/icon.svg.import b/addons/godot-ndi/demo/assets/icon.svg.import new file mode 100644 index 0000000..62036c0 --- /dev/null +++ b/addons/godot-ndi/demo/assets/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfvxj5cdj3n45" +path="res://.godot/imported/icon.svg-5dbb75b05306facc4cf78b15baaae64d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/godot-ndi/demo/assets/icon.svg" +dest_files=["res://.godot/imported/icon.svg-5dbb75b05306facc4cf78b15baaae64d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/godot-ndi/demo/assets/liftoff_space_shuttle_atlantis.ogv b/addons/godot-ndi/demo/assets/liftoff_space_shuttle_atlantis.ogv new file mode 100644 index 0000000..340c1ca Binary files /dev/null and b/addons/godot-ndi/demo/assets/liftoff_space_shuttle_atlantis.ogv differ diff --git a/addons/godot-ndi/demo/assets/liftoff_space_shuttle_atlantis.ogv.uid b/addons/godot-ndi/demo/assets/liftoff_space_shuttle_atlantis.ogv.uid new file mode 100644 index 0000000..d3560db --- /dev/null +++ b/addons/godot-ndi/demo/assets/liftoff_space_shuttle_atlantis.ogv.uid @@ -0,0 +1 @@ +uid://0whnog13yrlf diff --git a/addons/godot-ndi/demo/assets/sub-d-more-jazz-guitar.wav b/addons/godot-ndi/demo/assets/sub-d-more-jazz-guitar.wav new file mode 100644 index 0000000..88fc3dd Binary files /dev/null and b/addons/godot-ndi/demo/assets/sub-d-more-jazz-guitar.wav differ diff --git a/addons/godot-ndi/demo/assets/sub-d-more-jazz-guitar.wav.import b/addons/godot-ndi/demo/assets/sub-d-more-jazz-guitar.wav.import new file mode 100644 index 0000000..6023b2f --- /dev/null +++ b/addons/godot-ndi/demo/assets/sub-d-more-jazz-guitar.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://dyvxmok1n3vu1" +path="res://.godot/imported/sub-d-more-jazz-guitar.wav-6bba7f91e7070b729764277f88bb96b0.sample" + +[deps] + +source_file="res://addons/godot-ndi/demo/assets/sub-d-more-jazz-guitar.wav" +dest_files=["res://.godot/imported/sub-d-more-jazz-guitar.wav-6bba7f91e7070b729764277f88bb96b0.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=true +edit/loop_mode=2 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/addons/godot-ndi/demo/assets/twitch_sync_footage.ogv b/addons/godot-ndi/demo/assets/twitch_sync_footage.ogv new file mode 100644 index 0000000..bc86d3a Binary files /dev/null and b/addons/godot-ndi/demo/assets/twitch_sync_footage.ogv differ diff --git a/addons/godot-ndi/demo/assets/twitch_sync_footage.ogv.uid b/addons/godot-ndi/demo/assets/twitch_sync_footage.ogv.uid new file mode 100644 index 0000000..613d934 --- /dev/null +++ b/addons/godot-ndi/demo/assets/twitch_sync_footage.ogv.uid @@ -0,0 +1 @@ +uid://4mp8kyfc212k diff --git a/addons/godot-ndi/demo/ndi_finder.gd b/addons/godot-ndi/demo/ndi_finder.gd new file mode 100644 index 0000000..83e41d1 --- /dev/null +++ b/addons/godot-ndi/demo/ndi_finder.gd @@ -0,0 +1,13 @@ +extends NDIFinder + +@onready var player: VideoStreamPlayer = $"../VideoStreamPlayer" + +func _on_sources_changed() -> void: + player.stream = get_sources().pop_front() + player.play() + +func _on_sources_found(found_sources: Array[VideoStreamNDI]) -> void: + print("NDI sources found: ", found_sources.map(func (source: VideoStreamNDI): return source.get_name())) + +func _on_sources_gone(gone_sources: Array[VideoStreamNDI]) -> void: + print("NDI sources gone: ", gone_sources.map(func (source: VideoStreamNDI): return source.get_name())) diff --git a/addons/godot-ndi/demo/ndi_finder.gd.uid b/addons/godot-ndi/demo/ndi_finder.gd.uid new file mode 100644 index 0000000..3d5d272 --- /dev/null +++ b/addons/godot-ndi/demo/ndi_finder.gd.uid @@ -0,0 +1 @@ +uid://kbgdevvugj7k diff --git a/addons/godot-ndi/demo/video_stream_player.gd b/addons/godot-ndi/demo/video_stream_player.gd new file mode 100644 index 0000000..c71df34 --- /dev/null +++ b/addons/godot-ndi/demo/video_stream_player.gd @@ -0,0 +1,17 @@ +extends VideoStreamPlayer + +@export var playlist: Array[VideoStream] = [] + +func _ready() -> void: + _on_finished() + +func _on_finished() -> void: + if playlist.is_empty(): + return + + var next_idx = playlist.find(stream) + 1 + if next_idx >= playlist.size(): + next_idx = 0 + + stream = playlist[next_idx] + play() diff --git a/addons/godot-ndi/demo/video_stream_player.gd.uid b/addons/godot-ndi/demo/video_stream_player.gd.uid new file mode 100644 index 0000000..76f0613 --- /dev/null +++ b/addons/godot-ndi/demo/video_stream_player.gd.uid @@ -0,0 +1 @@ +uid://d2sex4jisnkck diff --git a/addons/godot-ndi/godot-ndi.gdextension b/addons/godot-ndi/godot-ndi.gdextension new file mode 100644 index 0000000..79afb11 --- /dev/null +++ b/addons/godot-ndi/godot-ndi.gdextension @@ -0,0 +1,18 @@ +[configuration] + +entry_symbol = "godot_ndi_init" +compatibility_minimum = "4.4" +reloadable = false + +[libraries] + +linux.x86_64.single = "bin/linux/libgodot-ndi.linux.template_debug.x86_64.so" +linux.arm64.single = "bin/linux/libgodot-ndi.linux.template_debug.arm64.so" +windows.x86_64.single = "bin/windows/libgodot-ndi.windows.template_debug.x86_64.dll" +macos.single = "bin/macos/libgodot-ndi.macos.template_debug.universal.dylib" + +[icons] + +VideoStreamNDI = "icons/VideoStreamNDI.svg" +NDIOutput = "icons/NDIOutput.svg" +NDIFinder = "icons/NDIFinder.svg" diff --git a/addons/godot-ndi/godot-ndi.gdextension.uid b/addons/godot-ndi/godot-ndi.gdextension.uid new file mode 100644 index 0000000..631485f --- /dev/null +++ b/addons/godot-ndi/godot-ndi.gdextension.uid @@ -0,0 +1 @@ +uid://ccdh6w16n2no5 diff --git a/addons/godot-ndi/icons/NDIFinder.svg b/addons/godot-ndi/icons/NDIFinder.svg new file mode 100644 index 0000000..0e3d2b1 --- /dev/null +++ b/addons/godot-ndi/icons/NDIFinder.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/addons/godot-ndi/icons/NDIFinder.svg.import b/addons/godot-ndi/icons/NDIFinder.svg.import new file mode 100644 index 0000000..a6d7660 --- /dev/null +++ b/addons/godot-ndi/icons/NDIFinder.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjqtfd5qckc6l" +path="res://.godot/imported/NDIFinder.svg-6145b2a9070199a0c93ffe21a332ddf0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/godot-ndi/icons/NDIFinder.svg" +dest_files=["res://.godot/imported/NDIFinder.svg-6145b2a9070199a0c93ffe21a332ddf0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/godot-ndi/icons/NDIOutput.svg b/addons/godot-ndi/icons/NDIOutput.svg new file mode 100644 index 0000000..9925f58 --- /dev/null +++ b/addons/godot-ndi/icons/NDIOutput.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/addons/godot-ndi/icons/NDIOutput.svg.import b/addons/godot-ndi/icons/NDIOutput.svg.import new file mode 100644 index 0000000..d893aae --- /dev/null +++ b/addons/godot-ndi/icons/NDIOutput.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvv0gnogt1vac" +path="res://.godot/imported/NDIOutput.svg-b19965324d53dea421ada474b6902c23.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/godot-ndi/icons/NDIOutput.svg" +dest_files=["res://.godot/imported/NDIOutput.svg-b19965324d53dea421ada474b6902c23.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/godot-ndi/icons/VideoStreamNDI.svg b/addons/godot-ndi/icons/VideoStreamNDI.svg new file mode 100644 index 0000000..c917825 --- /dev/null +++ b/addons/godot-ndi/icons/VideoStreamNDI.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/godot-ndi/icons/VideoStreamNDI.svg.import b/addons/godot-ndi/icons/VideoStreamNDI.svg.import new file mode 100644 index 0000000..c20e013 --- /dev/null +++ b/addons/godot-ndi/icons/VideoStreamNDI.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://pfqe4htodj7k" +path="res://.godot/imported/VideoStreamNDI.svg-79e19fa3a194ede543418d01dcbbb6a7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/godot-ndi/icons/VideoStreamNDI.svg" +dest_files=["res://.godot/imported/VideoStreamNDI.svg-79e19fa3a194ede543418d01dcbbb6a7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/bin/~libgdexample.windows.template_debug.x86_64.dll b/bin/~libgdexample.windows.template_debug.x86_64.dll deleted file mode 100644 index c28b9ad..0000000 Binary files a/bin/~libgdexample.windows.template_debug.x86_64.dll and /dev/null differ diff --git a/main_scenes/NDIOutputManager.gd b/main_scenes/NDIOutputManager.gd new file mode 100644 index 0000000..87012ae --- /dev/null +++ b/main_scenes/NDIOutputManager.gd @@ -0,0 +1,110 @@ +class_name NDIOutputManager +extends Node + +## NDI Output Manager for PNGTuber-Plus +## Handles NDI streaming output for OBS integration +## +## This class manages the NDI output functionality, allowing the PNGTuber-Plus +## avatar to be streamed as an NDI source that can be captured by OBS Studio +## or other NDI-compatible applications. + +@export var ndi_source_name: String = "PNGTuber-Plus" +@export var enable_audio: bool = true +@export var audio_bus_name: String = "Master" +@export var auto_start: bool = true + +var ndi_output: NDIOutput +var is_streaming: bool = false + +## Called when the node enters the scene tree +func _ready() -> void: + if auto_start: + initialize_ndi_output() + +## Initialize the NDI output node and configure it +func initialize_ndi_output() -> void: + # Create NDI output node if it doesn't exist + if not ndi_output: + ndi_output = NDIOutput.new() + add_child(ndi_output) + + # Configure NDI output settings + configure_ndi_output() + + print("NDI Output initialized with source name: ", ndi_source_name) + +## Configure the NDI output with current settings +func configure_ndi_output() -> void: + if not ndi_output: + push_error("NDI Output node not found") + return + + # Set the NDI source name + ndi_output.name = ndi_source_name + + # Configure audio if enabled + if enable_audio: + ndi_output.audio_bus = StringName(audio_bus_name) + + print("NDI Output configured - Name: ", ndi_source_name, " Audio: ", enable_audio) + +## Start NDI streaming +func start_streaming() -> void: + if not ndi_output: + initialize_ndi_output() + + if ndi_output and not is_streaming: + is_streaming = true + print("NDI streaming started - Source: ", ndi_source_name) + +## Stop NDI streaming +func stop_streaming() -> void: + if ndi_output and is_streaming: + is_streaming = false + print("NDI streaming stopped") + +## Toggle NDI streaming on/off +func toggle_streaming() -> bool: + if is_streaming: + stop_streaming() + return false + else: + start_streaming() + return true + +## Update the NDI source name +func set_source_name(new_name: String) -> void: + ndi_source_name = new_name + if ndi_output: + ndi_output.name = ndi_source_name + print("NDI source name changed to: ", ndi_source_name) + +## Enable or disable audio streaming +func set_audio_enabled(enabled: bool) -> void: + enable_audio = enabled + if ndi_output: + if enable_audio: + ndi_output.audio_bus = StringName(audio_bus_name) + else: + ndi_output.audio_bus = StringName("") + print("NDI audio ", "enabled" if enabled else "disabled") + +## Change the audio bus for NDI output +func set_audio_bus(bus_name: String) -> void: + audio_bus_name = bus_name + if ndi_output and enable_audio: + ndi_output.audio_bus = StringName(audio_bus_name) + print("NDI audio bus changed to: ", audio_bus_name) + +## Get current streaming status +func get_streaming_status() -> bool: + return is_streaming + +## Get current NDI source name +func get_source_name() -> String: + return ndi_source_name + +## Clean up when node is removed +func _exit_tree() -> void: + if ndi_output and is_streaming: + stop_streaming() diff --git a/main_scenes/main.gd b/main_scenes/main.gd index 38e90d5..ff80a50 100644 --- a/main_scenes/main.gd +++ b/main_scenes/main.gd @@ -25,6 +25,8 @@ var editMode = true @onready var shadow = $shadowSprite +@onready var ndi_output = $NDIOutput + #Scene Reference @onready var spriteObject = preload("res://ui_scenes/selectedSprite/spriteObject.tscn") @@ -154,6 +156,9 @@ func _ready(): origin.position = s*0.5 updateCameraPosition() + # Test NDI output on startup + call_deferred("toggle_ndi") + func _process(delta): var hold = origin.get_parent().position.y @@ -238,6 +243,33 @@ func _input(event): # Invert the movement so dragging right moves character right (not left) var panSensitivity = 0.5 viewPanOffset = -totalDelta * panSensitivity + + # Handle NDI output hotkeys + elif event is InputEventKey and event.pressed: + # F9 key to toggle NDI streaming + if event.keycode == KEY_F9: + toggle_ndi_streaming() + # F10 key to restart NDI streaming with new settings + elif event.keycode == KEY_F10: + restart_ndi_streaming() + +## Toggle NDI streaming on/off +func toggle_ndi_streaming(): + if ndi_output: + print("NDI Output is available - Source: ", ndi_output.name) + Global.pushUpdate("NDI Output available - Source: " + ndi_output.name) + else: + print("NDI Output not found") + Global.pushUpdate("NDI Output not found") + +## Restart NDI streaming to apply any new settings +func restart_ndi_streaming(): + if ndi_output: + print("NDI Output available - Source: ", ndi_output.name) + Global.pushUpdate("NDI Output available - Source: " + ndi_output.name) + else: + print("NDI Output not found") + Global.pushUpdate("NDI Output not found") func followShadow(): # Shadow disabled for selected sprites @@ -888,4 +920,18 @@ func _on_background_input_capture_bg_key_pressed(node, keys_pressed): var i = costumeKeys.find(key) if i >= 0: changeCostume(i+1) + +## Simple NDI control function +func toggle_ndi(): + if ndi_output: + print("NDI Output is available - Source: ", ndi_output.name) + print("NDI should be streaming the main viewport") + else: + print("NDI Output not found") + +## Set NDI source name +func set_ndi_source_name(source_name: String): + if ndi_output: + ndi_output.name = source_name + print("NDI source name set to: ", source_name) diff --git a/main_scenes/main.tscn b/main_scenes/main.tscn index b457357..f835b01 100644 --- a/main_scenes/main.tscn +++ b/main_scenes/main.tscn @@ -14753,6 +14753,10 @@ position = Vector2(0, 720) modulate = Color(0, 0, 0, 0.478431) z_index = -4096 +[node name="NDIOutput" type="NDIOutput" parent="."] +name = "PNGTuber-Plus" +audio_bus = &"Master" + [connection signal="pressed" from="ControlPanel/MicButtong/Button" to="." method="_on_button_pressed"] [connection signal="pressed" from="ControlPanel/SettingsButton/settingsButtons" to="." method="_on_settings_buttons_pressed"] [connection signal="pressed" from="ControlPanel/Edit/Button" to="." method="swapMode"] diff --git a/project.godot b/project.godot index 64c536e..ec7d875 100644 --- a/project.godot +++ b/project.godot @@ -148,6 +148,6 @@ saveImages={ textures/canvas_textures/default_texture_filter=0 textures/canvas_textures/default_texture_repeat=1 -renderer/rendering_method="gl_compatibility" -renderer/rendering_method.mobile="gl_compatibility" +renderer/rendering_method="forward_plus" +renderer/rendering_method.mobile="forward_plus" environment/defaults/default_clear_color=Color(0.376471, 0.376471, 0.376471, 1) diff --git a/ui_scenes/settings/settings_menu.gd b/ui_scenes/settings/settings_menu.gd index 041d247..888c19a 100644 --- a/ui_scenes/settings/settings_menu.gd +++ b/ui_scenes/settings/settings_menu.gd @@ -48,6 +48,9 @@ func setvalues(): $experimentalMicLoudness/checkmark.button_pressed = Global.experimentalMicLoudness + # Initialize NDI settings + setup_ndi_settings() + func _on_color_picker_button_color_changed(color): get_viewport().transparent_bg = false RenderingServer.set_default_clear_color(color) @@ -264,3 +267,45 @@ func _on_delete_9_pressed(): func _on_delete_10_pressed(): var label = $CostumeInputs/ScrollContainer/VBoxContainer/costumeButton10/Label deleteKey(label,10) + +## NDI Output Settings Methods + +func _on_ndi_enabled_check_toggled(toggled_on: bool): + # Enable/disable NDI streaming + if Global.main.ndi_output: + print("NDI streaming ", "enabled" if toggled_on else "disabled") + update_ndi_status() + +func _on_ndi_source_name_text_changed(new_text: String): + # Update NDI source name + if Global.main.ndi_output: + Global.main.ndi_output.name = new_text + print("NDI source name changed to: ", new_text) + +func _on_ndi_audio_check_toggled(toggled_on: bool): + # Enable/disable audio in NDI stream + if Global.main.ndi_output: + if toggled_on: + Global.main.ndi_output.audio_bus = StringName("Master") + else: + Global.main.ndi_output.audio_bus = StringName("") + print("NDI audio ", "enabled" if toggled_on else "disabled") + +func _on_ndi_toggle_button_pressed(): + # Toggle NDI streaming on/off - NDI is always on when the node exists + print("NDI toggle pressed - NDI is active when node exists") + update_ndi_status() + +func update_ndi_status(): + # Update the status label and button text + if Global.main.ndi_output: + $NDIOutput/StatusLabel.text = "Status: Active (NDI Source: " + Global.main.ndi_output.name + ")" + $NDIOutput/toggleButton.text = "NDI Active" + $NDIOutput/ndiEnabledCheck.button_pressed = true + +func setup_ndi_settings(): + # Initialize NDI settings UI with current values + if Global.main.ndi_output: + $NDIOutput/ndiSourceName.text = Global.main.ndi_output.name + $NDIOutput/ndiAudioCheck.button_pressed = (Global.main.ndi_output.audio_bus != StringName("")) + update_ndi_status() diff --git a/ui_scenes/settings/settings_menu.tscn b/ui_scenes/settings/settings_menu.tscn index f98a483..f14ed59 100644 --- a/ui_scenes/settings/settings_menu.tscn +++ b/ui_scenes/settings/settings_menu.tscn @@ -14335,8 +14335,68 @@ offset_right = 154.0 offset_bottom = 25.0 metadata/_edit_use_anchors_ = true +[node name="NDIOutput" type="Node2D" parent="."] +position = Vector2(16, 433) + +[node name="Label" type="Label" parent="NDIOutput"] +offset_right = 138.0 +offset_bottom = 34.0 +text = "NDI streaming" +metadata/_edit_use_anchors_ = true + +[node name="ndiEnabledCheck" type="CheckBox" parent="NDIOutput"] +offset_left = 130.0 +offset_top = 1.0 +offset_right = 154.0 +offset_bottom = 25.0 +metadata/_edit_use_anchors_ = true + +[node name="SourceNameLabel" type="Label" parent="NDIOutput"] +offset_top = 32.0 +offset_right = 100.0 +offset_bottom = 50.0 +text = "source name:" +metadata/_edit_use_anchors_ = true + +[node name="ndiSourceName" type="LineEdit" parent="NDIOutput"] +offset_left = 105.0 +offset_top = 30.0 +offset_right = 250.0 +offset_bottom = 52.0 +text = "PNGTuber-Plus" +metadata/_edit_use_anchors_ = true + +[node name="AudioLabel" type="Label" parent="NDIOutput"] +offset_top = 60.0 +offset_right = 100.0 +offset_bottom = 78.0 +text = "include audio:" +metadata/_edit_use_anchors_ = true + +[node name="ndiAudioCheck" type="CheckBox" parent="NDIOutput"] +offset_left = 130.0 +offset_top = 58.0 +offset_right = 154.0 +offset_bottom = 82.0 +metadata/_edit_use_anchors_ = true + +[node name="StatusLabel" type="Label" parent="NDIOutput"] +offset_top = 88.0 +offset_right = 200.0 +offset_bottom = 106.0 +text = "Status: Not streaming" +metadata/_edit_use_anchors_ = true + +[node name="toggleButton" type="Button" parent="NDIOutput"] +offset_left = 260.0 +offset_top = 85.0 +offset_right = 340.0 +offset_bottom = 110.0 +text = "Start NDI" +metadata/_edit_use_anchors_ = true + [node name="bounceOnCostume" type="Node2D" parent="."] -position = Vector2(17, 305) +position = Vector2(17, 565) [node name="Label" type="Label" parent="bounceOnCostume"] offset_right = 138.0 @@ -14935,3 +14995,7 @@ metadata/_edit_use_anchors_ = true [connection signal="pressed" from="CostumeInputs/ScrollContainer/VBoxContainer/costumeButton10/delete10" to="." method="_on_delete_10_pressed"] [connection signal="value_changed" from="BlinkSpeed/blinkSpeed" to="." method="_on_blink_speed_value_changed"] [connection signal="value_changed" from="BlinkChance/blinkChance" to="." method="_on_blink_chance_value_changed"] +[connection signal="toggled" from="NDIOutput/ndiEnabledCheck" to="." method="_on_ndi_enabled_check_toggled"] +[connection signal="text_changed" from="NDIOutput/ndiSourceName" to="." method="_on_ndi_source_name_text_changed"] +[connection signal="toggled" from="NDIOutput/ndiAudioCheck" to="." method="_on_ndi_audio_check_toggled"] +[connection signal="pressed" from="NDIOutput/toggleButton" to="." method="_on_ndi_toggle_button_pressed"]