/* * Oak Video Editor - Non-Linear Video Editor * Copyright (C) 2025 Olive CE Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "oliveplugininstance.h" #include "oliveclip.h" #include "ofxGPURender.h" #include "ofxCore.h" #include "ofxMessage.h" #include "common/current.h" #include "coreengine.h" #include "pluginprogressreporter.h" #include "node/output/viewer/viewer.h" #include #include #include #include #include #include #include #include #include #include "paraminstance.h" namespace olive { namespace plugin { namespace { const std::string k_image_field_none_str(kOfxImageFieldNone); const std::string k_image_field_upper_str(kOfxImageFieldUpper); const std::string k_image_field_lower_str(kOfxImageFieldLower); QString format_ofx_message(const char *format, va_list args) { char buffer[1024]; va_list args_copy; va_copy(args_copy, args); const int needed = vsnprintf(buffer, sizeof(buffer), format, args_copy); va_end(args_copy); if (needed < 0) { return QString(); } if (needed < static_cast(sizeof(buffer))) { return QString::fromUtf8(buffer); } QByteArray dynamic_buffer(needed + 1, 0); const int written = vsnprintf(dynamic_buffer.data(), dynamic_buffer.size(), format, args); if (written < 0) { return QString(); } return QString::fromUtf8(dynamic_buffer.constData()); } const std::string &field_order_for_params(const VideoParams ¶ms) { switch (params.interlacing()) { case VideoParams::k_interlace_none: return k_image_field_none_str; case VideoParams::k_interlaced_top_first: return k_image_field_upper_str; case VideoParams::k_interlaced_bottom_first: return k_image_field_lower_str; } return k_image_field_none_str; } class DeferredRedoCommand : public UndoCommand { public: explicit DeferredRedoCommand(UndoCommand *inner) : inner_(inner) { } ~DeferredRedoCommand() override { delete inner_; } Project *get_relevant_project() const override { return inner_ ? inner_->get_relevant_project() : nullptr; } protected: void redo() override { if (skip_first_redo_) { skip_first_redo_ = false; return; } if (inner_) { inner_->redo_now(); } } void undo() override { if (inner_) { inner_->undo_now(); } } private: UndoCommand *inner_ = nullptr; bool skip_first_redo_ = true; }; ActiveViewerProvider active_viewer_provider_; ViewerOutput *get_active_viewer_output() { return active_viewer_provider_ ? active_viewer_provider_() : nullptr; } } // namespace void set_active_viewer_provider(ActiveViewerProvider provider) { active_viewer_provider_ = std::move(provider); } const std::string &OlivePluginInstance::getDefaultOutputFielding() const { return field_order_for_params(params_); } void OlivePluginInstance::setNode(std::shared_ptr node) { node_ = node; for (const auto &entry : getParams()) { if (!entry.second) { continue; } if (auto *bound = dynamic_cast(entry.second)) { bound->set_node(node_); } } } OfxStatus OlivePluginInstance::vmessage(const char *type, const char *id, const char *format, va_list args) { const QString message = format_ofx_message(format, args); if (message.isEmpty()) { return kOfxStatFailed; } const bool is_question = strncmp(type, kOfxMessageQuestion, strlen(kOfxMessageQuestion)) == 0; OfxStatus result = kOfxStatOK; auto show_message = [&]() { if (is_question) { const auto ret = QMessageBox::question( nullptr, "", message, QMessageBox::Ok, QMessageBox::Cancel); result = (ret == QMessageBox::Ok) ? kOfxStatReplyYes : kOfxStatReplyNo; } else { QMessageBox::information(nullptr, "", message); result = kOfxStatOK; } }; if (is_gui_thread()) { show_message(); } else if (auto *app = QCoreApplication::instance()) { if (is_question) { QMetaObject::invokeMethod(app, show_message, Qt::BlockingQueuedConnection); } else { QMetaObject::invokeMethod(app, show_message, Qt::QueuedConnection); } } else if (is_question) { result = kOfxStatReplyNo; } return result; } OfxStatus OlivePluginInstance::setPersistentMessage(const char *type, const char *id, const char *format, va_list args) { const QString message = format_ofx_message(format, args); if (message.isEmpty()) { return kOfxStatFailed; } ErrorType error_type; // If This is a error message if (strncmp(type, kOfxMessageError, strlen(kOfxMessageError)) == 0) { error_type = ErrorType::error; } // A warning else if (strncmp(type, kOfxMessageWarning, strlen(kOfxMessageWarning)) == 0) { error_type = ErrorType::warning; } // A simple information else if (strncmp(type, kOfxMessageMessage, strlen(kOfxMessageMessage)) == 0) { error_type = ErrorType::message; } else { return kOfxStatFailed; } auto update_ui = [this, error_type, message]() { persistentErrors_.append({ error_type, message }); switch (error_type) { case ErrorType::error: QMessageBox::critical(nullptr, "", message); break; case ErrorType::warning: QMessageBox::warning(nullptr, "", message); break; case ErrorType::message: QMessageBox::information(nullptr, "", message); break; } if (node_) { emit node_->message_count_changed(); } }; if (is_gui_thread()) { update_ui(); } else if (auto *app = QCoreApplication::instance()) { QMetaObject::invokeMethod(app, update_ui, Qt::QueuedConnection); } return kOfxStatOK; } OfxStatus OlivePluginInstance::clearPersistentMessage() { auto clear_ui = [this]() { persistentErrors_.clear(); // TODO: tell the shell to remove message. if (node_) { emit node_->message_count_changed(); } }; if (is_gui_thread()) { clear_ui(); } else if (auto *app = QCoreApplication::instance()) { QMetaObject::invokeMethod(app, clear_ui, Qt::QueuedConnection); } return kOfxStatOK; } void OlivePluginInstance::getProjectSize(double &x_size, double &y_size) const { double par = params_.pixel_aspect_ratio().to_double(); x_size = params_.width() * par; y_size = params_.height(); } void OlivePluginInstance::getProjectOffset(double &x_offset, double &y_offset) const { double par = params_.pixel_aspect_ratio().to_double(); x_offset = params_.x() * par; y_offset = params_.y(); } void OlivePluginInstance::getProjectExtent(double &x_size, double &y_size) const { double par = params_.pixel_aspect_ratio().to_double(); x_size = params_.width() * par; y_size = params_.height(); } double OlivePluginInstance::getProjectPixelAspectRatio() const { double par = params_.pixel_aspect_ratio().to_double(); if (par == 0.0) { return 1.0; // default PAR when not explicitly set } return par; } double OlivePluginInstance::getFrameRate() const { return params_.frame_rate().to_double(); } double OlivePluginInstance::getEffectDuration() const { // Return a default duration value return 100.0; } double OlivePluginInstance::getFrameRecursive() const { // Return current frame (this would typically be set by the host during rendering) return 0.0; } void OlivePluginInstance::getRenderScaleRecursive(double &x, double &y) const { // Return default render scale (1.0, 1.0) x = 1.0; y = 1.0; } OFX::Host::Param::Instance * OlivePluginInstance::newParam(const std::string &name, OFX::Host::Param::Descriptor &desc) { const std::string &type = desc.getType(); if (type == kOfxParamTypeInteger) { return new IntegerInstance(node_, desc, this); } else if (type == kOfxParamTypeDouble) { return new DoubleInstance(node_, name, desc, this); } else if (type == kOfxParamTypeBoolean) { return new BooleanInstance(node_, name, desc, this); } else if (type == kOfxParamTypeChoice) { return new ChoiceInstance(node_, name, desc, this); } else if (type == kOfxParamTypeString) { return new StringInstance(node_, name, desc, this); } else if (type == kOfxParamTypeRGBA) { return new RGBAInstance(node_, name, desc, this); } else if (type == kOfxParamTypeRGB) { return new RGBInstance(node_, name, desc, this); } else if (type == kOfxParamTypeDouble2D) { return new Double2DInstance(node_, name, desc, this); } else if (type == kOfxParamTypeInteger2D) { return new Integer2DInstance(node_, name, desc, this); } else if (type == kOfxParamTypeDouble3D) { return new Double3DInstance(node_, name, desc, this); } else if (type == kOfxParamTypeInteger3D) { return new Integer3DInstance(node_, name, desc, this); } else if (type == kOfxParamTypeCustom || type == kOfxParamTypeBytes) { return new CustomInstance(node_, name, desc, this); } else if (type == kOfxParamTypeGroup) { return new GroupInstance(desc, this); } else if (type == kOfxParamTypePage) { return new PageInstance(desc, this); } else if (type == kOfxParamTypePushButton) { return new PushbuttonInstance(node_, name, desc, this); } return nullptr; // 未实现的类型 } OfxStatus OlivePluginInstance::editBegin(const std::string &name) { edit_depth_++; if (edit_depth_ == 1) { edit_command_ = nullptr; edit_label_.clear(); edit_first_label_.clear(); edit_param_count_ = 0; if (!name.empty()) { edit_first_label_ = QCoreApplication::translate("OlivePluginInstance", "Change %1") .arg(QString::fromStdString(name)); } } return kOfxStatOK; } OfxStatus OlivePluginInstance::editEnd() { if (edit_depth_ > 0) { edit_depth_--; } if (edit_depth_ == 0 && edit_command_) { QString label = edit_label_; if (label.isEmpty()) { if (edit_param_count_ <= 1 && !edit_first_label_.isEmpty()) { label = edit_first_label_; } else if (edit_param_count_ > 1 && !edit_first_label_.isEmpty()) { label = QCoreApplication::translate("OlivePluginInstance", "%1 (+%2)") .arg(edit_first_label_) .arg(edit_param_count_ - 1); } else { label = QCoreApplication::translate("OlivePluginInstance", "Edit Parameters"); } } EngineCore::instance()->undo_stack()->push(edit_command_, label); edit_command_ = nullptr; edit_label_.clear(); edit_first_label_.clear(); edit_param_count_ = 0; } return kOfxStatOK; } void OlivePluginInstance::submit_undo_command(UndoCommand *command, const QString &label) { if (!command) { return; } if (edit_depth_ > 0) { if (!edit_command_) { edit_command_ = new MultiUndoCommand(); } edit_param_count_++; if (!label.isEmpty() && edit_first_label_.isEmpty()) { edit_first_label_ = label; } command->redo_now(); edit_command_->add_child(new DeferredRedoCommand(command)); return; } if (!is_gui_thread()) { command->redo_now(); delete command; return; } EngineCore::instance()->undo_stack()->push(command, label); } void OlivePluginInstance::progressStart(const std::string &message, const std::string &messageid) { (void)messageid; progress_cancelled_ = false; progress_active_ = true; auto *app = qobject_cast(QCoreApplication::instance()); if (!app) { return; } if (progress_reporter_) { progress_reporter_->close(); progress_reporter_->deleteLater(); } QString dialog_message = message.empty() ? QStringLiteral("Processing...") : QString::fromStdString(message); progress_reporter_ = create_plugin_progress_reporter( dialog_message, QStringLiteral("OpenFX")); QObject::connect(progress_reporter_, &PluginProgressReporter::cancelled, progress_reporter_, [this]() { progress_cancelled_ = true; }); progress_reporter_->show(); } void OlivePluginInstance::progressEnd() { progress_active_ = false; progress_cancelled_ = false; if (progress_reporter_) { progress_reporter_->close(); progress_reporter_->deleteLater(); } } bool OlivePluginInstance::progressUpdate(double t) { if (!progress_active_) { return true; } if (progress_reporter_) { double clamped = qBound(0.0, t, 1.0); progress_reporter_->set_progress(clamped); } return !progress_cancelled_; } #ifdef OFX_SUPPORTS_OPENGLRENDER OfxStatus OlivePluginInstance::contextAttachedAction() { if (!open_gl_enabled_) { return kOfxStatReplyDefault; } return kOfxStatOK; } OfxStatus OlivePluginInstance::contextDetachedAction() { if (!open_gl_enabled_) { return kOfxStatReplyDefault; } return kOfxStatOK; } #endif double OlivePluginInstance::timeLineGetTime() { if (ViewerOutput *viewer = get_active_viewer_output()) { return viewer->get_playhead().to_double(); } return 0.0; } void OlivePluginInstance::timeLineGotoTime(double t) { if (ViewerOutput *viewer = get_active_viewer_output()) { viewer->set_playhead(olive::core::Rational::from_double(t)); } } void OlivePluginInstance::timeLineGetBounds(double &t1, double &t2) { if (ViewerOutput *viewer = get_active_viewer_output()) { t1 = 0.0; t2 = viewer->get_length().to_double(); return; } t1 = 0.0; t2 = 0.0; } void OlivePluginInstance::setCustomInArgs(const std::string &action, OFX::Host::Property::Set &in_args) { if (action == kOfxImageEffectActionRender || action == kOfxImageEffectActionBeginSequenceRender || action == kOfxImageEffectActionEndSequenceRender) { in_args.setIntProperty(kOfxImageEffectPropOpenGLEnabled, open_gl_enabled_ ? 1 : 0); } } OFX::Host::ImageEffect::ClipInstance *OlivePluginInstance::newClipInstance( OFX::Host::ImageEffect::Instance *plugin, OFX::Host::ImageEffect::ClipDescriptor *descriptor, int index) { // Create a new clip instance OliveClipInstance *clip_instance = new OliveClipInstance(plugin, *descriptor, params_); // Initialize base class clip properties from VideoParams so that // setupClipPreferencesArgs and plugin constructors (which may fetch // clips and query their properties before getClipPreferences is called) // have valid defaults instead of kOfxImageComponentNone / kOfxBitDepthNone. std::string depth = kOfxBitDepthFloat; // host default std::string comp = kOfxImageComponentRGBA; // host default switch (params_.format()) { case core::PixelFormat::u8: depth = kOfxBitDepthByte; break; case core::PixelFormat::u16: depth = kOfxBitDepthShort; break; case core::PixelFormat::f16: depth = kOfxBitDepthHalf; break; case core::PixelFormat::f32: depth = kOfxBitDepthFloat; break; default: break; // keep F32 default } switch (params_.channel_count()) { case 1: comp = kOfxImageComponentAlpha; break; case 3: comp = kOfxImageComponentRGB; break; case 4: comp = kOfxImageComponentRGBA; break; default: break; // keep RGBA default } clip_instance->setPixelDepth(depth); clip_instance->setComponents(comp); return clip_instance; } OlivePluginInstance::~OlivePluginInstance() { if (!QCoreApplication::instance() || qEnvironmentVariableIsSet("OAK_OFX_ITEST")) { _created = false; } } } }