1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
44 except ImportError:
45 from collections import UserDict
46
47 import roslib.message
48 import roslib.packages
49
50 from .bag import Bag, Compression, ROSBagException, ROSBagFormatException, ROSBagUnindexedException, ROSBagEncryptNotSupportedException, ROSBagEncryptException
51 from .migration import MessageMigrator, fixbag2, checkbag
54 from_txt = '%s [%s]' % (old._type, old._md5sum)
55 if new is not None:
56 to_txt= '%s [%s]' % (new._type, new._md5sum)
57 else:
58 to_txt = 'Unknown'
59 print(' ' * indent + ' * From: %s' % from_txt)
60 print(' ' * indent + ' To: %s' % to_txt)
61
63 parser.values.split = True
64 if len(parser.rargs) > 0 and parser.rargs[0].isdigit():
65 print("Use of \"--split <MAX_SIZE>\" has been deprecated. Please use --split --size <MAX_SIZE> or --split --duration <MAX_DURATION>", file=sys.stderr)
66 parser.values.size = int(parser.rargs.pop(0))
67
70 process.terminate()
71 if old_handler:
72 old_handler(signum, frame)
73
76 process.send_signal(signal.SIGINT)
77 if old_handler:
78 old_handler(signum, frame)
79
82 parser = optparse.OptionParser(usage="rosbag record TOPIC1 [TOPIC2 TOPIC3 ...]",
83 description="Record a bag file with the contents of specified topics.",
84 formatter=optparse.IndentedHelpFormatter())
85
86 parser.add_option("-a", "--all", dest="all", default=False, action="store_true", help="record all topics")
87 parser.add_option("-e", "--regex", dest="regex", default=False, action="store_true", help="match topics using regular expressions")
88 parser.add_option("-p", "--publish", dest="publish", default=False, action="store_true", help="publish a msg when the record begin")
89 parser.add_option("-x", "--exclude", dest="exclude_regex", default="", action="store", help="exclude topics matching the follow regular expression (subtracts from -a or regex)")
90 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="suppress console output")
91 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)")
92 parser.add_option("-O", "--output-name", dest="name", default=None, action="store", help="record to bag with name NAME.bag")
93 parser.add_option( "--split", dest="split", default=False, callback=handle_split, action="callback", help="split the bag when maximum size or duration is reached")
94 parser.add_option( "--max-splits", dest="max_splits", type='int', action="store", help="Keep a maximum of N bag files, when reaching the maximum erase the oldest one to keep a constant number of files.", metavar="MAX_SPLITS")
95 parser.add_option( "--size", dest="size", type='int', action="store", help="record a bag of maximum size SIZE MB. (Default: infinite)", metavar="SIZE")
96 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")
97 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")
98 parser.add_option("--chunksize", dest="chunksize", default=768, type='int', action="store", help="Advanced. Record to chunks of SIZE KB (Default: %default)", metavar="SIZE")
99 parser.add_option("-l", "--limit", dest="num", default=0, type='int', action="store", help="only record NUM messages on each topic")
100 parser.add_option( "--node", dest="node", default=None, type='string',action="store", help="record all topics subscribed to by a specific node")
101 parser.add_option("-j", "--bz2", dest="compression", default=None, action="store_const", const='bz2', help="use BZ2 compression")
102 parser.add_option("--lz4", dest="compression", action="store_const", const='lz4', help="use LZ4 compression")
103 parser.add_option("--tcpnodelay", dest="tcpnodelay", action="store_true", help="Use the TCP_NODELAY transport hint when subscribing to topics.")
104 parser.add_option("--udp", dest="udp", action="store_true", help="Use the UDP transport hint when subscribing to topics.")
105
106 (options, args) = parser.parse_args(argv)
107
108 if len(args) == 0 and not options.all and not options.node:
109 parser.error("You must specify a topic name or else use the '-a' option.")
110
111 if options.prefix is not None and options.name is not None:
112 parser.error("Can't set both prefix and name.")
113
114 recordpath = roslib.packages.find_node('rosbag', 'record')
115 if not recordpath:
116 parser.error("Cannot find rosbag/record executable")
117 cmd = [recordpath[0]]
118
119 cmd.extend(['--buffsize', str(options.buffsize)])
120 cmd.extend(['--chunksize', str(options.chunksize)])
121
122 if options.num != 0: cmd.extend(['--limit', str(options.num)])
123 if options.quiet: cmd.extend(["--quiet"])
124 if options.prefix: cmd.extend(["-o", options.prefix])
125 if options.name: cmd.extend(["-O", options.name])
126 if options.exclude_regex: cmd.extend(["--exclude", options.exclude_regex])
127 if options.all: cmd.extend(["--all"])
128 if options.regex: cmd.extend(["--regex"])
129 if options.publish: cmd.extend(["--publish"])
130 if options.compression: cmd.extend(["--%s" % options.compression])
131 if options.split:
132 if not options.duration and not options.size:
133 parser.error("Split specified without giving a maximum duration or size")
134 cmd.extend(["--split"])
135 if options.max_splits:
136 cmd.extend(["--max-splits", str(options.max_splits)])
137 if options.duration: cmd.extend(["--duration", options.duration])
138 if options.size: cmd.extend(["--size", str(options.size)])
139 if options.node:
140 cmd.extend(["--node", options.node])
141 if options.tcpnodelay: cmd.extend(["--tcpnodelay"])
142 if options.udp: cmd.extend(["--udp"])
143
144 cmd.extend(args)
145
146 old_handler = signal.signal(
147 signal.SIGTERM,
148 lambda signum, frame: _stop_process(signum, frame, old_handler, process)
149 )
150
151 old_handler = signal.signal(
152 signal.SIGINT,
153 lambda signum, frame: _send_process_sigint(signum, frame, old_handler, process)
154 )
155
156
157
158 process = subprocess.Popen(cmd)
159 process.wait()
160
163 parser = optparse.OptionParser(usage='rosbag info [options] BAGFILE1 [BAGFILE2 BAGFILE3 ...]',
164 description='Summarize the contents of one or more bag files.')
165 parser.add_option('-y', '--yaml', dest='yaml', default=False, action='store_true', help='print information in YAML format')
166 parser.add_option('-k', '--key', dest='key', default=None, action='store', help='print information on the given key')
167 parser.add_option( '--freq', dest='freq', default=False, action='store_true', help='display topic message frequency statistics')
168 (options, args) = parser.parse_args(argv)
169
170 if len(args) == 0:
171 parser.error('You must specify at least 1 bag file.')
172 if options.key and not options.yaml:
173 parser.error('You can only specify key when printing in YAML format.')
174
175 for i, arg in enumerate(args):
176 try:
177 b = Bag(arg, 'r', skip_index=not options.freq)
178 if options.yaml:
179 info = b._get_yaml_info(key=options.key)
180 if info is not None:
181 print(info)
182 else:
183 print(b)
184 b.close()
185 if i < len(args) - 1:
186 print('---')
187
188 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex:
189 print('ERROR: %s' % str(ex), file=sys.stderr)
190 except ROSBagUnindexedException as ex:
191 print('ERROR bag unindexed: %s. Run rosbag reindex.' % arg,
192 file=sys.stderr)
193 sys.exit(1)
194 except ROSBagException as ex:
195 print('ERROR reading %s: %s' % (arg, str(ex)), file=sys.stderr)
196 sys.exit(1)
197 except IOError as ex:
198 print('ERROR reading %s: %s' % (arg, str(ex)), file=sys.stderr)
199 sys.exit(1)
200
203 topics = []
204 for arg in parser.rargs:
205 if arg[:2] == "--" and len(arg) > 2:
206 break
207 if arg[:1] == "-" and len(arg) > 1:
208 break
209 topics.append(arg)
210 parser.values.topics.extend(topics)
211 del parser.rargs[:len(topics)]
212
215 pause_topics = []
216 for arg in parser.rargs:
217 if arg[:2] == "--" and len(arg) > 2:
218 break
219 if arg[:1] == "-" and len(arg) > 1:
220 break
221 pause_topics.append(arg)
222 parser.values.pause_topics.extend(pause_topics)
223 del parser.rargs[:len(pause_topics)]
224
227 parser = optparse.OptionParser(usage="rosbag play BAGFILE1 [BAGFILE2 BAGFILE3 ...]",
228 description="Play back the contents of one or more bag files in a time-synchronized fashion.")
229 parser.add_option("-p", "--prefix", dest="prefix", default='', type='str', help="prefix all output topics")
230 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="suppress console output")
231 parser.add_option("-i", "--immediate", dest="immediate", default=False, action="store_true", help="play back all messages without waiting")
232 parser.add_option("--pause", dest="pause", default=False, action="store_true", help="start in paused mode")
233 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")
234 parser.add_option("--clock", dest="clock", default=False, action="store_true", help="publish the clock time")
235 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")
236 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")
237 parser.add_option("-r", "--rate", dest="rate", default=1.0, type='float', action="store", help="multiply the publish rate by FACTOR", metavar="FACTOR")
238 parser.add_option("-s", "--start", dest="start", default=0.0, type='float', action="store", help="start SEC seconds into the bag files", metavar="SEC")
239 parser.add_option("-u", "--duration", dest="duration", default=None, type='float', action="store", help="play only SEC seconds from the bag files", metavar="SEC")
240 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")
241 parser.add_option("-l", "--loop", dest="loop", default=False, action="store_true", help="loop playback")
242 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)")
243 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")
244 parser.add_option("--topics", dest="topics", default=[], callback=handle_topics, action="callback", help="topics to play back")
245 parser.add_option("--pause-topics", dest="pause_topics", default=[], callback=handle_pause_topics, action="callback", help="topics to pause on during playback")
246 parser.add_option("--bags", help="bags files to play back from")
247 parser.add_option("--wait-for-subscribers", dest="wait_for_subscribers", default=False, action="store_true", help="wait for at least one subscriber on each topic before publishing")
248 parser.add_option("--rate-control-topic", dest="rate_control_topic", default='', type='str', help="watch the given topic, and if the last publish was more than <rate-control-max-delay> ago, wait until the topic publishes again to continue playback")
249 parser.add_option("--rate-control-max-delay", dest="rate_control_max_delay", default=1.0, type='float', help="maximum time difference from <rate-control-topic> before pausing")
250
251 (options, args) = parser.parse_args(argv)
252
253 if options.bags:
254 args.append(options.bags)
255
256 if len(args) == 0:
257 parser.error('You must specify at least 1 bag file to play back.')
258
259 playpath = roslib.packages.find_node('rosbag', 'play')
260 if not playpath:
261 parser.error("Cannot find rosbag/play executable")
262 cmd = [playpath[0]]
263
264 if options.prefix:
265 cmd.extend(["--prefix", str(options.prefix)])
266
267 if options.quiet: cmd.extend(["--quiet"])
268 if options.pause: cmd.extend(["--pause"])
269 if options.immediate: cmd.extend(["--immediate"])
270 if options.loop: cmd.extend(["--loop"])
271 if options.keep_alive: cmd.extend(["--keep-alive"])
272 if options.try_future: cmd.extend(["--try-future-version"])
273 if options.wait_for_subscribers: cmd.extend(["--wait-for-subscribers"])
274
275 if options.clock:
276 cmd.extend(["--clock", "--hz", str(options.freq)])
277
278 cmd.extend(['--queue', str(options.queue)])
279 cmd.extend(['--rate', str(options.rate)])
280 cmd.extend(['--delay', str(options.delay)])
281 cmd.extend(['--start', str(options.start)])
282 if options.duration:
283 cmd.extend(['--duration', str(options.duration)])
284 if options.skip_empty:
285 cmd.extend(['--skip-empty', str(options.skip_empty)])
286
287 if options.topics:
288 cmd.extend(['--topics'] + options.topics)
289
290 if options.pause_topics:
291 cmd.extend(['--pause-topics'] + options.pause_topics)
292
293
294 if options.topics or options.pause_topics:
295 cmd.extend(['--bags'])
296
297 cmd.extend(args)
298
299 if options.rate_control_topic:
300 cmd.extend(['--rate-control-topic', str(options.rate_control_topic)])
301
302 if options.rate_control_max_delay:
303 cmd.extend(['--rate-control-max-delay', str(options.rate_control_max_delay)])
304
305 old_handler = signal.signal(
306 signal.SIGTERM,
307 lambda signum, frame: _stop_process(signum, frame, old_handler, process)
308 )
309
310 old_handler = signal.signal(
311 signal.SIGINT,
312 lambda signum, frame: _send_process_sigint(signum, frame, old_handler, process)
313 )
314
315
316
317 process = subprocess.Popen(cmd)
318 process.wait()
319
322 def expr_eval(expr):
323 return eval("lambda topic, m, t: %s" % expr)
324
325 parser = optparse.OptionParser(usage="""rosbag filter [options] INBAG OUTBAG EXPRESSION
326
327 EXPRESSION can be any Python-legal expression.
328
329 The following variables are available:
330 * topic: name of topic
331 * m: message
332 * t: time of message (t.secs, t.nsecs)""",
333 description='Filter the contents of the bag.')
334 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')
335
336 options, args = parser.parse_args(argv)
337 if len(args) == 0:
338 parser.error('You must specify an in bag, an out bag, and an expression.')
339 if len(args) == 1:
340 parser.error('You must specify an out bag and an expression.')
341 if len(args) == 2:
342 parser.error("You must specify an expression.")
343 if len(args) > 3:
344 parser.error("Too many arguments.")
345
346 inbag_filename, outbag_filename, expr = args
347
348 if not os.path.isfile(inbag_filename):
349 print('Cannot locate input bag file [%s]' % inbag_filename, file=sys.stderr)
350 sys.exit(2)
351
352 if os.path.realpath(inbag_filename) == os.path.realpath(outbag_filename):
353 print('Cannot use same file as input and output [%s]' % inbag_filename, file=sys.stderr)
354 sys.exit(3)
355
356 filter_fn = expr_eval(expr)
357
358 outbag = Bag(outbag_filename, 'w')
359
360 try:
361 inbag = Bag(inbag_filename)
362 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex:
363 print('ERROR: %s' % str(ex), file=sys.stderr)
364 return
365 except ROSBagUnindexedException as ex:
366 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename, file=sys.stderr)
367 sys.exit(1)
368
369 try:
370 meter = ProgressMeter(outbag_filename, inbag._uncompressed_size)
371 total_bytes = 0
372
373 if options.verbose_pattern:
374 verbose_pattern = expr_eval(options.verbose_pattern)
375
376 for topic, raw_msg, t, conn_header in inbag.read_messages(raw=True, return_connection_header=True):
377 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg
378 msg = pytype()
379 msg.deserialize(serialized_bytes)
380
381 if filter_fn(topic, msg, t):
382 print('MATCH', verbose_pattern(topic, msg, t))
383 outbag.write(topic, msg, t, connection_header=conn_header)
384 else:
385 print('NO MATCH', verbose_pattern(topic, msg, t))
386
387 total_bytes += len(serialized_bytes)
388 meter.step(total_bytes)
389 else:
390 for topic, raw_msg, t, conn_header in inbag.read_messages(raw=True, return_connection_header=True):
391 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg
392 msg = pytype()
393 msg.deserialize(serialized_bytes)
394
395 if filter_fn(topic, msg, t):
396 outbag.write(topic, msg, t, connection_header=conn_header)
397
398 total_bytes += len(serialized_bytes)
399 meter.step(total_bytes)
400
401 meter.finish()
402
403 finally:
404 inbag.close()
405 outbag.close()
406
408 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.')
409 parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins')
410 parser.add_option('--force', action='store_true', dest='force', help='proceed with migrations, even if not all rules defined')
411
412 (options, args) = parser.parse_args(argv)
413
414 if len(args) < 1:
415 parser.error('You must pass input and output bag files.')
416 if len(args) < 2:
417 parser.error('You must pass an output bag file.')
418
419 inbag_filename = args[0]
420 outbag_filename = args[1]
421 rules = args[2:]
422
423 ext = os.path.splitext(outbag_filename)[1]
424 if ext == '.bmr':
425 parser.error('Input file should be a bag file, not a rule file.')
426 if ext != '.bag':
427 parser.error('Output file must be a bag file.')
428
429 outname = outbag_filename + '.tmp'
430
431 if os.path.exists(outbag_filename):
432 if not os.access(outbag_filename, os.W_OK):
433 print('Don\'t have permissions to access %s' % outbag_filename, file=sys.stderr)
434 sys.exit(1)
435 else:
436 try:
437 file = open(outbag_filename, 'w')
438 file.close()
439 except IOError as e:
440 print('Cannot open %s for writing' % outbag_filename, file=sys.stderr)
441 sys.exit(1)
442
443 if os.path.exists(outname):
444 if not os.access(outname, os.W_OK):
445 print('Don\'t have permissions to access %s' % outname, file=sys.stderr)
446 sys.exit(1)
447 else:
448 try:
449 file = open(outname, 'w')
450 file.close()
451 except IOError as e:
452 print('Cannot open %s for writing' % outname, file=sys.stderr)
453 sys.exit(1)
454
455 if options.noplugins is None:
456 options.noplugins = False
457
458 migrator = MessageMigrator(rules, plugins=not options.noplugins)
459
460 try:
461 migrations = fixbag2(migrator, inbag_filename, outname, options.force)
462 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex:
463 print('ERROR: %s' % str(ex), file=sys.stderr)
464 return
465 except ROSBagUnindexedException as ex:
466 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename,
467 file=sys.stderr)
468 sys.exit(1)
469
470 if len(migrations) == 0:
471 os.rename(outname, outbag_filename)
472 print('Bag migrated successfully.')
473 else:
474 print('Bag could not be migrated. The following migrations could not be performed:')
475 for m in migrations:
476 print_trans(m[0][0].old_class, m[0][-1].new_class, 0)
477
478 if len(m[1]) > 0:
479 print(' %d rules missing:' % len(m[1]))
480 for r in m[1]:
481 print_trans(r.old_class, r.new_class,1)
482
483 print('Try running \'rosbag check\' to create the necessary rule files or run \'rosbag fix\' with the \'--force\' option.')
484 os.remove(outname)
485 sys.exit(1)
486
488 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.')
489 parser.add_option('-g', '--genrules', action='store', dest='rulefile', default=None, help='generate a rulefile named RULEFILE')
490 parser.add_option('-a', '--append', action='store_true', dest='append', help='append to the end of an existing rulefile after loading it')
491 parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins')
492 (options, args) = parser.parse_args(argv)
493
494 if len(args) == 0:
495 parser.error('You must specify a bag file to check.')
496 if options.append and options.rulefile is None:
497 parser.error('Cannot specify -a without also specifying -g.')
498 if options.rulefile is not None:
499 rulefile_exists = os.path.isfile(options.rulefile)
500 if rulefile_exists and not options.append:
501 parser.error('The file %s already exists. Include -a if you intend to append.' % options.rulefile)
502 if not rulefile_exists and options.append:
503 parser.error('The file %s does not exist, and so -a is invalid.' % options.rulefile)
504
505 if options.append:
506 append_rule = [options.rulefile]
507 else:
508 append_rule = []
509
510
511 try:
512 Bag(args[0])
513 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex:
514 print('ERROR: %s' % str(ex), file=sys.stderr)
515 return
516 except ROSBagUnindexedException as ex:
517 print('ERROR bag unindexed: %s. Run rosbag reindex.' % args[0], file=sys.stderr)
518 sys.exit(1)
519
520 mm = MessageMigrator(args[1:] + append_rule, not options.noplugins)
521
522 migrations = checkbag(mm, args[0])
523
524 if len(migrations) == 0:
525 print('Bag file does not need any migrations.')
526 exit(0)
527
528 print('The following migrations need to occur:')
529 all_rules = []
530 for m in migrations:
531 all_rules.extend(m[1])
532
533 print_trans(m[0][0].old_class, m[0][-1].new_class, 0)
534 if len(m[1]) > 0:
535 print(" %d rules missing:" % len(m[1]))
536 for r in m[1]:
537 print_trans(r.old_class, r.new_class, 1)
538
539 if options.rulefile is None:
540 if all_rules == []:
541 print("\nAll rules defined. Bag is ready to be migrated")
542 else:
543 print("\nTo generate rules, please run with -g <rulefile>")
544 exit(0)
545
546 output = ''
547 rules_left = mm.filter_rules_unique(all_rules)
548
549 if rules_left == []:
550 print("\nNo additional rule files needed to be generated. %s not created."%(options.rulefile))
551 exit(0)
552
553 while len(rules_left) > 0:
554 extra_rules = []
555 for r in rules_left:
556 if r.new_class is None:
557 print('The message type %s appears to have moved. Please enter the type to migrate it to.' % r.old_class._type)
558 new_type = raw_input('>')
559 new_class = roslib.message.get_message_class(new_type)
560 while new_class is None:
561 print("\'%s\' could not be found in your system. Please make sure it is built." % new_type)
562 new_type = raw_input('>')
563 new_class = roslib.message.get_message_class(new_type)
564 new_rule = mm.make_update_rule(r.old_class, new_class)
565 R = new_rule(mm, 'GENERATED.' + new_rule.__name__)
566 R.find_sub_paths()
567 new_rules = [r for r in mm.expand_rules(R.sub_rules) if r.valid == False]
568 extra_rules.extend(new_rules)
569 print('Creating the migration rule for %s requires additional missing rules:' % new_type)
570 for nr in new_rules:
571 print_trans(nr.old_class, nr.new_class,1)
572 output += R.get_class_def()
573 else:
574 output += r.get_class_def()
575 rules_left = mm.filter_rules_unique(extra_rules)
576 f = open(options.rulefile, 'a')
577 f.write(output)
578 f.close()
579
580 print('\nThe necessary rule files have been written to: %s' % options.rulefile)
581
583 parser = optparse.OptionParser(usage='rosbag compress [options] BAGFILE1 [BAGFILE2 ...]',
584 description='Compress one or more bag files.')
585 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR')
586 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists')
587 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages')
588 parser.add_option('-j', '--bz2', action='store_const', dest='compression', help='use BZ2 compression', const=Compression.BZ2, default=Compression.BZ2)
589 parser.add_option( '--lz4', action='store_const', dest='compression', help='use lz4 compression', const=Compression.LZ4)
590 (options, args) = parser.parse_args(argv)
591
592 if len(args) < 1:
593 parser.error('You must specify at least one bag file.')
594
595 op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, options.compression, options.quiet)
596
597 bag_op(args, False, True, lambda b: False, op, options.output_dir, options.force, options.quiet)
598
600 parser = optparse.OptionParser(usage='rosbag decompress [options] BAGFILE1 [BAGFILE2 ...]',
601 description='Decompress one or more bag files.')
602 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR')
603 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists')
604 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages')
605
606 (options, args) = parser.parse_args(argv)
607
608 if len(args) < 1:
609 parser.error('You must specify at least one bag file.')
610
611 op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, Compression.NONE, options.quiet)
612
613 bag_op(args, False, True, lambda b: False, op, options.output_dir, options.force, options.quiet)
614
616 parser = optparse.OptionParser(usage='rosbag reindex [options] BAGFILE1 [BAGFILE2 ...]',
617 description='Reindexes one or more bag files.')
618 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR')
619 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists')
620 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages')
621
622 (options, args) = parser.parse_args(argv)
623
624 if len(args) < 1:
625 parser.error('You must specify at least one bag file.')
626
627 op = lambda inbag, outbag, quiet: reindex_op(inbag, outbag, options.quiet)
628
629 bag_op(args, True, True, lambda b: b.version > 102, op, options.output_dir, options.force, options.quiet)
630
632 parser = optparse.OptionParser(usage='rosbag encrypt [options] BAGFILE1 [BAGFILE2 ...]',
633 description='Encrypt one or more bag files.')
634 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR')
635 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists')
636 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages')
637 parser.add_option("-p", "--plugin", action='store', dest="plugin", default='rosbag/AesCbcEncryptor', help='encryptor plugin name')
638 parser.add_option("-r", "--param", action='store', dest="param", default='*', help='encryptor plugin parameter')
639 parser.add_option('-j', '--bz2', action='store_const', dest='compression', help='use BZ2 compression', const=Compression.BZ2, default=Compression.NONE)
640 parser.add_option( '--lz4', action='store_const', dest='compression', help='use lz4 compression', const=Compression.LZ4)
641 (options, args) = parser.parse_args(argv)
642
643 if len(args) < 1:
644 parser.error('You must specify at least one bag file.')
645
646 op = lambda inbag, outbag, quiet: change_encryption_op(inbag, outbag, options.plugin, options.param, options.compression, options.quiet)
647
648 bag_op(args, False, True, lambda b: False, op, options.output_dir, options.force, options.quiet)
649
651 parser = optparse.OptionParser(usage='rosbag decrypt [options] BAGFILE1 [BAGFILE2 ...]',
652 description='Decrypt one or more bag files.')
653 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR')
654 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists')
655 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages')
656 parser.add_option('-j', '--bz2', action='store_const', dest='compression', help='use BZ2 compression', const=Compression.BZ2, default=Compression.NONE)
657 parser.add_option( '--lz4', action='store_const', dest='compression', help='use lz4 compression', const=Compression.LZ4)
658 (options, args) = parser.parse_args(argv)
659
660 if len(args) < 1:
661 parser.error('You must specify at least one bag file.')
662
663 op = lambda inbag, outbag, quiet: change_encryption_op(inbag, outbag, 'rosbag/NoEncryptor', '*', options.compression, options.quiet)
664
665 bag_op(args, True, False, lambda b: False, op, options.output_dir, options.force, options.quiet)
666
667 -def bag_op(inbag_filenames, allow_unindexed, open_inbag, copy_fn, op, output_dir=None, force=False, quiet=False):
668 for inbag_filename in inbag_filenames:
669 if open_inbag:
670
671 try:
672 inbag = Bag(inbag_filename, 'r', allow_unindexed=allow_unindexed)
673 except ROSBagUnindexedException:
674 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename, file=sys.stderr)
675 continue
676 except (ROSBagException, IOError) as ex:
677 print('ERROR reading %s: %s' % (inbag_filename, str(ex)), file=sys.stderr)
678 continue
679
680
681 copy = copy_fn(inbag)
682
683 inbag.close()
684 else:
685 copy = False
686
687
688 if output_dir is None:
689 outbag_filename = inbag_filename
690 else:
691 outbag_filename = os.path.join(output_dir, os.path.split(inbag_filename)[1])
692
693 backup_filename = None
694 if outbag_filename == inbag_filename:
695
696 backup_filename = '%s.orig%s' % os.path.splitext(inbag_filename)
697
698 if not force and os.path.exists(backup_filename):
699 if not quiet:
700 print('Skipping %s. Backup path %s already exists.' % (inbag_filename, backup_filename), file=sys.stderr)
701 continue
702
703 try:
704 if copy:
705 shutil.copy(inbag_filename, backup_filename)
706 else:
707 os.rename(inbag_filename, backup_filename)
708 except OSError as ex:
709 print('ERROR %s %s to %s: %s' % ('copying' if copy else 'moving', inbag_filename, backup_filename, str(ex)), file=sys.stderr)
710 continue
711
712 source_filename = backup_filename
713 else:
714 if copy:
715 shutil.copy(inbag_filename, outbag_filename)
716 source_filename = outbag_filename
717 else:
718 source_filename = inbag_filename
719
720 try:
721 if open_inbag:
722 inbag = Bag(source_filename, 'r', allow_unindexed=allow_unindexed)
723
724
725 try:
726 if copy:
727 outbag = Bag(outbag_filename, 'a', allow_unindexed=allow_unindexed)
728 else:
729 outbag = Bag(outbag_filename, 'w')
730 except (ROSBagException, IOError) as ex:
731 print('ERROR writing to %s: %s' % (outbag_filename, str(ex)), file=sys.stderr)
732 inbag.close()
733 continue
734
735
736 try:
737 op(inbag, outbag, quiet=quiet)
738 except ROSBagException as ex:
739 print('\nERROR operating on %s: %s' % (source_filename, str(ex)), file=sys.stderr)
740 inbag.close()
741 outbag.close()
742 continue
743
744 outbag.close()
745 inbag.close()
746 else:
747
748 try:
749 if copy:
750 outbag = Bag(outbag_filename, 'a', allow_unindexed=allow_unindexed)
751 else:
752 outbag = Bag(outbag_filename, 'w')
753 except (ROSBagException, IOError) as ex:
754 print('ERROR writing to %s: %s' % (outbag_filename, str(ex)), file=sys.stderr)
755 continue
756
757
758 try:
759 op(source_filename, outbag, quiet=quiet)
760 except ROSBagException as ex:
761 print('\nERROR operating on %s: %s' % (source_filename, str(ex)), file=sys.stderr)
762 outbag.close()
763 continue
764
765 outbag.close()
766
767 except KeyboardInterrupt:
768 if backup_filename is not None:
769 try:
770 if copy:
771 os.remove(backup_filename)
772 else:
773 os.rename(backup_filename, inbag_filename)
774 except OSError as ex:
775 print('ERROR %s %s to %s: %s', ('removing' if copy else 'moving', backup_filename, inbag_filename, str(ex)), file=sys.stderr)
776 break
777
778 except (ROSBagException, IOError) as ex:
779 print('ERROR operating on %s: %s' % (inbag_filename, str(ex)), file=sys.stderr)
780
782 outbag.compression = compression
783
784 if quiet:
785 for topic, msg, t, conn_header in inbag.read_messages(raw=True, return_connection_header=True):
786 outbag.write(topic, msg, t, raw=True, connection_header=conn_header)
787 else:
788 meter = ProgressMeter(outbag.filename, inbag._uncompressed_size)
789
790 total_bytes = 0
791 for topic, msg, t, conn_header in inbag.read_messages(raw=True, return_connection_header=True):
792 msg_type, serialized_bytes, md5sum, pos, pytype = msg
793
794 outbag.write(topic, msg, t, raw=True, connection_header=conn_header)
795
796 total_bytes += len(serialized_bytes)
797 meter.step(total_bytes)
798
799 meter.finish()
800
802 if inbag.version == 102:
803 if quiet:
804 try:
805 for offset in inbag.reindex():
806 pass
807 except:
808 pass
809
810 for (topic, msg, t, conn_header) in inbag.read_messages(return_connection_header=True):
811 outbag.write(topic, msg, t, connection_header=conn_header)
812 else:
813 meter = ProgressMeter(outbag.filename, inbag.size)
814 try:
815 for offset in inbag.reindex():
816 meter.step(offset)
817 except:
818 pass
819 meter.finish()
820
821 meter = ProgressMeter(outbag.filename, inbag.size)
822 for (topic, msg, t, conn_header) in inbag.read_messages(return_connection_header=True):
823 outbag.write(topic, msg, t, connection_header=conn_header)
824 meter.step(inbag._file.tell())
825 meter.finish()
826 else:
827 if quiet:
828 try:
829 for offset in outbag.reindex():
830 pass
831 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex:
832 raise
833 except:
834 pass
835 else:
836 meter = ProgressMeter(outbag.filename, outbag.size)
837 try:
838 for offset in outbag.reindex():
839 meter.step(offset)
840 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex:
841 raise
842 except:
843 pass
844 meter.finish()
845
847
848 outbag.close()
849
850 encryptpath = roslib.packages.find_node('rosbag', 'encrypt')
851 if not encryptpath:
852 parser.error("Cannot find rosbag/encrypt executable")
853 cmd = [encryptpath[0]]
854 if type(inbag) is str:
855 cmd.extend([inbag])
856 else:
857 cmd.extend([inbag.filename])
858 cmd.extend(['-o', outbag.filename])
859 cmd.extend(['-p', plugin])
860 cmd.extend(['-r', param])
861 if compression == 'bz2':
862 cmd.extend(['-j'])
863 elif compression == 'lz4':
864 cmd.extend(['--lz4'])
865 if quiet:
866 cmd.extend(['-q'])
867
868 old_handler = signal.signal(
869 signal.SIGTERM,
870 lambda signum, frame: _stop_process(signum, frame, old_handler, process)
871 )
872
873 process = subprocess.Popen(cmd)
874 process.wait()
875
878 UserDict.__init__(self)
879 self._description = {}
880 self['help'] = self.help_cmd
881
882 - def add_cmd(self, name, function, description):
883 self[name] = function
884 self._description[name] = description
885
887 str = "Available subcommands:\n"
888 for k in sorted(self.keys()):
889 str += " %s " % k
890 if k in self._description.keys():
891 str +="\t%s" % self._description[k]
892 str += "\n"
893 return str
894
896 argv = [a for a in argv if a != '-h' and a != '--help']
897
898 if len(argv) == 0:
899 print('Usage: rosbag <subcommand> [options] [args]')
900 print()
901 print("A bag is a file format in ROS for storing ROS message data. The rosbag command can record, replay and manipulate bags.")
902 print()
903 print(self.get_valid_cmds())
904 print('For additional information, see http://wiki.ros.org/rosbag')
905 print()
906 return
907
908 cmd = argv[0]
909 if cmd in self:
910 self[cmd](['-h'])
911 else:
912 print("Unknown command: '%s'" % cmd, file=sys.stderr)
913 print(self.get_valid_cmds(), file=sys.stderr)
914
916 - def __init__(self, path, bytes_total, refresh_rate=1.0):
917 self.path = path
918 self.bytes_total = bytes_total
919 self.refresh_rate = refresh_rate
920
921 self.elapsed = 0.0
922 self.update_elapsed = 0.0
923 self.bytes_read = 0.0
924
925 self.start_time = time.time()
926
927 self._update_progress()
928
929 - def step(self, bytes_read, force_update=False):
930 self.bytes_read = bytes_read
931 self.elapsed = time.time() - self.start_time
932
933 if force_update or self.elapsed - self.update_elapsed > self.refresh_rate:
934 self._update_progress()
935 self.update_elapsed = self.elapsed
936
938 max_path_len = self.terminal_width() - 37
939 path = self.path
940 if len(path) > max_path_len:
941 path = '...' + self.path[-max_path_len + 3:]
942
943 bytes_read_str = self._human_readable_size(float(self.bytes_read))
944 bytes_total_str = self._human_readable_size(float(self.bytes_total))
945
946 if self.bytes_read < self.bytes_total:
947 complete_fraction = float(self.bytes_read) / self.bytes_total
948 pct_complete = int(100.0 * complete_fraction)
949
950 if complete_fraction > 0.0:
951 eta = self.elapsed * (1.0 / complete_fraction - 1.0)
952 eta_min, eta_sec = eta / 60, eta % 60
953 if eta_min > 99:
954 eta_str = '--:--'
955 else:
956 eta_str = '%02d:%02d' % (eta_min, eta_sec)
957 else:
958 eta_str = '--:--'
959
960 progress = '%-*s %3d%% %8s / %8s %s ETA' % (max_path_len, path, pct_complete, bytes_read_str, bytes_total_str, eta_str)
961 else:
962 progress = '%-*s 100%% %19s %02d:%02d ' % (max_path_len, path, bytes_total_str, self.elapsed / 60, self.elapsed % 60)
963
964 print('\r', progress, end='')
965 sys.stdout.flush()
966
968 multiple = 1024.0
969 for suffix in ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']:
970 size /= multiple
971 if size < multiple:
972 return '%.1f %s' % (size, suffix)
973
974 raise ValueError('number too large')
975
977 self.step(self.bytes_total, force_update=True)
978 print()
979
980 @staticmethod
982 """Estimate the width of the terminal"""
983 width = 0
984 try:
985 import struct, fcntl, termios
986 s = struct.pack('HHHH', 0, 0, 0, 0)
987 x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
988 width = struct.unpack('HHHH', x)[1]
989 except (IOError, ImportError):
990 pass
991 if width <= 0:
992 try:
993 width = int(os.environ['COLUMNS'])
994 except:
995 pass
996 if width <= 0:
997 width = 80
998
999 return width
1000
1001 -def rosbagmain(argv=None):
1002 cmds = RosbagCmds()
1003 cmds.add_cmd('record', record_cmd, "Record a bag file with the contents of specified topics.")
1004 cmds.add_cmd('info', info_cmd, 'Summarize the contents of one or more bag files.')
1005 cmds.add_cmd('play', play_cmd, "Play back the contents of one or more bag files in a time-synchronized fashion.")
1006 cmds.add_cmd('check', check_cmd, 'Determine whether a bag is playable in the current system, or if it can be migrated.')
1007 cmds.add_cmd('fix', fix_cmd, 'Repair the messages in a bag file so that it can be played in the current system.')
1008 cmds.add_cmd('filter', filter_cmd, 'Filter the contents of the bag.')
1009 cmds.add_cmd('compress', compress_cmd, 'Compress one or more bag files.')
1010 cmds.add_cmd('decompress', decompress_cmd, 'Decompress one or more bag files.')
1011 cmds.add_cmd('reindex', reindex_cmd, 'Reindexes one or more bag files.')
1012 if sys.platform != 'win32':
1013 cmds.add_cmd('encrypt', encrypt_cmd, 'Encrypt one or more bag files.')
1014 cmds.add_cmd('decrypt', decrypt_cmd, 'Decrypt one or more bag files.')
1015
1016 if argv is None:
1017 argv = sys.argv
1018
1019 if '-h' in argv or '--help' in argv:
1020 argv = [a for a in argv if a != '-h' and a != '--help']
1021 argv.insert(1, 'help')
1022
1023 if len(argv) > 1:
1024 cmd = argv[1]
1025 else:
1026 cmd = 'help'
1027
1028 try:
1029 if cmd in cmds:
1030 cmds[cmd](argv[2:])
1031 else:
1032 cmds['help']([cmd])
1033 except KeyboardInterrupt:
1034 pass
1035