From 1369f5235d8469889ec895117a5a06c43b8ca11e Mon Sep 17 00:00:00 2001 From: crosstyan Date: Fri, 20 Mar 2026 10:19:19 +0000 Subject: [PATCH] feat: add shared TTY progress bars for ZED offline tools Extract the tqdm-like stderr progress bar into a shared helper and reuse it across zed_svo_to_mp4, zed_svo_grid_to_mp4, and zed_svo_to_mcap. For zed_svo_to_mcap, single-source exports now report exact frame totals and bundled multi-camera exports report exact synced-group totals on TTY. When bundled mode runs without --end-frame, the tool performs a counting pass first so the progress total remains exact instead of estimated. Also document the bundled MCAP progress behavior in the README and record the current third_party dependency state in third_party/README. That note now makes it explicit that CLI11 and proxy are the active submodules, while tomlplusplus and mcap are vendored source drops, and adds a TODO to revisit converting mcap into a submodule later. Verified with the Debug build during implementation, including single-camera and bundled zed_svo_to_mcap runs that rendered clean progress output to a TTY. --- CMakeLists.txt | 11 ++ README.md | 1 + .../tools/zed_progress_bar.hpp | 27 +++ .../tools/zed_svo_mp4_support.hpp | 13 -- src/tools/zed_progress_bar.cpp | 121 +++++++++++++ src/tools/zed_svo_grid_to_mp4.cpp | 1 + src/tools/zed_svo_mp4_support.cpp | 100 ----------- src/tools/zed_svo_to_mcap.cpp | 160 +++++++++++++++--- src/tools/zed_svo_to_mp4.cpp | 1 + third_party/README.md | 7 + 10 files changed, 307 insertions(+), 135 deletions(-) create mode 100644 include/cvmmap_streamer/tools/zed_progress_bar.hpp create mode 100644 src/tools/zed_progress_bar.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f80c991..6acb9a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -356,6 +356,14 @@ set_target_properties(mcap_replay_tester PROPERTIES set(CVMMAP_STREAMER_INSTALL_TARGETS cvmmap_streamer) if (CVMMAP_HAS_ZED_SDK) + add_library( + cvmmap_streamer_zed_progress_support + STATIC + src/tools/zed_progress_bar.cpp) + target_include_directories(cvmmap_streamer_zed_progress_support + PUBLIC + "${CMAKE_CURRENT_LIST_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}") add_executable( zed_svo_to_mcap src/tools/zed_svo_to_mcap.cpp @@ -372,6 +380,7 @@ if (CVMMAP_HAS_ZED_SDK) ${CUDA_LIBRARY_DIRS}) target_link_libraries(zed_svo_to_mcap PRIVATE + cvmmap_streamer_zed_progress_support cvmmap_streamer_record_support CLI11::CLI11 tomlplusplus::tomlplusplus @@ -421,6 +430,7 @@ if (CVMMAP_HAS_ZED_SDK) target_link_libraries(zed_svo_to_mp4 PRIVATE CLI11::CLI11 + cvmmap_streamer_zed_progress_support cvmmap_streamer_zed_svo_mp4_support ${ZED_LIBRARIES} ${CUDA_CUDA_LIBRARY} @@ -453,6 +463,7 @@ if (CVMMAP_HAS_ZED_SDK) target_link_libraries(zed_svo_grid_to_mp4 PRIVATE CLI11::CLI11 + cvmmap_streamer_zed_progress_support cvmmap_streamer_zed_svo_mp4_support ${ZED_LIBRARIES} ${CUDA_CUDA_LIBRARY} diff --git a/README.md b/README.md index d0261de..1f9930d 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ uv run python scripts/zed_batch_svo_to_mcap.py \ The batch MCAP wrapper writes `/.mcap` by default, skips existing outputs unless told otherwise, and returns a nonzero exit code if any segment fails. The repo includes a minimal pose config at `config/zed_pose_config.toml` so MCAP conversion does not depend on a separate `cv-mmap` checkout. In bundled multi-camera mode, `--end-frame` means the last emitted synced frame-group index from the common start timestamp. +When stderr is attached to a TTY, `zed_svo_to_mcap` shows a tqdm-like progress bar. In bundled mode without `--end-frame`, it first counts synced groups to make that progress total exact. Use `--probe-existing` to validate existing MCAPs before skipping them. Invalid outputs are treated as missing and requeued: diff --git a/include/cvmmap_streamer/tools/zed_progress_bar.hpp b/include/cvmmap_streamer/tools/zed_progress_bar.hpp new file mode 100644 index 0000000..e6b72e8 --- /dev/null +++ b/include/cvmmap_streamer/tools/zed_progress_bar.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace cvmmap_streamer::zed_tools { + +[[nodiscard]] +bool stderr_supports_progress_bar(); + +class ProgressBar { +public: + explicit ProgressBar(std::uint64_t total_frames); + ~ProgressBar(); + + [[nodiscard]] + bool enabled() const; + + void update(std::uint64_t completed_frames); + void finish(std::uint64_t completed_frames, bool success); + +private: + struct Impl; + std::unique_ptr impl_{}; +}; + +} // namespace cvmmap_streamer::zed_tools diff --git a/include/cvmmap_streamer/tools/zed_svo_mp4_support.hpp b/include/cvmmap_streamer/tools/zed_svo_mp4_support.hpp index 1ef4e2e..59a7f7d 100644 --- a/include/cvmmap_streamer/tools/zed_svo_mp4_support.hpp +++ b/include/cvmmap_streamer/tools/zed_svo_mp4_support.hpp @@ -65,19 +65,6 @@ std::uint64_t frame_period_ns(float fps); [[nodiscard]] std::filesystem::path derive_output_path(const std::filesystem::path &input_path); -class ProgressBar { -public: - explicit ProgressBar(std::uint64_t total_frames); - ~ProgressBar(); - - void update(std::uint64_t completed_frames); - void finish(std::uint64_t completed_frames, bool success); - -private: - struct Impl; - std::unique_ptr impl_{}; -}; - class Mp4Writer { public: Mp4Writer(); diff --git a/src/tools/zed_progress_bar.cpp b/src/tools/zed_progress_bar.cpp new file mode 100644 index 0000000..2cf131f --- /dev/null +++ b/src/tools/zed_progress_bar.cpp @@ -0,0 +1,121 @@ +#include "cvmmap_streamer/tools/zed_progress_bar.hpp" + +#include +#include +#include +#include + +#include + +namespace cvmmap_streamer::zed_tools { +namespace { + +[[nodiscard]] +std::string format_duration(const double seconds_raw) { + const auto seconds = seconds_raw > 0.0 ? static_cast(std::llround(seconds_raw)) : 0ll; + const auto hours = seconds / 3600; + const auto minutes = (seconds % 3600) / 60; + const auto secs = seconds % 60; + + char buffer[32]{}; + if (hours > 0) { + std::snprintf(buffer, sizeof(buffer), "%02lld:%02lld:%02lld", hours, minutes, secs); + } else { + std::snprintf(buffer, sizeof(buffer), "%02lld:%02lld", minutes, secs); + } + return std::string(buffer); +} + +} // namespace + +bool stderr_supports_progress_bar() { + return ::isatty(STDERR_FILENO) == 1; +} + +struct ProgressBar::Impl { + using Clock = std::chrono::steady_clock; + + explicit Impl(const std::uint64_t total_frames_arg) + : total_frames(total_frames_arg), + enabled(stderr_supports_progress_bar()), + started_at(Clock::now()), + last_render_at(started_at) {} + + void render(const std::uint64_t completed_frames, const bool force) { + if (!enabled || total_frames == 0) { + return; + } + + const auto now = Clock::now(); + if (!force && rendered && now - last_render_at < std::chrono::milliseconds(125)) { + return; + } + last_render_at = now; + rendered = true; + + const auto bounded_completed = completed_frames > total_frames ? total_frames : completed_frames; + const double ratio = static_cast(bounded_completed) / static_cast(total_frames); + const auto filled = static_cast(std::llround(ratio * 24.0)); + std::string bar{}; + bar.reserve(24); + for (std::size_t index = 0; index < 24; ++index) { + bar.push_back(index < filled ? '#' : '-'); + } + + const auto elapsed_seconds = std::chrono::duration(now - started_at).count(); + const auto fps = elapsed_seconds > 0.0 ? static_cast(bounded_completed) / elapsed_seconds : 0.0; + const auto eta_seconds = fps > 0.0 ? static_cast(total_frames - bounded_completed) / fps : 0.0; + + char line[256]{}; + std::snprintf( + line, + sizeof(line), + "\r[%s] %6.2f%% %llu/%llu | %5.1f fps | %s elapsed | %s ETA\x1b[K", + bar.c_str(), + ratio * 100.0, + static_cast(bounded_completed), + static_cast(total_frames), + fps, + format_duration(elapsed_seconds).c_str(), + format_duration(eta_seconds).c_str()); + std::fprintf(stderr, "%s", line); + std::fflush(stderr); + } + + std::uint64_t total_frames{0}; + bool enabled{false}; + bool rendered{false}; + Clock::time_point started_at{}; + Clock::time_point last_render_at{}; +}; + +ProgressBar::ProgressBar(const std::uint64_t total_frames) + : impl_(std::make_unique(total_frames)) {} + +ProgressBar::~ProgressBar() = default; + +bool ProgressBar::enabled() const { + return impl_ != nullptr && impl_->enabled; +} + +void ProgressBar::update(const std::uint64_t completed_frames) { + impl_->render(completed_frames, false); +} + +void ProgressBar::finish(const std::uint64_t completed_frames, const bool success) { + if (impl_ == nullptr || !impl_->enabled) { + return; + } + + if (!(success && impl_->rendered && completed_frames >= impl_->total_frames)) { + impl_->render(completed_frames, true); + if (!impl_->rendered) { + return; + } + } + + std::fprintf(stderr, "%s", success ? "\n" : " [failed]\n"); + std::fflush(stderr); +} + +} // namespace cvmmap_streamer::zed_tools diff --git a/src/tools/zed_svo_grid_to_mp4.cpp b/src/tools/zed_svo_grid_to_mp4.cpp index 5a2e296..4855fab 100644 --- a/src/tools/zed_svo_grid_to_mp4.cpp +++ b/src/tools/zed_svo_grid_to_mp4.cpp @@ -6,6 +6,7 @@ #include #include +#include "cvmmap_streamer/tools/zed_progress_bar.hpp" #include "cvmmap_streamer/tools/zed_svo_mp4_support.hpp" #include diff --git a/src/tools/zed_svo_mp4_support.cpp b/src/tools/zed_svo_mp4_support.cpp index 3b7e523..c040583 100644 --- a/src/tools/zed_svo_mp4_support.cpp +++ b/src/tools/zed_svo_mp4_support.cpp @@ -11,17 +11,13 @@ extern "C" { #include } -#include #include -#include #include #include #include #include #include -#include - namespace cvmmap_streamer::zed_tools { namespace { @@ -68,22 +64,6 @@ AVRational frame_rate_rational(const float fps) { return AVRational{scaled, 1000}; } -[[nodiscard]] -std::string format_duration(const double seconds_raw) { - const auto seconds = seconds_raw > 0.0 ? static_cast(std::llround(seconds_raw)) : 0ll; - const auto hours = seconds / 3600; - const auto minutes = (seconds % 3600) / 60; - const auto secs = seconds % 60; - - char buffer[32]{}; - if (hours > 0) { - std::snprintf(buffer, sizeof(buffer), "%02lld:%02lld:%02lld", hours, minutes, secs); - } else { - std::snprintf(buffer, sizeof(buffer), "%02lld:%02lld", minutes, secs); - } - return std::string(buffer); -} - [[nodiscard]] std::vector encoder_candidates(const CodecType codec, const EncoderDeviceType device) { const std::string hardware_name = codec == CodecType::H265 ? "hevc_nvenc" : "h264_nvenc"; @@ -320,63 +300,6 @@ std::expected open_encoder( } // namespace -struct ProgressBar::Impl { - using Clock = std::chrono::steady_clock; - - explicit Impl(const std::uint64_t total_frames_arg) - : total_frames(total_frames_arg), - enabled(::isatty(STDERR_FILENO) == 1), - started_at(Clock::now()), - last_render_at(started_at) {} - - void render(const std::uint64_t completed_frames, const bool force) { - if (!enabled || total_frames == 0) { - return; - } - - const auto now = Clock::now(); - if (!force && rendered && now - last_render_at < std::chrono::milliseconds(125)) { - return; - } - last_render_at = now; - rendered = true; - - const auto bounded_completed = completed_frames > total_frames ? total_frames : completed_frames; - const double ratio = static_cast(bounded_completed) / static_cast(total_frames); - const auto filled = static_cast(std::llround(ratio * 24.0)); - std::string bar{}; - bar.reserve(24); - for (std::size_t i = 0; i < 24; ++i) { - bar.push_back(i < filled ? '#' : '-'); - } - - const auto elapsed_seconds = std::chrono::duration(now - started_at).count(); - const auto fps = elapsed_seconds > 0.0 ? static_cast(bounded_completed) / elapsed_seconds : 0.0; - const auto eta_seconds = fps > 0.0 ? static_cast(total_frames - bounded_completed) / fps : 0.0; - - char line[256]{}; - std::snprintf( - line, - sizeof(line), - "\r[%s] %6.2f%% %llu/%llu | %5.1f fps | %s elapsed | %s ETA\x1b[K", - bar.c_str(), - ratio * 100.0, - static_cast(bounded_completed), - static_cast(total_frames), - fps, - format_duration(elapsed_seconds).c_str(), - format_duration(eta_seconds).c_str()); - std::fprintf(stderr, "%s", line); - std::fflush(stderr); - } - - std::uint64_t total_frames{0}; - bool enabled{false}; - bool rendered{false}; - Clock::time_point started_at{}; - Clock::time_point last_render_at{}; -}; - struct Mp4Writer::Impl { [[nodiscard]] std::expected open( @@ -726,29 +649,6 @@ std::filesystem::path derive_output_path(const std::filesystem::path &input_path return output_path; } -ProgressBar::ProgressBar(const std::uint64_t total_frames) - : impl_(std::make_unique(total_frames)) {} - -ProgressBar::~ProgressBar() = default; - -void ProgressBar::update(const std::uint64_t completed_frames) { - impl_->render(completed_frames, false); -} - -void ProgressBar::finish(const std::uint64_t completed_frames, const bool success) { - if (impl_ == nullptr || !impl_->enabled) { - return; - } - - impl_->render(completed_frames, true); - if (!impl_->rendered) { - return; - } - - std::fprintf(stderr, "%s", success ? "\n" : " [failed]\n"); - std::fflush(stderr); -} - Mp4Writer::Mp4Writer() : impl_(std::make_unique()) {} diff --git a/src/tools/zed_svo_to_mcap.cpp b/src/tools/zed_svo_to_mcap.cpp index 49d23f2..a2e84c0 100644 --- a/src/tools/zed_svo_to_mcap.cpp +++ b/src/tools/zed_svo_to_mcap.cpp @@ -9,6 +9,7 @@ #include "cvmmap_streamer/encode/encoder_backend.hpp" #include "cvmmap_streamer/ipc/contracts.hpp" #include "cvmmap_streamer/record/mcap_record_sink.hpp" +#include "cvmmap_streamer/tools/zed_progress_bar.hpp" #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +34,9 @@ namespace { +using cvmmap_streamer::zed_tools::ProgressBar; +using cvmmap_streamer::zed_tools::stderr_supports_progress_bar; + volatile std::sig_atomic_t g_signal_count = 0; volatile std::sig_atomic_t g_last_signal = 0; @@ -922,7 +927,8 @@ std::expected register_mcap_streams( [[nodiscard]] std::expected sync_streams_to_timestamp( std::vector &streams, - const std::uint64_t effective_start_ts) { + const std::uint64_t effective_start_ts, + const bool log_sync_info = true) { bool shutdown_logged{false}; for (auto &stream : streams) { if (log_shutdown_request(shutdown_logged, "multi-camera sync")) { @@ -962,14 +968,16 @@ std::expected sync_streams_to_timestamp( } } - spdlog::info( - "ZED_SVO_MCAP_SYNC input={} label={} sync_position={} first_timestamp_ns={} current_timestamp_ns={} next_timestamp_ns={}", - stream.source.path.string(), - stream.source.label, - stream.sync_position, - stream.first_timestamp_ns, - stream.current_timestamp_ns, - stream.has_next ? stream.next_timestamp_ns : 0); + if (log_sync_info) { + spdlog::info( + "ZED_SVO_MCAP_SYNC input={} label={} sync_position={} first_timestamp_ns={} current_timestamp_ns={} next_timestamp_ns={}", + stream.source.path.string(), + stream.source.label, + stream.sync_position, + stream.first_timestamp_ns, + stream.current_timestamp_ns, + stream.has_next ? stream.next_timestamp_ns : 0); + } } return {}; } @@ -1146,6 +1154,58 @@ std::expected advance_after_emit(std::vector &s return {}; } +[[nodiscard]] +std::expected count_synced_groups( + std::vector &streams, + const std::uint64_t tolerance_ns, + const std::uint64_t common_end_ts, + const std::optional max_groups) { + bool shutdown_logged{false}; + std::uint64_t counted_groups{0}; + + while (true) { + if (log_shutdown_request(shutdown_logged, "multi-camera progress scan")) { + return std::unexpected("interrupted"); + } + auto group_timestamp = next_synced_group_timestamp(streams, tolerance_ns, common_end_ts); + if (!group_timestamp) { + return std::unexpected(group_timestamp.error()); + } + if (!*group_timestamp) { + break; + } + + counted_groups += 1; + if (max_groups && counted_groups >= *max_groups) { + break; + } + + auto advance = advance_after_emit(streams); + if (!advance) { + if (advance.error() == "end-of-svo") { + break; + } + return std::unexpected(advance.error()); + } + } + + return counted_groups; +} + +void reset_streams_after_count(std::vector &streams) { + for (auto &stream : streams) { + stream.current_tracking = {}; + stream.next_tracking = {}; + stream.current_timestamp_ns = 0; + stream.next_timestamp_ns = 0; + stream.dropped_frames = 0; + stream.sync_position = -1; + stream.has_next = false; + stream.calibration_written = false; + stream.last_tracking_state.reset(); + } +} + [[nodiscard]] int run_single_source( const CliOptions &options, @@ -1339,6 +1399,8 @@ int run_single_source( ? options.end_frame : static_cast(total_frames - 1); const auto nominal_frame_period_ns = frame_period_ns(camera_config.fps); + const auto total_frames_to_emit = static_cast(last_frame - options.start_frame + 1); + ProgressBar progress{total_frames_to_emit}; while (options.start_frame + emitted_frames <= last_frame) { if (log_shutdown_request(shutdown_logged, "single-camera export")) { @@ -1350,6 +1412,7 @@ int run_single_source( break; } if (grab_status != sl::ERROR_CODE::SUCCESS) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1359,6 +1422,7 @@ int run_single_source( const auto image_status = camera.retrieveImage(left_frame, sl::VIEW::LEFT_BGR, sl::MEM::CPU); if (image_status != sl::ERROR_CODE::SUCCESS) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1366,6 +1430,7 @@ int run_single_source( return exit_code(ToolExitCode::RuntimeError); } if (auto valid = validate_u8c3_mat(left_frame, "left image"); !valid) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1375,6 +1440,7 @@ int run_single_source( const auto depth_status = camera.retrieveMeasure(depth_frame, sl::MEASURE::DEPTH_U16_MM, sl::MEM::CPU); if (depth_status != sl::ERROR_CODE::SUCCESS) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1382,6 +1448,7 @@ int run_single_source( return exit_code(ToolExitCode::RuntimeError); } if (auto valid = validate_u16c1_mat(depth_frame, "depth map"); !valid) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1410,6 +1477,7 @@ int run_single_source( }; if (auto push = backend->push_frame(raw_video); !push) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1419,6 +1487,7 @@ int run_single_source( auto drained = backend->drain(); if (!drained) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1426,6 +1495,7 @@ int run_single_source( return exit_code(ToolExitCode::RuntimeError); } if (auto write = write_access_units(*sink, *drained); !write) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1445,6 +1515,7 @@ int run_single_source( .projection_matrix = projection_matrix, }; if (auto write = sink->write_camera_calibration(calibration); !write) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1457,6 +1528,7 @@ int run_single_source( const auto depth_step_bytes = depth_frame.getStepBytes(sl::MEM::CPU); const auto packed_depth_bytes = static_cast(width) * sizeof(std::uint16_t); if (depth_step_bytes < packed_depth_bytes) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1482,6 +1554,7 @@ int run_single_source( .pixels = depth_pixels, }; if (auto write = sink->write_depth_map_u16(depth_map); !write) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1519,6 +1592,7 @@ int run_single_source( }, }; if (auto write = sink->write_pose(pose_view); !write) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1529,9 +1603,11 @@ int run_single_source( } emitted_frames += 1; + progress.update(emitted_frames); } if (auto flushed = flush_and_write(*sink, backend); !flushed) { + progress.finish(emitted_frames, false); sink->close(); backend->shutdown(); close_camera(); @@ -1544,6 +1620,7 @@ int run_single_source( close_camera(); if (interrupted) { + progress.finish(emitted_frames, false); spdlog::warn( "gracefully stopped after writing {} frame(s) from '{}' to '{}'", emitted_frames, @@ -1552,6 +1629,7 @@ int run_single_source( return interrupted_exit_code(); } + progress.finish(emitted_frames, true); spdlog::info( "wrote {} frame(s) from '{}' to '{}'", emitted_frames, @@ -1628,21 +1706,37 @@ int run_multi_source( common_start_ts, common_end_ts, tolerance_ns); - - auto sink = cvmmap_streamer::record::MultiMcapRecordSink::create(output_path.string(), compression); - if (!sink) { - close_camera_streams(streams); - spdlog::error("failed to create MCAP sink: {}", sink.error()); - return exit_code(ToolExitCode::RuntimeError); - } - if (auto registered = register_mcap_streams(*sink, streams, options); !registered) { - sink->close(); - close_camera_streams(streams); - spdlog::error("failed to register MCAP streams: {}", registered.error()); - return exit_code(ToolExitCode::RuntimeError); + const auto render_progress = stderr_supports_progress_bar(); + std::uint64_t total_groups_to_emit{0}; + if (render_progress) { + if (auto synced = sync_streams_to_timestamp(streams, common_start_ts, false); !synced) { + close_camera_streams(streams); + if (synced.error() == "interrupted") { + return interrupted_exit_code(); + } + spdlog::error("{}", synced.error()); + return exit_code(ToolExitCode::RuntimeError); + } + if (!options.has_end_frame) { + std::fprintf(stderr, "counting synced groups for exact progress...\n"); + std::fflush(stderr); + } + const auto max_groups = options.has_end_frame + ? std::optional{static_cast(options.end_frame) + 1} + : std::nullopt; + auto counted_groups = count_synced_groups(streams, tolerance_ns, common_end_ts, max_groups); + if (!counted_groups) { + close_camera_streams(streams); + if (counted_groups.error() == "interrupted") { + return interrupted_exit_code(); + } + spdlog::error("failed to count synced groups: {}", counted_groups.error()); + return exit_code(ToolExitCode::RuntimeError); + } + total_groups_to_emit = *counted_groups; + reset_streams_after_count(streams); } if (auto synced = sync_streams_to_timestamp(streams, common_start_ts); !synced) { - sink->close(); close_camera_streams(streams); if (synced.error() == "interrupted") { return interrupted_exit_code(); @@ -1650,6 +1744,22 @@ int run_multi_source( spdlog::error("{}", synced.error()); return exit_code(ToolExitCode::RuntimeError); } + ProgressBar progress{total_groups_to_emit}; + + auto sink = cvmmap_streamer::record::MultiMcapRecordSink::create(output_path.string(), compression); + if (!sink) { + progress.finish(0, false); + close_camera_streams(streams); + spdlog::error("failed to create MCAP sink: {}", sink.error()); + return exit_code(ToolExitCode::RuntimeError); + } + if (auto registered = register_mcap_streams(*sink, streams, options); !registered) { + progress.finish(0, false); + sink->close(); + close_camera_streams(streams); + spdlog::error("failed to register MCAP streams: {}", registered.error()); + return exit_code(ToolExitCode::RuntimeError); + } std::uint64_t emitted_groups{0}; while (true) { @@ -1659,6 +1769,7 @@ int run_multi_source( } auto group_timestamp = next_synced_group_timestamp(streams, tolerance_ns, common_end_ts); if (!group_timestamp) { + progress.finish(emitted_groups, false); sink->close(); close_camera_streams(streams); if (group_timestamp.error() == "interrupted") { @@ -1672,12 +1783,14 @@ int run_multi_source( } if (auto write = encode_and_write_group(*sink, streams, options, **group_timestamp); !write) { + progress.finish(emitted_groups, false); sink->close(); close_camera_streams(streams); spdlog::error("{}", write.error()); return exit_code(ToolExitCode::RuntimeError); } emitted_groups += 1; + progress.update(emitted_groups); if (options.has_end_frame && emitted_groups > options.end_frame) { break; } @@ -1687,6 +1800,7 @@ int run_multi_source( if (advance.error() == "end-of-svo") { break; } + progress.finish(emitted_groups, false); sink->close(); close_camera_streams(streams); spdlog::error("{}", advance.error()); @@ -1696,6 +1810,7 @@ int run_multi_source( for (auto &stream : streams) { if (auto flushed = flush_and_write(*sink, stream.mcap_stream_id, *stream.backend); !flushed) { + progress.finish(emitted_groups, false); sink->close(); close_camera_streams(streams); spdlog::error("failed to finalize encoded video for {}: {}", stream.source.label, flushed.error()); @@ -1704,6 +1819,7 @@ int run_multi_source( } sink->close(); + progress.finish(emitted_groups, !interrupted); for (const auto &stream : streams) { spdlog::info( "bundled {} dropped_frame(s) for {}", diff --git a/src/tools/zed_svo_to_mp4.cpp b/src/tools/zed_svo_to_mp4.cpp index 28e743c..321a332 100644 --- a/src/tools/zed_svo_to_mp4.cpp +++ b/src/tools/zed_svo_to_mp4.cpp @@ -3,6 +3,7 @@ #include +#include "cvmmap_streamer/tools/zed_progress_bar.hpp" #include "cvmmap_streamer/tools/zed_svo_mp4_support.hpp" #include diff --git a/third_party/README.md b/third_party/README.md index b419ee7..84ff905 100644 --- a/third_party/README.md +++ b/third_party/README.md @@ -8,9 +8,16 @@ Dependencies: - `tomlplusplus` (vendored source drop): TOML parsing, target `tomlplusplus::tomlplusplus` - `mcap` (vendored source drop): MCAP headers, target `mcap::mcap` +Current submodule state: +- Active git submodules: `third_party/CLI11`, `third_party/proxy` +- Vendored source drops: `third_party/tomlplusplus`, `third_party/mcap` + Bootstrap: - `git submodule sync --recursive` - `git submodule update --init --recursive` Rule: - New vendored dependencies must live under `third_party/` and be exposed through `third_party/CMakeLists.txt`. + +TODO: +- Revisit `third_party/mcap` and convert it from a vendored source drop to a git submodule once the local Zstd fix is either upstreamed or moved into a maintained fork.