diff --git a/CMakeLists.txt b/CMakeLists.txt index fd93193ff..9ed59875e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,7 +208,7 @@ if (USE_VCPKG) message(FATAL_ERROR "Failed to ensurepip.") else() execute_process( - COMMAND "${Python_EXECUTABLE}" -m pip install setuptools sphinx breathe sphinx-rtd-theme OpenTimelineIO-Plugins importlib_metadata zipp + COMMAND "${Python_EXECUTABLE}" -m pip install setuptools sphinx breathe sphinx-rtd-theme OpenTimelineIO-Plugins importlib_metadata zipp numpy RESULT_VARIABLE PIP_RESULT ) if(PIP_RESULT) diff --git a/src/plugin/colour_pipeline/ocio/src/ocio_python_plugin/ocio_py_plugin.py b/src/plugin/colour_pipeline/ocio/src/ocio_python_plugin/ocio_py_plugin.py index 0a2803e8b..83852a6ff 100644 --- a/src/plugin/colour_pipeline/ocio/src/ocio_python_plugin/ocio_py_plugin.py +++ b/src/plugin/colour_pipeline/ocio/src/ocio_python_plugin/ocio_py_plugin.py @@ -255,6 +255,8 @@ def _plugin_metadata(self, media): metadata = media.media_source().get_metadata( "/colour_pipeline" ) + if not metadata: + metadata = {} return metadata.get("ocio_py_plugin", {}) def _set_plugin_metadata(self, media, plugin_metadata): diff --git a/src/plugin/video_output/bmd_decklink/src/decklink_audio_device.cpp b/src/plugin/video_output/bmd_decklink/src/decklink_audio_device.cpp index c17cca57d..28a4a8a1f 100644 --- a/src/plugin/video_output/bmd_decklink/src/decklink_audio_device.cpp +++ b/src/plugin/video_output/bmd_decklink/src/decklink_audio_device.cpp @@ -29,10 +29,16 @@ long DecklinkAudioOutputDevice::desired_samples() { } long DecklinkAudioOutputDevice::latency_microseconds() { + if (!bmd_output_) { + return 0; + } return (bmd_output_->num_samples_in_buffer() * 1000000) / sample_rate_; } bool DecklinkAudioOutputDevice::push_samples(const void *sample_data, const long num_samples) { + if (!bmd_output_) { + return false; + } bmd_output_->receive_samples_from_xstudio((int16_t *)sample_data, num_samples); return true; -} \ No newline at end of file +} diff --git a/src/plugin/video_output/bmd_decklink/src/decklink_output.cpp b/src/plugin/video_output/bmd_decklink/src/decklink_output.cpp index d5fc70717..e61005c18 100644 --- a/src/plugin/video_output/bmd_decklink/src/decklink_output.cpp +++ b/src/plugin/video_output/bmd_decklink/src/decklink_output.cpp @@ -113,7 +113,7 @@ DecklinkOutput::DecklinkOutput(BMDecklinkPlugin *decklink_xstudio_plugin) decklink_output_interface_(NULL), decklink_xstudio_plugin_(decklink_xstudio_plugin) { - init_decklink(); + is_available_ = init_decklink(); } DecklinkOutput::~DecklinkOutput() { @@ -436,6 +436,10 @@ bool DecklinkOutput::start_sdi_output() { try { + if (!decklink_output_interface_) { + throw std::runtime_error("No DeckLink device is available."); + } + bool mode_matched = false; // Get first avaliable video mode for Output if (decklink_output_interface_->GetDisplayModeIterator(&display_mode_iterator) == @@ -532,9 +536,11 @@ bool DecklinkOutput::stop_sdi_output(const std::string &error_message) { spdlog::info("Stopping Decklink output loop. {}", error_message); - decklink_output_interface_->StopScheduledPlayback(0, NULL, 0); - decklink_output_interface_->DisableVideoOutput(); - decklink_output_interface_->DisableAudioOutput(); + if (decklink_output_interface_) { + decklink_output_interface_->StopScheduledPlayback(0, NULL, 0); + decklink_output_interface_->DisableVideoOutput(); + decklink_output_interface_->DisableAudioOutput(); + } mutex_.lock(); @@ -908,6 +914,9 @@ long DecklinkOutput::num_samples_in_buffer() { // note this method is called by the xstudio audio output thread // Have to assume that GetBufferedAudioSampleFrameCount is not thread safe. BMD SDK // does not tell us otherwise + if (!decklink_output_interface_) { + return 0; + } std::unique_lock lk0(bmd_mutex_); uint32_t prerollAudioSampleCount; if (decklink_output_interface_->GetBufferedAudioSampleFrameCount( @@ -920,6 +929,12 @@ long DecklinkOutput::num_samples_in_buffer() { // Note, I have not yet understood the significance of the preroll flag void DecklinkOutput::copy_audio_samples_to_decklink_buffer(const bool /*preroll*/) { + if (!decklink_output_interface_) { + fetch_more_samples_from_xstudio_ = true; + audio_samples_cv_.notify_one(); + return; + } + std::unique_lock lk0(bmd_mutex_); // How many samples are sitting on the SDI card ready to be played? diff --git a/src/plugin/video_output/bmd_decklink/src/decklink_output.hpp b/src/plugin/video_output/bmd_decklink/src/decklink_output.hpp index e08f547ad..4ce9a2715 100644 --- a/src/plugin/video_output/bmd_decklink/src/decklink_output.hpp +++ b/src/plugin/video_output/bmd_decklink/src/decklink_output.hpp @@ -142,6 +142,8 @@ namespace bm_decklink_plugin_1_0 { hdr_metadata_mutex_.unlock(); } + [[nodiscard]] bool is_available() const { return is_available_; } + private: AVOutputCallback *output_callback_; std::mutex mutex_; @@ -195,6 +197,7 @@ namespace bm_decklink_plugin_1_0 { HDRMetadata hdr_metadata_; std::mutex hdr_metadata_mutex_; + bool is_available_ = {false}; }; class AVOutputCallback : public IDeckLinkVideoOutputCallback, @@ -234,4 +237,4 @@ namespace bm_decklink_plugin_1_0 { }; } // namespace bm_decklink_plugin_1_0 -} // namespace xstudio \ No newline at end of file +} // namespace xstudio diff --git a/src/plugin/video_output/bmd_decklink/src/decklink_plugin.cpp b/src/plugin/video_output/bmd_decklink/src/decklink_plugin.cpp index 4d31c17e0..7d147f4fc 100644 --- a/src/plugin/video_output/bmd_decklink/src/decklink_plugin.cpp +++ b/src/plugin/video_output/bmd_decklink/src/decklink_plugin.cpp @@ -284,6 +284,9 @@ void BMDecklinkPlugin::attribute_changed(const utility::Uuid &attribute_uuid, co audio::AudioOutputDevice * BMDecklinkPlugin::make_audio_output_device(const utility::JsonStore &prefs) { + if (!dcl_output_ || !dcl_output_->is_available()) { + return nullptr; + } return static_cast( new DecklinkAudioOutputDevice(prefs, dcl_output_)); } @@ -295,6 +298,13 @@ void BMDecklinkPlugin::initialise() { dcl_output_ = new DecklinkOutput(this); set_hdr_mode_and_metadata(); + if (!dcl_output_->is_available()) { + status_message_->set_value("No DeckLink device detected."); + is_in_error_->set_value(true); + spdlog::warn("Decklink drivers found, but no DeckLink device is available."); + return; + } + resolutions_->set_role_data( module::Attribute::StringChoices, dcl_output_->output_resolution_names());