Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions tools/python_tools/cuvslam_tools/bag2edex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,34 @@ rosbag_extract_urdf \
```


## Generating Missing `metadata.yaml` for MCAP Files

`make_mcap_metadata.py` regenerates the `metadata.yaml` file that `rosbag2` requires next to an `.mcap` file. Use it when you have a standalone `.mcap` file without its companion `metadata.yaml` — for example, after downloading only the `.mcap` from a recording system or after the metadata file was accidentally deleted.

**Prerequisites:** `ros2` must be on `PATH` (the script calls `ros2 bag info` internally).
Comment on lines +86 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document the MCAP storage plugin prerequisite. ros2 on PATH is not enough here; the sourced ROS 2 environment also needs the MCAP storage plugin available for ros2 bag info --storage mcap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/python_tools/cuvslam_tools/bag2edex/README.md` around lines 86 - 90,
Update the “Generating Missing metadata.yaml for MCAP Files” prerequisites
section to state that the ROS 2 environment must be sourced and the MCAP storage
plugin must be installed and available for `ros2 bag info --storage mcap`, in
addition to `ros2` being on PATH.


**Usage:**
```sh
./make_mcap_metadata.py path/to/rosbag.mcap
```

The script writes `metadata.yaml` into the same directory as the `.mcap` file. Once generated, the directory can be used with the extraction tools above.

**Optional argument:**
- `--version <int>` — sets the `rosbag2_bagfile_information.version` field in the metadata (default: `9`). Only change this if you need to match a specific rosbag2 version.
Comment on lines +99 to +100

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show surrounding README context
sed -n '1,180p' tools/python_tools/cuvslam_tools/bag2edex/README.md

# Find other mentions of rosbag2/version in this area
rg -n --hidden --no-messages "version|rosbag2|Humble|Jazzy" tools/python_tools/cuvslam_tools/bag2edex

Repository: nvidia-isaac/cuVSLAM

Length of output: 2029


🌐 Web query:

rosbag2_storage bag_metadata version default Humble Jazzy docs

💡 Result:

In ROS 2, the rosbag2 metadata format is versioned to track changes in the BagMetadata structure [1][2][3]. As of ROS 2 Jazzy, the BagMetadata version is 9 [1][4][3]. Key details regarding metadata versioning and compatibility between Humble and Jazzy include: 1. Version Evolution: The metadata version number is internal to rosbag2_storage and increments whenever the BagMetadata struct or its serialization format changes [1][2][3]. Version 9 introduced updates to QoS metadata serialization and enums [3]. 2. Compatibility Issues: Bag files recorded in newer versions of ROS 2 (like Jazzy, which uses version 9) are often incompatible with older versions (like Humble) [5][6][7]. Attempting to play a Jazzy-recorded bag in Humble typically results in a yaml-cpp parsing error due to differences in the metadata.yaml structure, specifically regarding the handling of offered_qos_profiles, the addition of fields like type_description_hash, custom_data, and ros_distro in newer versions [5][6][7]. 3. Default Behavior: While the rosbag2 tool provides default storage plugins (such as mcap or sqlite3) [8], the metadata.yaml file is generated by the rosbag2_storage library [9][8], which strictly enforces the schema corresponding to the current ROS distribution's BagMetadata version [2][3]. Users experiencing compatibility errors when moving bags from newer distributions to older ones often must convert or manually refactor the metadata.yaml file to match the expected format of the older distribution [7].

Citations:


🌐 Web query:

site:docs.ros.org humble rosbag2_storage bag_metadata version 5

💡 Result:

In ROS 2 Humble, the rosbag2_storage package defines the bag metadata structure with version 5 [1][2]. The BagMetadata structure is primarily used to manage recording information and includes a version field, which is incremented when the structure of the metadata changes to ensure backward compatibility and proper serialization [2]. Specifically, in version 5 of the metadata schema, the structure introduced support for the files field, which contains a vector of FileInformation objects [3][4]. This field allows the system to explicitly track multiple files associated with a single bag, which is critical for storage formats that split recordings across several files [1][4]. The metadata is typically serialized into a metadata.yaml file [5]. When the system reads this file, it uses the version number to decide which fields to parse [3][4]. For instance, the decoding logic in rosbag2_storage specifically checks if the version is 5 or greater to correctly deserialize the files information [3][4]. While version 5 was current for Humble, newer versions of ROS 2 have subsequently introduced higher metadata versions to support additional features, such as custom data maps (version 6) and ROS distribution tracking (version 8) [3][6].

Citations:


🌐 Web query:

site:docs.ros.org/en/humble/p/rosbag2_storage/generated/program_listing_file_include_rosbag2_storage_bag_metadata.hpp.html BagMetadata version

💡 Result:

In the context of the ROS 2 (Robot Operating System) rosbag2_storage library, the BagMetadata structure includes a version field that is used to track changes to the content of the structure [1]. As of the documentation for rosbag2_storage version 0.15.16, the defined version is 5 [1]. The documentation explicitly notes that this number should be upgraded whenever the content of the struct is changed [1].

Citations:


Clarify --version as ROS-distro-specific The default depends on the target rosbag2 release: Humble uses metadata version 5, while Jazzy uses 9. Document the supported distro/version pairs here instead of implying 9 is universal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/python_tools/cuvslam_tools/bag2edex/README.md` around lines 99 - 100,
Update the --version option documentation in the bag2edex README to state that
the default is ROS-distribution-specific: Humble uses metadata version 5 and
Jazzy uses version 9. Document these supported distro/version pairs instead of
presenting 9 as the universal default.


**Example:**
```sh
Comment on lines +92 to +103

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank lines around fenced code blocks.

markdownlint reports MD031 for both added fences. Insert a blank line before each opening fence.

Proposed fix
 **Usage:**
+
 ```sh
 ...
 **Example:**
+
 ```sh
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Usage:**
```sh
./make_mcap_metadata.py path/to/rosbag.mcap
```
The script writes `metadata.yaml` into the same directory as the `.mcap` file. Once generated, the directory can be used with the extraction tools above.
**Optional argument:**
- `--version <int>` — sets the `rosbag2_bagfile_information.version` field in the metadata (default: `9`). Only change this if you need to match a specific rosbag2 version.
**Example:**
```sh
**Usage:**
🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 93-93: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 103-103: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/python_tools/cuvslam_tools/bag2edex/README.md` around lines 92 - 103,
Update the Markdown in the Usage and Example sections of README.md by adding a
blank line before each opening fenced code block, ensuring both fences satisfy
markdownlint MD031 without changing their content.

Source: Linters/SAST tools

./make_mcap_metadata.py /data/bags/my_recording/my_recording_0.mcap
# Wrote: /data/bags/my_recording/metadata.yaml

rosbag_extract_edex \
--config configs/my_config.yaml \
--rosbag_path /data/bags/my_recording \
--output_path /data/edex/my_recording
```


## License

Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
109 changes: 109 additions & 0 deletions tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA software released under the NVIDIA Community License is intended to be used to enable
# the further development of AI and robotics technologies. Such software has been designed, tested,
# and optimized for use with NVIDIA hardware, and this License grants permission to use the software
# solely with such hardware.
# Subject to the terms of this License, NVIDIA confirms that you are free to commercially use,
# modify, and distribute the software with NVIDIA hardware. NVIDIA does not claim ownership of any
# outputs generated using the software or derivative works thereof. Any code contributions that you
# share with NVIDIA are licensed to NVIDIA as feedback under this License and may be incorporated
# in future releases without notice or attribution.
# By using, reproducing, modifying, distributing, performing, or displaying any portion or element
# of the software or derivative works thereof, you agree to be bound by this License.

import argparse
import re
import subprocess
from pathlib import Path


def ns_from_seconds(seconds: str) -> int:
return int(round(float(seconds) * 1_000_000_000))


def ns_from_epoch(sec: str, nsec: str) -> int:
return int(sec) * 1_000_000_000 + int(nsec.ljust(9, "0")[:9])


def main():
parser = argparse.ArgumentParser()
parser.add_argument("mcap", help="Path to .mcap file")
parser.add_argument("--version", type=int, default=9)
args = parser.parse_args()

mcap_path = Path(args.mcap).resolve()
bag_dir = mcap_path.parent
metadata_path = bag_dir / "metadata.yaml"

result = subprocess.run(
["ros2", "bag", "info", "--storage", "mcap", str(mcap_path)],
check=True,
text=True,
capture_output=True,
)

text = result.stdout

duration = re.search(r"Duration:\s+([0-9.]+)s", text)
start = re.search(r"Start:.*\((\d+)\.(\d+)\)", text)
messages = re.search(r"Messages:\s+(\d+)", text)

if not duration or not start or not messages:
raise RuntimeError("Could not parse ros2 bag info output")

duration_ns = ns_from_seconds(duration.group(1))
start_ns = ns_from_epoch(start.group(1), start.group(2))
total_messages = int(messages.group(1))

topic_re = re.compile(
r"Topic:\s+(.+?)\s+\|\s+Type:\s+(.+?)\s+\|\s+Count:\s+(\d+)\s+\|\s+Serialization Format:\s+(\S+)"
)

topics = topic_re.findall(text)

if not topics:
raise RuntimeError("No topics found in ros2 bag info output")

rel_file = mcap_path.name

yaml = []
yaml.append("rosbag2_bagfile_information:")
yaml.append(f" version: {args.version}")
yaml.append(" storage_identifier: mcap")
yaml.append(" duration:")
yaml.append(f" nanoseconds: {duration_ns}")
yaml.append(" starting_time:")
yaml.append(f" nanoseconds_since_epoch: {start_ns}")
yaml.append(f" message_count: {total_messages}")
yaml.append(" relative_file_paths:")
yaml.append(f" - {rel_file}")
yaml.append(" files:")
yaml.append(f" - path: {rel_file}")
yaml.append(" starting_time:")
yaml.append(f" nanoseconds_since_epoch: {start_ns}")
yaml.append(" duration:")
yaml.append(f" nanoseconds: {duration_ns}")
yaml.append(f" message_count: {total_messages}")
yaml.append(" topics_with_message_count:")

for name, msg_type, count, serialization in topics:
yaml.append(" - topic_metadata:")
yaml.append(f" name: {name}")
yaml.append(f" type: {msg_type}")
yaml.append(f" serialization_format: {serialization}")
yaml.append(' offered_qos_profiles: ""')
Comment on lines +73 to +96

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File outline ==\n'
ast-grep outline tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py --view expanded || true

printf '\n== Relevant source excerpts ==\n'
sed -n '1,220p' tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py | cat -n

printf '\n== README excerpt ==\n'
sed -n '1,180p' tools/python_tools/cuvslam_tools/bag2edex/README.md | cat -n

printf '\n== Search for type_description_hash / version handling ==\n'
rg -n "type_description_hash|rosbag2_bagfile_information.version|version:" tools/python_tools/cuvslam_tools/bag2edex -S

Repository: nvidia-isaac/cuVSLAM

Length of output: 2029


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py')
text = p.read_text()
print('HAS type_description_hash:', 'type_description_hash' in text)
print('HAS version 9 literal:', 'version: 9' in text or 'version = 9' in text)
PY

Repository: nvidia-isaac/cuVSLAM

Length of output: 2029


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the generator and any local documentation for metadata version/schema requirements.
sed -n '1,220p' tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py | cat -n
printf '\n--- README ---\n'
sed -n '1,180p' tools/python_tools/cuvslam_tools/bag2edex/README.md | cat -n
printf '\n--- Search ---\n'
rg -n "type_description_hash|rosbag2_bagfile_information.version|version 9|version 5|Jazzy|Humble" tools/python_tools/cuvslam_tools/bag2edex -S

Repository: nvidia-isaac/cuVSLAM

Length of output: 2029


🌐 Web query:

rosbag2_bagfile_information metadata version 9 type_description_hash required Humble Jazzy

💡 Result:

The metadata version 9 in rosbag2 introduces schema changes that are incompatible with older ROS 2 distributions such as Humble [1][2]. The type_description_hash is a field added to the TopicMetadata structure in newer ROS 2 versions (like Jazzy) and is serialized in version 9 metadata files [3][4][5]. In ROS 2 Humble, the rosbag2 metadata parser does not recognize this field, nor does it handle the updated serialization format for offered_qos_profiles, leading to parsing errors (e.g., yaml-cpp: error ... bad conversion) when attempting to play bags recorded in newer distributions [2][6][7]. Key differences in version 9 metadata include: - Inclusion of the type_description_hash field for topics [3][5]. - Changes to the serialization of offered_qos_profiles (using string names/enums instead of raw integer values) [4][8][7]. - Inclusion of additional fields such as ros_distro and custom_data [9][2]. Because these changes involve structural updates to how metadata is parsed and validated, version 9 metadata files are generally not backward compatible with Humble [1][2]. Users encountering these issues often need to manually edit the metadata.yaml file to remove incompatible fields (like type_description_hash, ros_distro, and custom_data) and revert offered_qos_profiles to a format compatible with Humble, or use third-party conversion scripts [6].

Citations:


🌐 Web query:

site:docs.ros.org rosbag2 metadata version 9 type_description_hash version 5 Humble

💡 Result:

In ROS 2, rosbag2 metadata files use a versioning system to track changes in the metadata schema [1][2][3]. The specific fields you mentioned are associated with different versions of this schema: The type_description_hash field was introduced to TopicMetadata in metadata version 7 [1][2]. In versions prior to 7, this field is not present or is treated as an empty string [1][2]. Regarding metadata version 9: This version introduced updates to how QoS (Quality of Service) profiles are serialized and handled within the metadata files [2]. Specifically, in versions 9 and later, the offered_qos_profiles field is serialized using YAML::convert rather than being stored as a raw string [2]. For ROS 2 Humble, the metadata version is generally lower than 9 (Humble typically uses version 5 or 6 depending on the specific release patch), meaning it does not support the version 9 metadata schema features such as the updated QoS serialization or the field structures associated with newer ROS 2 distributions [4][1][2]. The type_description_hash field may also be unavailable or handled differently depending on the specific patch version of Humble, as it was added in later development cycles [1][2]. In summary, metadata version 9 is a more recent schema evolution used in newer ROS 2 distributions (such as Rolling or Jazzy), whereas Humble relies on an older version of the schema [4][1][2][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where the metadata file and version flag are used.
rg -n "make_mcap_metadata|--version|metadata.version|topics_with_message_count|type_description_hash" tools/python_tools -S

# Show the script in smaller slices if it is large.
wc -l tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py
sed -n '1,140p' tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py | cat -n

Repository: nvidia-isaac/cuVSLAM

Length of output: 2029


🌐 Web query:

"rosbag2_bagfile_information" "type_description_hash" required version 9

💡 Result:

In the context of ROS 2 rosbag2 metadata, version 9 is a storage format version that includes the type_description_hash field within the topic metadata [1][2]. This field is a string, typically prefixed with RIHS01_ (standing for ROS Interface Hash Specification), which provides a unique identifier representing the structure and type of the ROS 2 message [3][4]. The type_description_hash was originally introduced in version 7 of the rosbag2 metadata format [1][2]. Version 9 specifically introduced changes to how Quality of Service (QoS) metadata is serialized and handled, moving away from string-based representations toward deserialized structures in the code [1][2][5]. Because of these changes to the metadata structure and the handling of QoS profiles, older versions of ROS 2 (such as Humble) that only support earlier metadata formats cannot natively parse metadata.yaml files generated by version 9 [6][4]. Summary of Key Aspects: - Format Version: The version: 9 entry in metadata.yaml indicates the bag is using the current, more structured serialization format [3][2]. - Type Description Hash: The type_description_hash (e.g., RIHS01_...) allows tools to verify that the message type used during recording matches the type expected during playback, helping to avoid issues with changed message definitions [3][4]. - Compatibility: Version 9 metadata is not backward-compatible with older ROS 2 distributions (e.g., Humble) [4]. Attempting to open these bags in older environments often results in parsing errors because the YAML structure for QoS and other fields has evolved [6][4].

Citations:


Align the metadata schema with the ROS distribution you’re targeting.

  • tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py#L73-L96: version: 9 expects newer metadata fields like type_description_hash and updated QoS serialization, so the emitted YAML won’t be compatible with Humble-era rosbag2. Either emit the v9 fields or default to the older schema.
  • tools/python_tools/cuvslam_tools/bag2edex/README.md#L99-L100: document the supported Humble/Jazzy version split instead of presenting 9 as a universal default.
📍 Affects 2 files
  • tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py#L73-L96 (this comment)
  • tools/python_tools/cuvslam_tools/bag2edex/README.md#L99-L100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py` around lines
73 - 96, Update metadata generation around the YAML-building flow in
make_mcap_metadata.py to use a Humble-compatible default schema, or
conditionally emit the v9 fields including type_description_hash and updated QoS
serialization when version 9 is selected; preserve the existing topic metadata
values. In tools/python_tools/cuvslam_tools/bag2edex/README.md lines 99-100,
document the supported Humble/Jazzy version split and stop presenting version 9
as universal.

yaml.append(f" message_count: {count}")
Comment on lines +69 to +97

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Escape all dynamic YAML scalars.

The MCAP filename and parsed ros2 bag info values are interpolated directly into YAML. Filenames or metadata containing YAML syntax can make the generated file invalid or change the parsed value. Quote/escape every dynamic scalar, preferably with a YAML emitter or JSON-style quoted strings.

Proposed fix
+import json
...
-    yaml.append(f"    - {rel_file}")
+    yaml.append(f"    - {json.dumps(rel_file)}")
...
-    yaml.append(f"    - path: {rel_file}")
+    yaml.append(f"    - path: {json.dumps(rel_file)}")
...
-        yaml.append(f"        name: {name}")
-        yaml.append(f"        type: {msg_type}")
-        yaml.append(f"        serialization_format: {serialization}")
+        yaml.append(f"        name: {json.dumps(name)}")
+        yaml.append(f"        type: {json.dumps(msg_type)}")
+        yaml.append(f"        serialization_format: {json.dumps(serialization)}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rel_file = mcap_path.name
yaml = []
yaml.append("rosbag2_bagfile_information:")
yaml.append(f" version: {args.version}")
yaml.append(" storage_identifier: mcap")
yaml.append(" duration:")
yaml.append(f" nanoseconds: {duration_ns}")
yaml.append(" starting_time:")
yaml.append(f" nanoseconds_since_epoch: {start_ns}")
yaml.append(f" message_count: {total_messages}")
yaml.append(" relative_file_paths:")
yaml.append(f" - {rel_file}")
yaml.append(" files:")
yaml.append(f" - path: {rel_file}")
yaml.append(" starting_time:")
yaml.append(f" nanoseconds_since_epoch: {start_ns}")
yaml.append(" duration:")
yaml.append(f" nanoseconds: {duration_ns}")
yaml.append(f" message_count: {total_messages}")
yaml.append(" topics_with_message_count:")
for name, msg_type, count, serialization in topics:
yaml.append(" - topic_metadata:")
yaml.append(f" name: {name}")
yaml.append(f" type: {msg_type}")
yaml.append(f" serialization_format: {serialization}")
yaml.append(' offered_qos_profiles: ""')
yaml.append(f" message_count: {count}")
rel_file = mcap_path.name
yaml = []
yaml.append("rosbag2_bagfile_information:")
yaml.append(f" version: {args.version}")
yaml.append(" storage_identifier: mcap")
yaml.append(" duration:")
yaml.append(f" nanoseconds: {duration_ns}")
yaml.append(" starting_time:")
yaml.append(f" nanoseconds_since_epoch: {start_ns}")
yaml.append(f" message_count: {total_messages}")
yaml.append(" relative_file_paths:")
yaml.append(f" - {json.dumps(rel_file)}")
yaml.append(" files:")
yaml.append(f" - path: {json.dumps(rel_file)}")
yaml.append(" starting_time:")
yaml.append(f" nanoseconds_since_epoch: {start_ns}")
yaml.append(" duration:")
yaml.append(f" nanoseconds: {duration_ns}")
yaml.append(f" message_count: {total_messages}")
yaml.append(" topics_with_message_count:")
for name, msg_type, count, serialization in topics:
yaml.append(" - topic_metadata:")
yaml.append(f" name: {json.dumps(name)}")
yaml.append(f" type: {json.dumps(msg_type)}")
yaml.append(f" serialization_format: {json.dumps(serialization)}")
yaml.append(' offered_qos_profiles: ""')
yaml.append(f" message_count: {count}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py` around lines
69 - 97, Update the YAML generation block around the yaml list and topics loop
to serialize every interpolated scalar safely, including rel_file, args.version,
duration_ns, start_ns, total_messages, and topic fields. Use a YAML emitter or
JSON-style quoting/escaping for dynamic values while preserving numeric types
where required by the metadata schema, and ensure filenames and parsed ros2 bag
info values cannot alter YAML structure or parsed content.


yaml.append(' compression_format: ""')
yaml.append(' compression_mode: ""')
yaml.append(" custom_data: {}")
yaml.append("")

metadata_path.write_text("\n".join(yaml))
Comment on lines +40 to +104

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the CLI contract.

Mock representative ros2 bag info output and cover timestamp conversion, special filenames, version ≥7 metadata, empty-topic bags, and existing metadata.yaml behavior.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 63-63: XPath query is request-/variable-derived; use parameterized XPath to prevent injection.
Context: topic_re.findall(text)
Note: [CWE-643] Improper Neutralization of Data within XPath Expressions ('XPath Injection').

(xpath-injection-python)

🪛 Ruff (0.15.21)

[error] 40-40: subprocess call: check for execution of untrusted input

(S603)


[error] 41-41: Starting a process with a partial executable path

(S607)


[warning] 54-54: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 67-67: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py` around lines
40 - 104, Add regression tests for the CLI metadata-generation flow, mocking
representative ros2 bag info output. Cover timestamp conversion, filenames
requiring safe handling, version values of at least 7, bags with no topics, and
behavior when metadata.yaml already exists; anchor the tests to the relevant CLI
entry point and metadata-writing logic without changing unrelated behavior.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not silently overwrite existing metadata.

write_text() truncates an existing metadata.yaml, even though this workflow is documented for missing metadata. Refuse by default or require an explicit --force, and write through a temporary file before replacing the destination to avoid leaving a partial file after interruption.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/python_tools/cuvslam_tools/bag2edex/make_mcap_metadata.py` at line 104,
Update the metadata-writing flow around metadata_path.write_text to refuse
overwriting an existing metadata.yaml by default, allowing replacement only when
an explicit --force option is provided. Write the generated YAML to a temporary
file in the destination directory, then atomically replace metadata_path after
the write succeeds so interruptions cannot leave a partial metadata file.

print(f"Wrote: {metadata_path}")


if __name__ == "__main__":
main()
Loading