diff --git a/ros2bag/ros2bag/verb/convert.py b/ros2bag/ros2bag/verb/convert.py new file mode 100644 index 0000000000..1e2ccae063 --- /dev/null +++ b/ros2bag/ros2bag/verb/convert.py @@ -0,0 +1,71 @@ +# Copyright 2018 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import os + +from ros2bag.verb import VerbExtension + + +class ConvertVerb(VerbExtension): + """ros2 bag convert.""" + + def add_arguments(self, parser, cli_name): # noqa: D102 + parser.add_argument( + 'bag_file', help='bag file to convert') + parser.add_argument( + '-o', '--output', + help='destination of the bagfile to create, \ + defaults to a timestamped folder in the current directory') + parser.add_argument( + '-s', '--storage', default='sqlite3', + help='storage identifier to be used for the input bag, defaults to "sqlite3"') + parser.add_argument( + '--out-storage', default='sqlite3', + help='storage identifier to be used for the output bag, defaults to "sqlite3"') + parser.add_argument( + '-f', '--serialization-format', default='', + help='rmw serialization format in which the messages are saved, defaults to the' + ' rmw currently in use') + + def create_bag_directory(self, uri): + try: + os.makedirs(uri) + except OSError: + return "[ERROR] [ros2bag]: Could not create bag folder '{}'.".format(uri) + + def main(self, *, args): # noqa: D102 + bag_file = args.bag_file + if not os.path.exists(bag_file): + return "[ERROR] [ros2bag] bag file '{}' does not exist!".format(bag_file) + + uri = args.output or datetime.datetime.now().strftime('rosbag2_%Y_%m_%d-%H_%M_%S') + self.create_bag_directory(uri) + + # NOTE(hidmic): in merged install workspaces on Windows, Python entrypoint lookups + # combined with constrained environments (as imposed by colcon test) + # may result in DLL loading failures when attempting to import a C + # extension. Therefore, do not import rosbag2_transport at the module + # level but on demand, right before first use. + from rosbag2_transport import rosbag2_transport_py + + rosbag2_transport_py.convert( + in_uri=bag_file, + in_storage_id=args.storage, + out_uri=uri, + out_storage_id=args.out_storage, + serialization_format=args.serialization_format) + + if os.path.isdir(uri) and not os.listdir(uri): + os.rmdir(uri) \ No newline at end of file diff --git a/ros2bag/setup.py b/ros2bag/setup.py index 3d30d32605..48048d4ea2 100644 --- a/ros2bag/setup.py +++ b/ros2bag/setup.py @@ -41,6 +41,7 @@ 'info = ros2bag.verb.info:InfoVerb', 'play = ros2bag.verb.play:PlayVerb', 'record = ros2bag.verb.record:RecordVerb', + 'convert = ros2bag.verb.convert:ConvertVerb', ], } ) diff --git a/rosbag2_transport/CMakeLists.txt b/rosbag2_transport/CMakeLists.txt index ff9e43ea98..50cac0a285 100644 --- a/rosbag2_transport/CMakeLists.txt +++ b/rosbag2_transport/CMakeLists.txt @@ -31,6 +31,7 @@ find_package(rosbag2_cpp REQUIRED) find_package(shared_queues_vendor REQUIRED) add_library(${PROJECT_NAME} SHARED + src/rosbag2_transport/converter.cpp src/rosbag2_transport/player.cpp src/rosbag2_transport/formatter.cpp src/rosbag2_transport/generic_publisher.cpp diff --git a/rosbag2_transport/include/rosbag2_transport/rosbag2_transport.hpp b/rosbag2_transport/include/rosbag2_transport/rosbag2_transport.hpp index f04ebcb335..4e1e945adf 100644 --- a/rosbag2_transport/include/rosbag2_transport/rosbag2_transport.hpp +++ b/rosbag2_transport/include/rosbag2_transport/rosbag2_transport.hpp @@ -76,6 +76,18 @@ class Rosbag2Transport ROSBAG2_TRANSPORT_PUBLIC void play(const StorageOptions & storage_options, const PlayOptions & play_options); + /** + * Convert a bagfile from one storage to another. + * + * \param input_storage_options Options regarding the input storage (e.g. input bag file name) + * \param output_storage_options Options regarding the output storage (e.g. bag file name) + */ + ROSBAG2_TRANSPORT_PUBLIC + void convert( + const StorageOptions & input_storage_options, + const StorageOptions & output_storage_options, + std::string output_serialization_format); + /** * Print the bag info contained in the metadata yaml file. * diff --git a/rosbag2_transport/src/rosbag2_transport/converter.cpp b/rosbag2_transport/src/rosbag2_transport/converter.cpp new file mode 100644 index 0000000000..4ca00d7c62 --- /dev/null +++ b/rosbag2_transport/src/rosbag2_transport/converter.cpp @@ -0,0 +1,40 @@ +// Copyright 2018 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "converter.hpp" + +#include "rosbag2_cpp/reader.hpp" +#include "rosbag2_cpp/writer.hpp" +#include "rosbag2_transport/logging.hpp" + +namespace rosbag2_transport +{ +Converter::Converter( + std::shared_ptr reader, + std::shared_ptr writer) +: reader_(std::move(reader)), writer_(std::move(writer)) {} + +void Converter::convert(std::string serialization_format) +{ + for (auto topic : reader_->get_all_topics_and_types()) { + topic.serialization_format = serialization_format; + writer_->create_topic(topic); + } + while (reader_->has_next()) { + auto msg = reader_->read_next(); + writer_->write(msg); + } +} + +} // namespace rosbag2_transport diff --git a/rosbag2_transport/src/rosbag2_transport/converter.hpp b/rosbag2_transport/src/rosbag2_transport/converter.hpp new file mode 100644 index 0000000000..2af9ce070a --- /dev/null +++ b/rosbag2_transport/src/rosbag2_transport/converter.hpp @@ -0,0 +1,56 @@ +// Copyright 2018 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ROSBAG2_TRANSPORT__CONVERTER_HPP_ +#define ROSBAG2_TRANSPORT__CONVERTER_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include "rosbag2_cpp/types.hpp" +#include "rosbag2_cpp/reader.hpp" +#include "rosbag2_cpp/writer.hpp" +#include "rosbag2_transport/storage_options.hpp" + +namespace rosbag2 +{ +class Writer; +class Reader; +} + +namespace rosbag2_transport +{ + +class Converter +{ +public: + explicit Converter( + std::shared_ptr reader, + std::shared_ptr writer); + + void convert(std::string serialization_format); + +private: + std::shared_ptr reader_; + std::shared_ptr writer_; +}; + +} // namespace rosbag2_transport + +#endif // ROSBAG2_TRANSPORT__CONVERTER_HPP_ diff --git a/rosbag2_transport/src/rosbag2_transport/rosbag2_transport.cpp b/rosbag2_transport/src/rosbag2_transport/rosbag2_transport.cpp index 063e22d020..34d8371390 100644 --- a/rosbag2_transport/src/rosbag2_transport/rosbag2_transport.cpp +++ b/rosbag2_transport/src/rosbag2_transport/rosbag2_transport.cpp @@ -33,6 +33,7 @@ #include "rosbag2_transport/logging.hpp" +#include "converter.hpp" #include "formatter.hpp" #include "player.hpp" #include "recorder.hpp" @@ -104,6 +105,24 @@ void Rosbag2Transport::play( } } +void Rosbag2Transport::convert( + const StorageOptions & input_storage_options, + const StorageOptions & output_storage_options, + std::string output_serialization_format) +{ + try { + reader_->open(input_storage_options, {"", output_serialization_format}); + writer_->open( + output_storage_options, + {output_serialization_format, output_serialization_format}); + + Converter converter(reader_, writer_); + converter.convert(output_serialization_format); + } catch (std::runtime_error & e) { + ROSBAG2_TRANSPORT_LOG_ERROR("Failed to convert: %s", e.what()); + } +} + void Rosbag2Transport::print_bag_info(const std::string & uri, const std::string & storage_id) { rosbag2_storage::BagMetadata metadata; diff --git a/rosbag2_transport/src/rosbag2_transport/rosbag2_transport_python.cpp b/rosbag2_transport/src/rosbag2_transport/rosbag2_transport_python.cpp index e62e22a154..54ca4c13fb 100644 --- a/rosbag2_transport/src/rosbag2_transport/rosbag2_transport_python.cpp +++ b/rosbag2_transport/src/rosbag2_transport/rosbag2_transport_python.cpp @@ -220,6 +220,43 @@ rosbag2_transport_info(PyObject * Py_UNUSED(self), PyObject * args, PyObject * k Py_RETURN_NONE; } +static PyObject * +rosbag2_transport_convert(PyObject * Py_UNUSED(self), PyObject * args, PyObject * kwargs) +{ + rosbag2_transport::StorageOptions in_options{}; + rosbag2_transport::StorageOptions out_options{}; + static const char * kwlist[] = {"in_uri", "in_storage_id", "out_uri", "out_storage_id", "serialization_format", nullptr}; + + char * in_uri; + char * in_storage_id; + char * out_uri; + char * out_storage_id; + char * serialization_format; + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "sssss", const_cast(kwlist), &in_uri, &in_storage_id, + &out_uri, + &out_storage_id, + &serialization_format)) + { + return nullptr; + } + + in_options.uri = std::string(in_uri); + in_options.storage_id = std::string(in_storage_id); + + out_options.uri = std::string(out_uri); + out_options.storage_id = std::string(out_storage_id); + + std::string rmw_serialization_format = std::string(serialization_format).empty() ? + rmw_get_serialization_format() : + serialization_format; + + rosbag2_transport::Rosbag2Transport transport; + transport.convert(in_options, out_options, rmw_serialization_format); + + Py_RETURN_NONE; +} + /// Define the public methods of this module #if __GNUC__ >= 8 # pragma GCC diagnostic push @@ -238,6 +275,11 @@ static PyMethodDef rosbag2_transport_methods[] = { "info", reinterpret_cast(rosbag2_transport_info), METH_VARARGS | METH_KEYWORDS, "Print bag info" }, + { + "convert", reinterpret_cast(rosbag2_transport_convert), + METH_VARARGS | METH_KEYWORDS, + "Convert bag storage" + }, {nullptr, nullptr, 0, nullptr} /* sentinel */ }; #if __GNUC__ >= 8