From fd6236bf4d35315d8db79a6a2b5774bbbff86b58 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 2 Oct 2025 16:38:14 +0100 Subject: [PATCH 1/9] Add tools menu and redact function Initial implementation of a tools menu and an example tool, redact. Redact is based of the otiotool command of the same name that replaces all names and references in and OTIO file as well as deleting all metadata. Added new source files tools.cpp and tools.h Signed-off-by: Thomas Wilshaw --- CMakeLists.txt | 2 ++ app.cpp | 12 +++++++ inspector.cpp | 2 +- tools.cpp | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++ tools.h | 5 +++ 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 tools.cpp create mode 100644 tools.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab6427c..7f64eb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources(raven editing.h inspector.h timeline.h + tools.h colors.h widgets.h @@ -21,6 +22,7 @@ target_sources(raven editing.cpp inspector.cpp timeline.cpp + tools.cpp colors.cpp widgets.cpp diff --git a/app.cpp b/app.cpp index c6e4c0d..cd6de2b 100644 --- a/app.cpp +++ b/app.cpp @@ -12,6 +12,7 @@ #include "imgui_internal.h" #include "widgets.h" +#include "tools.h" #ifndef EMSCRIPTEN #include "nfd.h" @@ -1066,6 +1067,17 @@ void DrawMenu() { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Tools", GetActiveRoot())) { + if (ImGui::MenuItem("Redact OTIO File")) { + if (Redact()) { + std::cout << "Successfully redacted " << appState.active_tab->file_path << std::endl; + } else { + std::cout << "Failed to redact " << appState.active_tab->file_path << std::endl; + } + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("View")) { bool showTimecodeOnClips = appState.track_height >= appState.default_track_height * 2; if (ImGui::MenuItem( diff --git a/inspector.cpp b/inspector.cpp index 5b4eadc..ab74850 100644 --- a/inspector.cpp +++ b/inspector.cpp @@ -803,7 +803,7 @@ void DrawInspector() { } // Set the active media ref key based on user selection - if (ImGui::Combo("", &appState.selected_reference_index, reference_names.data(), num_references)) { + if (ImGui::Combo("##", &appState.selected_reference_index, reference_names.data(), num_references)) { if (appState.selected_reference_index >= 0 && appState.selected_reference_index < num_references) { clip->set_active_media_reference_key(reference_names[appState.selected_reference_index]); } diff --git a/tools.cpp b/tools.cpp new file mode 100644 index 0000000..4a6cc58 --- /dev/null +++ b/tools.cpp @@ -0,0 +1,87 @@ +// Tools +#include "tools.h" + +#include "app.h" + +#include +#include +#include +#include +#include + +#include + +// Check to see if the lookup already contians the name. If it does, return its count and if not add +// it to the lookup, assign it a count and return that. +std::string name_from_lookup(std::map& lookup, std::string name, int& count) { + if (lookup.count(name)) { + return std::to_string(lookup.at(name)); + } else { + count++; + lookup.emplace(name, count); + return std::to_string(count); + } +} + +bool Redact() { + otio::SerializableObjectWithMetadata* root = GetActiveRoot(); + std::map item_lookup; + + int count = -1; + + // Handle root item + root->set_name(root->schema_name() + name_from_lookup(item_lookup, root->name(), count)); + root->metadata().clear(); + + // If the root is a Timeline, loop through it's children and redact them + if (const auto& timeline = dynamic_cast(root)) { + for (auto track : timeline->tracks()->children()) { + track->set_name(track->schema_name() + name_from_lookup(item_lookup, track->name(), count)); + track->metadata().clear(); + } + for (auto child : timeline->find_children()) { + // Clear child anme and metadata + child->set_name(child->schema_name() + name_from_lookup(item_lookup, child->name(), count)); + child->metadata().clear(); + + // Clear childs data + if (const auto& item = dynamic_cast(&*child)) { + // Markers + for (const auto& marker : item->markers()) { + marker->set_name(marker->schema_name() + name_from_lookup(item_lookup, marker->name(), count)); + marker->metadata().clear(); + } + + // Effects + for (const auto& effect : item->effects()) { + // At least in examples I have seen effect->name tends to be empty, would it be better use effect->effect_name? + // Also is losing the effects metadata bad as it could lose effects settings like blur amount? + effect->set_name(effect->schema_name() + name_from_lookup(item_lookup, effect->name(), count)); + effect->metadata().clear(); + // Should we also clear effect->effect_name()? + } + + // Media reference + // Note: Can Clips have multiple media references and hwo does that work? + if (const auto& clip = dynamic_cast(&*child)) { + if (auto media_ref = clip->media_reference()) { + media_ref->set_name(media_ref->schema_name() + name_from_lookup(item_lookup, media_ref->name(), count)); + media_ref->metadata().clear(); + + if (const auto& ref = dynamic_cast(media_ref)) { + ref->set_target_url("URL " + name_from_lookup(item_lookup, ref->target_url(), count)); + } + + if (const auto& ref = dynamic_cast(media_ref)) { + ref->set_target_url_base("URL " + name_from_lookup(item_lookup, ref->target_url_base(), count)); + // DO we need to redact name_prefix/name_suffix? + } + } + } + } + } + } + + + return true; +} \ No newline at end of file diff --git a/tools.h b/tools.h new file mode 100644 index 0000000..cef3ac8 --- /dev/null +++ b/tools.h @@ -0,0 +1,5 @@ +// Tools + +// Remove all metadata, names, or other identifying information from this +// timeline. Only the structure, schema and timing will remain. +bool Redact(); \ No newline at end of file From 3dede6f59730fc2d5c016a2702bc3d83dac1d8fd Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 4 Oct 2025 19:32:13 +0100 Subject: [PATCH 2/9] Use subprocess to run otiotool rather than cpp implementation Signed-off-by: Thomas Wilshaw --- app.cpp | 33 ++++++++++-- app.h | 3 ++ tools.cpp | 154 +++++++++++++++++++++++++++++++----------------------- tools.h | 11 +++- 4 files changed, 131 insertions(+), 70 deletions(-) diff --git a/app.cpp b/app.cpp index cd6de2b..3d0ca7a 100644 --- a/app.cpp +++ b/app.cpp @@ -582,6 +582,14 @@ void MainInit(int argc, char** argv, int initial_width, int initial_height) { LoadFonts(); + // Check for otiotool + if (otiotool_found()) { + appState.otiotool_found = true; + Message("otiotool found, relevant tools have been enabled"); + } else { + Message("oitotool not found, rlevant tools have been disabled"); + } + if (argc > 1) { LoadFile(argv[1]); } @@ -699,6 +707,7 @@ void MainGui() { char window_id[1024]; snprintf(window_id, sizeof(window_id), "%s###MainWindow", window_title); + // Avoid double window padding since we have a dockspace window // which fills our whole main window. ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); @@ -1067,13 +1076,31 @@ void DrawMenu() { ImGui::EndMenu(); } - if (ImGui::BeginMenu("Tools", GetActiveRoot())) { + if (ImGui::BeginMenu("Tools", GetActiveRoot() && appState.otiotool_found)) { + std::string current_file = appState.active_tab->file_path; if (ImGui::MenuItem("Redact OTIO File")) { if (Redact()) { - std::cout << "Successfully redacted " << appState.active_tab->file_path << std::endl; + Message("Successfully redacted %s\n", current_file.c_str()); } else { - std::cout << "Failed to redact " << appState.active_tab->file_path << std::endl; + ErrorMessage("Failed to redact %s\n", current_file.c_str()); + } + } + if (ImGui::BeginMenu("Extract Track Type")) { + if (ImGui::MenuItem("Video Tracks Only")) { + if (VideoOnly()) { + Message("Sucessfully extracted video tracks from %s\n", current_file.c_str()); + } else { + ErrorMessage("Failed to extract video tracks from %s\n", current_file.c_str()); + } + } + if (ImGui::MenuItem("Audio Tracks Only")) { + if (AudioOnly()) { + Message("Sucessfully extracted audio tracks from %s\n", current_file.c_str()); + } else { + ErrorMessage("Failed to extract audio tracks from %s\n", current_file.c_str()); + } } + ImGui::EndMenu(); } ImGui::EndMenu(); } diff --git a/app.h b/app.h index c4275d8..280e264 100644 --- a/app.h +++ b/app.h @@ -136,6 +136,9 @@ struct AppState { bool show_demo_window = false; bool show_metrics = false; bool show_implot_demo_window = false; + + // Was otiotool found? + bool otiotool_found = false; }; extern AppState appState; diff --git a/tools.cpp b/tools.cpp index 4a6cc58..afa5b5b 100644 --- a/tools.cpp +++ b/tools.cpp @@ -10,78 +10,100 @@ #include #include +#include +#include -// Check to see if the lookup already contians the name. If it does, return its count and if not add -// it to the lookup, assign it a count and return that. -std::string name_from_lookup(std::map& lookup, std::string name, int& count) { - if (lookup.count(name)) { - return std::to_string(lookup.at(name)); +std::string run_subprocess(const std::string cmd, int& return_val) +{ + #ifdef _WIN32 + auto pipe = _popen(cmd.c_str(), "r"); + #else + auto pipe = popen(cmd.c_str(), "r"); + #endif + + if (pipe == nullptr) { + std::cout << "Failed to open pipe" << std::endl; + return std::string(); + } + + char buffer[128]; + + std::string result; + + while (fgets(buffer, sizeof buffer, pipe) != NULL){ + result += buffer; + } + + return_val = _pclose(pipe); + + + return result; +} + +bool otiotool_found() +{ + int result; + + #ifdef _WIN32 + run_subprocess("where /Q otiotool", result); + #else + run_subprocess("whereis otiotool", result); + #endif + + return !result; +} + +bool run_otiotool_command(std::string options) +{ + // Write the current root to a temp json file + std::filesystem::path file = std::filesystem::temp_directory_path(); + file.replace_filename(std::tmpnam(nullptr)); + file.replace_extension("otio"); + std::cout << file << std::endl; + GetActiveRoot()->to_json_file(file.generic_string()); + + // Build command + std::string command = "otiotool --input " + file.generic_string() + " " + options + " --output -"; + std::cout << command << std::endl; + + // Run subproces + int return_val = 0; + std::string result = run_subprocess(command, return_val); + + // Load new otio file + if (!result.empty() && return_val == 0) { + LoadString(result); } else { - count++; - lookup.emplace(name, count); - return std::to_string(count); + ErrorMessage("Error trying to redact file, see console"); + return false; } + + // Clean up temp file + std::remove(file.generic_string().c_str()); + + return true; } bool Redact() { - otio::SerializableObjectWithMetadata* root = GetActiveRoot(); - std::map item_lookup; - - int count = -1; - - // Handle root item - root->set_name(root->schema_name() + name_from_lookup(item_lookup, root->name(), count)); - root->metadata().clear(); - - // If the root is a Timeline, loop through it's children and redact them - if (const auto& timeline = dynamic_cast(root)) { - for (auto track : timeline->tracks()->children()) { - track->set_name(track->schema_name() + name_from_lookup(item_lookup, track->name(), count)); - track->metadata().clear(); - } - for (auto child : timeline->find_children()) { - // Clear child anme and metadata - child->set_name(child->schema_name() + name_from_lookup(item_lookup, child->name(), count)); - child->metadata().clear(); - - // Clear childs data - if (const auto& item = dynamic_cast(&*child)) { - // Markers - for (const auto& marker : item->markers()) { - marker->set_name(marker->schema_name() + name_from_lookup(item_lookup, marker->name(), count)); - marker->metadata().clear(); - } - - // Effects - for (const auto& effect : item->effects()) { - // At least in examples I have seen effect->name tends to be empty, would it be better use effect->effect_name? - // Also is losing the effects metadata bad as it could lose effects settings like blur amount? - effect->set_name(effect->schema_name() + name_from_lookup(item_lookup, effect->name(), count)); - effect->metadata().clear(); - // Should we also clear effect->effect_name()? - } - - // Media reference - // Note: Can Clips have multiple media references and hwo does that work? - if (const auto& clip = dynamic_cast(&*child)) { - if (auto media_ref = clip->media_reference()) { - media_ref->set_name(media_ref->schema_name() + name_from_lookup(item_lookup, media_ref->name(), count)); - media_ref->metadata().clear(); - - if (const auto& ref = dynamic_cast(media_ref)) { - ref->set_target_url("URL " + name_from_lookup(item_lookup, ref->target_url(), count)); - } - - if (const auto& ref = dynamic_cast(media_ref)) { - ref->set_target_url_base("URL " + name_from_lookup(item_lookup, ref->target_url_base(), count)); - // DO we need to redact name_prefix/name_suffix? - } - } - } - } - } + if (run_otiotool_command("--redact")) { + return true; + } else { + return false; } - +} - return true; +bool VideoOnly() { + if (run_otiotool_command("--video-only")) { + return true; + } else { + return false; + } +} + +bool AudioOnly() { + if (run_otiotool_command("--audio-only")) { + return true; + } else { + return false; + } } \ No newline at end of file diff --git a/tools.h b/tools.h index cef3ac8..31436dc 100644 --- a/tools.h +++ b/tools.h @@ -1,5 +1,14 @@ // Tools + +bool otiotool_found(); + // Remove all metadata, names, or other identifying information from this // timeline. Only the structure, schema and timing will remain. -bool Redact(); \ No newline at end of file +bool Redact(); + +// Output only video tracks +bool VideoOnly(); + +// Output only audio tracks +bool AudioOnly(); \ No newline at end of file From 5362786292ba24bc758b967cab3c3c82a720c76b Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 4 Oct 2025 19:38:10 +0100 Subject: [PATCH 3/9] Cleanup Signed-off-by: Thomas Wilshaw --- app.cpp | 3 +-- tools.cpp | 18 ++++++++---------- tools.h | 2 +- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/app.cpp b/app.cpp index 3d0ca7a..dad6467 100644 --- a/app.cpp +++ b/app.cpp @@ -587,7 +587,7 @@ void MainInit(int argc, char** argv, int initial_width, int initial_height) { appState.otiotool_found = true; Message("otiotool found, relevant tools have been enabled"); } else { - Message("oitotool not found, rlevant tools have been disabled"); + Message("oitotool not found, relevant tools have been disabled"); } if (argc > 1) { @@ -707,7 +707,6 @@ void MainGui() { char window_id[1024]; snprintf(window_id, sizeof(window_id), "%s###MainWindow", window_title); - // Avoid double window padding since we have a dockspace window // which fills our whole main window. ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); diff --git a/tools.cpp b/tools.cpp index afa5b5b..c5603ef 100644 --- a/tools.cpp +++ b/tools.cpp @@ -3,12 +3,6 @@ #include "app.h" -#include -#include -#include -#include -#include - #include #include #include @@ -53,18 +47,22 @@ bool otiotool_found() return !result; } -bool run_otiotool_command(std::string options) +bool run_otiotool_command(std::string options, bool debug = false) { // Write the current root to a temp json file std::filesystem::path file = std::filesystem::temp_directory_path(); file.replace_filename(std::tmpnam(nullptr)); file.replace_extension("otio"); - std::cout << file << std::endl; + if (debug) { + std::cout << file << std::endl; + } GetActiveRoot()->to_json_file(file.generic_string()); // Build command std::string command = "otiotool --input " + file.generic_string() + " " + options + " --output -"; - std::cout << command << std::endl; + if (debug) { + std::cout << command << std::endl; + } // Run subproces int return_val = 0; @@ -106,4 +104,4 @@ bool AudioOnly() { } else { return false; } -} \ No newline at end of file +} diff --git a/tools.h b/tools.h index 31436dc..ad57149 100644 --- a/tools.h +++ b/tools.h @@ -11,4 +11,4 @@ bool Redact(); bool VideoOnly(); // Output only audio tracks -bool AudioOnly(); \ No newline at end of file +bool AudioOnly(); From 190f826d71c66cc714f9c7d87ec93de12f5356a3 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 4 Oct 2025 19:50:45 +0100 Subject: [PATCH 4/9] Add a few extra example tools Signed-off-by: Thomas Wilshaw --- app.cpp | 24 ++++++++++++++++++++++++ tools.cpp | 24 ++++++++++++++++++++++++ tools.h | 9 +++++++++ 3 files changed, 57 insertions(+) diff --git a/app.cpp b/app.cpp index dad6467..1dd56eb 100644 --- a/app.cpp +++ b/app.cpp @@ -1101,6 +1101,30 @@ void DrawMenu() { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("Flatten Tracks")) { + if (ImGui::MenuItem("All")) { + if (FlattenAllTracks()) { + Message("Sucessfully flattened tracks from %s\n", current_file.c_str()); + } else { + ErrorMessage("Failed to flatten tracks from %s\n", current_file.c_str()); + } + } + if (ImGui::MenuItem("Video")) { + if (FlattenVideoTracks()) { + Message("Sucessfully flattened video tracks from %s\n", current_file.c_str()); + } else { + ErrorMessage("Failed to flatten video tracks from %s\n", current_file.c_str()); + } + } + if (ImGui::MenuItem("Audio")) { + if (FlattenAudioTracks()) { + Message("Sucessfully flattened audio tracks from %s\n", current_file.c_str()); + } else { + ErrorMessage("Failed to flatten audio tracks from %s\n", current_file.c_str()); + } + } + ImGui::EndMenu(); + } ImGui::EndMenu(); } diff --git a/tools.cpp b/tools.cpp index c5603ef..f31e52b 100644 --- a/tools.cpp +++ b/tools.cpp @@ -105,3 +105,27 @@ bool AudioOnly() { return false; } } + +bool FlattenAllTracks() { + if (run_otiotool_command("--flatten all")) { + return true; + } else { + return false; + } +} + +bool FlattenVideoTracks() { + if (run_otiotool_command("--flatten video")) { + return true; + } else { + return false; + } +} + +bool FlattenAudioTracks() { + if (run_otiotool_command("--flatten audio")) { + return true; + } else { + return false; + } +} \ No newline at end of file diff --git a/tools.h b/tools.h index ad57149..be13fe0 100644 --- a/tools.h +++ b/tools.h @@ -12,3 +12,12 @@ bool VideoOnly(); // Output only audio tracks bool AudioOnly(); + +// Flatten all tracks +bool FlattenAllTracks(); + +// Flatten vieo tracks +bool FlattenVideoTracks(); + +// Flatten audio tracks +bool FlattenAudioTracks(); \ No newline at end of file From 0855ffe0b8be4f93c48b06f44b513cbd9b73cddc Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 4 Oct 2025 19:56:12 +0100 Subject: [PATCH 5/9] Fix cross platform support Signed-off-by: Thomas Wilshaw --- tools.cpp | 9 +++++---- tools.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tools.cpp b/tools.cpp index f31e52b..1ac5d69 100644 --- a/tools.cpp +++ b/tools.cpp @@ -21,15 +21,16 @@ std::string run_subprocess(const std::string cmd, int& return_val) } char buffer[128]; - std::string result; - while (fgets(buffer, sizeof buffer, pipe) != NULL){ result += buffer; } + #ifdef _WIN32 return_val = _pclose(pipe); - + #else + return_val = pclose(pipe); + #endif return result; } @@ -128,4 +129,4 @@ bool FlattenAudioTracks() { } else { return false; } -} \ No newline at end of file +} diff --git a/tools.h b/tools.h index be13fe0..3c6251b 100644 --- a/tools.h +++ b/tools.h @@ -20,4 +20,4 @@ bool FlattenAllTracks(); bool FlattenVideoTracks(); // Flatten audio tracks -bool FlattenAudioTracks(); \ No newline at end of file +bool FlattenAudioTracks(); From ba164c2d67e3700877ba1a7e95bac295c5bc21f9 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 4 Oct 2025 20:02:25 +0100 Subject: [PATCH 6/9] Simplify finding otiotool Signed-off-by: Thomas Wilshaw --- tools.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tools.cpp b/tools.cpp index 1ac5d69..93901bd 100644 --- a/tools.cpp +++ b/tools.cpp @@ -39,11 +39,7 @@ bool otiotool_found() { int result; - #ifdef _WIN32 - run_subprocess("where /Q otiotool", result); - #else - run_subprocess("whereis otiotool", result); - #endif + run_subprocess("otiotool -h", result); return !result; } From eefd88b76c18e77bf0cc31e2028d2ccd6fad9e99 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 4 Oct 2025 20:15:32 +0100 Subject: [PATCH 7/9] Remove unecessary conditionals Signed-off-by: Thomas Wilshaw --- tools.cpp | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/tools.cpp b/tools.cpp index 93901bd..af4fc4c 100644 --- a/tools.cpp +++ b/tools.cpp @@ -80,49 +80,25 @@ bool run_otiotool_command(std::string options, bool debug = false) } bool Redact() { - if (run_otiotool_command("--redact")) { - return true; - } else { - return false; - } + return run_otiotool_command("--redact"); } bool VideoOnly() { - if (run_otiotool_command("--video-only")) { - return true; - } else { - return false; - } + return run_otiotool_command("--video-only"); } bool AudioOnly() { - if (run_otiotool_command("--audio-only")) { - return true; - } else { - return false; - } + return run_otiotool_command("--audio-only"); } bool FlattenAllTracks() { - if (run_otiotool_command("--flatten all")) { - return true; - } else { - return false; - } + return run_otiotool_command("--flatten all"); } bool FlattenVideoTracks() { - if (run_otiotool_command("--flatten video")) { - return true; - } else { - return false; - } + return run_otiotool_command("--flatten video"); } bool FlattenAudioTracks() { - if (run_otiotool_command("--flatten audio")) { - return true; - } else { - return false; - } + return run_otiotool_command("--flatten audio"); } From 60601eaa49c91a74b9f554df19ab7155553afb1b Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 12 Oct 2025 16:28:14 +0100 Subject: [PATCH 8/9] Fix code review comments Signed-off-by: Thomas Wilshaw --- tools.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools.cpp b/tools.cpp index af4fc4c..91d8986 100644 --- a/tools.cpp +++ b/tools.cpp @@ -17,10 +17,11 @@ std::string run_subprocess(const std::string cmd, int& return_val) if (pipe == nullptr) { std::cout << "Failed to open pipe" << std::endl; + return_val = 1; return std::string(); } - char buffer[128]; + char buffer[8192]; std::string result; while (fgets(buffer, sizeof buffer, pipe) != NULL){ result += buffer; @@ -55,8 +56,8 @@ bool run_otiotool_command(std::string options, bool debug = false) } GetActiveRoot()->to_json_file(file.generic_string()); - // Build command - std::string command = "otiotool --input " + file.generic_string() + " " + options + " --output -"; + // Build command, the file path is wrapped in quotation marks in case of spaces + std::string command = "otiotool --input \"" + file.generic_string() + "\" " + options + " --output -"; if (debug) { std::cout << command << std::endl; } @@ -69,7 +70,7 @@ bool run_otiotool_command(std::string options, bool debug = false) if (!result.empty() && return_val == 0) { LoadString(result); } else { - ErrorMessage("Error trying to redact file, see console"); + ErrorMessage("Error trying to run otiotool command, see console for details"); return false; } From d48edc97e86bd360b5a50022876e8dd532320cc6 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 12 Oct 2025 19:05:44 +0100 Subject: [PATCH 9/9] Refactor code to allow more complex otiotool commands to modified and called Signed-off-by: Thomas Wilshaw --- app.cpp | 31 ++++++++++++ app.h | 5 ++ tools.cpp | 141 +++++++++++++++++++++++++++++++++++++++++++++++++----- tools.h | 5 ++ 4 files changed, 169 insertions(+), 13 deletions(-) diff --git a/app.cpp b/app.cpp index 1dd56eb..cf9e4ab 100644 --- a/app.cpp +++ b/app.cpp @@ -951,6 +951,24 @@ void MainGui() { if (appState.show_implot_demo_window) { ImPlot::ShowDemoWindow(); } + + // Handle tool popups + // These modal popups are all triggered by the Tools menu and + // therefore can't have their draw code in the menu code. To + // get around that the menu flags if a modal is to be drawn and + // then we call the relevant ImGui::OpenPopup here. We then call + // our all encompassing DrawToolPopups function. + if (appState.draw_stat_popup) { + ImGui::OpenPopup("Statistics"); + appState.draw_stat_popup = false; + } + if (appState.draw_extract_clips) { + ImGui::OpenPopup("Extract Clips"); + appState.draw_extract_clips = false; + } + if (GetActiveRoot()) { + DrawToolPopups(); + } } void SaveTheme() { @@ -1125,6 +1143,19 @@ void DrawMenu() { } ImGui::EndMenu(); } + if (ImGui::MenuItem("Extract Clips")) { + // This option requires user input before the otiotool command + // can be called so we simply flag the popup for drawing here. + appState.draw_extract_clips = true; + } + if (ImGui::MenuItem("Statistics")) { + if (Statistics()) { + Message("Sucessfully returned statistics from %s\n", current_file.c_str()); + appState.draw_stat_popup = true; + } else { + ErrorMessage("Failed to return statistics from %s\n", current_file.c_str()); + } + } ImGui::EndMenu(); } diff --git a/app.h b/app.h index 280e264..e56b8c5 100644 --- a/app.h +++ b/app.h @@ -139,6 +139,11 @@ struct AppState { // Was otiotool found? bool otiotool_found = false; + + // otiotool data for popup windows + std::string otiotool_return_value; // Value returned by call to otiotool + bool draw_stat_popup = false; // Draw statistics popup + bool draw_extract_clips = false; // Draw clip extraction popup }; extern AppState appState; diff --git a/tools.cpp b/tools.cpp index 91d8986..a6da064 100644 --- a/tools.cpp +++ b/tools.cpp @@ -45,7 +45,7 @@ bool otiotool_found() return !result; } -bool run_otiotool_command(std::string options, bool debug = false) +std::string run_otiotool_command(std::string options, bool output = true, bool debug = false) { // Write the current root to a temp json file std::filesystem::path file = std::filesystem::temp_directory_path(); @@ -57,7 +57,13 @@ bool run_otiotool_command(std::string options, bool debug = false) GetActiveRoot()->to_json_file(file.generic_string()); // Build command, the file path is wrapped in quotation marks in case of spaces - std::string command = "otiotool --input \"" + file.generic_string() + "\" " + options + " --output -"; + std::string command = "otiotool --input \"" + file.generic_string() + "\" " + options; + + // Output otio file? + if (output) { + command += " --output -"; + } + if (debug) { std::cout << command << std::endl; } @@ -66,40 +72,149 @@ bool run_otiotool_command(std::string options, bool debug = false) int return_val = 0; std::string result = run_subprocess(command, return_val); + // Clean up temp file + std::remove(file.generic_string().c_str()); + // Load new otio file if (!result.empty() && return_val == 0) { - LoadString(result); + return result; } else { ErrorMessage("Error trying to run otiotool command, see console for details"); - return false; + return ""; } +} - // Clean up temp file - std::remove(file.generic_string().c_str()); +bool load_otio_file_from_otiotool_command(std::string options,bool output = true, bool debug = false) +{ + std::string result = run_otiotool_command(options, output, debug); - return true; + // Load new otio file + if (!result.empty()) { + LoadString(result); + return true; + } else { + ErrorMessage("Error trying to run otiotool command, see console for details"); + return false; + } } bool Redact() { - return run_otiotool_command("--redact"); + return load_otio_file_from_otiotool_command("--redact"); } bool VideoOnly() { - return run_otiotool_command("--video-only"); + return load_otio_file_from_otiotool_command("--video-only"); } bool AudioOnly() { - return run_otiotool_command("--audio-only"); + return load_otio_file_from_otiotool_command("--audio-only"); } bool FlattenAllTracks() { - return run_otiotool_command("--flatten all"); + return load_otio_file_from_otiotool_command("--flatten all"); } bool FlattenVideoTracks() { - return run_otiotool_command("--flatten video"); + return load_otio_file_from_otiotool_command("--flatten video"); } bool FlattenAudioTracks() { - return run_otiotool_command("--flatten audio"); + return load_otio_file_from_otiotool_command("--flatten audio"); +} + +bool Statistics() { + std::string result = run_otiotool_command("--stats", false, false); + + if (!result.empty()) { + appState.otiotool_return_value = result; + return true; + } else { + ErrorMessage("Error trying to run otiotool command, see console for details"); + return false; + } +} + +void DrawStatisticsPopup() +{ + ImGui::Text("Statistics for %s", appState.active_tab->file_path.c_str()); + ImGui::Separator(); + + ImGui::Text("%s", appState.otiotool_return_value.c_str()); + + if (ImGui::Button("OK", ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + appState.otiotool_return_value = ""; + } + + ImGui::EndPopup(); +} + +void DrawExtractClipsPopup() +{ + static bool use_regex = false; + + ImGui::BeginDisabled(use_regex); + static char clip_input[1024]; + ImGui::InputText("Clip Name(s)", clip_input, IM_ARRAYSIZE(clip_input)); + ImGui::EndDisabled(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::Checkbox("Use regex", &use_regex); + ImGui::PopStyleVar(); + + ImGui::BeginDisabled(!use_regex); + static char regex_input[1024]; + ImGui::InputText("Regex", regex_input, IM_ARRAYSIZE(regex_input)); + ImGui::EndDisabled(); + + if (ImGui::Button("OK", ImVec2(120, 0))) { + std::string current_file = appState.active_tab->file_path; + if (!use_regex) { + std::string command = "--only-clips-with-name " + std::string(clip_input); + if (load_otio_file_from_otiotool_command(command, true, true)) { + Message("Sucessfully extracted clips from %s", current_file.c_str()); + } else { + ErrorMessage("Failed to extract clips from %s", current_file.c_str()); + } + strcpy(clip_input, ""); + } else { + std::string command = "--only-clips-with-name-regex " + std::string(regex_input); + if (load_otio_file_from_otiotool_command(command, true, true)) { + Message("Sucessfully extracted clips from %s", current_file.c_str()); + } else { + ErrorMessage("Failed to extract clips from %s", current_file.c_str()); + } + strcpy(regex_input, ""); + } + ImGui::CloseCurrentPopup(); + } + + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + strcpy(clip_input, ""); + strcpy(regex_input, ""); + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); +} + +void DrawToolPopups() +{ + // Always center this window when appearing + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + bool close; + + // Statistics popup + if (ImGui::BeginPopupModal("Statistics", &close, ImGuiWindowFlags_AlwaysAutoResize)) { + DrawStatisticsPopup(); + } + + // Clip extraction popup + if (ImGui::BeginPopupModal("Extract Clips", &close, ImGuiWindowFlags_AlwaysAutoResize)) { + DrawExtractClipsPopup(); + } } diff --git a/tools.h b/tools.h index 3c6251b..1cb2c60 100644 --- a/tools.h +++ b/tools.h @@ -3,6 +3,8 @@ bool otiotool_found(); +void DrawToolPopups(); + // Remove all metadata, names, or other identifying information from this // timeline. Only the structure, schema and timing will remain. bool Redact(); @@ -21,3 +23,6 @@ bool FlattenVideoTracks(); // Flatten audio tracks bool FlattenAudioTracks(); + +// Display statistics about the current OTIO file +bool Statistics();