From f75b384c179669606a1734c5656ba2c2ea8b86e9 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Tue, 8 Aug 2023 17:43:46 +0200 Subject: [PATCH] New support for gstreamer video effects in MediaPlayer Implementation of the GUI allowing to set the pipeline element description --- src/ImGuiToolkit.cpp | 14 ++-- src/SourceControlWindow.cpp | 136 +++++++++++++++++++++++++++-------- src/UserInterfaceManager.cpp | 1 + 3 files changed, 116 insertions(+), 35 deletions(-) diff --git a/src/ImGuiToolkit.cpp b/src/ImGuiToolkit.cpp index 0a6d778..3984917 100644 --- a/src/ImGuiToolkit.cpp +++ b/src/ImGuiToolkit.cpp @@ -1949,6 +1949,12 @@ bool ImGuiToolkit::InputCodeMultiline(const char* label, std::string *str, const char hiddenlabel[256]; sprintf(hiddenlabel, "##%s", label); + // Draw the label with default font + ImVec2 pos_top = ImGui::GetCursorPos(); + ImGui::SetCursorPosX(pos_top.x + size.x + 8); + ImGui::Text("%s", label); + + // Draw code with mono font ImGuiToolkit::PushFont(FONT_MONO); static ImVec2 onechar = ImGui::CalcTextSize("C"); @@ -1958,9 +1964,11 @@ bool ImGuiToolkit::InputCodeMultiline(const char* label, std::string *str, const // work on a string wrapped to the number of chars in a line std::string edited = wrapp( *str, (size_t) floor(size.x / onechar.x) - 1); + ImGui::SetCursorPos(pos_top); + // Input text into std::string with callback ImGui::InputTextMultiline(hiddenlabel, (char*)edited.c_str(), edited.capacity() + 1, size, flags, InputTextCallback, &edited); - if (ImGui::IsItemDeactivatedAfterEdit() ){ + if (ImGui::IsItemDeactivated() ){ // unwrap after edit *str = unwrapp(edited); // return number of lines @@ -1973,10 +1981,6 @@ bool ImGuiToolkit::InputCodeMultiline(const char* label, std::string *str, const ImGui::PopFont(); - // show label - ImGui::SameLine(); - ImGui::Text("%s", label); - return ret; } diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index 2906033..d75a28f 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -20,6 +20,8 @@ #include #include +#include + // ImGui #include "ImGuiToolkit.h" #include "imgui_internal.h" @@ -423,8 +425,8 @@ void SourceControlWindow::Render() ImGui::EndMenu(); } // TODO finalize pipeline editor -// if (ImGui::MenuItem(ICON_FA_HAT_WIZARD " gst pipeline")) -// mediaplayer_edit_pipeline_ = true; + if (ImGui::MenuItem(ICON_FA_MAGIC " Video effect")) + mediaplayer_edit_pipeline_ = true; ImGui::EndMenu(); } @@ -1792,73 +1794,147 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::EndPopup(); } - + static std::string _effect_description = ""; + static bool _effect_description_changed = false; if (mediaplayer_edit_pipeline_) { - ImGui::OpenPopup("Edit gstreamer pipeline"); + // open dialog + ImGui::OpenPopup(ICON_FA_MAGIC " Video effect"); mediaplayer_edit_pipeline_ = false; + // initialize dialog + _effect_description = mediaplayer_active_->videoEffect(); + _effect_description_changed = true; } const ImVec2 mpp_dialog_size(buttons_width_ * 3.f, buttons_height_ * 6); ImGui::SetNextWindowSize(mpp_dialog_size, ImGuiCond_Always); const ImVec2 mpp_dialog_pos = top + rendersize * 0.5f - mpp_dialog_size * 0.5f; ImGui::SetNextWindowPos(mpp_dialog_pos, ImGuiCond_Always); - if (ImGui::BeginPopupModal("Edit gstreamer pipeline", NULL, ImGuiWindowFlags_NoResize)) + if (ImGui::BeginPopupModal(ICON_FA_MAGIC " Video effect", NULL, ImGuiWindowFlags_NoResize)) { - bool update_new_source = false; const ImVec2 pos = ImGui::GetCursorPos(); const ImVec2 area = ImGui::GetContentRegionAvail(); - + static uint _status = 0; // 0:unknown, 1:ok, 2:error + static std::string _status_message = ""; static std::vector< std::pair< std::string, std::string> > _examples = { {"Primary color", "frei0r-filter-primaries" }, - {"Histogram", "frei0r-filter-rgb-parade"}, + {"Histogram", "frei0r-filter-rgb-parade mix=0.5"}, {"Emboss", "frei0r-filter-emboss"}, - {"B&W", "frei0r-filter-bw0r"}, - {"Gamma", "frei0r-filter-gamma gamma=0.5"}, - {"Broken tv", "frei0r-filter-nosync0r "} + {"Denoise", "frei0r-filter-hqdn3d spatial=0.05 temporal=0.1"}, + {"Thermal", "coloreffects preset=heat"}, + {"Afterimage", "streaktv"} }; - static std::string _description = _examples[0].second; - static ImVec2 fieldsize(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 100); + static ImVec2 fieldsize(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 70); static int numlines = 0; const ImGuiContext& g = *GImGui; - fieldsize.y = MAX(3, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y; + fieldsize.y = MAX(2.5, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y; ImGui::Spacing(); - ImGui::Text("Enter pipeline element and apply:"); + ImGui::Text("Enter a gstreamer video effect description and apply.\nLeave empty for no effect."); + ImGui::SameLine(); + ImGuiToolkit::HelpToolTip("Video effects are directly integrated in the gstreamer pipeline " + "and performed on CPU (might be slow). Vimix recommends using " + "GPU accelerated filters by cloning the source."); ImGui::Spacing(); // Editor - if ( ImGuiToolkit::InputCodeMultiline("Filter", &_description, fieldsize, &numlines) ) - update_new_source = true; + if ( ImGuiToolkit::InputCodeMultiline("Effect", &_effect_description, fieldsize, &numlines) ) + _effect_description_changed = true; + if ( ImGui::IsItemActive() ) + _status = 0; // Local menu for list of examples ImVec2 pos_bot = ImGui::GetCursorPos(); ImGui::SetCursorPos( pos_bot + ImVec2(fieldsize.x + IMGUI_SAME_LINE, -ImGui::GetFrameHeightWithSpacing())); - if (ImGui::BeginCombo("##Examples", "Examples", ImGuiComboFlags_NoPreview)) { - ImGui::TextDisabled("Gstreamer examples"); - ImGui::Separator(); + if (ImGui::BeginCombo("##ExamplesVideoEffect", "ExamplesVideoEffect", ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLarge)) { + ImGui::TextDisabled("Examples"); for (auto it = _examples.begin(); it != _examples.end(); ++it) { if (ImGui::Selectable( it->first.c_str() ) ) { - _description = it->second; - update_new_source = true; + _effect_description = it->second; + _effect_description_changed = true; } } + ImGui::Separator(); + ImGui::TextDisabled("Explore online"); + if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Frei0r" ) ) + SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/frei0r"); + if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Effectv" ) ) + SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/effectv"); + if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Gaudi" ) ) + SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/gaudieffects"); + if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Geometric" ) ) + SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/geometrictransform"); ImGui::EndCombo(); } + // Local menu for clearing + ImGui::SameLine(); + if ( ImGuiToolkit::ButtonIcon(11,13,"Clear") ) { + _effect_description = ""; + _effect_description_changed = true; + } - // TODO use update_new_source + // if desciption changed, start a timeout to test the pipeline + if ( _effect_description_changed ){ + // reset to status 'unknown' and no message + _status = 0; + _status_message.clear(); + + // No description, no effect + if ( _effect_description.empty() ) { + _status = 1; + _status_message = "(no video effect)"; + } + // has a description, we test it + else { + GError *error = NULL; + GstElement *effect = gst_parse_launch( _effect_description.c_str(), &error); + // on error + if (effect == NULL || error != NULL) { + _status = 2; + if (error != NULL) + _status_message = error->message; + g_clear_error (&error); + if (effect) + gst_object_unref(effect); + } + // on success + else + _status = 1; + } + // done + _effect_description_changed = false; + } + + // display message line + if (_status > 1) { + // On Error + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0, 0.2, 0.2, 0.95f)); + ImGui::Text("Error - %s", _status_message.c_str()); + ImGui::PopStyleColor(1); + } + else if (_status > 0) { + // All ok + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2, 1.0, 0.2, 0.85f)); + ImGui::Text("Ok %s", _status_message.c_str()); + ImGui::PopStyleColor(1); + } bool close = false; ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_)); if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0))) close = true; ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_)); - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab)); - if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0)) - || ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) { - close = true; + // on success status, offer to apply + if (_status == 1) { + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab)); + if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0)) + || ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) { + close = true; - // apply to pipeline - mediaplayer_active_->setVideoEffect(_description); + // apply to pipeline + mediaplayer_active_->setVideoEffect(_effect_description); + } + ImGui::PopStyleColor(1); } - ImGui::PopStyleColor(1); + else + ImGuiToolkit::ButtonDisabled(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0)); if (close) ImGui::CloseCurrentPopup(); diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index d3bb2c8..08ae475 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include