Package rosbag :: Module rosbag_main
[frames] | no frames]

Source Code for Module rosbag.rosbag_main

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2010, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32   
 33  from __future__ import print_function 
 34   
 35  import optparse 
 36  import os 
 37  import shutil 
 38  import signal 
 39  import subprocess 
 40  import sys 
 41  import time 
 42  try: 
 43      from UserDict import UserDict  # Python 2.x 
 44  except ImportError: 
 45      from collections import UserDict  # Python 3.x 
 46   
 47  import roslib.message 
 48  import roslib.packages 
 49   
 50  from .bag import Bag, Compression, ROSBagException, ROSBagFormatException, ROSBagUnindexedException 
 51  from .migration import MessageMigrator, fixbag2, checkbag 
 61   
62 -def handle_split(option, opt_str, value, parser):
63 parser.values.split = True 64 if len(parser.rargs) > 0 and parser.rargs[0].isdigit(): 65 sys.stderr.write("Use of \"--split <MAX_SIZE>\" has been deprecated. Please use --split --size <MAX_SIZE> or --split --duration <MAX_DURATION>") 66 parser.values.size = int(parser.rargs.pop(0))
67
68 -def record_cmd(argv):
69 parser = optparse.OptionParser(usage="rosbag record TOPIC1 [TOPIC2 TOPIC3 ...]", 70 description="Record a bag file with the contents of specified topics.", 71 formatter=optparse.IndentedHelpFormatter()) 72 73 parser.add_option("-a", "--all", dest="all", default=False, action="store_true", help="record all topics") 74 parser.add_option("-e", "--regex", dest="regex", default=False, action="store_true", help="match topics using regular expressions") 75 parser.add_option("-x", "--exclude", dest="exclude_regex", default="", action="store", help="exclude topics matching the follow regular expression (subtracts from -a or regex)") 76 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="suppress console output") 77 parser.add_option("-o", "--output-prefix", dest="prefix", default=None, action="store", help="prepend PREFIX to beginning of bag name (name will always end with date stamp)") 78 parser.add_option("-O", "--output-name", dest="name", default=None, action="store", help="record to bag with name NAME.bag") 79 parser.add_option( "--split", dest="split", default=False, callback=handle_split, action="callback", help="split the bag when maximum size or duariton is reached") 80 parser.add_option( "--size", dest="size", type='int', action="store", help="record a bag of maximum size SIZE", metavar="SIZE") 81 parser.add_option( "--duration", dest="duration", type='string',action="store", help="record a bag of maximum duration DURATION in seconds, unless 'm', or 'h' is appended.", metavar="DURATION") 82 parser.add_option("-b", "--buffsize", dest="buffsize", default=256, type='int', action="store", help="use an internal buffer of SIZE MB (Default: %default, 0 = infinite)", metavar="SIZE") 83 parser.add_option("-l", "--limit", dest="num", default=0, type='int', action="store", help="only record NUM messages on each topic") 84 parser.add_option( "--node", dest="node", default=None, type='string',action="store", help="record all topics subscribed to by a specific node") 85 parser.add_option("-j", "--bz2", dest="bz2", default=False, action="store_true", help="use BZ2 compression") 86 87 (options, args) = parser.parse_args(argv) 88 89 if len(args) == 0 and not options.all and not options.node: 90 parser.error("You must specify a topic name or else use the '-a' option.") 91 92 if options.prefix is not None and options.name is not None: 93 parser.error("Can't set both prefix and name.") 94 95 cmd = ['record'] 96 97 cmd.extend(['--buffsize', str(options.buffsize)]) 98 99 if options.num != 0: cmd.extend(['--limit', str(options.num)]) 100 if options.quiet: cmd.extend(["--quiet"]) 101 if options.prefix: cmd.extend(["-o", options.prefix]) 102 if options.name: cmd.extend(["-O", options.name]) 103 if options.exclude_regex: cmd.extend(["--exclude", options.exclude_regex]) 104 if options.all: cmd.extend(["--all"]) 105 if options.regex: cmd.extend(["--regex"]) 106 if options.bz2: cmd.extend(["--bz2"]) 107 if options.split: 108 if not options.duration and not options.size: 109 parser.error("Split specified without giving a maximum duration or size") 110 cmd.extend(["--split"]) 111 if options.duration: 112 cmd.extend(["--duration", options.duration]) 113 if options.size: 114 cmd.extend(["--size", str(options.size)]) 115 if options.node: 116 cmd.extend(["--node", options.node]) 117 118 cmd.extend(args) 119 120 recordpath = roslib.packages.find_node('rosbag', 'record') 121 if not recordpath: 122 parser.error("Cannot find rosbag/record executable") 123 os.execv(recordpath[0], cmd)
124
125 -def info_cmd(argv):
126 parser = optparse.OptionParser(usage='rosbag info [options] BAGFILE1 [BAGFILE2 BAGFILE3 ...]', 127 description='Summarize the contents of one or more bag files.') 128 parser.add_option('-y', '--yaml', dest='yaml', default=False, action='store_true', help='print information in YAML format') 129 parser.add_option('-k', '--key', dest='key', default=None, action='store', help='print information on the given key') 130 parser.add_option( '--freq', dest='freq', default=False, action='store_true', help='display topic message frequency statistics') 131 (options, args) = parser.parse_args(argv) 132 133 if len(args) == 0: 134 parser.error('You must specify at least 1 bag file.') 135 if options.key and not options.yaml: 136 parser.error('You can only specify key when printing in YAML format.') 137 138 for i, arg in enumerate(args): 139 try: 140 b = Bag(arg, 'r', skip_index=not options.freq) 141 if options.yaml: 142 info = b._get_yaml_info(key=options.key) 143 if info is not None: 144 print(info) 145 else: 146 print(b) 147 b.close() 148 if i < len(args) - 1: 149 print('---') 150 151 except ROSBagUnindexedException as ex: 152 sys.stderr.write('ERROR bag unindexed: %s. Run rosbag reindex.' % arg) 153 except ROSBagException as ex: 154 sys.stderr.write('ERROR reading %s: %s' % (arg, str(ex))) 155 except IOError as ex: 156 sys.stderr.write('ERROR reading %s: %s' % (arg, str(ex))) 157 158 print
159
160 161 -def handle_topics(option, opt_str, value, parser):
162 topics = [] 163 for arg in parser.rargs: 164 if arg[:2] == "--" and len(arg) > 2: 165 break 166 if arg[:1] == "-" and len(arg) > 1: 167 break 168 topics.append(arg) 169 parser.values.topics.extend(topics) 170 del parser.rargs[:len(topics)]
171
172 173 -def play_cmd(argv):
174 parser = optparse.OptionParser(usage="rosbag play BAGFILE1 [BAGFILE2 BAGFILE3 ...]", 175 description="Play back the contents of one or more bag files in a time-synchronized fashion.") 176 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="suppress console output") 177 parser.add_option("-i", "--immediate", dest="immediate", default=False, action="store_true", help="play back all messages without waiting") 178 parser.add_option("--pause", dest="pause", default=False, action="store_true", help="start in paused mode") 179 parser.add_option("--queue", dest="queue", default=100, type='int', action="store", help="use an outgoing queue of size SIZE (defaults to %default)", metavar="SIZE") 180 parser.add_option("--clock", dest="clock", default=False, action="store_true", help="publish the clock time") 181 parser.add_option("--hz", dest="freq", default=100, type='float', action="store", help="use a frequency of HZ when publishing clock time (default: %default)", metavar="HZ") 182 parser.add_option("-d", "--delay", dest="delay", default=0.2, type='float', action="store", help="sleep SEC seconds after every advertise call (to allow subscribers to connect)", metavar="SEC") 183 parser.add_option("-r", "--rate", dest="rate", default=1.0, type='float', action="store", help="multiply the publish rate by FACTOR", metavar="FACTOR") 184 parser.add_option("-s", "--start", dest="start", default=0.0, type='float', action="store", help="start SEC seconds into the bag files", metavar="SEC") 185 parser.add_option("--skip-empty", dest="skip_empty", default=None, type='float', action="store", help="skip regions in the bag with no messages for more than SEC seconds", metavar="SEC") 186 parser.add_option("-l", "--loop", dest="loop", default=False, action="store_true", help="loop playback") 187 parser.add_option("-k", "--keep-alive", dest="keep_alive", default=False, action="store_true", help="keep alive past end of bag (useful for publishing latched topics)") 188 parser.add_option("--try-future-version", dest="try_future", default=False, action="store_true", help="still try to open a bag file, even if the version number is not known to the player") 189 parser.add_option("--topics", dest="topics", default=[], callback=handle_topics, action="callback", help="the list topics to play back") 190 parser.add_option("--bags", help="the list bags to play back from") 191 192 (options, args) = parser.parse_args(argv) 193 194 if len(args) == 0: 195 parser.error('You must specify at least 1 bag file to play back.') 196 197 cmd = ['play'] 198 199 if options.quiet: cmd.extend(["--quiet"]) 200 if options.pause: cmd.extend(["--pause"]) 201 if options.immediate: cmd.extend(["--immediate"]) 202 if options.loop: cmd.extend(["--loop"]) 203 if options.keep_alive: cmd.extend(["--keep-alive"]) 204 if options.try_future: cmd.extend(["--try-future-version"]) 205 206 if options.clock: 207 cmd.extend(["--clock", "--hz", str(options.freq)]) 208 209 cmd.extend(['--queue', str(options.queue)]) 210 cmd.extend(['--rate', str(options.rate)]) 211 cmd.extend(['--delay', str(options.delay)]) 212 cmd.extend(['--start', str(options.start)]) 213 if options.skip_empty: 214 cmd.extend(['--skip-empty', str(options.skip_empty)]) 215 216 if options.topics: 217 cmd.extend(['--topics'] + options.topics + ['--bags']) 218 219 cmd.extend(args) 220 221 playpath = roslib.packages.find_node('rosbag', 'play') 222 if not playpath: 223 parser.error("Cannot find rosbag/play executable") 224 os.execv(playpath[0], cmd)
225
226 -def filter_cmd(argv):
227 def expr_eval(expr): 228 def eval_fn(topic, m, t): 229 return eval(expr)
230 return eval_fn 231 232 parser = optparse.OptionParser(usage="""rosbag filter [options] INBAG OUTBAG EXPRESSION 233 234 EXPRESSION can be any Python-legal expression. 235 236 The following variables are available: 237 * topic: name of topic 238 * m: message 239 * t: time of message (t.secs, t.nsecs)""", 240 description='Filter the contents of the bag.') 241 parser.add_option('-p', '--print', action='store', dest='verbose_pattern', default=None, metavar='PRINT-EXPRESSION', help='Python expression to print for verbose debugging. Uses same variables as filter-expression') 242 243 options, args = parser.parse_args(argv) 244 if len(args) == 0: 245 parser.error('You must specify an in bag, an out bag, and an expression.') 246 if len(args) == 1: 247 parser.error('You must specify an out bag and an expression.') 248 if len(args) == 2: 249 parser.error("You must specify an expression.") 250 if len(args) > 3: 251 parser.error("Too many arguments.") 252 253 inbag_filename, outbag_filename, expr = args 254 255 if not os.path.isfile(inbag_filename): 256 sys.stderr.write('Cannot locate input bag file [%s]' % inbag_filename) 257 sys.exit(2) 258 259 filter_fn = expr_eval(expr) 260 261 outbag = Bag(outbag_filename, 'w') 262 263 try: 264 inbag = Bag(inbag_filename) 265 except ROSBagUnindexedException as ex: 266 sys.stderr.write('ERROR bag unindexed: %s. Run rosbag reindex.' % arg) 267 return 268 269 try: 270 meter = ProgressMeter(outbag_filename, inbag.size) 271 total_bytes = 0 272 273 if options.verbose_pattern: 274 verbose_pattern = expr_eval(options.verbose_pattern) 275 276 for topic, raw_msg, t in inbag.read_messages(raw=True): 277 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg 278 msg = pytype() 279 msg.deserialize(serialized_bytes) 280 281 if filter_fn(topic, msg, t): 282 print('MATCH', verbose_pattern(topic, msg, t)) 283 outbag.write(topic, msg, t) 284 else: 285 print('NO MATCH', verbose_pattern(topic, msg, t)) 286 287 total_bytes += len(serialized_bytes) 288 meter.step(total_bytes) 289 else: 290 for topic, raw_msg, t in inbag.read_messages(raw=True): 291 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg 292 msg = pytype() 293 msg.deserialize(serialized_bytes) 294 295 if filter_fn(topic, msg, t): 296 outbag.write(topic, msg, t) 297 298 total_bytes += len(serialized_bytes) 299 meter.step(total_bytes) 300 301 meter.finish() 302 303 finally: 304 inbag.close() 305 outbag.close() 306
307 -def fix_cmd(argv):
308 parser = optparse.OptionParser(usage='rosbag fix INBAG OUTBAG [EXTRARULES1 EXTRARULES2 ...]', description='Repair the messages in a bag file so that it can be played in the current system.') 309 parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins') 310 parser.add_option('--force', action='store_true', dest='force', help='proceed with migrations, even if not all rules defined') 311 312 (options, args) = parser.parse_args(argv) 313 314 if len(args) < 1: 315 parser.error('You must pass input and output bag files.') 316 if len(args) < 2: 317 parser.error('You must pass an output bag file.') 318 319 inbag_filename = args[0] 320 outbag_filename = args[1] 321 rules = args[2:] 322 323 ext = os.path.splitext(outbag_filename)[1] 324 if ext == '.bmr': 325 parser.error('Input file should be a bag file, not a rule file.') 326 if ext != '.bag': 327 parser.error('Output file must be a bag file.') 328 329 outname = outbag_filename + '.tmp' 330 331 if os.path.exists(outbag_filename): 332 if not os.access(outbag_filename, os.W_OK): 333 sys.stderr.write('Don\'t have permissions to access %s' % outbag_filename) 334 sys.exit(1) 335 else: 336 try: 337 file = open(outbag_filename, 'w') 338 file.close() 339 except IOError as e: 340 sys.stderr.write('Cannot open %s for writing' % outbag_filename) 341 sys.exit(1) 342 343 if os.path.exists(outname): 344 if not os.access(outname, os.W_OK): 345 sys.stderr.write('Don\'t have permissions to access %s' % outname) 346 sys.exit(1) 347 else: 348 try: 349 file = open(outname, 'w') 350 file.close() 351 except IOError as e: 352 sys.stderr.write('Cannot open %s for writing' % outname) 353 sys.exit(1) 354 355 if options.noplugins is None: 356 options.noplugins = False 357 358 migrator = MessageMigrator(rules, plugins=not options.noplugins) 359 360 try: 361 migrations = fixbag2(migrator, inbag_filename, outname, options.force) 362 except ROSBagUnindexedException as ex: 363 sys.stderr.write('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename) 364 return 365 366 if len(migrations) == 0: 367 os.rename(outname, outbag_filename) 368 print('Bag migrated successfully.') 369 else: 370 print('Bag could not be migrated. The following migrations could not be performed:') 371 for m in migrations: 372 print_trans(m[0][0].old_class, m[0][-1].new_class, 0) 373 374 if len(m[1]) > 0: 375 print(' %d rules missing:' % len(m[1])) 376 for r in m[1]: 377 print_trans(r.old_class, r.new_class,1) 378 379 print('Try running \'rosbag check\' to create the necessary rule files or run \'rosbag fix\' with the \'--force\' option.') 380 os.remove(outname)
381
382 -def check_cmd(argv):
383 parser = optparse.OptionParser(usage='rosbag check BAG [-g RULEFILE] [EXTRARULES1 EXTRARULES2 ...]', description='Determine whether a bag is playable in the current system, or if it can be migrated.') 384 parser.add_option('-g', '--genrules', action='store', dest='rulefile', default=None, help='generate a rulefile named RULEFILE') 385 parser.add_option('-a', '--append', action='store_true', dest='append', help='append to the end of an existing rulefile after loading it') 386 parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins') 387 (options, args) = parser.parse_args(argv) 388 389 if len(args) == 0: 390 parser.error('You must specify a bag file to check.') 391 if options.append and options.rulefile is None: 392 parser.error('Cannot specify -a without also specifying -g.') 393 if options.rulefile is not None: 394 rulefile_exists = os.path.isfile(options.rulefile) 395 if rulefile_exists and not options.append: 396 parser.error('The file %s already exists. Include -a if you intend to append.' % options.rulefile) 397 if not rulefile_exists and options.append: 398 parser.error('The file %s does not exist, and so -a is invalid.' % options.rulefile) 399 400 if options.append: 401 append_rule = [options.rulefile] 402 else: 403 append_rule = [] 404 405 # First check that the bag is not unindexed 406 try: 407 Bag(args[0]) 408 except ROSBagUnindexedException as ex: 409 sys.stderr.write('ERROR bag unindexed: %s. Run rosbag reindex.' % args[0]) 410 return 411 412 mm = MessageMigrator(args[1:] + append_rule, not options.noplugins) 413 414 migrations = checkbag(mm, args[0]) 415 416 if len(migrations) == 0: 417 print('Bag file is up to date.') 418 exit(0) 419 420 print('The following migrations need to occur:') 421 all_rules = [] 422 for m in migrations: 423 all_rules.extend(m[1]) 424 425 print_trans(m[0][0].old_class, m[0][-1].new_class, 0) 426 if len(m[1]) > 0: 427 print(" %d rules missing:" % len(m[1])) 428 for r in m[1]: 429 print_trans(r.old_class, r.new_class, 1) 430 431 if options.rulefile is None: 432 if all_rules == []: 433 print("\nAll rules defined. Bag is ready to be migrated") 434 else: 435 print("\nTo generate rules, please run with -g <rulefile>") 436 exit(0) 437 438 output = '' 439 rules_left = mm.filter_rules_unique(all_rules) 440 441 if rules_left == []: 442 print("\nNo additional rule files needed to be generated. %s not created."%(options.rulefile)) 443 exit(0) 444 445 while len(rules_left) > 0: 446 extra_rules = [] 447 for r in rules_left: 448 if r.new_class is None: 449 print('The message type %s appears to have moved. Please enter the type to migrate it to.' % r.old_class._type) 450 new_type = raw_input('>') 451 new_class = roslib.message.get_message_class(new_type) 452 while new_class is None: 453 print("\'%s\' could not be found in your system. Please make sure it is built." % new_type) 454 new_type = raw_input('>') 455 new_class = roslib.message.get_message_class(new_type) 456 new_rule = mm.make_update_rule(r.old_class, new_class) 457 R = new_rule(mm, 'GENERATED.' + new_rule.__name__) 458 R.find_sub_paths() 459 new_rules = [r for r in mm.expand_rules(R.sub_rules) if r.valid == False] 460 extra_rules.extend(new_rules) 461 print('Creating the migration rule for %s requires additional missing rules:' % new_type) 462 for nr in new_rules: 463 print_trans(nr.old_class, nr.new_class,1) 464 output += R.get_class_def() 465 else: 466 output += r.get_class_def() 467 rules_left = mm.filter_rules_unique(extra_rules) 468 f = open(options.rulefile, 'a') 469 f.write(output) 470 f.close() 471 472 print('\nThe necessary rule files have been written to: %s' % options.rulefile)
473
474 -def compress_cmd(argv):
475 parser = optparse.OptionParser(usage='rosbag compress [options] BAGFILE1 [BAGFILE2 ...]', 476 description='Compress one or more bag files.') 477 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR') 478 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists') 479 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages') 480 481 (options, args) = parser.parse_args(argv) 482 483 if len(args) < 1: 484 parser.error('You must specify at least one bag file.') 485 486 op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, Compression.BZ2, options.quiet) 487 488 bag_op(args, False, lambda b: False, op, options.output_dir, options.force, options.quiet)
489
490 -def decompress_cmd(argv):
491 parser = optparse.OptionParser(usage='rosbag decompress [options] BAGFILE1 [BAGFILE2 ...]', 492 description='Decompress one or more bag files.') 493 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR') 494 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists') 495 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages') 496 497 (options, args) = parser.parse_args(argv) 498 499 if len(args) < 1: 500 parser.error('You must specify at least one bag file.') 501 502 op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, Compression.NONE, options.quiet) 503 504 bag_op(args, False, lambda b: False, op, options.output_dir, options.force, options.quiet)
505
506 -def reindex_cmd(argv):
507 parser = optparse.OptionParser(usage='rosbag reindex [options] BAGFILE1 [BAGFILE2 ...]', 508 description='Reindexes one or more bag files.') 509 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR') 510 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists') 511 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages') 512 513 (options, args) = parser.parse_args(argv) 514 515 if len(args) < 1: 516 parser.error('You must specify at least one bag file.') 517 518 op = lambda inbag, outbag, quiet: reindex_op(inbag, outbag, options.quiet) 519 520 bag_op(args, True, lambda b: b.version > 102, op, options.output_dir, options.force, options.quiet)
521
522 -def bag_op(inbag_filenames, allow_unindexed, copy_fn, op, output_dir=None, force=False, quiet=False):
523 for inbag_filename in inbag_filenames: 524 # Check we can read the file 525 try: 526 inbag = Bag(inbag_filename, 'r', allow_unindexed=allow_unindexed) 527 except ROSBagUnindexedException: 528 sys.stderr.write('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename) 529 continue 530 except (ROSBagException, IOError) as ex: 531 sys.stderr.write('ERROR reading %s: %s' % (inbag_filename, str(ex))) 532 continue 533 534 # Determine whether we should copy the bag 535 copy = copy_fn(inbag) 536 537 inbag.close() 538 539 # Determine filename for output bag 540 if output_dir is None: 541 outbag_filename = inbag_filename 542 else: 543 outbag_filename = os.path.join(output_dir, os.path.split(inbag_filename)[1]) 544 545 backup_filename = None 546 if outbag_filename == inbag_filename: 547 # Rename the input bag to ###.orig.###, and open for reading 548 backup_filename = '%s.orig%s' % os.path.splitext(inbag_filename) 549 550 if not force and os.path.exists(backup_filename): 551 if not quiet: 552 sys.stderr.write('Skipping %s. Backup path %s already exists.' % (inbag_filename, backup_filename)) 553 continue 554 555 try: 556 if copy: 557 shutil.copy(inbag_filename, backup_filename) 558 else: 559 os.rename(inbag_filename, backup_filename) 560 except OSError as ex: 561 sys.stderr.write('ERROR %s %s to %s: %s' % ('copying' if copy else 'moving', inbag_filename, backup_filename, str(ex))) 562 continue 563 564 source_filename = backup_filename 565 else: 566 if copy: 567 shutil.copy(inbag_filename, outbag_filename) 568 source_filename = outbag_filename 569 else: 570 source_filename = inbag_filename 571 572 try: 573 inbag = Bag(source_filename, 'r', allow_unindexed=allow_unindexed) 574 575 # Open the output bag file for writing 576 try: 577 if copy: 578 outbag = Bag(outbag_filename, 'a', allow_unindexed=allow_unindexed) 579 else: 580 outbag = Bag(outbag_filename, 'w') 581 except (ROSBagException, IOError) as ex: 582 sys.stderr.write('ERROR writing to %s: %s' % (outbag_filename, str(ex))) 583 inbag.close() 584 continue 585 586 # Perform the operation 587 try: 588 op(inbag, outbag, quiet=quiet) 589 except ROSBagException as ex: 590 sys.stderr.write('\nERROR operating on %s: %s' % (source_filename, str(ex))) 591 inbag.close() 592 outbag.close() 593 continue 594 595 outbag.close() 596 inbag.close() 597 598 except KeyboardInterrupt: 599 if backup_filename is not None: 600 try: 601 if copy: 602 os.remove(backup_filename) 603 else: 604 os.rename(backup_filename, inbag_filename) 605 except OSError as ex: 606 sys.stderr.write('ERROR %s %s to %s: %s', ('removing' if copy else 'moving', backup_filename, inbag_filename, str(ex))) 607 break 608 609 except (ROSBagException, IOError) as ex: 610 sys.stderr.write('ERROR operating on %s: %s' % (inbag_filename, str(ex)))
611
612 -def change_compression_op(inbag, outbag, compression, quiet):
613 outbag.compression = compression 614 615 if quiet: 616 for topic, msg, t in inbag.read_messages(raw=True): 617 outbag.write(topic, msg, t, raw=True) 618 else: 619 meter = ProgressMeter(outbag.filename, inbag._uncompressed_size) 620 621 total_bytes = 0 622 for topic, msg, t in inbag.read_messages(raw=True): 623 msg_type, serialized_bytes, md5sum, pos, pytype = msg 624 625 outbag.write(topic, msg, t, raw=True) 626 627 total_bytes += len(serialized_bytes) 628 meter.step(total_bytes) 629 630 meter.finish()
631
632 -def reindex_op(inbag, outbag, quiet):
633 if inbag.version == 102: 634 if quiet: 635 try: 636 for offset in inbag.reindex(): 637 pass 638 except: 639 pass 640 641 for (topic, msg, t) in inbag.read_messages(): 642 outbag.write(topic, msg, t) 643 else: 644 meter = ProgressMeter(outbag.filename, inbag.size) 645 try: 646 for offset in inbag.reindex(): 647 meter.step(offset) 648 except: 649 pass 650 meter.finish() 651 652 meter = ProgressMeter(outbag.filename, inbag.size) 653 for (topic, msg, t) in inbag.read_messages(): 654 outbag.write(topic, msg, t) 655 meter.step(inbag._file.tell()) 656 meter.finish() 657 else: 658 if quiet: 659 try: 660 for offset in outbag.reindex(): 661 pass 662 except: 663 pass 664 else: 665 meter = ProgressMeter(outbag.filename, outbag.size) 666 try: 667 for offset in outbag.reindex(): 668 meter.step(offset) 669 except: 670 pass 671 meter.finish()
672
673 -class RosbagCmds(UserDict):
674 - def __init__(self):
675 UserDict.__init__(self) 676 self._description = {} 677 self['help'] = self.help_cmd
678
679 - def add_cmd(self, name, function, description):
680 self[name] = function 681 self._description[name] = description
682
683 - def get_valid_cmds(self):
684 str = "Available subcommands:\n" 685 for k in sorted(self.keys()): 686 str += " %s " % k 687 if k in self._description.keys(): 688 str +="\t%s" % self._description[k] 689 str += "\n" 690 return str
691
692 - def help_cmd(self,argv):
693 argv = [a for a in argv if a != '-h' and a != '--help'] 694 695 if len(argv) == 0: 696 print('Usage: rosbag <subcommand> [options] [args]') 697 print 698 print("A bag is a file format in ROS for storing ROS message data. The rosbag command can record, replay and manipulate bags.") 699 print 700 print(self.get_valid_cmds()) 701 print('For additional information, see http://code.ros.org/wiki/rosbag/') 702 print 703 return 704 705 cmd = argv[0] 706 if cmd in self: 707 self[cmd](['-h']) 708 else: 709 sys.stderr.write("Unknown command: '%s'" % cmd) 710 sys.stderr.write() 711 sys.stderr.write(self.get_valid_cmds())
712
713 -class ProgressMeter(object):
714 - def __init__(self, path, bytes_total, refresh_rate=1.0):
715 self.path = path 716 self.bytes_total = bytes_total 717 self.refresh_rate = refresh_rate 718 719 self.elapsed = 0.0 720 self.update_elapsed = 0.0 721 self.bytes_read = 0.0 722 723 self.start_time = time.time() 724 725 self._update_progress()
726
727 - def step(self, bytes_read, force_update=False):
728 self.bytes_read = bytes_read 729 self.elapsed = time.time() - self.start_time 730 731 if force_update or self.elapsed - self.update_elapsed > self.refresh_rate: 732 self._update_progress() 733 self.update_elapsed = self.elapsed
734
735 - def _update_progress(self):
736 max_path_len = self.terminal_width() - 37 737 path = self.path 738 if len(path) > max_path_len: 739 path = '...' + self.path[-max_path_len + 3:] 740 741 bytes_read_str = self._human_readable_size(float(self.bytes_read)) 742 bytes_total_str = self._human_readable_size(float(self.bytes_total)) 743 744 if self.bytes_read < self.bytes_total: 745 complete_fraction = float(self.bytes_read) / self.bytes_total 746 pct_complete = int(100.0 * complete_fraction) 747 748 if complete_fraction > 0.0: 749 eta = self.elapsed * (1.0 / complete_fraction - 1.0) 750 eta_min, eta_sec = eta / 60, eta % 60 751 if eta_min > 99: 752 eta_str = '--:--' 753 else: 754 eta_str = '%02d:%02d' % (eta_min, eta_sec) 755 else: 756 eta_str = '--:--' 757 758 progress = '%-*s %3d%% %8s / %8s %s ETA' % (max_path_len, path, pct_complete, bytes_read_str, bytes_total_str, eta_str) 759 else: 760 progress = '%-*s 100%% %19s %02d:%02d ' % (max_path_len, path, bytes_total_str, self.elapsed / 60, self.elapsed % 60) 761 762 print('\r', progress, sys.stdout.flush())
763
764 - def _human_readable_size(self, size):
765 multiple = 1024.0 766 for suffix in ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']: 767 size /= multiple 768 if size < multiple: 769 return '%.1f %s' % (size, suffix) 770 771 raise ValueError('number too large')
772
773 - def finish(self):
774 self.step(self.bytes_total, force_update=True) 775 print
776 777 @staticmethod
778 - def terminal_width():
779 """Estimate the width of the terminal""" 780 width = 0 781 try: 782 import struct, fcntl, termios 783 s = struct.pack('HHHH', 0, 0, 0, 0) 784 x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) 785 width = struct.unpack('HHHH', x)[1] 786 except IOError: 787 pass 788 if width <= 0: 789 try: 790 width = int(os.environ['COLUMNS']) 791 except: 792 pass 793 if width <= 0: 794 width = 80 795 796 return width
797
798 -def rosbagmain(argv=None):
799 cmds = RosbagCmds() 800 cmds.add_cmd('record', record_cmd, "Record a bag file with the contents of specified topics.") 801 cmds.add_cmd('info', info_cmd, 'Summarize the contents of one or more bag files.') 802 cmds.add_cmd('play', play_cmd, "Play back the contents of one or more bag files in a time-synchronized fashion.") 803 cmds.add_cmd('check', check_cmd, 'Determine whether a bag is playable in the current system, or if it can be migrated.') 804 cmds.add_cmd('fix', fix_cmd, 'Repair the messages in a bag file so that it can be played in the current system.') 805 cmds.add_cmd('filter', filter_cmd, 'Filter the contents of the bag.') 806 cmds.add_cmd('compress', compress_cmd, 'Compress one or more bag files.') 807 cmds.add_cmd('decompress', decompress_cmd, 'Decompress one or more bag files.') 808 cmds.add_cmd('reindex', reindex_cmd, 'Reindexes one or more bag files.') 809 810 if argv is None: 811 argv = sys.argv 812 813 if '-h' in argv or '--help' in argv: 814 argv = [a for a in argv if a != '-h' and a != '--help'] 815 argv.insert(1, 'help') 816 817 if len(argv) > 1: 818 cmd = argv[1] 819 else: 820 cmd = 'help' 821 822 try: 823 if cmd in cmds: 824 cmds[cmd](argv[2:]) 825 else: 826 cmds['help']([cmd]) 827 except KeyboardInterrupt: 828 pass
829