|
37 | 37 | from kdl_parser_py.urdf import treeFromUrdfModel |
38 | 38 | from ros_numpy import msgify, numpify |
39 | 39 | from sensor_msgs.msg import CameraInfo, CompressedImage, Image, JointState |
40 | | -from std_msgs.msg import Header |
| 40 | +from std_msgs.msg import Header, String |
41 | 41 | from tf2_msgs.msg import TFMessage |
42 | 42 | from tf2_py import BufferCore |
43 | 43 | from urdf_parser_py import urdf, xml_reflection |
44 | 44 | from vision_msgs.msg import Detection2DArray, Detection2D, ObjectHypothesisWithPose |
45 | 45 |
|
46 | | -from .message_filter import DeserializedMessageFilter, DeserializedMessageFilterWithTF, RawMessageFilter, TopicSet |
| 46 | +from .message_filter import DeserializedMessageFilter, DeserializedMessageFilterWithTF, Passthrough, RawMessageFilter, \ |
| 47 | + TopicSet |
47 | 48 |
|
48 | 49 |
|
49 | 50 | def urdf_error(message): |
@@ -1247,6 +1248,105 @@ def _str_params(self): |
1247 | 1248 | return ", ".join(parts) |
1248 | 1249 |
|
1249 | 1250 |
|
| 1251 | +class AddSubtitles(Passthrough): |
| 1252 | + """Read subtitles from a text file and add them to the output as std_msgs/String messages.""" |
| 1253 | + |
| 1254 | + def __init__(self, subtitles_file, topic, latch=False, time_offset=0.0): |
| 1255 | + """ |
| 1256 | + :param subtitles_file: The file to read subtitles from. |
| 1257 | + :param topic: The topic to put the subtitles on. |
| 1258 | + :param latch: Whether the topic should be latched. |
| 1259 | + :param time_offset: Relative time offset to add to the absolute subtitle time. |
| 1260 | + """ |
| 1261 | + super(AddSubtitles, self).__init__() |
| 1262 | + self._subtitles_file = subtitles_file |
| 1263 | + self._topic = topic |
| 1264 | + self._start_time = None |
| 1265 | + self._latch = latch |
| 1266 | + self._time_offset = rospy.Duration(time_offset) |
| 1267 | + |
| 1268 | + self._subtitles = self._parse_subtitles(subtitles_file) |
| 1269 | + |
| 1270 | + def _parse_subtitles(self, subtitles_file): |
| 1271 | + if subtitles_file.endswith(".srt"): |
| 1272 | + return self._parse_srt(subtitles_file) |
| 1273 | + raise RuntimeError("Unsupported subtitles type. Only SRT is supported so far.") |
| 1274 | + |
| 1275 | + def _parse_srt(self, subtitles_file): |
| 1276 | + class State(Enum): |
| 1277 | + INIT = 0 |
| 1278 | + COUNTER_READ = 1 |
| 1279 | + TIME_READ = 2 |
| 1280 | + |
| 1281 | + state = State.INIT |
| 1282 | + start_time = None |
| 1283 | + end_time = None |
| 1284 | + lines = list() |
| 1285 | + |
| 1286 | + subtitles = list() |
| 1287 | + |
| 1288 | + def parse_time_str(time_str): |
| 1289 | + h, m, s = time_str.split(':') |
| 1290 | + s, ss = s.split(',') |
| 1291 | + return rospy.Duration( |
| 1292 | + int(h, base=10) * 3600 + int(m, base=10) * 60 + int(s, base=10), |
| 1293 | + int(float('0.' + ss) * 1e9)) |
| 1294 | + |
| 1295 | + with open(subtitles_file, 'r') as f: |
| 1296 | + for line in f: |
| 1297 | + line = line.strip() |
| 1298 | + if state == State.INIT: |
| 1299 | + _ = int(line) |
| 1300 | + state = State.COUNTER_READ |
| 1301 | + elif state == State.COUNTER_READ: |
| 1302 | + start_time_str, end_time_str = line.split(" --> ") |
| 1303 | + start_time = parse_time_str(start_time_str) |
| 1304 | + end_time = parse_time_str(end_time_str) |
| 1305 | + state = State.TIME_READ |
| 1306 | + elif state == State.TIME_READ: |
| 1307 | + if len(line) == 0: |
| 1308 | + subtitles.append((start_time, end_time, "\n".join(lines))) |
| 1309 | + lines = list() |
| 1310 | + state = State.INIT |
| 1311 | + else: |
| 1312 | + lines.append(line) |
| 1313 | + |
| 1314 | + # in case the last empty line is missing |
| 1315 | + if len(lines) > 0: |
| 1316 | + subtitles.append((start_time, end_time, "\n".join(lines))) |
| 1317 | + |
| 1318 | + return subtitles |
| 1319 | + |
| 1320 | + def set_bag(self, bag): |
| 1321 | + super(AddSubtitles, self).set_bag(bag) |
| 1322 | + self._start_time = rospy.Time(bag.get_start_time()) + self._time_offset |
| 1323 | + |
| 1324 | + def extra_initial_messages(self): |
| 1325 | + connection_header = { |
| 1326 | + "callerid": "/bag_filter", |
| 1327 | + "topic": self._topic, |
| 1328 | + "message_definition": String._full_text, |
| 1329 | + "type": String._type, |
| 1330 | + "md5sum": String._md5sum, |
| 1331 | + } |
| 1332 | + if self._latch: |
| 1333 | + connection_header["latching"] = "1" |
| 1334 | + |
| 1335 | + for start_time, end_time, subtitle in self._subtitles: |
| 1336 | + msg = String(data=subtitle) |
| 1337 | + abs_time = self._start_time + start_time |
| 1338 | + yield self._topic, msg, abs_time, connection_header |
| 1339 | + |
| 1340 | + def _str_params(self): |
| 1341 | + params = ["topic=" + self._topic, "subtitles_file=" + self._subtitles_file] |
| 1342 | + if self._time_offset != rospy.Duration(0, 0): |
| 1343 | + params.append("time_offset=%f" % (self._time_offset.to_sec(),)) |
| 1344 | + parent_params = super(AddSubtitles, self)._str_params() |
| 1345 | + if len(parent_params) > 0: |
| 1346 | + params.append(parent_params) |
| 1347 | + return ",".join(params) |
| 1348 | + |
| 1349 | + |
1250 | 1350 | class FixSpotCams(RawMessageFilter): |
1251 | 1351 | """Fix a problem with Spot robot cameras that publish a bit weird message header.""" |
1252 | 1352 |
|
|
0 commit comments