gstlal 1.13.0
Loading...
Searching...
No Matches
sink.py
1""""Module for producing sink elements
2
3"""
4import math
5import os
6import sys
7import threading
8from typing import Tuple
9
10import numpy
11from lal import LIGOTimeGPS
12from lal.utils import CacheEntry
13from ligo import segments
14
15import gi
16
17gi.require_version('Gst', '1.0')
18from gi.repository import GObject
19from gi.repository import Gst
20
21GObject.threads_init()
22Gst.init(None)
23
24from gstlal.pipeparts import pipetools, pipedot, mux, encode, filters, transform
25
26BYTE_ORDER = 'LE' if sys.byteorder == "little" else 'BE'
27
28
29def framecpp_filesink_ldas_path_handler(elem: pipetools.Element, pspec, path_digits: Tuple[str, int]):
30 """Add path for file sink to element
31
32 Args:
33 elem:
34 Element, the element to which to add a filesink path property
35 pspec:
36 Unknown
37 path_digits:
38 Tuple[str, int], a string outpath and a directory digits int
39
40 Examples:
41 >>> filesinkelem.connect("notify::timestamp", framecpp_filesink_ldas_path_handler, (".", 5))
42
43 Returns:
44 Element, with the formatted outpath attached as the "path" property
45 """
46 outpath, dir_digits = path_digits
47
48 # get timestamp and truncate to integer seconds
49 timestamp = elem.get_property("timestamp") // Gst.SECOND
50
51 # extract leading digits
52 leading_digits = timestamp // 10 ** int(math.log10(timestamp) + 1 - dir_digits)
53
54 # get other metadata
55 instrument = elem.get_property("instrument")
56 frame_type = elem.get_property("frame-type")
57
58 # make target directory, and set path
59 path = os.path.join(outpath, "%s-%s-%d" % (instrument, frame_type, leading_digits))
60 if not os.path.exists(path):
61 os.makedirs(path)
62 elem.set_property("path", path)
63
64
66 """Translate an element message posted by the multifilesink element
67 inside a framecpp_filesink bin into a lal.utils.CacheEntry object
68 describing the file being written by the multifilesink element.
69 """
70 # extract the segment spanned by the file from the message directly
71 start = LIGOTimeGPS(0, message.get_structure()["timestamp"])
72 end = start + LIGOTimeGPS(0, message.get_structure()["duration"])
73
74 # retrieve the framecpp_filesink bin (for instrument/observatory
75 # and frame file type)
76 parent = message.src.get_parent()
77
78 # construct and return a CacheEntry object
79 return CacheEntry(parent.get_property("instrument"), parent.get_property("frame-type"), segments.segment(start, end),
80 "file://localhost%s" % os.path.abspath(message.get_structure()["filename"]))
81
82
83
84def multi_file(pipeline: pipetools.Pipeline, src: pipetools.Element, next_file: int = 0, sync: bool = False, async_: bool = False, **properties) -> pipetools.Element:
85 """Adds a sink element to a pipeline with useful default properties
86
87 Args:
88 pipeline:
89 Gst.Pipeline, the pipeline to which the new element will be added
90 src:
91 Gst.Element, the source element
92 next_file:
93 int, default 0
94 sync:
95 bool, default False
96 async_:
97 bool, default False
98 **properties:
99
100 Returns:
101 Element
102 """
103 properties["async"] = async_
104 return pipetools.make_element_with_src(pipeline, src, "multifilesink", next_file=next_file, sync=sync, **properties)
105
106
107def gwf(pipeline: pipetools.Pipeline, src: pipetools.Element, message_forward: bool = True, **properties) -> pipetools.Element:
108 """Add a framecpp file sink element to pipeline, that will write out a GWF file
109
110 Args:
111 pipeline:
112 Gst.Pipeline, the pipeline to which the new element will be added
113 src:
114 Gst.Element, the source element
115 message_forward:
116 bool, default True
117 **properties:
118
119 References:
120 Implementation: gstlal-ugly/gst/framecpp/framecpp_filesink.c
121
122 Returns:
123 Element
124 """
125 post_messages = properties.pop("post_messages", True)
126 elem = pipetools.make_element_with_src(pipeline, src, "framecpp_filesink", message_forward=message_forward, **properties)
127 # FIXME: there's supposed to be some sort of proxy mechanism for
128 # setting properties on child elements, but we can't seem to get
129 # anything to work
130 elem.get_by_name("multifilesink").set_property("post-messages", post_messages)
131 return elem
132
133
134
135def fake(pipeline: pipetools.Pipeline, src: pipetools.Element) -> pipetools.Element:
136 """Create a fake sink element
137
138 Args:
139 pipeline:
140 Gst.Pipeline, the pipeline to which the new element will be added
141 src:
142 Gst.Element, the source element
143
144 Returns:
145 Element
146 """
147 return pipetools.make_element_with_src(pipeline, src, "fakesink", sync=False, **{"async": False})
148
149
150
151def file(pipeline: pipetools.Pipeline, src: pipetools.Element, filename: str, sync: bool = False, async_: bool = False) -> pipetools.Element:
152 """Add file sink to pipeline
153
154 Args:
155 pipeline:
156 Gst.Pipeline, the pipeline to which the new element will be added
157 src:
158 Gst.Element, the source element
159 filename:
160 str, the name of the output file
161 sync:
162 bool, default False
163 async_:
164 bool, default False
165
166 Returns:
167 Element
168 """
169 return pipetools.make_element_with_src(pipeline, src, "filesink", sync=sync, buffer_mode=2, location=filename, **{"async": async_})
170
171
172
173def tsv(pipeline: pipetools.Pipeline, src: pipetools.Element, filename: str, segment: pipetools.Segment = None) -> pipetools.Element:
174 """Converts audio time-series to tab-separated ascii text, a format compatible with most plotting utilities.
175 The output is multi-column tab-separated ASCII text. The first column is the time, the remaining columns are
176 the values of the channels in order.
177
178 Args:
179 pipeline:
180 Gst.Pipeline, the pipeline to which the new element will be added
181 src:
182 Gst.Element, the source element
183 filename:
184 str, the filename of the output text file
185 segment:
186 Segment, default None, a ligo.segments.segment
187
188 Returns:
189 Element
190 """
191 if segment is not None:
192 elem = pipetools.make_element_with_src(pipeline, src, "lal_nxydump", start_time=segment[0].ns(), stop_time=segment[1].ns())
193 else:
194 elem = pipetools.make_element_with_src(pipeline, src, "lal_nxydump")
195 return file(pipeline, elem, filename)
196
197
198def ogm_video(pipeline: pipetools.Pipeline, videosrc: pipetools.Element, filename: str, audiosrc: pipetools.Element = None, verbose: bool = False):
199 """Make a ogm video sink element
200
201 Args:
202 pipeline:
203 Gst.Pipeline, the pipeline to which the new element will be added
204 videosrc:
205 Gst.Element, the video source element
206 filename:
207 str, the name of the output video file
208 audiosrc:
209 Gst.Element, default None, the audio source element
210 verbose:
211 bool, default False
212
213 Returns:
214 Element, the sink element
215 """
216 src = transform.colorspace(pipeline, videosrc)
217 src = filters.caps(pipeline, src, "video/x-raw-yuv, format=(fourcc)I420")
218 src = encode.theora(pipeline, src,
219 # border=2,
220 quality=48,
221 # quick=False
222 )
223 src = mux.ogg_mux(pipeline, src)
224 if audiosrc is not None:
225 encode.flac(pipeline, filters.caps(pipeline, transform.audio_convert(pipeline, audiosrc), "audio/x-raw, format=S24%s" % BYTE_ORDER)).link(src)
226 if verbose:
227 src = progress_report(pipeline, src, filename)
228 return file(pipeline, src, filename)
229
230
231def auto_video(pipeline: pipetools.Pipeline, src: pipetools.Element) -> pipetools.Element:
232 """Create a video sink that automatically detects an appropriate video sink to use. It does so by scanning the
233 registry for all elements that have "Sink" and "Video" in the class field of their element information, and
234 also have a non-zero autoplugging rank.
235
236 Args:
237 pipeline:
238 Gst.Pipeline, the pipeline to which the new element will be added
239 src:
240 Gst.Element, the source element
241
242 References:
243 [1] https://gstreamer.freedesktop.org/documentation/autodetect/autovideosink.html?gi-language=python
244
245 Returns:
246 Element
247 """
248 return pipetools.make_element_with_src(pipeline, transform.colorspace(pipeline, src), "autovideosink")
249
250
251
252def auto_audio(pipeline: pipetools.Pipeline, src: pipetools.Element) -> pipetools.Element:
253 """Create an audio sink that automatically detects an appropriate audio sink to use. It does so by
254 scanning the registry for all elements that have "Sink" and "Audio" in the class field of their element
255 information, and also have a non-zero autoplugging rank.
256
257 Args:
258 pipeline:
259 Gst.Pipeline, the pipeline to which the new element will be added
260 src:
261 Gst.Element, the source element
262
263 References:
264 [1] https://gstreamer.freedesktop.org/documentation/autodetect/autoaudiosink.html?gi-language=python
265
266 Returns:
267 Element
268 """
269 return pipetools.make_element_with_src(pipeline, transform.queue(pipeline, src), "autoaudiosink")
270
271
272def playback(pipeline: pipetools.Pipeline, src: pipetools.Element, amplification: float = 0.1) -> pipetools.Element:
273 """Create a playback pipeline and add it to existing pipeline
274
275 Args:
276 pipeline:
277 Gst.Pipeline, the pipeline to which the new element will be added
278 src:
279 Gst.Element, the source element
280 amplification:
281 float, default 0.1
282
283 Returns:
284 Element
285 """
286 elems = (
287 Gst.ElementFactory.make("audioconvert", None),
288 Gst.ElementFactory.make("capsfilter", None),
289 Gst.ElementFactory.make("audioamplify", None),
290 Gst.ElementFactory.make("audioconvert", None),
291 Gst.ElementFactory.make("queue", None),
292 Gst.ElementFactory.make("autoaudiosink", None)
293 )
294 elems[1].set_property("caps", Gst.Caps.from_string("audio/x-raw, format=F32%s" % BYTE_ORDER))
295 elems[2].set_property("amplification", amplification)
296 elems[4].set_property("max-size-time", 1 * Gst.SECOND)
297 pipeline.add(*elems)
298 return Gst.element_link_many(src, *elems) # MOD: Error line [733]: element_link_many not yet implemented. See web page **
299
300
301def tsv_tee(pipeline: pipetools.Pipeline, src: pipetools.Element, *args, **properties) -> pipetools.Element:
302 """Split data from source to an nxy dump
303
304 Args:
305 pipeline:
306 Gst.Pipeline, the pipeline to which the new element will be added
307 src:
308 Gst.Element, the source element
309 *args:
310 **properties:
311
312 Returns:
313 Element
314 """
315 t = transform.tee(pipeline, src)
316 tsv(pipeline, transform.queue(pipeline, t), *args, **properties)
317 return t
318
319
320def trigger_xml_writer(pipeline: pipetools.Pipeline, src: pipetools.Element, filename: str):
321 """Write xml file
322
323 Args:
324 pipeline:
325 Gst.Pipeline, the pipeline to which the new element will be added
326 src:
327 Gst.Element, the source element
328 filename:
329 str, output path for xml file
330
331 References:
332 Implementation
333
334 Returns:
335 Element
336 """
337 return pipetools.make_element_with_src(pipeline, src, "lal_triggerxmlwriter", location=filename, sync=False, **{"async": False})
338
339
340# FIXME no specific alias for this url since this library only has one element.
341# DO NOT DOCUMENT OTHER CODES THIS WAY! Use @gstdoc @gstpluginsbasedoc etc.
342
343def app(pipeline: pipetools.Pipeline, src: pipetools.Element, max_buffers: int = 1, drop: bool = False, sync: bool = False, async_: bool = False, **properties):
344 """Create an app sink, Appsink is a sink plugin that supports many different methods for making the
345 application get a handle on the GStreamer data in a pipeline. Unlike most GStreamer elements,
346 Appsink provides external API functions.
347
348 Args:
349 pipeline:
350 Gst.Pipeline, the pipeline to which the new element will be added
351 src:
352 Gst.Element, the source element
353 max_buffers:
354 int, default 1
355 drop:
356 bool, default False
357 sync:
358 bool, default False
359 async_:
360 bool, default False
361 **properties:
362
363 References:
364 [1] https://gstreamer.freedesktop.org/documentation/app/appsink.html?gi-language=python
365
366 Returns:
367 Element
368 """
369 properties["async"] = async_
370 return pipetools.make_element_with_src(pipeline, src, "appsink", sync=sync, emit_signals=True, max_buffers=max_buffers, drop=drop, **properties)
371
372
373class AppSync(object):
374 def __init__(self, appsink_new_buffer, appsinks=[]):
375 self.lock = threading.Lock()
376 # handler to invoke on availability of new time-ordered
377 # buffer
378 self.appsink_new_buffer = appsink_new_buffer
379 # element --> timestamp of current buffer or None if no
380 # buffer yet available
381 self.appsinks = {}
382 # set of sink elements that are currently at EOS
383 self.at_eos = set()
384 # attach handlers to appsink elements provided at this time
385 for elem in appsinks:
386 self.attach(elem)
387
388 def add_sink(self, pipeline, src, drop=False, **properties):
389 return self.attach(app(pipeline, src, drop=drop, **properties))
390
391 def attach(self, appsink):
392 """
393 connect this AppSync's signal handlers to the given appsink
394 element. the element's max-buffers property will be set to
395 1 (required for AppSync to work).
396 """
397 if appsink in self.appsinks:
398 raise ValueError("duplicate appsinks %s" % repr(appsink))
399 appsink.set_property("max-buffers", 1)
400 handler_id = appsink.connect("new-preroll", self.new_preroll_handler)
401 assert handler_id > 0
402 handler_id = appsink.connect("new-sample", self.new_sample_handler)
403 assert handler_id > 0
404 handler_id = appsink.connect("eos", self.eos_handler)
405 assert handler_id > 0
406 self.appsinks[appsink] = None
407 return appsink
408
409 def new_preroll_handler(self, elem):
410 with self.lock:
411 # clear eos status
412 self.at_eos.discard(elem)
413 # ignore preroll buffers
414 elem.emit("pull-preroll")
415 return Gst.FlowReturn.OK
416
417 def new_sample_handler(self, elem):
418 with self.lock:
419 # clear eos status, and retrieve buffer timestamp
420 self.at_eos.discard(elem)
421 assert self.appsinks[elem] is None
422 self.appsinks[elem] = elem.get_last_sample().get_buffer().pts
423 # pull available buffers from appsink elements
424 return self.pull_buffers(elem)
425
426 def eos_handler(self, elem):
427 with self.lock:
428 # set eos status
429 self.at_eos.add(elem)
430 # pull available buffers from appsink elements
431 return self.pull_buffers(elem)
432
433 def pull_buffers(self, elem):
434 """
435 for internal use. must be called with lock held.
436 """
437 # keep looping while we can process buffers
438 while 1:
439 # retrieve the timestamps of all elements that
440 # aren't at eos and all elements at eos that still
441 # have buffers in them
442 timestamps = [(t, e) for e, t in self.appsinks.items() if e not in self.at_eos or t is not None]
443 # if all elements are at eos and none have buffers,
444 # then we're at eos
445 if not timestamps:
446 return Gst.FlowReturn.EOS
447 # find the element with the oldest timestamp. None
448 # compares as less than everything, so we'll find
449 # any element (that isn't at eos) that doesn't yet
450 # have a buffer (elements at eos and that are
451 # without buffers aren't in the list)
452 timestamp, elem_with_oldest = min(timestamps, key=lambda x: x[0] if x[0] is not None else -numpy.inf)
453 # if there's an element without a buffer, quit for
454 # now --- we require all non-eos elements to have
455 # buffers before proceding
456 if timestamp is None:
457 return Gst.FlowReturn.OK
458 # clear timestamp and pass element to handler func.
459 # function call is done last so that all of our
460 # book-keeping has been taken care of in case an
461 # exception gets raised
462 self.appsinks[elem_with_oldest] = None
463 self.appsink_new_buffer(elem_with_oldest)
464
465
467 """Add a signal handler to write a pipeline graph upon receipt of the
468 first trigger buffer. the caps in the pipeline graph are not fully
469 negotiated until data comes out the end, so this version of the graph
470 shows the final formats on all links
471 """
472
473 def __init__(self, pipeline, appsinks, basename, verbose=False):
474 self.pipeline = pipeline
475 self.filestem = "%s.%s" % (basename, "TRIGGERS")
476 self.verbose = verbose
477 # map element to handler ID
478 self.remaining_lock = threading.Lock()
479 self.remaining = {}
480 for sink in appsinks:
481 self.remaining[sink] = sink.connect_after("new-preroll", self.execute)
482 assert self.remaining[sink] > 0
483
484 def execute(self, elem):
485 with self.remaining_lock:
486 handler_id = self.remaining.pop(elem)
487 if not self.remaining:
488 pipedot.write_dump_dot(self.pipeline, self.filestem, verbose=self.verbose)
489 elem.disconnect(handler_id)
490 return Gst.FlowReturn.OK
491
492
493def tcp_server(pipeline: pipetools.Pipeline, src: pipetools.Element, **properties) -> pipetools.Element:
494 """Create a sink via TCP server
495
496 Args:
497 pipeline:
498 Gst.Pipeline, the pipeline to which the new element will be added
499 src:
500 Gst.Element, the source element
501 **properties:
502
503 References:
504 [1] https://gstreamer.freedesktop.org/documentation/tcp/tcpserversink.html?gi-language=python
505
506 Returns:
507 Element
508 """
509 # units_soft_max = 1 GB
510 # FIXME: are these sensible defaults?
511 return pipetools.make_element_with_src(pipeline, src, "tcpserversink", sync=True, sync_method="latest-keyframe", recover_policy="keyframe", unit_type="bytes",
512 units_soft_max=1024 ** 3,
513 **properties)
attach(self, appsink)
Definition sink.py:391
new_sample_handler(self, elem)
Definition sink.py:417
new_preroll_handler(self, elem)
Definition sink.py:409
pipetools.Element auto_audio(pipetools.Pipeline pipeline, pipetools.Element src)
Adds a autoaudiosink element to a pipeline with useful default properties.
Definition sink.py:252
trigger_xml_writer(pipetools.Pipeline pipeline, pipetools.Element src, str filename)
Definition sink.py:320
pipetools.Element tsv_tee(pipetools.Pipeline pipeline, pipetools.Element src, *args, **properties)
Definition sink.py:301
pipetools.Element gwf(pipetools.Pipeline pipeline, pipetools.Element src, bool message_forward=True, **properties)
Definition sink.py:107
pipetools.Element auto_video(pipetools.Pipeline pipeline, pipetools.Element src)
Definition sink.py:231
pipetools.Element file(pipetools.Pipeline pipeline, pipetools.Element src, str filename, bool sync=False, bool async_=False)
Adds a filesink element to a pipeline with useful default properties.
Definition sink.py:151
framecpp_filesink_ldas_path_handler(pipetools.Element elem, pspec, Tuple[str, int] path_digits)
Definition sink.py:29
pipetools.Element tsv(pipetools.Pipeline pipeline, pipetools.Element src, str filename, pipetools.Segment segment=None)
Adds a lal_nxydump element to a pipeline with useful default properties.
Definition sink.py:173
framecpp_filesink_cache_entry_from_mfs_message(message)
Definition sink.py:65
pipetools.Element playback(pipetools.Pipeline pipeline, pipetools.Element src, float amplification=0.1)
Definition sink.py:272
pipetools.Element fake(pipetools.Pipeline pipeline, pipetools.Element src)
Adds a fakesink element to a pipeline with useful default properties.
Definition sink.py:135
ogm_video(pipetools.Pipeline pipeline, pipetools.Element videosrc, str filename, pipetools.Element audiosrc=None, bool verbose=False)
Definition sink.py:198
app(pipetools.Pipeline pipeline, pipetools.Element src, int max_buffers=1, bool drop=False, bool sync=False, bool async_=False, **properties)
Adds a appsink element to a pipeline with useful default properties.
Definition sink.py:343
pipetools.Element multi_file(pipetools.Pipeline pipeline, pipetools.Element src, int next_file=0, bool sync=False, bool async_=False, **properties)
Adds a multifilesink element to a pipeline with useful default properties.
Definition sink.py:84
pipetools.Element tcp_server(pipetools.Pipeline pipeline, pipetools.Element src, **properties)
Definition sink.py:493