diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index 5f3f4d6b..4791cb3f 100644 Binary files a/rsc/images/icons.dds and b/rsc/images/icons.dds differ diff --git a/share/metainfo/io.github.brunoherbelin.Vimix.metainfo.xml b/share/metainfo/io.github.brunoherbelin.Vimix.metainfo.xml index f29811ab..6b730eee 100644 --- a/share/metainfo/io.github.brunoherbelin.Vimix.metainfo.xml +++ b/share/metainfo/io.github.brunoherbelin.Vimix.metainfo.xml @@ -29,12 +29,13 @@ vimix - + -

Stabilized version 0.8.2

+

Version 0.8.3

- https://github.com/brunoherbelin/vimix/releases/tag/0.8.2d + https://github.com/brunoherbelin/vimix/releases/tag/0.8.3
+ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index efe2d272..a60b216b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: vimix base: core22 -version: '0.8.2d' +version: '0.8.3' summary: Video live mixer title: vimix description: | diff --git a/src/CloneSource.h b/src/CloneSource.h index 582ed57a..14ac426b 100644 --- a/src/CloneSource.h +++ b/src/CloneSource.h @@ -28,6 +28,7 @@ class CloneSource : public Source void render() override; glm::ivec2 icon() const override; std::string info() const override; + bool texturePostProcessed() const override { return true; } // implementation of cloning mechanism void detach(); diff --git a/src/GeometryView.cpp b/src/GeometryView.cpp index 7e796388..0a030651 100644 --- a/src/GeometryView.cpp +++ b/src/GeometryView.cpp @@ -1019,8 +1019,8 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p // prepare overlay frame showing full image size glm::vec3 _c_s = glm::vec3(s->stored_status_->crop_[0] - s->stored_status_->crop_[1], - s->stored_status_->crop_[3] - - s->stored_status_->crop_[2], + s->stored_status_->crop_[2] + - s->stored_status_->crop_[3], 2.f) * 0.5f; overlay_crop_->scale_ = s->stored_status_->scale_ / _c_s; @@ -1028,7 +1028,7 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p overlay_crop_->rotation_.z = s->stored_status_->rotation_.z; overlay_crop_->translation_ = s->stored_status_->translation_; glm::vec3 _t((s->stored_status_->crop_[1] + _c_s.x) * overlay_crop_->scale_.x, - -s->stored_status_->crop_[3] + _c_s.y, 0.f); + (-s->stored_status_->crop_[2] + _c_s.y) * overlay_crop_->scale_.y, 0.f); _t = glm::rotate(glm::identity(), overlay_crop_->rotation_.z, glm::vec3(0.f, 0.f, 1.f)) @@ -1112,8 +1112,8 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p // prepare overlay frame showing full image size glm::vec3 _c_s = glm::vec3(s->stored_status_->crop_[0] - s->stored_status_->crop_[1], - s->stored_status_->crop_[3] - - s->stored_status_->crop_[2], + s->stored_status_->crop_[2] + - s->stored_status_->crop_[3], 2.f) * 0.5f; overlay_crop_->scale_ = s->stored_status_->scale_ / _c_s; @@ -1121,7 +1121,7 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p overlay_crop_->rotation_.z = s->stored_status_->rotation_.z; overlay_crop_->translation_ = s->stored_status_->translation_; glm::vec3 _t((s->stored_status_->crop_[1] + _c_s.x) * overlay_crop_->scale_.x, - -s->stored_status_->crop_[3] + _c_s.y, 0.f); + (-s->stored_status_->crop_[2] + _c_s.y) * overlay_crop_->scale_.y, 0.f); _t = glm::rotate(glm::identity(), overlay_crop_->rotation_.z, glm::vec3(0.f, 0.f, 1.f)) diff --git a/src/MediaPlayer.cpp b/src/MediaPlayer.cpp index ff2e3c84..2eba6843 100644 --- a/src/MediaPlayer.cpp +++ b/src/MediaPlayer.cpp @@ -76,6 +76,7 @@ MediaPlayer::MediaPlayer() video_filter_available_ = true; position_ = GST_CLOCK_TIME_NONE; loop_ = LoopMode::LOOP_REWIND; + loop_status_ = LoopStatus::LOOP_STATUS_DEFAULT; fading_mode_ = FadingMode::FADING_COLOR; // start index in frame_ stack @@ -513,13 +514,13 @@ void MediaPlayer::execute_open() if (media_.hasaudio) Log::Info("MediaPlayer %s Audio track %s", std::to_string(id_).c_str(), audio_enabled_ ? "enabled" : "disabled"); - opened_ = true; - // keep name in pipeline gst_element_set_name(pipeline_, std::to_string(id_).c_str()); // register media player MediaPlayer::registered_.push_back(pipeline_); + + opened_ = true; } #else @@ -956,7 +957,7 @@ void MediaPlayer::execute_play_command(bool on) desired_state_ = requested_state; // if not ready yet, the requested state will be handled later - if ( pipeline_ == nullptr ) + if ( !opened_ || pipeline_ == nullptr ) return; // requesting to play, but stopped at end of stream : rewind first ! @@ -980,6 +981,9 @@ void MediaPlayer::execute_play_command(bool on) Log::Info("MediaPlayer %s Stop [%ld]", std::to_string(id_).c_str(), position()); #endif + // Revert loop status to default when playing + if (on) + loop_status_ = LoopStatus::LOOP_STATUS_DEFAULT; } void MediaPlayer::play(bool on) @@ -992,6 +996,7 @@ void MediaPlayer::play(bool on) if (metro_sync_ > Metronome::SYNC_NONE) { // busy with this delayed action pending_ = true; + // delayed execution function std::function playlater = std::bind([](MediaPlayer *p, bool o) { p->execute_play_command(o); p->pending_=false; }, this, on); @@ -1013,7 +1018,7 @@ bool MediaPlayer::isPlaying(bool testpipeline) const return false; // if not ready yet, answer with requested state - if ( !testpipeline || pipeline_ == nullptr || !enabled_) + if ( !testpipeline || !opened_ || pipeline_ == nullptr || !enabled_) return desired_state_ == GST_STATE_PLAYING; // if ready, answer with actual state @@ -1399,14 +1404,19 @@ void MediaPlayer::execute_loop_command() rate_ *= -1.f; execute_seek_command(); } - else { //LOOP_NONE + else { + if (loop_ == LOOP_BLACKOUT) + loop_status_ = LoopStatus::LOOP_STATUS_BLACKOUT; + else + loop_status_ = LoopStatus::LOOP_STATUS_STOPPED; + // stop play(false); } } void MediaPlayer::execute_seek_command(GstClockTime target, bool force) { - if ( pipeline_ == nullptr || !media_.seekable ) + if ( !opened_ || pipeline_ == nullptr || !media_.seekable ) return; // ignore request to current position @@ -1420,15 +1430,12 @@ void MediaPlayer::execute_seek_command(GstClockTime target, bool force) int seek_flags = GST_SEEK_FLAG_FLUSH; // no target given - if (target == GST_CLOCK_TIME_NONE) { + if (target == GST_CLOCK_TIME_NONE) // create seek event with current position (called for rate changed) // CLAMP the time to ensure we do not bounce outside of timeline seek_pos = CLAMP(position_, timeline_.first(), timeline_.last() - timeline_.step()); - // seek with KEY mode if playing - seek_flags |= GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_AFTER; - } else - // seek with accurate timing if paused + // seek with accurate timing if given target seek_flags |= GST_SEEK_FLAG_ACCURATE; // create seek event depending on direction @@ -1538,6 +1545,9 @@ Timeline *MediaPlayer::timeline() float MediaPlayer::currentTimelineFading() { + if (loop_status_ == LOOP_STATUS_BLACKOUT && !isPlaying()) + return 0.f; + return timeline_.fadingAt(position_); } @@ -1748,7 +1758,7 @@ void MediaPlayer::setAudioEnabled(bool on) // toggle audio_enabled_ = on; // if openned - if (media_.hasaudio ) { + if ( media_.hasaudio && opened_) { // apply reopen(); } diff --git a/src/MediaPlayer.h b/src/MediaPlayer.h index fa91b2b4..7cda45e0 100644 --- a/src/MediaPlayer.h +++ b/src/MediaPlayer.h @@ -151,7 +151,8 @@ class MediaPlayer { typedef enum { LOOP_NONE = 0, LOOP_REWIND = 1, - LOOP_BIDIRECTIONAL = 2 + LOOP_BIDIRECTIONAL = 2, + LOOP_BLACKOUT = 3 } LoopMode; /** * Get the current loop mode @@ -161,6 +162,12 @@ class MediaPlayer { * Set the loop mode * */ void setLoop(LoopMode mode); + + typedef enum { LOOP_STATUS_DEFAULT = 0, + LOOP_STATUS_STOPPED = 1, + LOOP_STATUS_BLACKOUT = 2 + } LoopStatus; + LoopStatus loopStatus() const { return loop_status_; } /** * Step when paused * (aka next frame) @@ -320,6 +327,7 @@ class MediaPlayer { // GST & Play status GstClockTime position_; LoopMode loop_; + LoopStatus loop_status_; GstState desired_state_; GstElement *pipeline_; GstBus *bus_; diff --git a/src/MediaSource.cpp b/src/MediaSource.cpp index 11c870e9..4102a9bf 100644 --- a/src/MediaSource.cpp +++ b/src/MediaSource.cpp @@ -49,7 +49,6 @@ void MediaSource::setPath(const std::string &p) // open gstreamer mediaplayer_->open(path_); - mediaplayer_->play(true); // will be ready after init and one frame rendered ready_ = false; @@ -243,3 +242,13 @@ void MediaSource::accept(Visitor& v) Source::accept(v); v.visit(*this); } + +bool MediaSource::texturePostProcessed() const +{ + if (Source::texturePostProcessed()) + return true; + + return !mediaplayer_->timeline()->fadingIsClear() || + mediaplayer_->loopStatus() == MediaPlayer::LOOP_STATUS_BLACKOUT; +} + diff --git a/src/MediaSource.h b/src/MediaSource.h index 70b4a906..007e59ef 100644 --- a/src/MediaSource.h +++ b/src/MediaSource.h @@ -25,6 +25,7 @@ class MediaSource : public Source uint texture() const override; void accept (Visitor& v) override; void updateAudio() override; + bool texturePostProcessed () const override; // Media specific interface void setPath(const std::string &p); diff --git a/src/Mixer.cpp b/src/Mixer.cpp index 960a8c75..310ae16e 100644 --- a/src/Mixer.cpp +++ b/src/Mixer.cpp @@ -300,6 +300,7 @@ Source * Mixer::createSourceFile(const std::string &path, bool disable_hw_decodi MediaSource *ms = new MediaSource; ms->mediaplayer()->setSoftwareDecodingForced(disable_hw_decoding); ms->setPath(path); + ms->play(true); s = ms; } // propose a new name based on uri diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp index f4fe74b7..5f80bcdc 100644 --- a/src/SessionCreator.cpp +++ b/src/SessionCreator.cpp @@ -1047,7 +1047,7 @@ void SessionLoader::visit (Source& s) s.call( new Lock(l) ); bool p = true; sourceNode->QueryBoolAttribute("play", &p); - s.call( new Play(p) ); + s.call( new Play(p, session_) ); xmlCurrent_ = sourceNode->FirstChildElement("Mixing"); if (xmlCurrent_) s.groupNode(View::MIXING)->accept(*this); @@ -1150,7 +1150,7 @@ void SessionLoader::visit (MediaSource& s) s.mediaplayer()->accept(*this); // add a callback to activate play speed - s.call( new PlaySpeed( s.mediaplayer()->playSpeed(), 300 ) ); + s.call( new PlaySpeed( s.mediaplayer()->playSpeed() ) ); } void SessionLoader::visit (SessionFileSource& s) diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp index f76ab1c1..5040e249 100644 --- a/src/SessionVisitor.cpp +++ b/src/SessionVisitor.cpp @@ -436,6 +436,11 @@ void SessionVisitor::visit(MediaPlayer &n) if (!n.singleFrame()) { newelement->SetAttribute("loop", (int) n.loop()); + // special case; if media player stopped by loop (not by user) + // then override the play status of the Source to play the video + if (n.loopStatus() != MediaPlayer::LOOP_STATUS_DEFAULT) + xmlCurrent_->SetAttribute("play", true ); + newelement->SetAttribute("speed", n.playSpeed()); newelement->SetAttribute("video_effect", n.videoEffect().c_str()); newelement->SetAttribute("software_decoding", n.softwareDecodingForced()); diff --git a/src/Source.cpp b/src/Source.cpp index 5e3ad090..3dea33d3 100644 --- a/src/Source.cpp +++ b/src/Source.cpp @@ -542,7 +542,7 @@ bool Source::textureMirrored () return texturesurface_->mirrorTexture(); } -bool Source::imageProcessingEnabled() +bool Source::imageProcessingEnabled() const { return ( renderingshader_ == processingshader_ ); } @@ -733,8 +733,11 @@ bool Source::visible() const } -bool Source::textureTransformed () const +bool Source::texturePostProcessed () const { + if (imageProcessingEnabled()) + return true; + return frame()->projectionArea() != glm::vec4(-1.f, 1.f, 1.f, -1.f) || // cropped group(View::TEXTURE)->rotation_.z != 0.f || // rotation group(View::TEXTURE)->scale_ != glm::vec3(1.f) || // scaled diff --git a/src/Source.h b/src/Source.h index 2d44023b..08a5ff0c 100644 --- a/src/Source.h +++ b/src/Source.h @@ -134,7 +134,7 @@ class Source : public SourceCore // the image processing shader can be enabled or disabled // (NB: when disabled, a simple ImageShader is applied) void setImageProcessingEnabled (bool on); - bool imageProcessingEnabled (); + bool imageProcessingEnabled () const; // a Source has a shader to control mixing effects inline ImageShader *blendingShader () const { return blendingshader_; } @@ -223,7 +223,7 @@ class Source : public SourceCore float depth () const; float alpha () const; bool visible() const; - bool textureTransformed () const; + virtual bool texturePostProcessed () const; // groups for mixing MixingGroup *mixingGroup() const { return mixinggroup_; } diff --git a/src/SourceCallback.cpp b/src/SourceCallback.cpp index 340635dc..f3663b6f 100644 --- a/src/SourceCallback.cpp +++ b/src/SourceCallback.cpp @@ -157,7 +157,7 @@ void ValueSourceCallback::update(Source *s, float dt) } // update when active - if ( status_ == ACTIVE ) { + if ( status_ == ACTIVE && s->ready() ) { // time passed since start float progress = elapsed_ - delay_; @@ -494,7 +494,8 @@ void SetDepth::accept(Visitor& v) v.visit(*this); } -Play::Play(bool on, bool revert) : SourceCallback(), play_(on), bidirectional_(revert) +Play::Play(bool on, Session *parentsession, bool revert) : SourceCallback(), + session_(parentsession), play_(on), bidirectional_(revert) { } @@ -503,7 +504,8 @@ void Play::update(Source *s, float dt) SourceCallback::update(s, dt); // toggle play status when ready - if ( status_ == READY ){ + if ( status_ == READY && s->ready() && + (session_ ? session_->ready() : true) ){ if (s->playing() != play_) // call play function @@ -516,7 +518,7 @@ void Play::update(Source *s, float dt) SourceCallback *Play::clone() const { - return new Play(play_, bidirectional_); + return new Play(play_, session_, bidirectional_); } SourceCallback *Play::reverse(Source *s) const @@ -574,7 +576,7 @@ void PlaySpeed::writeValue(Source *s, float val) { // access media player if target source is a media source MediaSource *ms = dynamic_cast(s); - if (ms != nullptr && ms->ready()) { + if (ms != nullptr) { ms->mediaplayer()->setPlaySpeed((double) val); } } diff --git a/src/SourceCallback.h b/src/SourceCallback.h index 0daac3df..5943dff6 100644 --- a/src/SourceCallback.h +++ b/src/SourceCallback.h @@ -212,11 +212,12 @@ class SetDepth : public SourceCallback class Play : public SourceCallback { + class Session *session_; bool play_; bool bidirectional_; public: - Play (bool on = true, bool revert = false); + Play (bool on = true, Session *parentsession = nullptr, bool revert = false); bool value () const { return play_;} void setValue (bool on) { play_ = on; } diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index 4aa9c0a8..a8568452 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -697,17 +697,6 @@ bool EditTimeline(const char *label, hovered = true; cursor_dot = false; - // if a timing is given, ignore cursor timing - if (pl->timing != GST_CLOCK_TIME_NONE) { - // replace drop time by payload timing - // time = pl->timing; - // // replace mouse coordinate by position from time - // size_t index = ((pl->timing - begin) * static_cast(MAX_TIMELINE_ARRAY)) / end ; - // double x = static_cast(index) / static_cast(MAX_TIMELINE_ARRAY); - // mouse_pos_in_canvas.x = ( x * (size.x - 2.f * _h_space)) + _h_space; - - } - // dragged onto the timeline : apply changes on local copy switch (pl->action) { case TimelinePayload::CUT: @@ -751,7 +740,7 @@ bool EditTimeline(const char *label, } // dropped into the timeline : confirm change to timeline - if (ImGui::AcceptDragDropPayload("DND_TIMELINE")) { + if (ImGui::AcceptDragDropPayload("DND_TIMELINE", ImGuiDragDropFlags_AcceptNoDrawDefaultRect)) { // copy temporary timeline into given timeline *tl = _tl; // like a mouse release @@ -864,7 +853,7 @@ std::list< std::pair > DrawTimeline(const char* label, Timeline // PLOT of opacity is inside the bbox, at the top const ImVec2 plot_pos = frame_pos + style.FramePadding; - const ImRect plot_bbox( plot_pos, plot_pos + ImVec2(timeline_size.x, frame_size.y - 2.f * style.FramePadding.y - timeline_size.y)); + const ImRect plot_bbox( plot_pos, plot_pos + ImVec2(timeline_size.x, frame_size.y - 4.f * style.FramePadding.y - timeline_size.y)); // // THIRD RENDER @@ -1363,10 +1352,7 @@ void SourceControlWindow::DrawSource(Source *s, ImVec2 framesize, ImVec2 top_ima ImVec2 slider = framesize * ImVec2(Settings::application.widget.media_player_slider,1.f); // draw pre and post-processed parts if necessary - if ( (mediaplayer_active_ && !mediaplayer_active_->timeline()->fadingIsClear())|| - s->imageProcessingEnabled() || - s->textureTransformed() || - s->icon() == glm::ivec2(ICON_SOURCE_CLONE)) + if ( s->texturePostProcessed() ) { // // LEFT of slider : original texture @@ -2051,7 +2037,6 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) /// /// media player buttons bar (custom) /// - bottom.x = top.x; bottom.y += 2.f * timeline_height_ + scrollbar_; @@ -2100,8 +2085,8 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) // loop modes button ImGui::SameLine(0, h_space_); static int current_loop = 0; - static std::vector< std::pair > icons_loop = { {0,15}, {1,15}, {19,14} }; - static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)" }; + static std::vector< std::pair > icons_loop = { {0, 15}, {1, 15}, {19, 14}, {18, 14} }; + static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)", "Stop and blackout at end" }; current_loop = (int) mediaplayer_active_->loop(); if ( ImGuiToolkit::IconMultistate(icons_loop, ¤t_loop, tooltips_loop) ) mediaplayer_active_->setLoop( (MediaPlayer::LoopMode) current_loop ); @@ -2157,16 +2142,21 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) } else { - + /// + /// Disabled areas for timeline and button bar + /// + // disabled timeline ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f)); - const ImGuiContext& g = *GImGui; const double width_ratio = static_cast(scrollwindow.x - slider_zoom_width + g.Style.FramePadding.x ) / static_cast(mediaplayer_active_->timeline()->sectionsDuration()); DrawTimeline("##timeline_mediaplayers", mediaplayer_active_->timeline(), mediaplayer_active_->position(), width_ratio, 2.f * timeline_height_); - /// - /// Play button bar - /// + // disabled area for timeline actions and zoom + bottom += ImVec2(scrollwindow.x + 2.f, 0.f); + draw_list->AddRectFilled(bottom, bottom + ImVec2(slider_zoom_width, 2.f * timeline_height_ -1.f), + ImGui::GetColorU32(ImGuiCol_FrameBgActive)); + // disabled button bar + bottom.x = top.x; bottom.y += 2.f * timeline_height_ + scrollbar_; DrawButtonBar(bottom, rendersize.x); } @@ -2209,7 +2199,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) /// /// Window area to edit gaps or fading /// - if (mediaplayer_edit_panel_) { + if (mediaplayer_edit_panel_ && mediaplayer_active_->isEnabled()) { const ImVec2 gap_dialog_size(buttons_width_ * 3.0f, buttons_height_ * 1.42); const ImVec2 gap_dialog_pos = rendersize + ImVec2(h_space_, buttons_height_) - gap_dialog_size; @@ -2274,75 +2264,45 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) } /// - /// SEPARATOR + /// SECTION WITH BUTTONS /// ImGui::SameLine(0, 0); ImGui::Text("|"); - /// - /// Enter time or percentage of time - /// + /// Enter time value for CUT target_time = MIN(target_time, tl->duration()); - ImGui::SameLine(0, 0); ImVec2 draw_pos = ImGui::GetCursorPos(); - ImGui::SetCursorPosY(ImGui::GetTextLineHeightWithSpacing() * 0.333 ); - float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing() ; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11)); ImGui::SetNextItemWidth(w - draw_pos.x - IMGUI_SAME_LINE); // VARIABLE WIDTH ImGuiToolkit::InputTime("##Time", &target_time); + ImGui::PopStyleVar(); - // ImGui::SetCursorPos(ImVec2(draw_pos.x, draw_pos.y + 35)); - // ImGuiToolkit::HSliderUInt64("#SliderTime", ImVec2(180, 14), &target_time, 0, tl->duration()); - - // static int p = 5; - // ImGuiToolkit::HSliderInt("#toto", ImVec2(140, 14), &p, 1, 9); - // if (ImGui::IsItemActive()) { - // ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT); - // ImGui::SetTooltip("%d%%", p * 10); - // ImGui::PopFont(); - // } - // if (ImGui::IsItemDeactivatedAfterEdit()){ - - // } - - // ImGui::SameLine(0, IMGUI_SAME_LINE); - // ImVec2 draw_pos = ImGui::GetCursorPos() + ImVec2(0, v_space_); - // ImGui::SetCursorPos(ImVec2(gap_dialog_size.x - ImGui::GetTextLineHeightWithSpacing()- IMGUI_SAME_LINE, - // draw_pos.y)); - // static bool percentage = false; - // ImGuiToolkit::IconToggle(19, 10, 3, 7, &percentage); - // ImGui::SetCursorPos(draw_pos); - // if (percentage) { - // int target_percent = (int) ( (100 * target_time) / tl->duration() ); - // ImGui::SetNextItemWidth(-ImGui::GetTextLineHeightWithSpacing()); - // ImGui::DragInt("##percent", &target_percent, 1, 0, 100); - // target_time = (tl->duration() * (guint64) target_percent) / 100; - // } - // else { - // ImGui::SetNextItemWidth(-ImGui::GetTextLineHeightWithSpacing()); - // ImGuiToolkit::InputTime("##Time", &target_time); - // } - - // Action buttons + /// CUT LEFT TIME ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); ImGui::SetCursorPos( ImVec2(w, draw_pos.y)); - - // ImGui::SameLine(0, IMGUI_SAME_LINE); if (ImGuiToolkit::ButtonIcon(17, 3, "Cut left at given time")) { tl->cut(target_time, true); tl->refresh(); + oss << ": Timeline cut"; + Action::manager().store(oss.str()); } - + /// CUT RIGHT TIME ImGui::SameLine(0, IMGUI_SAME_LINE); if (ImGuiToolkit::ButtonIcon(18, 3, "Cut right at given time")){ tl->cut(target_time, false); tl->refresh(); + oss << ": Timeline cut"; + Action::manager().store(oss.str()); } - + /// CLEAR ImGui::SameLine(0, IMGUI_SAME_LINE); - if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all gaps")) + if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all gaps")) { tl->clearGaps(); + oss << ": Timeline clear gaps"; + Action::manager().store(oss.str()); + } ImGui::PopStyleColor(); @@ -2368,9 +2328,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) {14, 12}}; ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - // ImGuiToolkit::ButtonIconMultistate(options_curve, ¤t_curve, tooltips_curve); ImGuiToolkit::IconMultistate(options_curve, ¤t_curve, tooltips_curve); - ImGui::PopStyleColor(); /// @@ -2395,12 +2353,6 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::SameLine(0, IMGUI_SAME_LINE); DragButtonIcon(9, 10, "Drop in timeline to insert\nSharp fade in & out", TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SHARP)); - /// - /// FADE OUT & IN - /// - ImGui::SameLine(0, IMGUI_SAME_LINE); - DragButtonIcon(10, 10, "Drop in timeline to insert\nSharp fade out & in", - TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_SHARP)); } /// /// FADE LINEAR @@ -2424,12 +2376,6 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::SameLine(0, IMGUI_SAME_LINE); DragButtonIcon(7, 10, "Drop in timeline to insert\nLinear fade in & out", TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_LINEAR)); - /// - /// FADE OUT & IN - /// - ImGui::SameLine(0, IMGUI_SAME_LINE); - DragButtonIcon(8, 10, "Drop in timeline to insert\nLinear fade out & in", - TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_LINEAR)); } /// /// FADE SMOOTH @@ -2453,89 +2399,70 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::SameLine(0, IMGUI_SAME_LINE); DragButtonIcon(17, 10, "Drop in timeline to insert\nSmooth fade in & out", TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SMOOTH)); - /// - /// FADE OUT & IN - /// - ImGui::SameLine(0, IMGUI_SAME_LINE); - DragButtonIcon(18, 10, "Drop in timeline to insert\nSmooth fade out & in", - TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_SMOOTH)); - } - // float md = 10000; - ImGui::SameLine(0, IMGUI_SAME_LINE); + /// + /// DURATION SLIDER + /// + float seconds = (float) d / 1000.f; + float maxi = (float) GST_TIME_AS_MSECONDS(tl->duration()) / 1000.f; + float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing(); + ImGui::SameLine(0, IMGUI_SAME_LINE); ImVec2 draw_pos = ImGui::GetCursorPos(); - float w = gap_dialog_size.x - 3.f * ImGui::GetTextLineHeightWithSpacing() - IMGUI_SAME_LINE; + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11)); ImGui::SetNextItemWidth(w - draw_pos.x); - float seconds = (float) d / 1000.f; - - if (current_curve > 0) { - // ImGuiToolkit::SliderTiming("##Duration", &d, 60, md+50, 50, "Auto"); - // if (d > md) d = UINT_MAX; - if (ImGui::SliderFloat("##DurationFading", - &seconds, - 0.05f, - 60.f, - seconds < 59.5f ? "%.2f s" : "Auto")) { - if (seconds > 59.5f) - d = UINT_MAX; - else - d = (uint) floor(seconds * 1000); - } - } else { - // ImGui::TextDisabled("%.2f s", seconds); - static char dummy_str[512]; - if (seconds < 59.5f ) - snprintf(dummy_str, 512, "%.2f s", seconds); + if (ImGui::SliderFloat("##DurationFading", + &seconds, + 0.05f, + maxi, + d < UINT_MAX ? "%.2f s" : "Auto")) { + if (seconds > maxi - 0.05f) + d = UINT_MAX; else - snprintf(dummy_str, 512, "Auto"); - - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); - ImGui::InputText("##disabledduration", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); - ImGui::PopStyleColor(1); - + d = (uint) floor(seconds * 1000); } - - - // Action buttons - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PopStyleVar(); + ImGui::PopFont(); /// - /// SEPARATOR + /// SECTION WITH BUTTONS /// - ImGui::SameLine(0, 0); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::SetCursorPos( ImVec2(w, draw_pos.y)); ImGui::Text("|"); - // action smooth + /// SMOOTH Curve static int _actionsmooth = 0; ImGui::SameLine(0, 0); ImGui::PushButtonRepeat(true); if (ImGuiToolkit::ButtonIcon(2, 7, "Apply smoothing filter")){ - tl->smoothFading( 5 ); + tl->smoothFading( 15 ); ++_actionsmooth; } ImGui::PopButtonRepeat(); if (_actionsmooth > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { - oss << ": Timeline opacity smooth"; + oss << ": Timeline smooth curve"; Action::manager().store(oss.str()); _actionsmooth = 0; } + /// CLEANUP + ImGui::SameLine(0, 0); + if (ImGuiToolkit::ButtonIcon(3, 7, "Clean curve in gaps")) { + tl->autoFadeInGaps(); + oss << ": Timeline cleanup curve"; + Action::manager().store(oss.str()); + } - // ImGui::SameLine(0, IMGUI_SAME_LINE); - // ImGui::SetNextItemWidth(140); // TODO VARIABLE WIDTH - // ImVec2 draw_pos = ImGui::GetCursorPos(); - // ImGui::SetCursorPosY(draw_pos.y + 8); // TODO VARIABLE H - // ImGuiToolkit::InputTime("##Time", &target_time); - - - /// /// CLEAR - /// - ImGui::SameLine(0, IMGUI_SAME_LINE); - if (ImGuiToolkit::ButtonIcon(11, 14, "Clear Fading curve")) + ImGui::SameLine(0, 0); + if (ImGuiToolkit::ButtonIcon(11, 14, "Clear fading curve")){ tl->clearFading(); + oss << ": Timeline clear fading"; + Action::manager().store(oss.str()); + } ImGui::PopStyleColor(); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 7d5b7a2e..f2c91d62 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -40,6 +40,10 @@ float linestep(size_t edge0, size_t edge1, size_t i) { return x; } +float sharpstep(size_t edge0, size_t edge1, size_t i) { + float x = clamp( static_cast(i - edge0) / static_cast(edge1 - edge0)); + return fabs(x)<1.f ? 0.f : 1.f; +} @@ -545,7 +549,6 @@ void Timeline::clearFading() fading_array_allones_ = true; } - bool Timeline::fadingIsClear() { if (fading_array_changed_) { @@ -696,18 +699,16 @@ void Timeline::fadeOut(const GstClockTime from, const GstClockTime duration, Fad N = 2; } - // linear fade out starts at s + // fade out starts at s size_t i = s; // float val = fadingArray_[s]; for (; i <= s+n; ++i) { - const float x = static_cast(e-i) / static_cast(n); - // const float x = 1.f - static_cast(i-s) / static_cast(n); if (curve==FADING_LINEAR) - fadingArray_[i] = x; + fadingArray_[i] = 1.f - linestep(s, s+n, i); else if (curve==FADING_SMOOTH) fadingArray_[i] = 1.f - smootherstep(s, s+n, i); else - fadingArray_[i] = 0.f; + fadingArray_[i] = sharpstep(s, s+n, i); // fadingArray_[i] *= val; } fading_array_changed_ = true; @@ -746,18 +747,16 @@ void Timeline::fadeIn(const GstClockTime to, const GstClockTime duration, Fading // calculate size of the smooth transition in [s e] interval const size_t n = MIN( e-s, N ); - // linear fade in ends at e + // fade in ends at e size_t i = e-n; // float val = fadingArray_[e]; for (; i < e; ++i) { - // const float x = static_cast(i-s) / static_cast(n); - const float x = 1.f - static_cast(e - i) / static_cast(n); if (curve==FADING_LINEAR) - fadingArray_[i] = x; + fadingArray_[i] = linestep(e-n, e, i); else if (curve==FADING_SMOOTH) fadingArray_[i] = smootherstep(e-n, e, i); else - fadingArray_[i] = 0.f; + fadingArray_[i] = sharpstep(e-n, e, i); // fadingArray_[i] *= val; } fading_array_changed_ = true; @@ -771,12 +770,7 @@ void Timeline::fadeInOutRange(const GstClockTime t, const GstClockTime duration, // find cuts at left and right of given time for (auto g = gaps_.begin(); g != gaps_.end(); g++) { if (g->begin < t) { - if (g->end > t) { - // inside a gap - range.begin = g->begin; - range.end = g->end; - break; - } else { + if (g->end < t) { // after a gap range.begin = g->end; } @@ -794,48 +788,56 @@ void Timeline::fadeInOutRange(const GstClockTime t, const GstClockTime duration, const size_t e = (range.end * MAX_TIMELINE_ARRAY) / timing_.end; // get index of time in section - const size_t m = (t * MAX_TIMELINE_ARRAY) / timing_.end; - - size_t l = m; - size_t r = m; + size_t l = ( MIN(t, range.begin + duration) * MAX_TIMELINE_ARRAY) / timing_.end; + size_t r = ( MAX(t, range.end - duration) * MAX_TIMELINE_ARRAY) / timing_.end; // if duration too short for a linear or smooth if (duration < 2 * step_) { curve = FADING_SHARP; } - // if duration allows to fade in and out - else if (2 * duration < range.duration()) { - l = ( (range.begin + duration) * MAX_TIMELINE_ARRAY) / timing_.end; - r = ( (range.end - duration) * MAX_TIMELINE_ARRAY) / timing_.end; + else if (duration > range.duration()) { + if (curve == FADING_SHARP) { + l = s+1; + r = e-1; + } + else + l = r = (t * MAX_TIMELINE_ARRAY) / timing_.end; } // fill values inside range - for (size_t k = s; k < e; ++k) { + for (size_t k = s; k <= e; ++k) { if (curve == FADING_LINEAR){ if (k 0 ? fadingArray_[i-1] : 0; + } + } +} + void Timeline::updateGapsFromArray(float *array, size_t array_size) { // reset gaps diff --git a/src/Timeline.h b/src/Timeline.h index 9383f4f4..39cf96e8 100644 --- a/src/Timeline.h +++ b/src/Timeline.h @@ -165,7 +165,10 @@ class Timeline void fadeIn(const GstClockTime from, const GstClockTime duration, FadingCurve curve = FADING_SHARP); void fadeOut(const GstClockTime to, const GstClockTime duration, FadingCurve curve = FADING_SHARP); void fadeInOutRange(const GstClockTime t, const GstClockTime duration, bool in_and_out, FadingCurve curve = FADING_SHARP); - bool autoCut(); + + // link Fading and Gaps + bool autoGapInFade(); + void autoFadeInGaps(); private: diff --git a/src/TransitionView.cpp b/src/TransitionView.cpp index 4c5ebfa2..94acf7f6 100644 --- a/src/TransitionView.cpp +++ b/src/TransitionView.cpp @@ -234,7 +234,7 @@ void TransitionView::draw() // toggle transition mode ImGui::SetCursorScreenPos(ImVec2(pos_tran.x - 60.f, pos_tran.y +2.f)); const char *tooltip[2] = {"Fade to black", "Cross fading"}; - ImGuiToolkit::IconToggle(0, 2, 0, 8, &Settings::application.transition.cross_fade, tooltip ); + ImGuiToolkit::IconToggle(9, 8, 0, 8, &Settings::application.transition.cross_fade, tooltip ); ImGui::SetCursorScreenPos(ImVec2(pos_tran.x + 10.f, pos_tran.y + 2.f)); const char *_tooltip[2] = {"Linear", "Quadratic"}; diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index 40fd25fd..222c430f 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -6021,7 +6021,7 @@ void Navigator::RenderTransitionPannel(const ImVec2 &iconsize) static std::vector< std::tuple > profile_fading = { {0, 8, "Cross fading"}, - {0, 2, "Fade to black"} + {9, 8, "Fade to black"} }; ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); int tmp = Settings::application.transition.cross_fade ? 0 : 1;