From cc3f0226efde6a5849fb24f27eb68aa4dce8382c Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Sat, 1 Nov 2025 23:38:28 +0100 Subject: [PATCH] Add timeline flag management functionality --- rsc/images/icons.dds | Bin 1638528 -> 1638528 bytes src/SessionCreator.cpp | 12 ++++ src/SessionVisitor.cpp | 12 ++++ src/Settings.cpp | 2 +- src/Settings.h | 4 +- src/SourceControlWindow.cpp | 128 ++++++++++++++++++++++++++++++------ src/Timeline.cpp | 114 +++++++++++++++++++++++++++++++- src/Timeline.h | 16 +++++ 8 files changed, 263 insertions(+), 25 deletions(-) diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index fbbc4b3ffc3f2351e8ec0ef9f9596011059a50ab..26d142a1c4bb524e76d5f5e9b938599b2ef57558 100644 GIT binary patch delta 39077 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 zUHrLKUa@+QueryUnsignedAttribute("mode", &mode); n.setTimelineFadingMode((MediaPlayer::FadingMode) mode); } + XMLElement *flagselement = timelineelement->FirstChildElement("Flags"); + if (flagselement) { + XMLElement* flag = flagselement->FirstChildElement("Interval"); + for( ; flag ; flag = flag->NextSiblingElement()) + { + uint64_t a = GST_CLOCK_TIME_NONE; + uint64_t b = GST_CLOCK_TIME_NONE; + flag->QueryUnsigned64Attribute("begin", &a); + flag->QueryUnsigned64Attribute("end", &b); + tl.addFlag( TimeInterval( (GstClockTime) a, (GstClockTime) b ) ); + } + } n.setTimeline(tl); } diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp index 5ba7a96..eb5c347 100644 --- a/src/SessionVisitor.cpp +++ b/src/SessionVisitor.cpp @@ -469,6 +469,18 @@ void SessionVisitor::visit(MediaPlayer &n) XMLElement *array = XMLElementEncodeArray(xmlDoc_, n.timeline()->fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float)); fadingelement->InsertEndChild(array); timelineelement->InsertEndChild(fadingelement); + + // flags in timeline + XMLElement *flagselement = xmlDoc_->NewElement("Flags"); + TimeIntervalSet flags = n.timeline()->flags(); + for( auto it = flags.begin(); it!= flags.end(); ++it) { + XMLElement *f = xmlDoc_->NewElement("Interval"); + f->SetAttribute("begin", (uint64_t) (*it).begin); + f->SetAttribute("end", (uint64_t) (*it).end); + flagselement->InsertEndChild(f); + } + timelineelement->InsertEndChild(flagselement); + newelement->InsertEndChild(timelineelement); fadingelement->SetAttribute("mode", (uint) n.timelineFadingMode()); } diff --git a/src/Settings.cpp b/src/Settings.cpp index 5266295..1d1f277 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -521,7 +521,7 @@ void Settings::Load(const std::string &filename) widgetsNode->QueryIntAttribute("inputs_view", &application.widget.inputs_view); widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player); widgetsNode->QueryIntAttribute("media_player_view", &application.widget.media_player_view); - widgetsNode->QueryBoolAttribute("timeline_editmode", &application.widget.media_player_timeline_editmode); + widgetsNode->QueryIntAttribute("timeline_editmode", &application.widget.media_player_timeline_editmode); widgetsNode->QueryFloatAttribute("media_player_slider", &application.widget.media_player_slider); widgetsNode->QueryBoolAttribute("shader_editor", &application.widget.shader_editor); widgetsNode->QueryIntAttribute("shader_editor_view", &application.widget.shader_editor_view); diff --git a/src/Settings.h b/src/Settings.h index f2efe7a..525d976 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -23,7 +23,7 @@ struct WidgetsConfig int preview_view; bool media_player; int media_player_view; - bool media_player_timeline_editmode; + int media_player_timeline_editmode; float media_player_slider; bool timer; int timer_view; @@ -46,7 +46,7 @@ struct WidgetsConfig preview_view = -1; media_player = false; media_player_view = -1; - media_player_timeline_editmode = false; + media_player_timeline_editmode = 0; media_player_slider = 0.f; toolbox = false; help = false; diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index c086852..eda0473 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -57,7 +57,9 @@ typedef struct payload FADE_OUT_IN, CUT, CUT_MERGE, - CUT_ERASE + CUT_ERASE, + FLAG_ADD, + FLAG_REMOVE }; Action action; guint64 drop_time; @@ -464,6 +466,7 @@ void SourceControlWindow::Render() mediaplayer_timeline_zoom_ = 1.f; mediaplayer_active_->timeline()->clearFading(); mediaplayer_active_->timeline()->clearGaps(); + mediaplayer_active_->timeline()->clearFlags(); mediaplayer_active_->setVideoEffect(""); std::ostringstream oss; oss << SystemToolkit::base_filename( mediaplayer_active_->filename() ); @@ -566,7 +569,7 @@ void DrawTimeScale(const char* label, guint64 duration, double width_ratio) bool EditTimeline(const char *label, Timeline *tl, - bool edit_histogram, + int edit_mode, bool *released, const ImVec2 size) { @@ -575,11 +578,13 @@ bool EditTimeline(const char *label, const guint64 begin = tl->begin(); const guint64 end = tl->end(); - bool cursor_dot = !edit_histogram; + bool cursor_dot = edit_mode == 1; + bool cursor_flag = false; Timeline _tl; bool array_changed = false; float *lines_array = tl->fadingArray(); - float *histogram_array = tl->gapsArray(); + float *gaps_array = tl->gapsArray(); + float *flags_array = tl->flagsArray(); // get window ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -629,6 +634,7 @@ bool EditTimeline(const char *label, size_t index = CLAMP( (int) floor(static_cast(MAX_TIMELINE_ARRAY) * x), 0, MAX_TIMELINE_ARRAY); char cursor_text[64]; guint64 time = begin + (index * end) / static_cast(MAX_TIMELINE_ARRAY); + static guint64 removed_flag_time = 0; // enter edit if widget is active if (ImGui::GetActiveID() == id) { @@ -649,31 +655,55 @@ bool EditTimeline(const char *label, const size_t left = MIN(previous_index, index); const size_t right = MAX(previous_index, index); - if (edit_histogram){ + if (edit_mode == 0) { static float target_value = values_min; // toggle value histo if (!active) { - target_value = histogram_array[index] > 0.f ? 0.f : 1.f; + target_value = gaps_array[index] > 0.f ? 0.f : 1.f; active = true; } for (size_t i = left; i < right; ++i) - histogram_array[i] = target_value; + gaps_array[i] = target_value; } - else { + else if (edit_mode == 1) { const float target_value = values_max - val; for (size_t i = left; i < right; ++i) lines_array[i] = target_value; } + else if (edit_mode == 2) { + if (!active) { + // remove flag on mouse press + if ( tl->isFlagged(time) ) { + removed_flag_time = time; + tl->removeFlagAt(time); + } + // add flag on mouse release + else + active = true; + } + else if (! tl->isFlagged(time) ) { + cursor_flag = true; + } + } previous_index = index; array_changed = true; } // release active widget on mouse release else { + // add flag on mouse release + if (edit_mode == 2 && active) { + // exception: if flag was removed at same time, do not add it back + if ( removed_flag_time != time ) { + tl->addFlag(time); + } + removed_flag_time = 0; + } + active = false; ImGui::ClearActiveID(); previous_index = UINT32_MAX; @@ -699,17 +729,17 @@ bool EditTimeline(const char *label, case TimelinePayload::CUT: _tl = *tl; _tl.cut(time, (bool) pl->argument); - histogram_array = _tl.gapsArray(); + gaps_array = _tl.gapsArray(); break; case TimelinePayload::CUT_MERGE: _tl = *tl; _tl.mergeGapstAt(time); - histogram_array = _tl.gapsArray(); + gaps_array = _tl.gapsArray(); break; case TimelinePayload::CUT_ERASE: _tl = *tl; _tl.removeGaptAt(time); - histogram_array = _tl.gapsArray(); + gaps_array = _tl.gapsArray(); break; case TimelinePayload::FADE_IN: _tl = *tl; @@ -731,6 +761,16 @@ bool EditTimeline(const char *label, _tl.fadeInOutRange(time, pl->timing, false, (Timeline::FadingCurve) pl->argument); lines_array = _tl.fadingArray(); break; + case TimelinePayload::FLAG_ADD: + _tl = *tl; + _tl.addFlag(time); + flags_array = _tl.flagsArray(); + break; + case TimelinePayload::FLAG_REMOVE: + _tl = *tl; + _tl.removeFlagAt(time); + flags_array = _tl.flagsArray(); + break; default: break; } @@ -745,16 +785,21 @@ bool EditTimeline(const char *label, } ImGui::EndDragDropTarget(); } + else { + if ( edit_mode == 2 && tl->isFlagged(time) ) { + cursor_flag = true; + } + } // back to draw ImGui::SetCursorScreenPos(canvas_pos); - // plot histogram (with frame) + // plot gaps (with frame) ImGui::PushStyleColor(ImGuiCol_FrameBg, bg_color); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, style.Colors[ImGuiCol_ModalWindowDimBg]); // a dark color char buf[128]; snprintf(buf, 128, "##Histo%s", label); - ImGui::PlotHistogram(buf, histogram_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); + ImGui::PlotHistogram(buf, gaps_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); ImGui::PopStyleColor(2); // back to draw @@ -766,6 +811,15 @@ bool EditTimeline(const char *label, ImGui::PlotLines(buf, lines_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); ImGui::PopStyleColor(1); + // back to draw + ImGui::SetCursorScreenPos(canvas_pos); + + // plot flags (transparent background) + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, style.Colors[ImGuiCol_TabActive]); // cursor color + snprintf(buf, 128, "##Flags%s", label); + ImGui::PlotHistogram(buf, flags_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); + ImGui::PopStyleColor(2); // draw the cursor if (hovered) { @@ -785,6 +839,11 @@ bool EditTimeline(const char *label, cursor_pos = cursor_pos + mouse_pos_in_canvas; window->DrawList->AddCircleFilled( cursor_pos, 3.f, cur_color, 8); } + else if (cursor_flag) { + cursor_pos = cursor_pos + ImVec2(mouse_pos_in_canvas.x, 12.f); + window->DrawList->AddLine( cursor_pos, cursor_pos + ImVec2(0.f, size.y - 8.f), cur_color); + _drawIcon(cursor_pos - ImVec2(2.f, 1.f), 12, 6, true, window); + } else { cursor_pos = cursor_pos + ImVec2(mouse_pos_in_canvas.x, 4.f); window->DrawList->AddLine( cursor_pos, cursor_pos + ImVec2(0.f, size.y - 8.f), cur_color); @@ -1850,6 +1909,8 @@ void DragButtonIcon(int i, int j, const char *tooltip, TimelinePayload payload) void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) { static bool show_overlay_info = false; + static std::vector< std::pair > editmode_icon = { {8, 3}, {7, 4}, {12, 6} }; + static std::vector< std::string > editmode_tooltip = { "Cutting tool", "Fading tool", "Flag tool" }; mediaplayer_active_ = ms->mediaplayer(); @@ -1999,10 +2060,12 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) else if (released) { tl->refresh(); - if (Settings::application.widget.media_player_timeline_editmode) + if (Settings::application.widget.media_player_timeline_editmode == 0) oss << ": Timeline cut"; - else + else if (Settings::application.widget.media_player_timeline_editmode == 1) oss << ": Timeline fading"; + else + oss << ": Timeline flags"; Action::manager().store(oss.str()); } // custom timeline slider @@ -2022,8 +2085,8 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) if (mediaplayer_edit_panel_ && ImGuiToolkit::IconButton(10, 0, "Close panel")) mediaplayer_edit_panel_ = false; ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.5f * timeline_height_)); - const char *tooltip[2] = {"Fading tool", "Cutting tool"}; - ImGuiToolkit::IconToggle(7,4,8,3, &Settings::application.widget.media_player_timeline_editmode, tooltip); + // select timeline editmode; cut, fade, flag + ImGuiToolkit::IconMultistate(editmode_icon, &Settings::application.widget.media_player_timeline_editmode, editmode_tooltip ); // zoom slider ImGui::SetCursorScreenPos(bottom + ImVec2(0.f, timeline_height_)); @@ -2220,7 +2283,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) /// PANEL FOR CUT TIMELINE MODE /// ImGui::Spacing(); - if (Settings::application.widget.media_player_timeline_editmode) { + if (Settings::application.widget.media_player_timeline_editmode == 0) { // PANEL WITH LARGE FONTS ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); @@ -2308,9 +2371,9 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::PopFont(); } /// - /// FADING + /// PANEL FOR FADING /// - else { + else if (Settings::application.widget.media_player_timeline_editmode == 1) { // Icons for Drag & Drop ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); @@ -2398,6 +2461,9 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) DragButtonIcon(17, 10, "Drop in timeline to insert\nSmooth fade in & out", TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SMOOTH)); } + else { + + } /// /// DURATION SLIDER @@ -2467,6 +2533,28 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) // end icons ImGui::PopFont(); } + /// + /// PANEL FOR FLAGS + /// + else if (Settings::application.widget.media_player_timeline_editmode == 2) { + + // PANEL WITH LARGE FONTS + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + + /// + /// CUT LEFT OF CURSOR + /// + DragButtonIcon(12, 6, "Drop in timeline to\nAdd flag", + TimelinePayload(TimelinePayload::FLAG_ADD, 0, 1) ); + + ImGui::SameLine(0, IMGUI_SAME_LINE); + + DragButtonIcon(6, 0, "Drop in timeline to\nDelete flag", + TimelinePayload(TimelinePayload::FLAG_REMOVE, 0, 1) ); + + // end icons + ImGui::PopFont(); + } ImGui::EndChild(); ImGui::PopStyleColor(); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index f2c91d6..1c37599 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -86,6 +86,9 @@ Timeline& Timeline::operator = (const Timeline& b) memcpy( this->gapsArray_, b.gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float)); memcpy( this->fadingArray_, b.fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float)); this->fading_array_changed_ = true; + this->flags_ = b.flags_; + this->flags_array_need_update_ = b.flags_array_need_update_; + memcpy( this->flagsArray_, b.flagsArray_, MAX_TIMELINE_ARRAY * sizeof(float)); } return *this; } @@ -100,6 +103,7 @@ void Timeline::reset() clearGaps(); clearFading(); + clearFlags(); } bool Timeline::is_valid() const @@ -175,6 +179,7 @@ void Timeline::update() void Timeline::refresh() { fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY); + fillArrayFromFlags(flagsArray_, MAX_TIMELINE_ARRAY); } bool Timeline::gapAt(const GstClockTime t) const @@ -511,8 +516,7 @@ void Timeline::clearGaps() { gaps_.clear(); - for(int i=0;i timing_.begin + (step_ * 2) + && t < timing_.end - (step_ * 2) + && !isFlagged(t)) { + + // compute nearest frame time + GstClockTime t_frame = ( (t - timing_.begin) / step_ ) * step_ + timing_.begin + step_; + + // Flag interval centered on t_frame + TimeInterval f(t_frame - step_ - FLAG_MARGIN, t_frame + step_ + FLAG_MARGIN); + + flags_array_need_update_ = true; + return flags_.insert(f).second; + } + + return false; +} + +bool Timeline::addFlag(TimeInterval s) +{ + if ( s.is_valid() ) { + flags_array_need_update_ = true; + return flags_.insert(s).second; + } + return false; +} + + +bool Timeline::removeFlagAt(GstClockTime t) +{ + if (flags_.empty()) + return false; + + TimeIntervalSet::const_iterator f = std::find_if(flags_.begin(), flags_.end(), includesTime(t)); + + if ( f != flags_.end() ) { + flags_.erase(f); + flags_array_need_update_ = true; + return true; + } + + return false; +} + +bool Timeline::isFlagged(GstClockTime t) const +{ + TimeIntervalSet::const_iterator f = std::find_if(flags_.begin(), flags_.end(), includesTime(t)); + return ( f != flags_.end() ); +} + + +GstClockTime Timeline::getFlagAt(GstClockTime t) const +{ + TimeIntervalSet::const_iterator f = std::find_if(flags_.begin(), flags_.end(), includesTime(t)); + + if ( f != flags_.end() ) { + return ( (*f).begin + step_ + FLAG_MARGIN); + } + + return GST_CLOCK_TIME_NONE; +} + +void Timeline::clearFlags() +{ + flags_.clear(); + memcpy(flagsArray_, empty_zeros, MAX_TIMELINE_ARRAY * sizeof(float)); + + flags_array_need_update_ = true; +} + +void Timeline::fillArrayFromFlags(float *array, size_t array_size) +{ + // fill the array from flags list + if (array != nullptr && array_size > 0 && timing_.is_valid()) { + + // clear with static array + memcpy(flagsArray_, empty_zeros, MAX_TIMELINE_ARRAY * sizeof(float)); + + // for each flag + GstClockTime d = timing_.duration(); + for (auto it = flags_.begin(); it != flags_.end(); ++it) + { + size_t e = ( ( (*it).begin + step_ + FLAG_MARGIN ) * array_size ) / d ; + + // fill with 1 where there is a flag + flagsArray_[e-1] = 1.f; + flagsArray_[ e ] = 1.f; + flagsArray_[e+1] = 1.f; + } + + // done ! + flags_array_need_update_ = false; + } +} \ No newline at end of file diff --git a/src/Timeline.h b/src/Timeline.h index 39cf96e..cc7d5fe 100644 --- a/src/Timeline.h +++ b/src/Timeline.h @@ -137,6 +137,17 @@ public: bool gapAt(const GstClockTime t) const; bool getGapAt(const GstClockTime t, TimeInterval &gap) const; + // Manipulation of flags + inline TimeIntervalSet flags() const { return flags_; }; + inline size_t numFlags() const { return flags_.size(); }; + float *flagsArray(); + bool addFlag(GstClockTime t); + bool addFlag(TimeInterval s); + bool removeFlagAt(GstClockTime t); + bool isFlagged(GstClockTime t) const; + GstClockTime getFlagAt(GstClockTime t) const; + void clearFlags(); + // inverse of gaps: sections of play areas TimeIntervalSet sections() const; GstClockTime sectionsDuration() const; @@ -190,6 +201,11 @@ private: float fadingArray_[MAX_TIMELINE_ARRAY]; bool fading_array_changed_, fading_array_allones_; + TimeIntervalSet flags_; + float flagsArray_[MAX_TIMELINE_ARRAY]; + bool flags_array_need_update_; + // synchronize data structures + void fillArrayFromFlags(float *array, size_t array_size); }; #endif // TIMELINE_H