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