00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import print_function
00034
00035 import optparse
00036 import os
00037 import shutil
00038 import signal
00039 import subprocess
00040 import sys
00041 import time
00042 try:
00043 from UserDict import UserDict
00044 except ImportError:
00045 from collections import UserDict
00046
00047 import roslib.message
00048 import roslib.packages
00049
00050 from .bag import Bag, Compression, ROSBagException, ROSBagFormatException, ROSBagUnindexedException
00051 from .migration import MessageMigrator, fixbag2, checkbag
00052
00053 def print_trans(old, new, indent):
00054 from_txt = '%s [%s]' % (old._type, old._md5sum)
00055 if new is not None:
00056 to_txt= '%s [%s]' % (new._type, new._md5sum)
00057 else:
00058 to_txt = 'Unknown'
00059 print(' ' * indent + ' * From: %s' % from_txt)
00060 print(' ' * indent + ' To: %s' % to_txt)
00061
00062 def handle_split(option, opt_str, value, parser):
00063 parser.values.split = True
00064 if len(parser.rargs) > 0 and parser.rargs[0].isdigit():
00065 print("Use of \"--split <MAX_SIZE>\" has been deprecated. Please use --split --size <MAX_SIZE> or --split --duration <MAX_DURATION>", file=sys.stderr)
00066 parser.values.size = int(parser.rargs.pop(0))
00067
00068 def record_cmd(argv):
00069 parser = optparse.OptionParser(usage="rosbag record TOPIC1 [TOPIC2 TOPIC3 ...]",
00070 description="Record a bag file with the contents of specified topics.",
00071 formatter=optparse.IndentedHelpFormatter())
00072
00073 parser.add_option("-a", "--all", dest="all", default=False, action="store_true", help="record all topics")
00074 parser.add_option("-e", "--regex", dest="regex", default=False, action="store_true", help="match topics using regular expressions")
00075 parser.add_option("-x", "--exclude", dest="exclude_regex", default="", action="store", help="exclude topics matching the follow regular expression (subtracts from -a or regex)")
00076 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="suppress console output")
00077 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)")
00078 parser.add_option("-O", "--output-name", dest="name", default=None, action="store", help="record to bag with name NAME.bag")
00079 parser.add_option( "--split", dest="split", default=False, callback=handle_split, action="callback", help="split the bag when maximum size or duariton is reached")
00080 parser.add_option( "--size", dest="size", type='int', action="store", help="record a bag of maximum size SIZE", metavar="SIZE")
00081 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")
00082 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")
00083 parser.add_option("--chunksize", dest="chunksize", default=768, type='int', action="store", help="Advanced. Record to chunks of SIZE KB (Default: %default)", metavar="SIZE")
00084 parser.add_option("-l", "--limit", dest="num", default=0, type='int', action="store", help="only record NUM messages on each topic")
00085 parser.add_option( "--node", dest="node", default=None, type='string',action="store", help="record all topics subscribed to by a specific node")
00086 parser.add_option("-j", "--bz2", dest="bz2", default=False, action="store_true", help="use BZ2 compression")
00087
00088 (options, args) = parser.parse_args(argv)
00089
00090 if len(args) == 0 and not options.all and not options.node:
00091 parser.error("You must specify a topic name or else use the '-a' option.")
00092
00093 if options.prefix is not None and options.name is not None:
00094 parser.error("Can't set both prefix and name.")
00095
00096 recordpath = roslib.packages.find_node('rosbag', 'record')
00097 if not recordpath:
00098 parser.error("Cannot find rosbag/record executable")
00099 cmd = [recordpath[0]]
00100
00101 cmd.extend(['--buffsize', str(options.buffsize)])
00102 cmd.extend(['--chunksize', str(options.chunksize)])
00103
00104 if options.num != 0: cmd.extend(['--limit', str(options.num)])
00105 if options.quiet: cmd.extend(["--quiet"])
00106 if options.prefix: cmd.extend(["-o", options.prefix])
00107 if options.name: cmd.extend(["-O", options.name])
00108 if options.exclude_regex: cmd.extend(["--exclude", options.exclude_regex])
00109 if options.all: cmd.extend(["--all"])
00110 if options.regex: cmd.extend(["--regex"])
00111 if options.bz2: cmd.extend(["--bz2"])
00112 if options.split:
00113 if not options.duration and not options.size:
00114 parser.error("Split specified without giving a maximum duration or size")
00115 cmd.extend(["--split"])
00116 if options.duration: cmd.extend(["--duration", options.duration])
00117 if options.size: cmd.extend(["--size", str(options.size)])
00118 if options.node:
00119 cmd.extend(["--node", options.node])
00120
00121 cmd.extend(args)
00122
00123
00124
00125 subprocess.call(cmd)
00126
00127 def info_cmd(argv):
00128 parser = optparse.OptionParser(usage='rosbag info [options] BAGFILE1 [BAGFILE2 BAGFILE3 ...]',
00129 description='Summarize the contents of one or more bag files.')
00130 parser.add_option('-y', '--yaml', dest='yaml', default=False, action='store_true', help='print information in YAML format')
00131 parser.add_option('-k', '--key', dest='key', default=None, action='store', help='print information on the given key')
00132 parser.add_option( '--freq', dest='freq', default=False, action='store_true', help='display topic message frequency statistics')
00133 (options, args) = parser.parse_args(argv)
00134
00135 if len(args) == 0:
00136 parser.error('You must specify at least 1 bag file.')
00137 if options.key and not options.yaml:
00138 parser.error('You can only specify key when printing in YAML format.')
00139
00140 for i, arg in enumerate(args):
00141 try:
00142 b = Bag(arg, 'r', skip_index=not options.freq)
00143 if options.yaml:
00144 info = b._get_yaml_info(key=options.key)
00145 if info is not None:
00146 print(info)
00147 else:
00148 print(b)
00149 b.close()
00150 if i < len(args) - 1:
00151 print('---')
00152
00153 except ROSBagUnindexedException as ex:
00154 print('ERROR bag unindexed: %s. Run rosbag reindex.' % arg,
00155 file=sys.stderr)
00156 except ROSBagException as ex:
00157 print('ERROR reading %s: %s' % (arg, str(ex)), file=sys.stderr)
00158 except IOError as ex:
00159 print('ERROR reading %s: %s' % (arg, str(ex)), file=sys.stderr)
00160
00161
00162 def handle_topics(option, opt_str, value, parser):
00163 topics = []
00164 for arg in parser.rargs:
00165 if arg[:2] == "--" and len(arg) > 2:
00166 break
00167 if arg[:1] == "-" and len(arg) > 1:
00168 break
00169 topics.append(arg)
00170 parser.values.topics.extend(topics)
00171 del parser.rargs[:len(topics)]
00172
00173
00174 def play_cmd(argv):
00175 parser = optparse.OptionParser(usage="rosbag play BAGFILE1 [BAGFILE2 BAGFILE3 ...]",
00176 description="Play back the contents of one or more bag files in a time-synchronized fashion.")
00177 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="suppress console output")
00178 parser.add_option("-i", "--immediate", dest="immediate", default=False, action="store_true", help="play back all messages without waiting")
00179 parser.add_option("--pause", dest="pause", default=False, action="store_true", help="start in paused mode")
00180 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")
00181 parser.add_option("--clock", dest="clock", default=False, action="store_true", help="publish the clock time")
00182 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")
00183 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")
00184 parser.add_option("-r", "--rate", dest="rate", default=1.0, type='float', action="store", help="multiply the publish rate by FACTOR", metavar="FACTOR")
00185 parser.add_option("-s", "--start", dest="start", default=0.0, type='float', action="store", help="start SEC seconds into the bag files", metavar="SEC")
00186 parser.add_option("-u", "--duration", dest="duration", default=None, type='float', action="store", help="play only SEC seconds from the bag files", metavar="SEC")
00187 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")
00188 parser.add_option("-l", "--loop", dest="loop", default=False, action="store_true", help="loop playback")
00189 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)")
00190 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")
00191 parser.add_option("--topics", dest="topics", default=[], callback=handle_topics, action="callback", help="topics to play back")
00192 parser.add_option("--bags", help="bags files to play back from")
00193
00194 (options, args) = parser.parse_args(argv)
00195
00196 if len(args) == 0:
00197 parser.error('You must specify at least 1 bag file to play back.')
00198
00199 playpath = roslib.packages.find_node('rosbag', 'play')
00200 if not playpath:
00201 parser.error("Cannot find rosbag/play executable")
00202 cmd = [playpath[0]]
00203
00204 if options.quiet: cmd.extend(["--quiet"])
00205 if options.pause: cmd.extend(["--pause"])
00206 if options.immediate: cmd.extend(["--immediate"])
00207 if options.loop: cmd.extend(["--loop"])
00208 if options.keep_alive: cmd.extend(["--keep-alive"])
00209 if options.try_future: cmd.extend(["--try-future-version"])
00210
00211 if options.clock:
00212 cmd.extend(["--clock", "--hz", str(options.freq)])
00213
00214 cmd.extend(['--queue', str(options.queue)])
00215 cmd.extend(['--rate', str(options.rate)])
00216 cmd.extend(['--delay', str(options.delay)])
00217 cmd.extend(['--start', str(options.start)])
00218 if options.duration:
00219 cmd.extend(['--duration', str(options.duration)])
00220 if options.skip_empty:
00221 cmd.extend(['--skip-empty', str(options.skip_empty)])
00222
00223 if options.topics:
00224 cmd.extend(['--topics'] + options.topics + ['--bags'])
00225
00226 cmd.extend(args)
00227
00228
00229 subprocess.call(cmd)
00230
00231 def filter_cmd(argv):
00232 def expr_eval(expr):
00233 def eval_fn(topic, m, t):
00234 return eval(expr)
00235 return eval_fn
00236
00237 parser = optparse.OptionParser(usage="""rosbag filter [options] INBAG OUTBAG EXPRESSION
00238
00239 EXPRESSION can be any Python-legal expression.
00240
00241 The following variables are available:
00242 * topic: name of topic
00243 * m: message
00244 * t: time of message (t.secs, t.nsecs)""",
00245 description='Filter the contents of the bag.')
00246 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')
00247
00248 options, args = parser.parse_args(argv)
00249 if len(args) == 0:
00250 parser.error('You must specify an in bag, an out bag, and an expression.')
00251 if len(args) == 1:
00252 parser.error('You must specify an out bag and an expression.')
00253 if len(args) == 2:
00254 parser.error("You must specify an expression.")
00255 if len(args) > 3:
00256 parser.error("Too many arguments.")
00257
00258 inbag_filename, outbag_filename, expr = args
00259
00260 if not os.path.isfile(inbag_filename):
00261 print('Cannot locate input bag file [%s]' % inbag_filename, file=sys.stderr)
00262 sys.exit(2)
00263
00264 if os.path.realpath(inbag_filename) == os.path.realpath(outbag_filename):
00265 print('Cannot use same file as input and output [%s]' % inbag_filename, file=sys.stderr)
00266 sys.exit(3)
00267
00268 filter_fn = expr_eval(expr)
00269
00270 outbag = Bag(outbag_filename, 'w')
00271
00272 try:
00273 inbag = Bag(inbag_filename)
00274 except ROSBagUnindexedException as ex:
00275 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename, file=sys.stderr)
00276 return
00277
00278 try:
00279 meter = ProgressMeter(outbag_filename, inbag.size)
00280 total_bytes = 0
00281
00282 if options.verbose_pattern:
00283 verbose_pattern = expr_eval(options.verbose_pattern)
00284
00285 for topic, raw_msg, t in inbag.read_messages(raw=True):
00286 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg
00287 msg = pytype()
00288 msg.deserialize(serialized_bytes)
00289
00290 if filter_fn(topic, msg, t):
00291 print('MATCH', verbose_pattern(topic, msg, t))
00292 outbag.write(topic, msg, t)
00293 else:
00294 print('NO MATCH', verbose_pattern(topic, msg, t))
00295
00296 total_bytes += len(serialized_bytes)
00297 meter.step(total_bytes)
00298 else:
00299 for topic, raw_msg, t in inbag.read_messages(raw=True):
00300 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg
00301 msg = pytype()
00302 msg.deserialize(serialized_bytes)
00303
00304 if filter_fn(topic, msg, t):
00305 outbag.write(topic, msg, t)
00306
00307 total_bytes += len(serialized_bytes)
00308 meter.step(total_bytes)
00309
00310 meter.finish()
00311
00312 finally:
00313 inbag.close()
00314 outbag.close()
00315
00316 def fix_cmd(argv):
00317 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.')
00318 parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins')
00319 parser.add_option('--force', action='store_true', dest='force', help='proceed with migrations, even if not all rules defined')
00320
00321 (options, args) = parser.parse_args(argv)
00322
00323 if len(args) < 1:
00324 parser.error('You must pass input and output bag files.')
00325 if len(args) < 2:
00326 parser.error('You must pass an output bag file.')
00327
00328 inbag_filename = args[0]
00329 outbag_filename = args[1]
00330 rules = args[2:]
00331
00332 ext = os.path.splitext(outbag_filename)[1]
00333 if ext == '.bmr':
00334 parser.error('Input file should be a bag file, not a rule file.')
00335 if ext != '.bag':
00336 parser.error('Output file must be a bag file.')
00337
00338 outname = outbag_filename + '.tmp'
00339
00340 if os.path.exists(outbag_filename):
00341 if not os.access(outbag_filename, os.W_OK):
00342 print('Don\'t have permissions to access %s' % outbag_filename, file=sys.stderr)
00343 sys.exit(1)
00344 else:
00345 try:
00346 file = open(outbag_filename, 'w')
00347 file.close()
00348 except IOError as e:
00349 print('Cannot open %s for writing' % outbag_filename, file=sys.stderr)
00350 sys.exit(1)
00351
00352 if os.path.exists(outname):
00353 if not os.access(outname, os.W_OK):
00354 print('Don\'t have permissions to access %s' % outname, file=sys.stderr)
00355 sys.exit(1)
00356 else:
00357 try:
00358 file = open(outname, 'w')
00359 file.close()
00360 except IOError as e:
00361 print('Cannot open %s for writing' % outname, file=sys.stderr)
00362 sys.exit(1)
00363
00364 if options.noplugins is None:
00365 options.noplugins = False
00366
00367 migrator = MessageMigrator(rules, plugins=not options.noplugins)
00368
00369 try:
00370 migrations = fixbag2(migrator, inbag_filename, outname, options.force)
00371 except ROSBagUnindexedException as ex:
00372 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename,
00373 file=sys.stderr)
00374 return
00375
00376 if len(migrations) == 0:
00377 os.rename(outname, outbag_filename)
00378 print('Bag migrated successfully.')
00379 else:
00380 print('Bag could not be migrated. The following migrations could not be performed:')
00381 for m in migrations:
00382 print_trans(m[0][0].old_class, m[0][-1].new_class, 0)
00383
00384 if len(m[1]) > 0:
00385 print(' %d rules missing:' % len(m[1]))
00386 for r in m[1]:
00387 print_trans(r.old_class, r.new_class,1)
00388
00389 print('Try running \'rosbag check\' to create the necessary rule files or run \'rosbag fix\' with the \'--force\' option.')
00390 os.remove(outname)
00391
00392 def check_cmd(argv):
00393 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.')
00394 parser.add_option('-g', '--genrules', action='store', dest='rulefile', default=None, help='generate a rulefile named RULEFILE')
00395 parser.add_option('-a', '--append', action='store_true', dest='append', help='append to the end of an existing rulefile after loading it')
00396 parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins')
00397 (options, args) = parser.parse_args(argv)
00398
00399 if len(args) == 0:
00400 parser.error('You must specify a bag file to check.')
00401 if options.append and options.rulefile is None:
00402 parser.error('Cannot specify -a without also specifying -g.')
00403 if options.rulefile is not None:
00404 rulefile_exists = os.path.isfile(options.rulefile)
00405 if rulefile_exists and not options.append:
00406 parser.error('The file %s already exists. Include -a if you intend to append.' % options.rulefile)
00407 if not rulefile_exists and options.append:
00408 parser.error('The file %s does not exist, and so -a is invalid.' % options.rulefile)
00409
00410 if options.append:
00411 append_rule = [options.rulefile]
00412 else:
00413 append_rule = []
00414
00415
00416 try:
00417 Bag(args[0])
00418 except ROSBagUnindexedException as ex:
00419 print('ERROR bag unindexed: %s. Run rosbag reindex.' % args[0], file=sys.stderr)
00420 return
00421
00422 mm = MessageMigrator(args[1:] + append_rule, not options.noplugins)
00423
00424 migrations = checkbag(mm, args[0])
00425
00426 if len(migrations) == 0:
00427 print('Bag file is up to date.')
00428 exit(0)
00429
00430 print('The following migrations need to occur:')
00431 all_rules = []
00432 for m in migrations:
00433 all_rules.extend(m[1])
00434
00435 print_trans(m[0][0].old_class, m[0][-1].new_class, 0)
00436 if len(m[1]) > 0:
00437 print(" %d rules missing:" % len(m[1]))
00438 for r in m[1]:
00439 print_trans(r.old_class, r.new_class, 1)
00440
00441 if options.rulefile is None:
00442 if all_rules == []:
00443 print("\nAll rules defined. Bag is ready to be migrated")
00444 else:
00445 print("\nTo generate rules, please run with -g <rulefile>")
00446 exit(0)
00447
00448 output = ''
00449 rules_left = mm.filter_rules_unique(all_rules)
00450
00451 if rules_left == []:
00452 print("\nNo additional rule files needed to be generated. %s not created."%(options.rulefile))
00453 exit(0)
00454
00455 while len(rules_left) > 0:
00456 extra_rules = []
00457 for r in rules_left:
00458 if r.new_class is None:
00459 print('The message type %s appears to have moved. Please enter the type to migrate it to.' % r.old_class._type)
00460 new_type = raw_input('>')
00461 new_class = roslib.message.get_message_class(new_type)
00462 while new_class is None:
00463 print("\'%s\' could not be found in your system. Please make sure it is built." % new_type)
00464 new_type = raw_input('>')
00465 new_class = roslib.message.get_message_class(new_type)
00466 new_rule = mm.make_update_rule(r.old_class, new_class)
00467 R = new_rule(mm, 'GENERATED.' + new_rule.__name__)
00468 R.find_sub_paths()
00469 new_rules = [r for r in mm.expand_rules(R.sub_rules) if r.valid == False]
00470 extra_rules.extend(new_rules)
00471 print('Creating the migration rule for %s requires additional missing rules:' % new_type)
00472 for nr in new_rules:
00473 print_trans(nr.old_class, nr.new_class,1)
00474 output += R.get_class_def()
00475 else:
00476 output += r.get_class_def()
00477 rules_left = mm.filter_rules_unique(extra_rules)
00478 f = open(options.rulefile, 'a')
00479 f.write(output)
00480 f.close()
00481
00482 print('\nThe necessary rule files have been written to: %s' % options.rulefile)
00483
00484 def compress_cmd(argv):
00485 parser = optparse.OptionParser(usage='rosbag compress [options] BAGFILE1 [BAGFILE2 ...]',
00486 description='Compress one or more bag files.')
00487 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR')
00488 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists')
00489 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages')
00490
00491 (options, args) = parser.parse_args(argv)
00492
00493 if len(args) < 1:
00494 parser.error('You must specify at least one bag file.')
00495
00496 op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, Compression.BZ2, options.quiet)
00497
00498 bag_op(args, False, lambda b: False, op, options.output_dir, options.force, options.quiet)
00499
00500 def decompress_cmd(argv):
00501 parser = optparse.OptionParser(usage='rosbag decompress [options] BAGFILE1 [BAGFILE2 ...]',
00502 description='Decompress one or more bag files.')
00503 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR')
00504 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists')
00505 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages')
00506
00507 (options, args) = parser.parse_args(argv)
00508
00509 if len(args) < 1:
00510 parser.error('You must specify at least one bag file.')
00511
00512 op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, Compression.NONE, options.quiet)
00513
00514 bag_op(args, False, lambda b: False, op, options.output_dir, options.force, options.quiet)
00515
00516 def reindex_cmd(argv):
00517 parser = optparse.OptionParser(usage='rosbag reindex [options] BAGFILE1 [BAGFILE2 ...]',
00518 description='Reindexes one or more bag files.')
00519 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR')
00520 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists')
00521 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages')
00522
00523 (options, args) = parser.parse_args(argv)
00524
00525 if len(args) < 1:
00526 parser.error('You must specify at least one bag file.')
00527
00528 op = lambda inbag, outbag, quiet: reindex_op(inbag, outbag, options.quiet)
00529
00530 bag_op(args, True, lambda b: b.version > 102, op, options.output_dir, options.force, options.quiet)
00531
00532 def bag_op(inbag_filenames, allow_unindexed, copy_fn, op, output_dir=None, force=False, quiet=False):
00533 for inbag_filename in inbag_filenames:
00534
00535 try:
00536 inbag = Bag(inbag_filename, 'r', allow_unindexed=allow_unindexed)
00537 except ROSBagUnindexedException:
00538 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename, file=sys.stderr)
00539 continue
00540 except (ROSBagException, IOError) as ex:
00541 print('ERROR reading %s: %s' % (inbag_filename, str(ex)), file=sys.stderr)
00542 continue
00543
00544
00545 copy = copy_fn(inbag)
00546
00547 inbag.close()
00548
00549
00550 if output_dir is None:
00551 outbag_filename = inbag_filename
00552 else:
00553 outbag_filename = os.path.join(output_dir, os.path.split(inbag_filename)[1])
00554
00555 backup_filename = None
00556 if outbag_filename == inbag_filename:
00557
00558 backup_filename = '%s.orig%s' % os.path.splitext(inbag_filename)
00559
00560 if not force and os.path.exists(backup_filename):
00561 if not quiet:
00562 print('Skipping %s. Backup path %s already exists.' % (inbag_filename, backup_filename), file=sys.stderr)
00563 continue
00564
00565 try:
00566 if copy:
00567 shutil.copy(inbag_filename, backup_filename)
00568 else:
00569 os.rename(inbag_filename, backup_filename)
00570 except OSError as ex:
00571 print('ERROR %s %s to %s: %s' % ('copying' if copy else 'moving', inbag_filename, backup_filename, str(ex)), file=sys.stderr)
00572 continue
00573
00574 source_filename = backup_filename
00575 else:
00576 if copy:
00577 shutil.copy(inbag_filename, outbag_filename)
00578 source_filename = outbag_filename
00579 else:
00580 source_filename = inbag_filename
00581
00582 try:
00583 inbag = Bag(source_filename, 'r', allow_unindexed=allow_unindexed)
00584
00585
00586 try:
00587 if copy:
00588 outbag = Bag(outbag_filename, 'a', allow_unindexed=allow_unindexed)
00589 else:
00590 outbag = Bag(outbag_filename, 'w')
00591 except (ROSBagException, IOError) as ex:
00592 print('ERROR writing to %s: %s' % (outbag_filename, str(ex)), file=sys.stderr)
00593 inbag.close()
00594 continue
00595
00596
00597 try:
00598 op(inbag, outbag, quiet=quiet)
00599 except ROSBagException as ex:
00600 print('\nERROR operating on %s: %s' % (source_filename, str(ex)), file=sys.stderr)
00601 inbag.close()
00602 outbag.close()
00603 continue
00604
00605 outbag.close()
00606 inbag.close()
00607
00608 except KeyboardInterrupt:
00609 if backup_filename is not None:
00610 try:
00611 if copy:
00612 os.remove(backup_filename)
00613 else:
00614 os.rename(backup_filename, inbag_filename)
00615 except OSError as ex:
00616 print('ERROR %s %s to %s: %s', ('removing' if copy else 'moving', backup_filename, inbag_filename, str(ex)), file=sys.stderr)
00617 break
00618
00619 except (ROSBagException, IOError) as ex:
00620 print('ERROR operating on %s: %s' % (inbag_filename, str(ex)), file=sys.stderr)
00621
00622 def change_compression_op(inbag, outbag, compression, quiet):
00623 outbag.compression = compression
00624
00625 if quiet:
00626 for topic, msg, t in inbag.read_messages(raw=True):
00627 outbag.write(topic, msg, t, raw=True)
00628 else:
00629 meter = ProgressMeter(outbag.filename, inbag._uncompressed_size)
00630
00631 total_bytes = 0
00632 for topic, msg, t in inbag.read_messages(raw=True):
00633 msg_type, serialized_bytes, md5sum, pos, pytype = msg
00634
00635 outbag.write(topic, msg, t, raw=True)
00636
00637 total_bytes += len(serialized_bytes)
00638 meter.step(total_bytes)
00639
00640 meter.finish()
00641
00642 def reindex_op(inbag, outbag, quiet):
00643 if inbag.version == 102:
00644 if quiet:
00645 try:
00646 for offset in inbag.reindex():
00647 pass
00648 except:
00649 pass
00650
00651 for (topic, msg, t) in inbag.read_messages():
00652 outbag.write(topic, msg, t)
00653 else:
00654 meter = ProgressMeter(outbag.filename, inbag.size)
00655 try:
00656 for offset in inbag.reindex():
00657 meter.step(offset)
00658 except:
00659 pass
00660 meter.finish()
00661
00662 meter = ProgressMeter(outbag.filename, inbag.size)
00663 for (topic, msg, t) in inbag.read_messages():
00664 outbag.write(topic, msg, t)
00665 meter.step(inbag._file.tell())
00666 meter.finish()
00667 else:
00668 if quiet:
00669 try:
00670 for offset in outbag.reindex():
00671 pass
00672 except:
00673 pass
00674 else:
00675 meter = ProgressMeter(outbag.filename, outbag.size)
00676 try:
00677 for offset in outbag.reindex():
00678 meter.step(offset)
00679 except:
00680 pass
00681 meter.finish()
00682
00683 class RosbagCmds(UserDict):
00684 def __init__(self):
00685 UserDict.__init__(self)
00686 self._description = {}
00687 self['help'] = self.help_cmd
00688
00689 def add_cmd(self, name, function, description):
00690 self[name] = function
00691 self._description[name] = description
00692
00693 def get_valid_cmds(self):
00694 str = "Available subcommands:\n"
00695 for k in sorted(self.keys()):
00696 str += " %s " % k
00697 if k in self._description.keys():
00698 str +="\t%s" % self._description[k]
00699 str += "\n"
00700 return str
00701
00702 def help_cmd(self,argv):
00703 argv = [a for a in argv if a != '-h' and a != '--help']
00704
00705 if len(argv) == 0:
00706 print('Usage: rosbag <subcommand> [options] [args]')
00707 print()
00708 print("A bag is a file format in ROS for storing ROS message data. The rosbag command can record, replay and manipulate bags.")
00709 print()
00710 print(self.get_valid_cmds())
00711 print('For additional information, see http://wiki.ros.org/rosbag')
00712 print()
00713 return
00714
00715 cmd = argv[0]
00716 if cmd in self:
00717 self[cmd](['-h'])
00718 else:
00719 print("Unknown command: '%s'" % cmd, file=sys.stderr)
00720 print(self.get_valid_cmds(), file=sys.stderr)
00721
00722 class ProgressMeter(object):
00723 def __init__(self, path, bytes_total, refresh_rate=1.0):
00724 self.path = path
00725 self.bytes_total = bytes_total
00726 self.refresh_rate = refresh_rate
00727
00728 self.elapsed = 0.0
00729 self.update_elapsed = 0.0
00730 self.bytes_read = 0.0
00731
00732 self.start_time = time.time()
00733
00734 self._update_progress()
00735
00736 def step(self, bytes_read, force_update=False):
00737 self.bytes_read = bytes_read
00738 self.elapsed = time.time() - self.start_time
00739
00740 if force_update or self.elapsed - self.update_elapsed > self.refresh_rate:
00741 self._update_progress()
00742 self.update_elapsed = self.elapsed
00743
00744 def _update_progress(self):
00745 max_path_len = self.terminal_width() - 37
00746 path = self.path
00747 if len(path) > max_path_len:
00748 path = '...' + self.path[-max_path_len + 3:]
00749
00750 bytes_read_str = self._human_readable_size(float(self.bytes_read))
00751 bytes_total_str = self._human_readable_size(float(self.bytes_total))
00752
00753 if self.bytes_read < self.bytes_total:
00754 complete_fraction = float(self.bytes_read) / self.bytes_total
00755 pct_complete = int(100.0 * complete_fraction)
00756
00757 if complete_fraction > 0.0:
00758 eta = self.elapsed * (1.0 / complete_fraction - 1.0)
00759 eta_min, eta_sec = eta / 60, eta % 60
00760 if eta_min > 99:
00761 eta_str = '--:--'
00762 else:
00763 eta_str = '%02d:%02d' % (eta_min, eta_sec)
00764 else:
00765 eta_str = '--:--'
00766
00767 progress = '%-*s %3d%% %8s / %8s %s ETA' % (max_path_len, path, pct_complete, bytes_read_str, bytes_total_str, eta_str)
00768 else:
00769 progress = '%-*s 100%% %19s %02d:%02d ' % (max_path_len, path, bytes_total_str, self.elapsed / 60, self.elapsed % 60)
00770
00771 print('\r', progress, end='')
00772 sys.stdout.flush()
00773
00774 def _human_readable_size(self, size):
00775 multiple = 1024.0
00776 for suffix in ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']:
00777 size /= multiple
00778 if size < multiple:
00779 return '%.1f %s' % (size, suffix)
00780
00781 raise ValueError('number too large')
00782
00783 def finish(self):
00784 self.step(self.bytes_total, force_update=True)
00785 print()
00786
00787 @staticmethod
00788 def terminal_width():
00789 """Estimate the width of the terminal"""
00790 width = 0
00791 try:
00792 import struct, fcntl, termios
00793 s = struct.pack('HHHH', 0, 0, 0, 0)
00794 x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
00795 width = struct.unpack('HHHH', x)[1]
00796 except (IOError, ImportError):
00797 pass
00798 if width <= 0:
00799 try:
00800 width = int(os.environ['COLUMNS'])
00801 except:
00802 pass
00803 if width <= 0:
00804 width = 80
00805
00806 return width
00807
00808 def rosbagmain(argv=None):
00809 cmds = RosbagCmds()
00810 cmds.add_cmd('record', record_cmd, "Record a bag file with the contents of specified topics.")
00811 cmds.add_cmd('info', info_cmd, 'Summarize the contents of one or more bag files.')
00812 cmds.add_cmd('play', play_cmd, "Play back the contents of one or more bag files in a time-synchronized fashion.")
00813 cmds.add_cmd('check', check_cmd, 'Determine whether a bag is playable in the current system, or if it can be migrated.')
00814 cmds.add_cmd('fix', fix_cmd, 'Repair the messages in a bag file so that it can be played in the current system.')
00815 cmds.add_cmd('filter', filter_cmd, 'Filter the contents of the bag.')
00816 cmds.add_cmd('compress', compress_cmd, 'Compress one or more bag files.')
00817 cmds.add_cmd('decompress', decompress_cmd, 'Decompress one or more bag files.')
00818 cmds.add_cmd('reindex', reindex_cmd, 'Reindexes one or more bag files.')
00819
00820 if argv is None:
00821 argv = sys.argv
00822
00823 if '-h' in argv or '--help' in argv:
00824 argv = [a for a in argv if a != '-h' and a != '--help']
00825 argv.insert(1, 'help')
00826
00827 if len(argv) > 1:
00828 cmd = argv[1]
00829 else:
00830 cmd = 'help'
00831
00832 try:
00833 if cmd in cmds:
00834 cmds[cmd](argv[2:])
00835 else:
00836 cmds['help']([cmd])
00837 except KeyboardInterrupt:
00838 pass