From d8d4322b2e626fac7e32c69270f756e60c2d69d9 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Wed, 20 Jul 2022 23:47:22 +0200 Subject: [PATCH] Added option Recorder file naming style VideoRecorder and PNGRecorder now have setting to decide how to name the files, with date prefix or sequentially numbered. A base name is given with session filename. --- Recorder.cpp | 48 +++++++++++++-------- Recorder.h | 6 ++- Settings.cpp | 2 + Settings.h | 2 + SystemToolkit.cpp | 84 +++++++++++++++++++++++++++++++++++- SystemToolkit.h | 6 +++ UserInterfaceManager.cpp | 92 +++++++++++++++++++++++----------------- 7 files changed, 178 insertions(+), 62 deletions(-) diff --git a/Recorder.cpp b/Recorder.cpp index 3b8ed5d..94c920c 100644 --- a/Recorder.cpp +++ b/Recorder.cpp @@ -41,7 +41,7 @@ #include "Recorder.h" -PNGRecorder::PNGRecorder() : FrameGrabber() +PNGRecorder::PNGRecorder(const std::string &basename) : FrameGrabber(), basename_(basename) { } @@ -63,11 +63,13 @@ std::string PNGRecorder::init(GstCaps *caps) return msg; } - // verify location path (path is always terminated by the OS dependent separator) - std::string path = SystemToolkit::path_directory(Settings::application.record.path); - if (path.empty()) - path = SystemToolkit::home_path(); - filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".png"; + // construct filename: + // if sequencial file naming + if (Settings::application.record.naming_mode == 0 ) + filename_ = SystemToolkit::filename_sequential(Settings::application.record.path, basename_, "png"); + // or prefixed with date + else + filename_ = SystemToolkit::filename_dateprefix(Settings::application.record.path, basename_, "png"); // setup file sink g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")), @@ -303,7 +305,7 @@ const char* VideoRecorder::framerate_preset_name[3] = { "15 FPS", "25 FPS", " const gint VideoRecorder::framerate_preset_value[3] = { 15, 25, 30 }; -VideoRecorder::VideoRecorder() : FrameGrabber() +VideoRecorder::VideoRecorder(const std::string &basename) : FrameGrabber(), basename_(basename) { // first run initialization of hardware encoders in linux #if GST_GL_HAVE_PLATFORM_GLX @@ -350,24 +352,34 @@ std::string VideoRecorder::init(GstCaps *caps) else description += profile_description[Settings::application.record.profile]; - // verify location path (path is always terminated by the OS dependent separator) - std::string path = SystemToolkit::path_directory(Settings::application.record.path); - if (path.empty()) - path = SystemToolkit::home_path(); - - // setup filename & muxer + // setup muxer and prepare filename if( Settings::application.record.profile == JPEG_MULTI) { - std::string folder = path + "vimix_" + SystemToolkit::date_time_string(); - filename_ = SystemToolkit::full_filename(folder, "%05d.jpg"); - if (SystemToolkit::create_directory(folder)) + std::string folder = SystemToolkit::filename_dateprefix(Settings::application.record.path, basename_, ""); + if (SystemToolkit::create_directory(folder)) { + filename_ = SystemToolkit::full_filename(folder, "%05d.jpg"); description += "multifilesink name=sink"; + } + else + return std::string("Video Recording : Failed to create folder ") + folder; } else if( Settings::application.record.profile == VP8) { - filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".webm"; + // if sequencial file naming + if (Settings::application.record.naming_mode == 0 ) + filename_ = SystemToolkit::filename_sequential(Settings::application.record.path, basename_, "webm"); + // or prefixed with date + else + filename_ = SystemToolkit::filename_dateprefix(Settings::application.record.path, basename_, "webm"); + description += "webmmux ! filesink name=sink"; } else { - filename_ = path + "vimix_" + SystemToolkit::date_time_string() + ".mov"; + // if sequencial file naming + if (Settings::application.record.naming_mode == 0 ) + filename_ = SystemToolkit::filename_sequential(Settings::application.record.path, basename_, "mov"); + // or prefixed with date + else + filename_ = SystemToolkit::filename_dateprefix(Settings::application.record.path, basename_, "mov"); + description += "qtmux ! filesink name=sink"; } diff --git a/Recorder.h b/Recorder.h index 4f1b99e..c4e138a 100644 --- a/Recorder.h +++ b/Recorder.h @@ -12,11 +12,12 @@ class PNGRecorder : public FrameGrabber { + std::string basename_; std::string filename_; public: - PNGRecorder(); + PNGRecorder(const std::string &basename = std::string()); std::string filename() const { return filename_; } protected: @@ -29,6 +30,7 @@ protected: class VideoRecorder : public FrameGrabber { + std::string basename_; std::string filename_; std::string init(GstCaps *caps) override; @@ -57,7 +59,7 @@ public: static const char* framerate_preset_name[3]; static const int framerate_preset_value[3]; - VideoRecorder(); + VideoRecorder(const std::string &basename = std::string()); std::string info() const override; std::string filename() const { return filename_; } }; diff --git a/Settings.cpp b/Settings.cpp index 5cebd1f..c863381 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -156,6 +156,7 @@ void Settings::Save(uint64_t runtime) RecordNode->SetAttribute("framerate_mode", application.record.framerate_mode); RecordNode->SetAttribute("buffering_mode", application.record.buffering_mode); RecordNode->SetAttribute("priority_mode", application.record.priority_mode); + RecordNode->SetAttribute("naming_mode", application.record.naming_mode); pRoot->InsertEndChild(RecordNode); // Transition @@ -399,6 +400,7 @@ void Settings::Load() recordnode->QueryIntAttribute("framerate_mode", &application.record.framerate_mode); recordnode->QueryIntAttribute("buffering_mode", &application.record.buffering_mode); recordnode->QueryIntAttribute("priority_mode", &application.record.priority_mode); + recordnode->QueryIntAttribute("naming_mode", &application.record.naming_mode); const char *path_ = recordnode->Attribute("path"); if (path_) diff --git a/Settings.h b/Settings.h index db27e0c..b9233be 100644 --- a/Settings.h +++ b/Settings.h @@ -90,6 +90,7 @@ struct RecordConfig int framerate_mode; int buffering_mode; int priority_mode; + int naming_mode; RecordConfig() : path("") { profile = 0; @@ -99,6 +100,7 @@ struct RecordConfig framerate_mode = 1; buffering_mode = 2; priority_mode = 1; + naming_mode = 1; } }; diff --git a/SystemToolkit.cpp b/SystemToolkit.cpp index d80c67b..23e16e8 100644 --- a/SystemToolkit.cpp +++ b/SystemToolkit.cpp @@ -276,7 +276,8 @@ string SystemToolkit::temp_path() string SystemToolkit::full_filename(const std::string& path, const string &filename) { string fullfilename = path; - fullfilename += PATH_SEP; + if (path.back() != PATH_SEP) + fullfilename += PATH_SEP; fullfilename += filename; return fullfilename; @@ -300,7 +301,9 @@ string SystemToolkit::path_directory(const string& path) DIR *dir; if ((dir = opendir (path.c_str())) != NULL) { - directorypath = path + PATH_SEP; + directorypath = path; + if (path.back() != PATH_SEP) + directorypath += PATH_SEP; closedir (dir); } @@ -451,3 +454,80 @@ string SystemToolkit::path_absolute_from_path(const string& relativePath, const return absolutePath; } + +std::string SystemToolkit::filename_sequential(const std::string& path, const std::string& base, const std::string& extension) +{ + std::ostringstream filename; + + // make sure extension is without the dot + string ext = extension; + auto loc = extension.find_last_of("."); + if (loc != string::npos) + ext = extension.substr( loc + 1 ); + + // make sure path is ok + std::string p = SystemToolkit::path_directory(path); + if (p.empty()) + p = home_path(); + + // list all files in the target directory that potentially match the sequence naming pattern + std::list pattern; + pattern.push_back( base + "*." + ext ); + std::list ls = SystemToolkit::list_directory(p, pattern); + + // establish a filename for a consecutive sequence of numbers + for (int i = 0; i < ls.size() + 1; ++i) { + // clear + filename.str(std::string()); + // add path + filename << p; + // add base filename + if (base.empty()) + filename << "vimix"; + else + filename << base ; + // add sequential number + filename << "_" << std::setw(4) << std::setfill('0') << i; + // add extension + if (!ext.empty()) + filename << "." << ext; + // use that filename if was not already used + if ( std::find( ls.begin(), ls.end(), filename.str() ) == ls.end() ) + break; + } + + return filename.str(); +} + +std::string SystemToolkit::filename_dateprefix(const std::string& path, const std::string& base, const std::string& extension) +{ + // make sure extension is without the dot + string ext = extension; + auto loc = extension.find_last_of("."); + if (loc != string::npos) + ext = extension.substr( loc + 1 ); + + // make sure path is ok + std::string p = SystemToolkit::path_directory(path); + if (p.empty()) + p = home_path(); + + // build filename + std::ostringstream filename; + // add path + filename << p; + // add date prefix + filename << SystemToolkit::date_time_string() << "_"; + // add base filename + if (base.empty()) + filename << "vimix"; + else + filename << base ; + // add extension + if (!ext.empty()) + filename << "." << ext; + + return filename.str(); +} + + diff --git a/SystemToolkit.h b/SystemToolkit.h index b80acb8..63e5d59 100644 --- a/SystemToolkit.h +++ b/SystemToolkit.h @@ -58,6 +58,12 @@ namespace SystemToolkit // builds the absolute path (starting with '/') of relativePath starting from relativeTo (e.g. ../c/d rel to /a/b/e -> /a/b/c/d) std::string path_absolute_from_path(const std::string& relativePath, const std::string& relativeTo); + // generates a filename at given path, with basename and inerative suffix + std::string filename_sequential(const std::string& path, const std::string& base, const std::string& extension); + + // generates a filename at given path, with basename and date prefix + std::string filename_dateprefix(const std::string& path, const std::string& base, const std::string& extension); + // true of file exists bool file_exists(const std::string& path); diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 02510e6..283523b 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -383,7 +383,7 @@ void UserInterface::handleKeyboard() else if (ImGui::IsKeyPressed( GLFW_KEY_F10, false )) sourcecontrol.Capture(); else if (ImGui::IsKeyPressed( GLFW_KEY_F11, false )) - FrameGrabbing::manager().add(new PNGRecorder); + FrameGrabbing::manager().add(new PNGRecorder(SystemToolkit::base_filename( Mixer::manager().session()->filename()))); else if (ImGui::IsKeyPressed( GLFW_KEY_F12, false )) { Settings::application.render.disabled = !Settings::application.render.disabled; } @@ -2244,32 +2244,15 @@ void SourceController::Update() if (source != nullptr) { // back from capture of FBO: can save file if ( capture.isFull() ){ - // default naming of file with date prefix - std::ostringstream filename; - filename << Settings::application.source.capture_path << PATH_SEP; - filename << SystemToolkit::date_time_string() << "_" << source->name() << ".png"; - + std::string filename; // if sequencial naming of file is selected if (Settings::application.source.capture_naming == 0 ) - { - // list all files in the target directory that potentially match the sequence naming pattern - std::list pattern; - pattern.push_back( source->name() + "*.png" ); - std::list ls = SystemToolkit::list_directory(Settings::application.source.capture_path, pattern); - // establish a filename for a consecutive sequence of numbers - for (int i = 0; i < ls.size() + 1; ++i) { - filename.str(std::string()); // clear - filename << Settings::application.source.capture_path << PATH_SEP; - filename << source->name() << "_" ; - filename << std::setw(4) << std::setfill('0') << i << ".png"; - // use that filename if was not already used - if ( std::find( ls.begin(), ls.end(), filename.str() ) == ls.end() ) - break; - } - } + filename = SystemToolkit::filename_sequential(Settings::application.source.capture_path, source->name(), "png"); + else + filename = SystemToolkit::filename_dateprefix(Settings::application.source.capture_path, source->name(), "png"); // save capture and inform user - capture.save( filename.str() ); - Log::Notify("Frame saved in %s", filename.str().c_str() ); + capture.save( filename ); + Log::Notify("Frame saved in %s", filename.c_str() ); } // request capture : initiate capture of FBO if ( capture_request_ ) { @@ -2385,13 +2368,15 @@ void SourceController::Render() capture_request_ = true; ImGui::PopStyleColor(1); - // path + ImGui::MenuItem("Settings", nullptr, false, false); + + // path menu selection static char* name_path[4] = { nullptr }; if ( name_path[0] == nullptr ) { for (int i = 0; i < 4; ++i) name_path[i] = (char *) malloc( 1024 * sizeof(char)); sprintf( name_path[1], "%s", ICON_FA_HOME " Home"); - sprintf( name_path[2], "%s", ICON_FA_FOLDER " Session location"); + sprintf( name_path[2], "%s", ICON_FA_FOLDER " File location"); sprintf( name_path[3], "%s", ICON_FA_FOLDER_PLUS " Select"); } if (Settings::application.source.capture_path.empty()) @@ -2402,14 +2387,28 @@ void SourceController::Render() ImGui::Combo("Path", &selected_path, name_path, 4); if (selected_path > 2) captureFolderDialog->open(); - else if (selected_path > 1) - Settings::application.source.capture_path = SystemToolkit::path_filename( Mixer::manager().session()->filename() ); + else if (selected_path > 1) { + // file location of media player + if (mediaplayer_active_) + Settings::application.source.capture_path = SystemToolkit::path_filename( mediaplayer_active_->filename() ); + // else file location of session + else + Settings::application.source.capture_path = SystemToolkit::path_filename( Mixer::manager().session()->filename() ); + } else if (selected_path > 0) Settings::application.source.capture_path = SystemToolkit::home_path(); - static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequence", ICON_FA_CALENDAR " Date" }; + // offer to open folder location + ImVec2 draw_pos = ImGui::GetCursorPos(); + ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -ImGui::GetFrameHeight()) ); + if (ImGuiToolkit::IconButton( ICON_FA_EXTERNAL_LINK_ALT, Settings::application.source.capture_path.c_str())) + SystemToolkit::open(Settings::application.source.capture_path); + ImGui::SetCursorPos(draw_pos); + + // Naming menu selection + static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequential", ICON_FA_CALENDAR " Date prefix" }; ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::Combo("Naming", &Settings::application.source.capture_naming, naming_style, IM_ARRAYSIZE(naming_style)); + ImGui::Combo("Filename", &Settings::application.source.capture_naming, naming_style, IM_ARRAYSIZE(naming_style)); ImGui::EndMenu(); } @@ -3821,7 +3820,7 @@ void OutputPreview::ToggleRecord(bool save_and_continue) UserInterface::manager().navigator.setNewMedia(Navigator::MEDIA_RECORDING); // 'save & continue' if ( save_and_continue) { - VideoRecorder *rec = new VideoRecorder; + VideoRecorder *rec = new VideoRecorder(SystemToolkit::base_filename( Mixer::manager().session()->filename())); FrameGrabbing::manager().chain(video_recorder_, rec); video_recorder_ = rec; } @@ -3831,7 +3830,7 @@ void OutputPreview::ToggleRecord(bool save_and_continue) video_recorder_->stop(); } else { - _video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder, + _video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder(SystemToolkit::base_filename( Mixer::manager().session()->filename())), std::chrono::seconds(Settings::application.record.delay)) ); } } @@ -3913,8 +3912,9 @@ void OutputPreview::Render() if (ImGui::BeginMenu( ICON_FA_COMPACT_DISC " Record")) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_CAPTURE, 0.8f)); - if ( ImGui::MenuItem( MENU_CAPTUREFRAME, SHORTCUT_CAPTUREFRAME) ) - FrameGrabbing::manager().add(new PNGRecorder); + if ( ImGui::MenuItem( MENU_CAPTUREFRAME, SHORTCUT_CAPTUREFRAME) ) { + FrameGrabbing::manager().add(new PNGRecorder(SystemToolkit::base_filename( Mixer::manager().session()->filename()))); + } ImGui::PopStyleColor(1); // temporary disabled @@ -3940,7 +3940,7 @@ void OutputPreview::Render() if (Settings::application.recentRecordings.load_at_start) UserInterface::manager().navigator.setNewMedia(Navigator::MEDIA_RECORDING); // create a new recorder chainned to the current one - VideoRecorder *rec = new VideoRecorder; + VideoRecorder *rec = new VideoRecorder(SystemToolkit::base_filename( Mixer::manager().session()->filename())); FrameGrabbing::manager().chain(video_recorder_, rec); // swap recorder video_recorder_ = rec; @@ -3951,7 +3951,8 @@ void OutputPreview::Render() else { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.9f)); if ( ImGui::MenuItem( MENU_RECORD, SHORTCUT_RECORD) ) { - _video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder, + _video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, + new VideoRecorder(SystemToolkit::base_filename( Mixer::manager().session()->filename())), std::chrono::seconds(Settings::application.record.delay)) ); } ImGui::MenuItem( MENU_RECORDCONT, SHORTCUT_RECORDCONT, false, false); @@ -3959,13 +3960,11 @@ void OutputPreview::Render() } // Options menu ImGui::Separator(); - ImGui::MenuItem("Options", nullptr, false, false); + ImGui::MenuItem("Settings", nullptr, false, false); // offer to open config panel from here for more options ImGui::SameLine(ImGui::GetContentRegionAvailWidth() + 1.2f * IMGUI_RIGHT_ALIGN); - if (ImGuiToolkit::IconButton(13, 5)) + if (ImGuiToolkit::IconButton(13, 5, "Advanced settings")) UserInterface::manager().navigator.showConfig(); - ImGui::SameLine(0); - ImGui::Text("Settings"); // BASIC OPTIONS static char* name_path[4] = { nullptr }; @@ -3989,6 +3988,19 @@ void OutputPreview::Render() else if (selected_path > 0) Settings::application.record.path = SystemToolkit::home_path(); + // offer to open folder location + ImVec2 draw_pos = ImGui::GetCursorPos(); + ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -ImGui::GetFrameHeight()) ); + if (ImGuiToolkit::IconButton( ICON_FA_EXTERNAL_LINK_ALT, Settings::application.record.path.c_str())) + SystemToolkit::open(Settings::application.record.path); + ImGui::SetCursorPos(draw_pos); + + // Naming menu selection + static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequential", ICON_FA_CALENDAR " Date prefix" }; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::Combo("Filename", &Settings::application.record.naming_mode, naming_style, IM_ARRAYSIZE(naming_style)); + + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGuiToolkit::SliderTiming ("Duration", &Settings::application.record.timeout, 1000, RECORD_MAX_TIMEOUT, 1000, "Until stopped");