diff --git a/src/ImGuiToolkit.cpp b/src/ImGuiToolkit.cpp index cc5d14a..f99ab0d 100644 --- a/src/ImGuiToolkit.cpp +++ b/src/ImGuiToolkit.cpp @@ -204,24 +204,44 @@ void ImGuiToolkit::Icon(int i, int j, bool enabled) ImGui::Image((void*)(intptr_t)textureicons, ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing()), uv0, uv1, tint_color); } -bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip) +bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip, bool expanded) { - // icons.dds is a 20 x 20 grid of icons - if (textureicons == 0) - textureicons = Resource::getTextureDDS("images/icons.dds"); - - ImVec2 uv0( static_cast(i) * 0.05, static_cast(j) * 0.05 ); - ImVec2 uv1( uv0.x + 0.05, uv0.y + 0.05 ); - - ImGui::PushID( i*20 + j); ImGuiContext& g = *GImGui; - bool ret = ImGui::ImageButton((void*)(intptr_t)textureicons, - ImVec2(g.FontSize, g.FontSize), - uv0, uv1, g.Style.FramePadding.y); - ImGui::PopID(); + bool ret = false; - if (tooltip != nullptr && ImGui::IsItemHovered()) - ImGuiToolkit::ToolTip(tooltip); + if (expanded) { + // make some space + char space_buf[] = " "; + const ImVec2 space_size = ImGui::CalcTextSize(" ", NULL); + const int space_num = static_cast( ceil(g.FontSize / space_size.x) ); + space_buf[space_num]='\0'; + + char text_buf[256]; + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%s %s", space_buf, tooltip); + + ImVec2 draw_pos = ImGui::GetCursorScreenPos() + g.Style.FramePadding * 0.5; + ret = ImGui::Button(text_buf); + + // overlay of icon on top of first item + _drawIcon(draw_pos, i, j); + } + else { + // icons.dds is a 20 x 20 grid of icons + if (textureicons == 0) + textureicons = Resource::getTextureDDS("images/icons.dds"); + + ImVec2 uv0( static_cast(i) * 0.05, static_cast(j) * 0.05 ); + ImVec2 uv1( uv0.x + 0.05, uv0.y + 0.05 ); + + ImGui::PushID( i*20 + j); + ret = ImGui::ImageButton((void*)(intptr_t)textureicons, + ImVec2(g.FontSize, g.FontSize), + uv0, uv1, g.Style.FramePadding.y); + ImGui::PopID(); + + if (tooltip != nullptr && ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip(tooltip); + } return ret; } diff --git a/src/ImGuiToolkit.h b/src/ImGuiToolkit.h index df41451..4670c56 100644 --- a/src/ImGuiToolkit.h +++ b/src/ImGuiToolkit.h @@ -24,7 +24,7 @@ namespace ImGuiToolkit void ShowIconsWindow(bool* p_open); // buttons and gui items with icon - bool ButtonIcon (int i, int j, const char* tooltip = nullptr); + bool ButtonIcon (int i, int j, const char* tooltip = nullptr, bool expanded = false); bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltip = nullptr); bool ButtonIconMultistate (std::vector > icons, int* state, std::vector tooltips); bool MenuItemIcon (int i, int j, const char* label, const char* shortcut = nullptr, bool selected = false, bool enabled = true); diff --git a/src/InfoVisitor.cpp b/src/InfoVisitor.cpp index 7d09319..4ce1436 100644 --- a/src/InfoVisitor.cpp +++ b/src/InfoVisitor.cpp @@ -98,14 +98,14 @@ void InfoVisitor::visit(MediaPlayer &mp) oss << SystemToolkit::filename(mp.filename()) << std::endl; oss << mp.media().codec_name.substr(0, mp.media().codec_name.find_first_of(" (,")) << ", "; oss << mp.width() << " x " << mp.height(); - if (!mp.isImage() && mp.frameRate() > 0.) + if (!mp.singleFrame() && mp.frameRate() > 0.) oss << ", " << std::fixed << std::setprecision(0) << mp.frameRate() << " fps"; } else { oss << mp.filename() << std::endl; oss << mp.media().codec_name << std::endl; oss << mp.width() << " x " << mp.height() ; - if (!mp.isImage() && mp.frameRate() > 0.) + if (!mp.singleFrame() && mp.frameRate() > 0.) oss << ", " << std::fixed << std::setprecision(1) << mp.frameRate() << " fps"; } } diff --git a/src/MediaPlayer.cpp b/src/MediaPlayer.cpp index 33531ef..54c95bc 100644 --- a/src/MediaPlayer.cpp +++ b/src/MediaPlayer.cpp @@ -352,6 +352,13 @@ void MediaPlayer::execute_open() video_filter_available_ = false; } + // hack to simulate a playable and seekable stream with an image + if (media_.isimage && !singleFrame()) { + media_.seekable = true; + video_filter_ = "imagefreeze"; + video_filter_available_ = false; + } + // Add a filter to playbin if provided if ( !video_filter_.empty()) { // try to create the pipeline element given @@ -403,7 +410,7 @@ void MediaPlayer::execute_open() callbacks.new_event = NULL; #endif callbacks.new_preroll = callback_new_preroll; - if (media_.isimage) { + if (singleFrame()) { callbacks.eos = NULL; callbacks.new_sample = NULL; } @@ -434,8 +441,8 @@ void MediaPlayer::execute_open() return; } - // in case discoverer failed to get duration - if (timeline_.end() == GST_CLOCK_TIME_NONE) { + // in case discoverer failed to get duration of a video + if (!media_.isimage && timeline_.end() == GST_CLOCK_TIME_NONE) { gint64 d = GST_CLOCK_TIME_NONE; if ( gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &d) ) timeline_.setEnd(d); @@ -445,7 +452,7 @@ void MediaPlayer::execute_open() Log::Info("MediaPlayer %s Opened '%s' (%s %d x %d)", std::to_string(id_).c_str(), SystemToolkit::filename(uri_).c_str(), media_.codec_name.c_str(), media_.width, media_.height); - if (!isImage()) + if (!singleFrame()) Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(), timeline_.begin(), timeline_.end(), timeline_.numFrames(), timeline_.numGaps()); @@ -506,6 +513,12 @@ void MediaPlayer::execute_open() description += std::to_string(media_.framerate_d) + " ! "; } + // hack to simulate a playable and seekable stream with an image + if (media_.isimage && !singleFrame()) { + media_.seekable = true; + description += "imagefreeze ! "; + } + // set app sink description += "queue ! appsink name=sink"; @@ -563,7 +576,7 @@ void MediaPlayer::execute_open() callbacks.new_event = NULL; #endif callbacks.new_preroll = callback_new_preroll; - if (media_.isimage) { + if (singleFrame()) { callbacks.eos = NULL; callbacks.new_sample = NULL; } @@ -591,8 +604,8 @@ void MediaPlayer::execute_open() return; } - // in case discoverer failed to get duration - if (timeline_.end() == GST_CLOCK_TIME_NONE) { + // in case discoverer failed to get duration of a video + if (!media_.isimage && timeline_.end() == GST_CLOCK_TIME_NONE) { gint64 d = GST_CLOCK_TIME_NONE; if ( gst_element_query_duration(pipeline_, GST_FORMAT_TIME, &d) ) timeline_.setEnd(d); @@ -602,7 +615,7 @@ void MediaPlayer::execute_open() Log::Info("MediaPlayer %s Opened '%s' (%s %d x %d)", std::to_string(id_).c_str(), SystemToolkit::filename(uri_).c_str(), media_.codec_name.c_str(), media_.width, media_.height); - if (!isImage()) + if (!singleFrame()) Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(), timeline_.begin(), timeline_.end(), timeline_.numFrames(), timeline_.numGaps()); @@ -783,6 +796,11 @@ bool MediaPlayer::isImage() const return media_.isimage; } +bool MediaPlayer::singleFrame() const +{ + return timeline_.end() == GST_CLOCK_TIME_NONE; +} + std::string MediaPlayer::decoderName() { if (decoder_name_.empty()) { @@ -856,8 +874,8 @@ void MediaPlayer::execute_play_command(bool on) void MediaPlayer::play(bool on) { - // ignore if disabled, and cannot play an image - if (!enabled_ || media_.isimage || pending_) + // ignore if disabled, and cannot play a single frame + if (!enabled_ || pending_ || singleFrame()) return; // Metronome @@ -881,7 +899,7 @@ void MediaPlayer::play(bool on) bool MediaPlayer::isPlaying(bool testpipeline) const { // image cannot play - if (media_.isimage) + if (singleFrame()) return false; // if not ready yet, answer with requested state @@ -1032,7 +1050,7 @@ void MediaPlayer::init_texture(guint index) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - if (!media_.isimage) { + if ( !singleFrame() ) { // set pbo image size pbo_size_ = media_.height * media_.width * 4; @@ -1146,9 +1164,10 @@ void MediaPlayer::update() if (media_.valid) { if (!media_.log.empty()) Log::Info("'%s' : %s", uri().c_str(), media_.log.c_str()); - - timeline_.setEnd( media_.end ); - timeline_.setStep( media_.dt ); + if (!media_.isimage) { + timeline_.setEnd( media_.end ); + timeline_.setStep( media_.dt ); + } execute_open(); } else { @@ -1163,7 +1182,7 @@ void MediaPlayer::update() } // prevent unnecessary updates: disabled or already filled image - if ( (!enabled_ && !force_update_) || (media_.isimage && textureindex_>0 ) ) + if ( (!enabled_ && !force_update_) || (singleFrame() && textureindex_>0 ) ) return; // local variables before trying to update @@ -1222,10 +1241,15 @@ void MediaPlayer::update() // do NOT do another seek yet } // otherwise check for need to seek (pipeline management) - else { - // manage timeline: test if position falls into a gap + else if (position_ != GST_CLOCK_TIME_NONE) { + // manage timeline: TimeInterval gap; - if (position_ != GST_CLOCK_TIME_NONE && timeline_.getGapAt(position_, gap)) { + // ensure we remain within the begin to end range + if (position_ > timeline_.last() || position_ < timeline_.first()) { + need_loop = true; + } + // test if position falls into a gap + else if (timeline_.getGapAt(position_, gap)) { // if in a gap, seek to next section if (gap.is_valid()) { // jump in one or the other direction @@ -1339,7 +1363,7 @@ void MediaPlayer::setPlaySpeed(double s) rate_ = SIGN(rate_) * MIN_PLAY_SPEED; // discard if too early or not possible - if (media_.isimage || !media_.seekable || pipeline_ == nullptr) + if (pipeline_ == nullptr || !media_.seekable) return; // @@ -1352,7 +1376,7 @@ void MediaPlayer::setPlaySpeed(double s) // are GST_CLOCK_TIME_NONE, the playback direction does not change // and the seek is not flushing. (Since: 1.18) // if all conditions to use GST_SEEK_FLAG_INSTANT_RATE_CHANGE are met - if ( rate_change_ == RATE_CHANGE_INSTANT && !change_direction ) { + if ( rate_change_ == RATE_CHANGE_INSTANT && !change_direction && !media_.isimage) { int seek_flags = GST_SEEK_FLAG_INSTANT_RATE_CHANGE; seek_flags |= GST_SEEK_FLAG_TRICKMODE; diff --git a/src/MediaPlayer.h b/src/MediaPlayer.h index 41954f5..266c8ca 100644 --- a/src/MediaPlayer.h +++ b/src/MediaPlayer.h @@ -118,7 +118,11 @@ public: /** * True if its an image * */ - bool isImage() const; + bool isImage() const; + /** + * True if it has only one frame + * */ + bool singleFrame() const; /** * Pause / Play * Can play backward if play speed is negative @@ -269,8 +273,10 @@ public: */ static std::list registered() { return registered_; } static std::list::const_iterator begin() { return registered_.cbegin(); } - static std::list::const_iterator end() { return registered_.cend(); } - + static std::list::const_iterator end() { return registered_.cend(); } + /** + * Discoverer to check uri and get media info + * */ static MediaInfo UriDiscoverer(const std::string &uri); std::string log() const { return media_.log; } diff --git a/src/MediaSource.cpp b/src/MediaSource.cpp index 3971a5d..d3ab9cb 100644 --- a/src/MediaSource.cpp +++ b/src/MediaSource.cpp @@ -154,7 +154,7 @@ void MediaSource::play (bool on) bool MediaSource::playable () const { - return !mediaplayer_->isImage(); + return !mediaplayer_->singleFrame(); } void MediaSource::replay () diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp index b9e04a9..81b74f0 100644 --- a/src/SessionCreator.cpp +++ b/src/SessionCreator.cpp @@ -851,7 +851,21 @@ void SessionLoader::visit(MediaPlayer &n) XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline"); if (timelineelement) { Timeline tl; - tl.setTiming( n.timeline()->interval(), n.timeline()->step()); + + TimeInterval interval_(n.timeline()->interval()); + if (interval_.is_valid()) + tl.setTiming( interval_, n.timeline()->step()); + else { + GstClockTime b = GST_CLOCK_TIME_NONE; + GstClockTime e = GST_CLOCK_TIME_NONE; + GstClockTime s = GST_CLOCK_TIME_NONE; + timelineelement->QueryUnsigned64Attribute("begin", &b); + timelineelement->QueryUnsigned64Attribute("end", &e); + timelineelement->QueryUnsigned64Attribute("step", &s); + interval_ = TimeInterval(b,e); + tl.setTiming( interval_, s); + } + XMLElement *gapselement = timelineelement->FirstChildElement("Gaps"); if (gapselement) { XMLElement* gap = gapselement->FirstChildElement("Interval"); diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp index 11d17a4..4766b0b 100644 --- a/src/SessionVisitor.cpp +++ b/src/SessionVisitor.cpp @@ -410,7 +410,7 @@ void SessionVisitor::visit(MediaPlayer &n) XMLElement *newelement = xmlDoc_->NewElement("MediaPlayer"); newelement->SetAttribute("id", n.id()); - if (!n.isImage()) { + if (!n.singleFrame()) { newelement->SetAttribute("play", n.isPlaying()); newelement->SetAttribute("loop", (int) n.loop()); newelement->SetAttribute("speed", n.playSpeed()); @@ -421,6 +421,9 @@ void SessionVisitor::visit(MediaPlayer &n) // timeline XMLElement *timelineelement = xmlDoc_->NewElement("Timeline"); + timelineelement->SetAttribute("begin", n.timeline()->begin()); + timelineelement->SetAttribute("end", n.timeline()->end()); + timelineelement->SetAttribute("step", n.timeline()->step()); // gaps in timeline XMLElement *gapselement = xmlDoc_->NewElement("Gaps"); diff --git a/src/SourceCallback.cpp b/src/SourceCallback.cpp index bcfdc1c..215d935 100644 --- a/src/SourceCallback.cpp +++ b/src/SourceCallback.cpp @@ -573,7 +573,7 @@ void PlayFastForward::update(Source *s, float dt) if (ms != nullptr) { MediaPlayer *mp = ms->mediaplayer(); // works only if media player is enabled and valid - if (mp && mp->isEnabled() && !mp->isImage()) { + if (mp && mp->isEnabled() && !mp->singleFrame()) { media_ = mp; playspeed_ = media_->playSpeed(); } diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index 2a3c506..481bc09 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -372,61 +372,85 @@ void SourceControlWindow::Render() // // Menu for video Mediaplayer control // - if (ImGui::BeginMenu(ICON_FA_FILM " Video", mediaplayer_active_) ) + if (ImGui::BeginMenu(ICON_FA_PHOTO_VIDEO " Media", mediaplayer_active_) ) { - if (ImGui::MenuItem( ICON_FA_REDO_ALT " Reload" )) - mediaplayer_active_->reopen(); - if (ImGuiToolkit::MenuItemIcon(16, 16, "Gstreamer effect", nullptr, - false, mediaplayer_active_->videoEffectAvailable()) ) - mediaplayer_edit_pipeline_ = true; - if (ImGui::BeginMenu(ICON_FA_SNOWFLAKE " On deactivation")) - { - bool option = !mediaplayer_active_->rewindOnDisabled(); - if (ImGui::MenuItem(ICON_FA_STOP " Stop", NULL, &option )) - mediaplayer_active_->setRewindOnDisabled(false); - option = mediaplayer_active_->rewindOnDisabled(); - if (ImGui::MenuItem(ICON_FA_FAST_BACKWARD " Rewind & Stop", NULL, &option )) - mediaplayer_active_->setRewindOnDisabled(true); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu(ICON_FA_MICROCHIP " Hardware decoding")) - { - bool hwdec = !mediaplayer_active_->softwareDecodingForced(); - if (ImGui::MenuItem("Auto", "", &hwdec )) - mediaplayer_active_->setSoftwareDecodingForced(false); - hwdec = mediaplayer_active_->softwareDecodingForced(); - if (ImGui::MenuItem("Disabled", "", &hwdec )) - mediaplayer_active_->setSoftwareDecodingForced(true); - ImGui::EndMenu(); - } - ImGui::Separator(); - ImGui::TextDisabled("Timeline"); - if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Reset")){ - mediaplayer_timeline_zoom_ = 1.f; - mediaplayer_active_->timeline()->clearFading(); - mediaplayer_active_->timeline()->clearGaps(); - std::ostringstream oss; - oss << SystemToolkit::base_filename( mediaplayer_active_->filename() ); - oss << ": Reset timeline"; - Action::manager().store(oss.str()); - } + if ( !mediaplayer_active_->singleFrame() ) { + if (ImGui::MenuItem( ICON_FA_REDO_ALT " Reload" )) + mediaplayer_active_->reopen(); + if (ImGuiToolkit::MenuItemIcon(16, 16, "Gstreamer effect", nullptr, + false, mediaplayer_active_->videoEffectAvailable()) ) + mediaplayer_edit_pipeline_ = true; + if (ImGui::BeginMenu(ICON_FA_MICROCHIP " Hardware decoding")) + { + bool hwdec = !mediaplayer_active_->softwareDecodingForced(); + if (ImGui::MenuItem("Auto", "", &hwdec )) + mediaplayer_active_->setSoftwareDecodingForced(false); + hwdec = mediaplayer_active_->softwareDecodingForced(); + if (ImGui::MenuItem("Disabled", "", &hwdec )) + mediaplayer_active_->setSoftwareDecodingForced(true); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu(ICON_FA_SNOWFLAKE " On deactivation")) + { + bool option = !mediaplayer_active_->rewindOnDisabled(); + if (ImGui::MenuItem(ICON_FA_STOP " Stop", NULL, &option )) + mediaplayer_active_->setRewindOnDisabled(false); + option = mediaplayer_active_->rewindOnDisabled(); + if (ImGui::MenuItem(ICON_FA_FAST_BACKWARD " Rewind & Stop", NULL, &option )) + mediaplayer_active_->setRewindOnDisabled(true); + ImGui::EndMenu(); + } + ImGui::Separator(); + ImGui::TextDisabled("Timeline"); - if (ImGui::MenuItem(LABEL_EDIT_FADING)) - mediaplayer_edit_fading_ = true; + if ( mediaplayer_active_->isImage()) { + if ( ImGuiToolkit::MenuItemIcon(1, 14, "Remove")){ + // set empty timeline + Timeline tl; + mediaplayer_active_->setTimeline(tl); + mediaplayer_active_->play(false); + // re-open the image with NO timeline + mediaplayer_active_->reopen(); + mediaplayer_active_ = nullptr; + } + + if ( ImGui::MenuItem(ICON_FA_HOURGLASS_HALF " Duration")){ + mediaplayer_set_duration_ = true; + } + } + + if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Reset")){ + mediaplayer_timeline_zoom_ = 1.f; + mediaplayer_active_->timeline()->clearFading(); + mediaplayer_active_->timeline()->clearGaps(); + std::ostringstream oss; + oss << SystemToolkit::base_filename( mediaplayer_active_->filename() ); + oss << ": Reset timeline"; + Action::manager().store(oss.str()); + } + + if (ImGui::MenuItem(LABEL_EDIT_FADING)) + mediaplayer_edit_fading_ = true; + + if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome")) + { + Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome(); + bool active = sync == Metronome::SYNC_NONE; + if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", NULL, active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE); + active = sync == Metronome::SYNC_BEAT; + if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT); + active = sync == Metronome::SYNC_PHASE; + if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE); + ImGui::EndMenu(); + } + } + else { + if (ImGui::MenuItem( ICON_FA_REDO_ALT " Reload" )) + mediaplayer_active_->reopen(); - if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome")) - { - Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome(); - bool active = sync == Metronome::SYNC_NONE; - if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", NULL, active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE); - active = sync == Metronome::SYNC_BEAT; - if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT); - active = sync == Metronome::SYNC_PHASE; - if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE); - ImGui::EndMenu(); } ImGui::EndMenu(); @@ -640,7 +664,7 @@ void SourceControlWindow::RenderSelection(size_t i) for (auto source = selection_.begin(); source != selection_.end(); ++source) { // collect durations of all media sources MediaSource *ms = dynamic_cast(*source); - if (ms != nullptr && !ms->mediaplayer()->isImage()) + if (ms != nullptr && !ms->mediaplayer()->singleFrame()) durations.push_back(static_cast(static_cast(ms->mediaplayer()->timeline()->sectionsDuration()) / fabs(ms->mediaplayer()->playSpeed()))); // compute the displayed width of frames of this source, and keep the max to align afterwards float w = 1.5f * timeline_height_ * (*source)->frame()->aspectRatio(); @@ -677,7 +701,7 @@ void SourceControlWindow::RenderSelection(size_t i) // get media source MediaSource *ms = dynamic_cast(*source); - if (ms == nullptr || ms->mediaplayer()->isImage()) { + if (ms == nullptr || ms->mediaplayer()->singleFrame()) { // leave the source in the list and continue ++source; continue; @@ -1284,7 +1308,7 @@ void SourceControlWindow::RenderSelectedSources() std::string label(ICON_FA_PLUS_CIRCLE); if (space > buttons_width_) { // enough space to show full button with label text label += LABEL_STORE_SELECTION; - width = buttons_width_; + width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing(); } ImGui::SameLine(0, space -width); ImGui::SetNextItemWidth(width); @@ -1385,6 +1409,47 @@ void SourceControlWindow::RenderSingleSource(Source *s) /// Play button bar /// DrawButtonBar(bottom, rendersize.x); + + // If possibly a media source, but is not playable + // then offer to make it playable by adding a timeline + if ( ms != nullptr ) + { + if (ms->mediaplayer()->isImage()) { + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); + + float space = ImGui::GetContentRegionAvail().x; + float width = buttons_height_ ; + if (space > buttons_width_) + width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing(); + ImGui::SameLine(0, space -width); + ImGui::SetNextItemWidth(width); + if (ImGuiToolkit::ButtonIcon( 0, 14, LABEL_ADD_TIMELINE, space > buttons_width_ )) { + + // activate mediaplayer + mediaplayer_active_ = ms->mediaplayer(); + + // set timeline to default 1 second + Timeline tl; + TimeInterval interval(0, GST_SECOND); + // set fixed framerate to 25 FPS + tl.setTiming( interval, 40 * GST_MSECOND); + mediaplayer_active_->setTimeline(tl); + + // set to play + mediaplayer_active_->play(true); + + // re-open the image with a timeline + mediaplayer_active_->reopen(); + + // open dialog to set duration + mediaplayer_set_duration_ = true; + } + + ImGui::PopStyleColor(2); + } + } } } @@ -1746,6 +1811,9 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) } + /// + /// Dialog to edit timeline fade in and out + /// if (mediaplayer_edit_fading_) { ImGui::OpenPopup(LABEL_EDIT_FADING); mediaplayer_edit_fading_ = false; @@ -1824,11 +1892,14 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::EndPopup(); } + /// + /// Dialog to set gstreamer video effect + /// static std::string _effect_description = ""; static bool _effect_description_changed = false; if (mediaplayer_edit_pipeline_) { // open dialog - ImGui::OpenPopup("Gstreamer Video effect"); + ImGui::OpenPopup(DIALOG_GST_EFFECT); mediaplayer_edit_pipeline_ = false; // initialize dialog _effect_description = mediaplayer_active_->videoEffect(); @@ -1838,7 +1909,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) 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("Gstreamer Video effect", NULL, ImGuiWindowFlags_NoResize)) + if (ImGui::BeginPopupModal(DIALOG_GST_EFFECT, NULL, ImGuiWindowFlags_NoResize)) { const ImVec2 pos = ImGui::GetCursorPos(); const ImVec2 area = ImGui::GetContentRegionAvail(); @@ -1970,6 +2041,58 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } + + //// + /// Dialog to set timeline duration + /// + static double timeline_duration_ = 0.0; + if (mediaplayer_set_duration_) { + mediaplayer_set_duration_ = false; + // open dialog + if (mediaplayer_active_) { + // get current duration of mediaplayer + GstClockTime end = mediaplayer_active_->timeline()->end(); + timeline_duration_ = (double) ( GST_TIME_AS_MSECONDS(end) ) / 1000.f; + // open dialog to change duration + ImGui::OpenPopup(DIALOG_TIMELINE_DURATION); + } + } + const ImVec2 tld_dialog_size(buttons_width_ * 2.f, buttons_height_ * 4); + ImGui::SetNextWindowSize(tld_dialog_size, ImGuiCond_Always); + const ImVec2 tld_dialog_pos = top + rendersize * 0.5f - tld_dialog_size * 0.5f; + ImGui::SetNextWindowPos(tld_dialog_pos, ImGuiCond_Always); + if (ImGui::BeginPopupModal(DIALOG_TIMELINE_DURATION, NULL, ImGuiWindowFlags_NoResize)) + { + const ImVec2 pos = ImGui::GetCursorPos(); + const ImVec2 area = ImGui::GetContentRegionAvail(); + + ImGui::Spacing(); + ImGui::Text("Set the duration of the timeline"); + ImGui::Spacing(); + + // get current timeline + Timeline tl = *mediaplayer_active_->timeline(); + ImGui::InputDouble("second", &timeline_duration_, 1.0f, 10.0f, "%.2f"); + timeline_duration_ = ABS(timeline_duration_); + + 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) ) { + // change timeline end + mediaplayer_active_->timeline()->setEnd( GST_MSECOND * (GstClockTime) ( timeline_duration_ * 1000.f ) ); + // close dialog + close = true; + } + ImGui::PopStyleColor(1); + if (close) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } } void SourceControlWindow::DrawButtonBar(ImVec2 bottom, float width) diff --git a/src/SourceControlWindow.h b/src/SourceControlWindow.h index 0746e65..1d004de 100644 --- a/src/SourceControlWindow.h +++ b/src/SourceControlWindow.h @@ -48,6 +48,7 @@ class SourceControlWindow : public WorkspaceWindow // Render a single media player MediaPlayer *mediaplayer_active_; bool mediaplayer_edit_fading_; + bool mediaplayer_set_duration_; bool mediaplayer_edit_pipeline_; bool mediaplayer_mode_; bool mediaplayer_slider_pressed_; diff --git a/src/Stream.h b/src/Stream.h index 591ad22..4035586 100644 --- a/src/Stream.h +++ b/src/Stream.h @@ -89,7 +89,7 @@ public: * */ bool enabled() const; /** - * True if its an image + * True if it has only one frame * */ bool singleFrame() const; /** diff --git a/src/defines.h b/src/defines.h index a183ccc..c654ac2 100644 --- a/src/defines.h +++ b/src/defines.h @@ -216,6 +216,9 @@ #define LABEL_AUTO_MEDIA_PLAYER ICON_FA_USER_CIRCLE " User selection" #define LABEL_STORE_SELECTION " Create batch" #define LABEL_EDIT_FADING ICON_FA_RANDOM " Fade in & out" -#define LABEL_VIDEO_SEQUENCE " Encode an image sequence" +#define LABEL_VIDEO_SEQUENCE " Encode an image sequence" +#define LABEL_ADD_TIMELINE "Add timeline" +#define DIALOG_TIMELINE_DURATION ICON_FA_HOURGLASS_HALF " Set timeline duration" +#define DIALOG_GST_EFFECT "Gstreamer Video effect" #endif // VMIX_DEFINES_H