diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bd9b95..fed2470 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -209,6 +209,7 @@ set(VMIX_SRCS View.cpp Source.cpp Session.cpp + SessionSource.cpp SessionVisitor.cpp GarbageVisitor.cpp SessionCreator.cpp @@ -218,6 +219,7 @@ set(VMIX_SRCS Resource.cpp FileDialog.cpp MediaPlayer.cpp + MediaSource.cpp FrameBuffer.cpp RenderingManager.cpp UserInterfaceManager.cpp diff --git a/MediaSource.cpp b/MediaSource.cpp new file mode 100644 index 0000000..ab8f214 --- /dev/null +++ b/MediaSource.cpp @@ -0,0 +1,155 @@ +#include + +#include "MediaSource.h" + +#include "defines.h" +#include "ImageShader.h" +#include "ImageProcessingShader.h" +#include "Resource.h" +#include "Primitives.h" +#include "MediaPlayer.h" +#include "Visitor.h" +#include "Log.h" + +MediaSource::MediaSource() : Source(), path_("") +{ + // create media player + mediaplayer_ = new MediaPlayer; + + // create media surface: + // - textured with original texture from media player + // - crop & repeat UV can be managed here + // - additional custom shader can be associated + mediasurface_ = new Surface(rendershader_); + +} + +MediaSource::~MediaSource() +{ + // TODO delete media surface with visitor + delete mediasurface_; + + delete mediaplayer_; + // TODO verify that all surfaces and node is deleted in Source destructor +} + +void MediaSource::setPath(const std::string &p) +{ + path_ = p; + mediaplayer_->open(path_); + mediaplayer_->play(true); + + Log::Notify("Opening %s", p.c_str()); +} + +std::string MediaSource::path() const +{ + return path_; +} + +MediaPlayer *MediaSource::mediaplayer() const +{ + return mediaplayer_; +} + +bool MediaSource::failed() const +{ + return mediaplayer_->failed(); +} + +void MediaSource::init() +{ + if ( mediaplayer_->isOpen() ) { + + // update video + mediaplayer_->update(); + + // once the texture of media player is created + if (mediaplayer_->texture() != Resource::getTextureBlack()) { + + // get the texture index from media player, apply it to the media surface + mediasurface_->setTextureIndex( mediaplayer_->texture() ); + + // create Frame buffer matching size of media player + float height = float(mediaplayer()->width()) / mediaplayer()->aspectRatio(); + renderbuffer_ = new FrameBuffer(mediaplayer()->width(), (uint)height); + + // create the surfaces to draw the frame buffer in the views + // TODO Provide the source custom effect shader + rendersurface_ = new FrameBufferSurface(renderbuffer_, blendingshader_); + groups_[View::RENDERING]->attach(rendersurface_); + groups_[View::GEOMETRY]->attach(rendersurface_); + groups_[View::MIXING]->attach(rendersurface_); + groups_[View::LAYER]->attach(rendersurface_); + + // for mixing view, add another surface to overlay (for stippled view in transparency) + Surface *surfacemix = new FrameBufferSurface(renderbuffer_); + ImageShader *is = static_cast(surfacemix->shader()); + if (is) is->stipple = 1.0; + groups_[View::MIXING]->attach(surfacemix); + groups_[View::LAYER]->attach(surfacemix); + if (mediaplayer_->duration() == GST_CLOCK_TIME_NONE) + overlays_[View::MIXING]->attach( new Mesh("mesh/icon_image.ply") ); + else + overlays_[View::MIXING]->attach( new Mesh("mesh/icon_video.ply") ); + + // scale all mixing nodes to match aspect ratio of the media + for (NodeSet::iterator node = groups_[View::MIXING]->begin(); + node != groups_[View::MIXING]->end(); node++) { + (*node)->scale_.x = mediaplayer_->aspectRatio(); + } + for (NodeSet::iterator node = groups_[View::GEOMETRY]->begin(); + node != groups_[View::GEOMETRY]->end(); node++) { + (*node)->scale_.x = mediaplayer_->aspectRatio(); + } + for (NodeSet::iterator node = groups_[View::LAYER]->begin(); + node != groups_[View::LAYER]->end(); node++) { + (*node)->scale_.x = mediaplayer_->aspectRatio(); + } + + // done init once and for all + initialized_ = true; + // make visible + groups_[View::RENDERING]->visible_ = true; + groups_[View::MIXING]->visible_ = true; + groups_[View::GEOMETRY]->visible_ = true; + groups_[View::LAYER]->visible_ = true; + } + } + +} + +void MediaSource::render() +{ + if (!initialized_) + init(); + else { + // update video + mediaplayer_->update(); + + // render the media player into frame buffer + static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f); + renderbuffer_->begin(); + mediasurface_->draw(glm::identity(), projection); + renderbuffer_->end(); + } +} + +FrameBuffer *MediaSource::frame() const +{ + if (initialized_ && renderbuffer_) + { + return renderbuffer_; + } + else { + static FrameBuffer *black = new FrameBuffer(640,480); + return black; + } + +} + +void MediaSource::accept(Visitor& v) +{ + Source::accept(v); + v.visit(*this); +} diff --git a/MediaSource.h b/MediaSource.h new file mode 100644 index 0000000..9553c1d --- /dev/null +++ b/MediaSource.h @@ -0,0 +1,32 @@ +#ifndef MEDIASOURCE_H +#define MEDIASOURCE_H + +#include "Source.h" + +class MediaSource : public Source +{ +public: + MediaSource(); + ~MediaSource(); + + // implementation of source API + FrameBuffer *frame() const override; + void render() override; + void accept (Visitor& v) override; + bool failed() const override; + + // Media specific interface + void setPath(const std::string &p); + std::string path() const; + MediaPlayer *mediaplayer() const; + +protected: + + void init() override; + + Surface *mediasurface_; + std::string path_; + MediaPlayer *mediaplayer_; +}; + +#endif // MEDIASOURCE_H diff --git a/Mixer.cpp b/Mixer.cpp index 530db8d..c2b9895 100644 --- a/Mixer.cpp +++ b/Mixer.cpp @@ -1,52 +1,54 @@ #include #include +#include +#include +#include #include #include "tinyxml2Toolkit.h" using namespace tinyxml2; #include "defines.h" +#include "Settings.h" #include "Log.h" #include "View.h" -#include "Primitives.h" -#include "Mesh.h" -#include "FrameBuffer.h" -#include "Settings.h" #include "SystemToolkit.h" -#include "ImageProcessingShader.h" -#include "GarbageVisitor.h" +//#include "GarbageVisitor.h" #include "SessionVisitor.h" #include "SessionCreator.h" -#include "MediaPlayer.h" +#include "SessionSource.h" +#include "MediaSource.h" #include "Mixer.h" // static objects for multithreaded session loading static std::atomic sessionThreadActive_ = false; static std::string sessionThreadFilename_ = ""; - static std::atomic sessionLoadFinished_ = false; + static void loadSession(const std::string& filename, Session *session) { - sessionThreadActive_ = true; - sessionLoadFinished_ = false; - sessionThreadFilename_ = ""; + while (sessionThreadActive_) + std::this_thread::sleep_for( std::chrono::milliseconds(50)); + sessionThreadActive_ = true; + sessionLoadFinished_ = false; + sessionThreadFilename_ = ""; - // actual loading of xml file - SessionCreator creator( session ); + // actual loading of xml file + SessionCreator creator( session ); - if (!creator.load(filename)) { - // error loading - Log::Notify("Failed to load Session file %s.", filename.c_str()); - } - else { - // loaded ok - Log::Notify("Session %s loaded. %d source(s) created.", filename.c_str(), session->numSource()); - } + if (!creator.load(filename)) { + // error loading + Log::Warning("Failed to load Session file %s.", filename.c_str()); + } + else { + // loaded ok + Log::Notify("Session %s loaded. %d source(s) created.", filename.c_str(), session->numSource()); + } - sessionThreadFilename_ = filename; - sessionLoadFinished_ = true; - sessionThreadActive_ = false; + sessionThreadFilename_ = filename; + sessionLoadFinished_ = true; + sessionThreadActive_ = false; } // static objects for multithreaded session saving @@ -54,6 +56,8 @@ static std::atomic sessionSaveFinished_ = false; static void saveSession(const std::string& filename, Session *session) { // reset + while (sessionThreadActive_) + std::this_thread::sleep_for( std::chrono::milliseconds(50)); sessionThreadActive_ = true; sessionSaveFinished_ = false; sessionThreadFilename_ = ""; @@ -175,22 +179,44 @@ void Mixer::draw() } // manangement of sources -void Mixer::createSourceMedia(std::string path) +void Mixer::createSourceFile(std::string path) { - // create source - MediaSource *m = new MediaSource(); - m->setPath(path); + // sanity check + if ( !SystemToolkit::file_exists( path ) ) { + Log::Notify("File %s does not exist.", path); + return; + } + // ready to create a source + Source *s = nullptr; + + // test type of file by extension + std::string ext = SystemToolkit::extension_filename(path); + if ( ext == "vmx" ) + { + // create a session source + SessionSource *ss = new SessionSource(); + ss->setPath(path); + s = ss; + } + else { + // (try to) create media source by default + MediaSource *ms = new MediaSource(); + ms->setPath(path); + s = ms; + } // remember in recent media - Settings::application.recentMedia.push(path); + Settings::application.recentImport.push(path); + Settings::application.recentImport.path = SystemToolkit::path_filename(path); // propose a new name based on uri - renameSource(m, SystemToolkit::base_filename(path)); + renameSource(s, SystemToolkit::base_filename(path)); // add to mixer - insertSource(m); + insertSource(s); } + void Mixer::insertSource(Source *s) { // Add source to Session and set it as current @@ -353,18 +379,12 @@ View *Mixer::currentView() void Mixer::save() { - if (sessionThreadActive_) - return; - if (!sessionFilename_.empty()) saveas(sessionFilename_); } void Mixer::saveas(const std::string& filename) { - if (sessionThreadActive_) - return; - // optional copy of views config session_->config(View::MIXING)->copyTransform( mixing_.scene.root() ); session_->config(View::GEOMETRY)->copyTransform( geometry_.scene.root() ); @@ -376,9 +396,6 @@ void Mixer::saveas(const std::string& filename) void Mixer::open(const std::string& filename) { - if (sessionThreadActive_) - return; - if (back_session_) delete back_session_; diff --git a/Mixer.h b/Mixer.h index a71ca49..5c84bf3 100644 --- a/Mixer.h +++ b/Mixer.h @@ -1,7 +1,6 @@ #ifndef MIXER_H #define MIXER_H -#include // GStreamer #include @@ -11,6 +10,7 @@ #include "Session.h" #include "Source.h" + class Mixer { // Private Constructor @@ -34,7 +34,7 @@ public: void draw(); // manangement of sources - void createSourceMedia(std::string path); + void createSourceFile(std::string path); void deleteSource(Source *s); void deleteCurrentSource(); diff --git a/RenderingManager.cpp b/RenderingManager.cpp index 19bda29..76ed2db 100644 --- a/RenderingManager.cpp +++ b/RenderingManager.cpp @@ -438,18 +438,8 @@ void Rendering::FileDropped(GLFWwindow *, int path_count, const char* paths[]) std::string filename(paths[i]); if (filename.empty()) break; - std::string ext = SystemToolkit::extension_filename(filename); -// Log::Info("Dropped file %s %s\n", filename.c_str(), ext.c_str()); - if ( ext == "vmx" ) - { - // try to load a session - Mixer::manager().open(filename); - } - else { - // try to create a media source - Mixer::manager().createSourceMedia( filename ); - } - + // try to create a source + Mixer::manager().createSourceFile( filename ); } } diff --git a/SessionCreator.cpp b/SessionCreator.cpp index cbefd8d..30cb870 100644 --- a/SessionCreator.cpp +++ b/SessionCreator.cpp @@ -6,6 +6,7 @@ #include "Primitives.h" #include "Mesh.h" #include "Source.h" +#include "MediaSource.h" #include "Session.h" #include "ImageShader.h" #include "ImageProcessingShader.h" diff --git a/SessionSource.cpp b/SessionSource.cpp new file mode 100644 index 0000000..480ab17 --- /dev/null +++ b/SessionSource.cpp @@ -0,0 +1,176 @@ +#include +#include +#include + +#include "SessionSource.h" + +#include "defines.h" +#include "Log.h" +#include "FrameBuffer.h" +#include "ImageShader.h" +#include "ImageProcessingShader.h" +#include "Resource.h" +#include "Primitives.h" +#include "Mesh.h" +#include "SearchVisitor.h" +#include "Session.h" +#include "SessionCreator.h" + + +void SessionSource::loadSession(const std::string& filename, SessionSource *source) +{ + source->loadFinished_ = false; + + // actual loading of xml file + SessionCreator creator( source->session_ ); + + if (!creator.load(filename)) { + // error loading + Log::Notify("Failed to load Session file %s.", filename.c_str()); + source->loadFailed_ = true; + } + + source->loadFinished_ = true; +} + +SessionSource::SessionSource() : Source(), path_("") +{ + loadFailed_ = false; + loadFinished_ = false; + + session_ = new Session; + + // create surface: + // - textured with original texture from session + // - crop & repeat UV can be managed here + // - additional custom shader can be associated + sessionsurface_ = new Surface(rendershader_); +} + +SessionSource::~SessionSource() +{ + // TODO delete media surface with visitor + delete sessionsurface_; + + // TODO close and delete session + delete session_; +} + +void SessionSource::setPath(const std::string &p) +{ + path_ = p; + + // launch a thread to load the session + std::thread ( SessionSource::loadSession, path_, this).detach(); + + Log::Notify("Opening %s", p.c_str()); +} + +std::string SessionSource::path() const +{ + return path_; +} + +Session *SessionSource::session() const +{ + return session_; +} + +bool SessionSource::failed() const +{ + return loadFailed_; +} + +void SessionSource::init() +{ + if ( loadFinished_ && !loadFailed_ ) { + loadFinished_ = false; + + // set resolution + session_->setResolution( session_->config(View::RENDERING)->scale_ ); + + // get the texture index from media player, apply it to the media surface + sessionsurface_->setTextureIndex( session_->frame()->texture() ); + + // create Frame buffer matching size of media player + renderbuffer_ = new FrameBuffer(session_->frame()->resolution()); + + // create the surfaces to draw the frame buffer in the views + rendersurface_ = new FrameBufferSurface(renderbuffer_, blendingshader_); + groups_[View::RENDERING]->attach(rendersurface_); + groups_[View::GEOMETRY]->attach(rendersurface_); + groups_[View::MIXING]->attach(rendersurface_); + groups_[View::LAYER]->attach(rendersurface_); + + // for mixing view, add another surface to overlay (for stippled view in transparency) + Surface *surfacemix = new FrameBufferSurface(renderbuffer_); + ImageShader *is = static_cast(surfacemix->shader()); + if (is) is->stipple = 1.0; + groups_[View::MIXING]->attach(surfacemix); + groups_[View::LAYER]->attach(surfacemix); + // TODO icon session +// overlays_[View::MIXING]->attach( new Mesh("mesh/icon_video.ply") ); + + // scale all mixing nodes to match aspect ratio of the media + for (NodeSet::iterator node = groups_[View::MIXING]->begin(); + node != groups_[View::MIXING]->end(); node++) { + (*node)->scale_.x = session_->frame()->aspectRatio(); + } + for (NodeSet::iterator node = groups_[View::GEOMETRY]->begin(); + node != groups_[View::GEOMETRY]->end(); node++) { + (*node)->scale_.x = session_->frame()->aspectRatio(); + } + for (NodeSet::iterator node = groups_[View::LAYER]->begin(); + node != groups_[View::LAYER]->end(); node++) { + (*node)->scale_.x = session_->frame()->aspectRatio(); + } + + // done init once and for all + initialized_ = true; + + // make visible + groups_[View::RENDERING]->visible_ = true; + groups_[View::MIXING]->visible_ = true; + groups_[View::GEOMETRY]->visible_ = true; + groups_[View::LAYER]->visible_ = true; + + Log::Info("Source Session %s loading %d sources.", path_.c_str(), session_->numSource()); + } +} + +void SessionSource::render() +{ + if (!initialized_) + init(); + else { + // update session + session_->update(dt_); + + sessionsurface_->setTextureIndex( session_->frame()->texture() ); + + // render the media player into frame buffer + static glm::mat4 projection = glm::ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f); + renderbuffer_->begin(); + sessionsurface_->draw(glm::identity(), projection); + renderbuffer_->end(); + } +} + +FrameBuffer *SessionSource::frame() const +{ + if (initialized_ && renderbuffer_) + { + return renderbuffer_; + } + else { + static FrameBuffer *black = new FrameBuffer(640,480); + return black; + } +} + +void SessionSource::accept(Visitor& v) +{ + Source::accept(v); + v.visit(*this); +} + diff --git a/SessionSource.h b/SessionSource.h new file mode 100644 index 0000000..476d226 --- /dev/null +++ b/SessionSource.h @@ -0,0 +1,36 @@ +#ifndef SESSIONSOURCE_H +#define SESSIONSOURCE_H + +#include +#include "Source.h" + +class SessionSource : public Source +{ +public: + SessionSource(); + ~SessionSource(); + + // implementation of source API + FrameBuffer *frame() const override; + void render() override; + void accept (Visitor& v) override; + bool failed() const override; + + // Media specific interface + void setPath(const std::string &p); + std::string path() const; + Session *session() const; + +protected: + + void init() override; + static void loadSession(const std::string& filename, SessionSource *source); + + Surface *sessionsurface_; + std::string path_; + Session *session_; + + std::atomic loadFailed_; + std::atomic loadFinished_; +}; +#endif // SESSIONSOURCE_H diff --git a/SessionVisitor.cpp b/SessionVisitor.cpp index 4fe74c1..fe1ff05 100644 --- a/SessionVisitor.cpp +++ b/SessionVisitor.cpp @@ -5,6 +5,7 @@ #include "Primitives.h" #include "Mesh.h" #include "Source.h" +#include "MediaSource.h" #include "ImageShader.h" #include "ImageProcessingShader.h" #include "MediaPlayer.h" diff --git a/Settings.cpp b/Settings.cpp index 17c640e..9d394fd 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -108,10 +108,10 @@ void Settings::Save() }; recent->InsertEndChild(recentsession); - XMLElement *recentmedia = xmlDoc.NewElement( "Media" ); - recentmedia->SetAttribute("path", application.recentMedia.path.c_str()); - for(auto it = application.recentMedia.filenames.begin(); - it != application.recentMedia.filenames.end(); it++) { + XMLElement *recentmedia = xmlDoc.NewElement( "Import" ); + recentmedia->SetAttribute("path", application.recentImport.path.c_str()); + for(auto it = application.recentImport.filenames.begin(); + it != application.recentImport.filenames.end(); it++) { XMLElement *fileNode = xmlDoc.NewElement("path"); XMLText *text = xmlDoc.NewText( (*it).c_str() ); @@ -245,21 +245,21 @@ void Settings::Load() } } // recent media uri - XMLElement * pMedia = pElement->FirstChildElement("Media"); - if (pMedia) + XMLElement * pImport = pElement->FirstChildElement("Import"); + if (pImport) { - const char *path_ = pMedia->Attribute("path"); + const char *path_ = pImport->Attribute("path"); if (path_) - application.recentMedia.path = std::string(path_); + application.recentImport.path = std::string(path_); else - application.recentMedia.path = SystemToolkit::home_path(); - application.recentMedia.filenames.clear(); - XMLElement* path = pMedia->FirstChildElement("path"); + application.recentImport.path = SystemToolkit::home_path(); + application.recentImport.filenames.clear(); + XMLElement* path = pImport->FirstChildElement("path"); for( ; path ; path = path->NextSiblingElement()) { const char *p = path->GetText(); if (p) - application.recentMedia.push( std::string (p) ); + application.recentImport.push( std::string (p) ); } } } diff --git a/Settings.h b/Settings.h index 4c4e0ca..01be91f 100644 --- a/Settings.h +++ b/Settings.h @@ -82,7 +82,7 @@ struct Application // recent files histories History recentSessions; - History recentMedia; + History recentImport; Application() : name(APP_NAME){ scale = 1.f; diff --git a/Source.cpp b/Source.cpp index 0e5facc..9d935ca 100644 --- a/Source.cpp +++ b/Source.cpp @@ -6,14 +6,11 @@ #include "defines.h" #include "FrameBuffer.h" -#include "ImageShader.h" -#include "ImageProcessingShader.h" -#include "Resource.h" #include "Primitives.h" #include "Mesh.h" -#include "MediaPlayer.h" #include "SearchVisitor.h" - +#include "ImageShader.h" +#include "ImageProcessingShader.h" #include "Log.h" Source::Source() : initialized_(false), need_update_(true) @@ -32,13 +29,18 @@ Source::Source() : initialized_(false), need_update_(true) groups_[View::MIXING]->visible_ = false; Frame *frame = new Frame(Frame::ROUND_THIN); frame->translation_.z = 0.1; - frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.9f); + frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.7f); groups_[View::MIXING]->attach(frame); groups_[View::MIXING]->scale_ = glm::vec3(0.15f, 0.15f, 1.f); + groups_[View::MIXING]->translation_ = glm::vec3(-1.f, 1.f, 0.f); overlays_[View::MIXING] = new Group; overlays_[View::MIXING]->translation_.z = 0.1; overlays_[View::MIXING]->visible_ = false; + frame = new Frame(Frame::ROUND_LARGE); + frame->translation_.z = 0.1; + frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); + overlays_[View::MIXING]->attach(frame); groups_[View::MIXING]->attach(overlays_[View::MIXING]); // default geometry nodes @@ -46,12 +48,31 @@ Source::Source() : initialized_(false), need_update_(true) groups_[View::GEOMETRY]->visible_ = false; frame = new Frame(Frame::SHARP_THIN); frame->translation_.z = 0.1; - frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.9f); + frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.7f); groups_[View::GEOMETRY]->attach(frame); overlays_[View::GEOMETRY] = new Group; overlays_[View::GEOMETRY]->translation_.z = 0.15; overlays_[View::GEOMETRY]->visible_ = false; + frame = new Frame(Frame::SHARP_LARGE); + frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); + overlays_[View::GEOMETRY]->attach(frame); + resize_handle_ = new Handles(Handles::RESIZE); + resize_handle_->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); + resize_handle_->translation_.z = 0.1; + overlays_[View::GEOMETRY]->attach(resize_handle_); + resize_H_handle_ = new Handles(Handles::RESIZE_H); + resize_H_handle_->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); + resize_H_handle_->translation_.z = 0.1; + overlays_[View::GEOMETRY]->attach(resize_H_handle_); + resize_V_handle_ = new Handles(Handles::RESIZE_V); + resize_V_handle_->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); + resize_V_handle_->translation_.z = 0.1; + overlays_[View::GEOMETRY]->attach(resize_V_handle_); + rotate_handle_ = new Handles(Handles::ROTATE); + rotate_handle_->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); + rotate_handle_->translation_.z = 0.1; + overlays_[View::GEOMETRY]->attach(rotate_handle_); groups_[View::GEOMETRY]->attach(overlays_[View::GEOMETRY]); // default mixing nodes @@ -59,7 +80,7 @@ Source::Source() : initialized_(false), need_update_(true) groups_[View::LAYER]->visible_ = false; frame = new Frame(Frame::ROUND_SHADOW); frame->translation_.z = 0.1; - frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.9f); + frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.8f); groups_[View::LAYER]->attach(frame); overlays_[View::LAYER] = new Group; @@ -72,10 +93,6 @@ Source::Source() : initialized_(false), need_update_(true) rendershader_ = new ImageProcessingShader; renderbuffer_ = nullptr; rendersurface_ = nullptr; - resize_handle_ = nullptr; - resize_H_handle_ = nullptr; - resize_V_handle_ = nullptr; - rotate_handle_ = nullptr; } Source::~Source() @@ -117,6 +134,10 @@ void Source::setOverlayVisible(bool on) void Source::update(float dt) { + // keep delta-t + dt_ = dt; + + // update nodes if needed if (need_update_) { // ADJUST alpha based on MIXING node @@ -173,175 +194,4 @@ bool hasNode::operator()(const Source* elem) const return false; } -MediaSource::MediaSource() : Source(), path_("") -{ - // create media player - mediaplayer_ = new MediaPlayer; - // create media surface: - // - textured with original texture from media player - // - crop & repeat UV can be managed here - // - additional custom shader can be associated - mediasurface_ = new Surface(rendershader_); - - // extra overlay for mixing view - Frame *frame = new Frame(Frame::ROUND_LARGE); - frame->translation_.z = 0.1; - frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); - overlays_[View::MIXING]->attach(frame); - - // extra overlays for geometry view - frame = new Frame(Frame::SHARP_LARGE); - frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); - overlays_[View::GEOMETRY]->attach(frame); - resize_handle_ = new Handles(Handles::RESIZE); - resize_handle_->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); - resize_handle_->translation_.z = 0.1; - overlays_[View::GEOMETRY]->attach(resize_handle_); - resize_H_handle_ = new Handles(Handles::RESIZE_H); - resize_H_handle_->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); - resize_H_handle_->translation_.z = 0.1; - overlays_[View::GEOMETRY]->attach(resize_H_handle_); - resize_V_handle_ = new Handles(Handles::RESIZE_V); - resize_V_handle_->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); - resize_V_handle_->translation_.z = 0.1; - overlays_[View::GEOMETRY]->attach(resize_V_handle_); - rotate_handle_ = new Handles(Handles::ROTATE); - rotate_handle_->color = glm::vec4( COLOR_DEFAULT_SOURCE, 1.f); - rotate_handle_->translation_.z = 0.1; - overlays_[View::GEOMETRY]->attach(rotate_handle_); - -} - -MediaSource::~MediaSource() -{ - // TODO delete media surface with visitor - delete mediasurface_; - - delete mediaplayer_; - // TODO verify that all surfaces and node is deleted in Source destructor -} - -void MediaSource::setPath(const std::string &p) -{ - path_ = p; - mediaplayer_->open(path_); - mediaplayer_->play(true); - - Log::Notify("Opening %s", p.c_str()); -} - -std::string MediaSource::path() const -{ - return path_; -} - -MediaPlayer *MediaSource::mediaplayer() const -{ - return mediaplayer_; -} - -bool MediaSource::failed() const -{ - return mediaplayer_->failed(); -} - -void MediaSource::init() -{ - if ( mediaplayer_->isOpen() ) { - - // update video - mediaplayer_->update(); - - // once the texture of media player is created - if (mediaplayer_->texture() != Resource::getTextureBlack()) { - - // get the texture index from media player, apply it to the media surface - mediasurface_->setTextureIndex( mediaplayer_->texture() ); - - // create Frame buffer matching size of media player - float height = float(mediaplayer()->width()) / mediaplayer()->aspectRatio(); - renderbuffer_ = new FrameBuffer(mediaplayer()->width(), (uint)height); - -// // setup shader resolution for texture 0 -// rendershader_->iChannelResolution[0] = glm::vec3(mediaplayer()->width(), mediaplayer()->height(), 0.f); - - // create the surfaces to draw the frame buffer in the views - // TODO Provide the source custom effect shader - rendersurface_ = new FrameBufferSurface(renderbuffer_, blendingshader_); - groups_[View::RENDERING]->attach(rendersurface_); - groups_[View::GEOMETRY]->attach(rendersurface_); - groups_[View::MIXING]->attach(rendersurface_); - groups_[View::LAYER]->attach(rendersurface_); - - // for mixing view, add another surface to overlay (for stippled view in transparency) - Surface *surfacemix = new FrameBufferSurface(renderbuffer_); - ImageShader *is = static_cast(surfacemix->shader()); - if (is) is->stipple = 1.0; - groups_[View::MIXING]->attach(surfacemix); - groups_[View::LAYER]->attach(surfacemix); - if (mediaplayer_->duration() == GST_CLOCK_TIME_NONE) - overlays_[View::MIXING]->attach( new Mesh("mesh/icon_image.ply") ); - else - overlays_[View::MIXING]->attach( new Mesh("mesh/icon_video.ply") ); - - // scale all mixing nodes to match aspect ratio of the media - for (NodeSet::iterator node = groups_[View::MIXING]->begin(); - node != groups_[View::MIXING]->end(); node++) { - (*node)->scale_.x = mediaplayer_->aspectRatio(); - } - for (NodeSet::iterator node = groups_[View::GEOMETRY]->begin(); - node != groups_[View::GEOMETRY]->end(); node++) { - (*node)->scale_.x = mediaplayer_->aspectRatio(); - } - for (NodeSet::iterator node = groups_[View::LAYER]->begin(); - node != groups_[View::LAYER]->end(); node++) { - (*node)->scale_.x = mediaplayer_->aspectRatio(); - } - - // done init once and for all - initialized_ = true; - // make visible - groups_[View::RENDERING]->visible_ = true; - groups_[View::MIXING]->visible_ = true; - groups_[View::GEOMETRY]->visible_ = true; - groups_[View::LAYER]->visible_ = true; - } - } - -} - -void MediaSource::render() -{ - if (!initialized_) - init(); - else { - // update video - mediaplayer_->update(); - - // render the media player into frame buffer - static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f); - renderbuffer_->begin(); - mediasurface_->draw(glm::identity(), projection); - renderbuffer_->end(); - } -} - -FrameBuffer *MediaSource::frame() const -{ - if (initialized_ && renderbuffer_) - { - return renderbuffer_; - } - else { - static FrameBuffer *black = new FrameBuffer(640,480); - return black; - } - -} - -void MediaSource::accept(Visitor& v) -{ - Source::accept(v); - v.visit(*this); -} diff --git a/Source.h b/Source.h index d4beedb..1e592eb 100644 --- a/Source.h +++ b/Source.h @@ -14,6 +14,7 @@ class FrameBuffer; class FrameBufferSurface; class MediaPlayer; class Surface; +class Session; class Frame; class Source @@ -43,12 +44,12 @@ public: // a Source has a shader to control mixing effects inline ImageShader *blendingShader() const { return blendingshader_; } - // a Source shall be updated before displayed (Mixing, Geometry and Layer) - void update (float dt); - // touch to request update inline void touch() { need_update_ = true; } + // a Source shall be updated before displayed (Mixing, Geometry and Layer) + void update (float dt); + // accept all kind of visitors virtual void accept (Visitor& v); @@ -95,6 +96,7 @@ protected: // update need bool need_update_; + float dt_; }; // TODO SourceSet sorted by shader @@ -122,32 +124,5 @@ private: Node *_n; }; -class MediaSource : public Source -{ -public: - MediaSource(); - ~MediaSource(); - - // implementation of source API - FrameBuffer *frame() const override; - void render() override; - void accept (Visitor& v) override; - bool failed() const override; - - // Media specific interface - void setPath(const std::string &p); - std::string path() const; - MediaPlayer *mediaplayer() const; - -protected: - - void init() override; - - Surface *mediasurface_; - std::string path_; - MediaPlayer *mediaplayer_; -}; - -// TODO dedicated source .cpp .h files for MediaSource #endif // SOURCE_H diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 1ea7ae1..7373da3 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -44,6 +44,7 @@ #include "Mixer.h" #include "FrameBuffer.h" #include "MediaPlayer.h" +#include "MediaSource.h" #include "PickingVisitor.h" #include "ImageShader.h" #include "ImageProcessingShader.h" @@ -58,14 +59,14 @@ void ShowAbout(bool* p_open); // static objects for multithreaded file dialog static std::atomic FileDialogPending_ = false; -static std::atomic FileDialogLoadFinished_ = false; -static std::atomic FileDialogSaveFinished_ = false; -static std::string FileDialogFilename_ = ""; +static std::atomic SessionFileDialogLoadFinished_ = false; +static std::atomic SessionFileDialogSaveFinished_ = false; +static std::string SessionFileDialogFilename_ = ""; -static void FileDialogOpen(std::string path) +static void SessionFileDialogOpen(std::string path) { FileDialogPending_ = true; - FileDialogLoadFinished_ = false; + SessionFileDialogLoadFinished_ = false; char const * open_file_name; char const * open_pattern[1] = { "*.vmx" }; @@ -73,17 +74,17 @@ static void FileDialogOpen(std::string path) open_file_name = tinyfd_openFileDialog( "Open a session file", path.c_str(), 1, open_pattern, "vimix session", 0); if (!open_file_name) - FileDialogFilename_ = ""; + SessionFileDialogFilename_ = ""; else - FileDialogFilename_ = std::string( open_file_name ); + SessionFileDialogFilename_ = std::string( open_file_name ); - FileDialogLoadFinished_ = true; + SessionFileDialogLoadFinished_ = true; } -static void FileDialogSave(std::string path) +static void SessionFileDialogSave(std::string path) { FileDialogPending_ = true; - FileDialogSaveFinished_ = false; + SessionFileDialogSaveFinished_ = false; char const * save_file_name; char const * save_pattern[1] = { "*.vmx" }; @@ -91,37 +92,32 @@ static void FileDialogSave(std::string path) save_file_name = tinyfd_saveFileDialog( "Save a session file", path.c_str(), 1, save_pattern, "vimix session"); if (!save_file_name) - FileDialogFilename_ = ""; + SessionFileDialogFilename_ = ""; else { - FileDialogFilename_ = std::string( save_file_name ); + SessionFileDialogFilename_ = std::string( save_file_name ); // check extension - std::string extension = FileDialogFilename_.substr(FileDialogFilename_.find_last_of(".") + 1); + std::string extension = SessionFileDialogFilename_.substr(SessionFileDialogFilename_.find_last_of(".") + 1); if (extension != "vmx") - FileDialogFilename_ += ".vmx"; + SessionFileDialogFilename_ += ".vmx"; } - FileDialogSaveFinished_ = true; + SessionFileDialogSaveFinished_ = true; } -static std::atomic MediaDialogFinished_ = false; -static std::string MediaDialogUri_ = ""; -static void MediaDialogOpen(std::string path) +static void ImportFileDialogOpen(char *filename, const std::string &path) { - FileDialogPending_ = true; - MediaDialogFinished_ = false; + if (FileDialogPending_) + return; + FileDialogPending_ = true; - char const * open_file_name; - char const * open_pattern[15] = { "*.mp4", "*.mpg", "*.avi", "*.mov", "*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg", "*.flv", "*.asf", "*.jpg", "*.png", "*.gif" }; + char const * open_pattern[17] = { "*vmx", "*.mp4", "*.mpg", "*.avi", "*.mov", "*.mkv", "*.webm", "*.mod", "*.wmv", "*.mxf", "*.ogg", "*.flv", "*.asf", "*.jpg", "*.png", "*.gif", "*.svg" }; + char const * open_file_name; - open_file_name = tinyfd_openFileDialog( "Open a Media file", path.c_str(), 15, open_pattern, "Video or image", 0); + open_file_name = tinyfd_openFileDialog( "Import a file", path.c_str(), 17, open_pattern, "All supported formats", 0); + sprintf(filename, "%s", open_file_name); - if (!open_file_name) - MediaDialogUri_ = ""; - else - MediaDialogUri_ = std::string( open_file_name ); - - MediaDialogFinished_ = true; + FileDialogPending_ = false; } UserInterface::UserInterface() @@ -211,7 +207,7 @@ void UserInterface::handleKeyboard() } else if (ImGui::IsKeyPressed( GLFW_KEY_O )) { // Open session - std::thread (FileDialogOpen, Settings::application.recentSessions.path).detach(); + std::thread (SessionFileDialogOpen, Settings::application.recentSessions.path).detach(); navigator.hidePannel(); } else if (ImGui::IsKeyPressed( GLFW_KEY_S )) { @@ -378,37 +374,29 @@ void UserInterface::NewFrame() handleMouse(); // handle FileDialog - if (FileDialogLoadFinished_) { - FileDialogLoadFinished_ = false; + if (SessionFileDialogLoadFinished_) { + SessionFileDialogLoadFinished_ = false; FileDialogPending_ = false; - if (!FileDialogFilename_.empty()) { - Mixer::manager().open(FileDialogFilename_); - Settings::application.recentSessions.path = SystemToolkit::path_filename(FileDialogFilename_); + if (!SessionFileDialogFilename_.empty()) { + Mixer::manager().open(SessionFileDialogFilename_); + Settings::application.recentSessions.path = SystemToolkit::path_filename(SessionFileDialogFilename_); } } - if (FileDialogSaveFinished_) { - FileDialogSaveFinished_ = false; + if (SessionFileDialogSaveFinished_) { + SessionFileDialogSaveFinished_ = false; FileDialogPending_ = false; - if (!FileDialogFilename_.empty()) { - Mixer::manager().saveas(FileDialogFilename_); - Settings::application.recentSessions.path = SystemToolkit::path_filename(FileDialogFilename_); - } - } - if (MediaDialogFinished_) { - MediaDialogFinished_ = false; - FileDialogPending_ = false; - if (!MediaDialogUri_.empty()) { - navigator.setMediaUri(MediaDialogUri_); - Settings::application.recentMedia.path = SystemToolkit::path_filename(MediaDialogUri_); + if (!SessionFileDialogFilename_.empty()) { + Mixer::manager().saveas(SessionFileDialogFilename_); + Settings::application.recentSessions.path = SystemToolkit::path_filename(SessionFileDialogFilename_); } } - // overlay when disabled + // overlay to ensure file dialog is modal if (FileDialogPending_){ ImGui::OpenPopup("Busy"); if (ImGui::BeginPopupModal("Busy", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Close dialog to resume..."); + ImGui::Text("Close file dialog box to resume."); ImGui::EndPopup(); } } @@ -480,7 +468,7 @@ void UserInterface::showMenuFile() { if (ImGui::MenuItem( ICON_FA_FILE_UPLOAD " Open", "Ctrl+O")) { // launch file dialog to open a session file - std::thread (FileDialogOpen, Settings::application.recentSessions.path).detach(); + std::thread (SessionFileDialogOpen, Settings::application.recentSessions.path).detach(); navigator.hidePannel(); } if (ImGui::MenuItem( ICON_FA_FILE_DOWNLOAD " Save", "Ctrl+S")) { @@ -488,7 +476,7 @@ void UserInterface::showMenuFile() navigator.hidePannel(); } if (ImGui::MenuItem( ICON_FA_FOLDER_OPEN " Save as")) { - std::thread (FileDialogSave, Settings::application.recentSessions.path).detach(); + std::thread (SessionFileDialogSave, Settings::application.recentSessions.path).detach(); navigator.hidePannel(); } @@ -1061,10 +1049,6 @@ void Navigator::RenderSourcePannel(Source *s) ImGui::End(); } -void Navigator::setMediaUri(std::string path) -{ - sprintf(media_path_, "%s", path.c_str()); -} void Navigator::RenderNewPannel() { @@ -1082,55 +1066,61 @@ void Navigator::RenderNewPannel() ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); static int new_source_type = 0; - ImGui::Combo("Type", &new_source_type, "Media\0Render\0Clone\0"); + ImGui::Combo("Origin", &new_source_type, "File\0Software\0Hardware\0"); + + // Media Source creation if (new_source_type == 0) { // helper ImGui::SetCursorPosX(pannel_width - 30 + IMGUI_RIGHT_ALIGN); - ImGuiToolkit::HelpMarker("A Media source displays an image or a video file."); - // browse folder + ImGuiToolkit::HelpMarker("Create a source from a file:\n- Video (*.mpg, *mov, *.avi, etc.)\n- Image (*.jpg, *.png, etc.)\n- Vector graphics (*.svg)\n- vimix session (*.vmx)\n\nEquivalent to dropping the file in the workspace."); + + // filename of the media to open + static char filename[2048]; + + // browse for a filename if (ImGuiToolkit::ButtonIcon(2, 5)) { - std::thread (MediaDialogOpen, Settings::application.recentMedia.path).detach(); + std::thread (ImportFileDialogOpen, filename, Settings::application.recentImport.path).detach(); } - // combo recent + // combo of recent media filenames ImGui::SameLine(0, 10); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("##RecentMedia", "Select recent")) + if (ImGui::BeginCombo("##RecentImport", "Select recent")) { - std::for_each(Settings::application.recentMedia.filenames.begin(), - Settings::application.recentMedia.filenames.end(), [](std::string& path) { + std::for_each(Settings::application.recentImport.filenames.begin(), + Settings::application.recentImport.filenames.end(), [](std::string& path) { int right = MIN( 40, path.size()); if (ImGui::Selectable( path.substr( path.size() - right ).c_str() )) { - UserInterface::manager().navigator.setMediaUri(path); + sprintf(filename, "%s", path.c_str()); } }); ImGui::EndCombo(); } - // uri text entry + // filename text entry - [Return] to validate ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::InputText("Path", media_path_, IM_ARRAYSIZE(media_path_), ImGuiInputTextFlags_EnterReturnsTrue) ) { - Mixer::manager().createSourceMedia(media_path_); + if (ImGui::InputText("Path", filename, IM_ARRAYSIZE(filename), ImGuiInputTextFlags_EnterReturnsTrue) ) { + Mixer::manager().createSourceFile( std::string(filename) ); selected_button[NAV_NEW] = false; } - - // Validate button + // or press Validate button ImGui::Text(" "); if ( ImGui::Button("Create !", ImVec2(pannel_width - padding_width, 0)) ) { - - if ( SystemToolkit::file_exists(media_path_) ) { - Mixer::manager().createSourceMedia(media_path_); - selected_button[NAV_NEW] = false; - } + Mixer::manager().createSourceFile( std::string(filename) ); + selected_button[NAV_NEW] = false; } } + // Render Source creator else if (new_source_type == 1){ + // helper ImGui::SetCursorPosX(pannel_width - 30 + IMGUI_RIGHT_ALIGN); - ImGuiToolkit::HelpMarker("A Render source replicates the rendering of the output."); + ImGuiToolkit::HelpMarker("Create a source from a software algorithm or from vimix objects."); + } + // Hardware else { // helper ImGui::SetCursorPosX(pannel_width - 30 + IMGUI_RIGHT_ALIGN); - ImGuiToolkit::HelpMarker("A Clone source duplicates the content of another source."); + ImGuiToolkit::HelpMarker("Create a source capturing images from an external hardware."); } } diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index 24ab27d..84205d7 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -33,8 +33,6 @@ class Navigator void RenderNewPannel(); void RenderMainPannel(); - char media_path_[1024]; - public: Navigator(); @@ -43,7 +41,6 @@ public: void showPannelSource(int index); void Render(); - void setMediaUri(std::string path); }; class ToolBox diff --git a/View.cpp b/View.cpp index 5ccf2df..641f236 100644 --- a/View.cpp +++ b/View.cpp @@ -304,6 +304,7 @@ void GeometryView::grab (glm::vec2 from, glm::vec2 to, Source *s, std::pairscale_ = start_scale * S_resize; + Log::Info(" resize ( %.1f, %.1f ) ", S_resize.x, S_resize.y); // glm::vec3 factor = S_resize * glm::vec3(0.5f, 0.5f, 1.f); //// glm::vec3 factor = S_resize * glm::vec3(1.f, 1.f, 1.f); //// factor *= glm::sign( glm::vec3(pick.second, 1.f) ); diff --git a/defines.h b/defines.h index 15b5e91..8a532d5 100644 --- a/defines.h +++ b/defines.h @@ -8,7 +8,7 @@ #define APP_VERSION_MINOR 1 #define XML_VERSION_MAJOR 0 #define XML_VERSION_MINOR 1 -#define MAX_RECENT_HISTORY 10 +#define MAX_RECENT_HISTORY 16 #define MINI(a, b) (((a) < (b)) ? (a) : (b)) #define MAXI(a, b) (((a) > (b)) ? (a) : (b))