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 [](https://godotengine.org) [](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!
+
+
+
+
+
+> 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.
+
+[](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"]