• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bossjones / scarlett_os / 2341

pending completion
2341

push

travis-ci

web-flow
Feature: feature-run-in-flatpak (#302)

464 of 1021 branches covered (45.45%)

56 of 56 new or added lines in 10 files covered. (100.0%)

2752 of 4339 relevant lines covered (63.42%)

0.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.85
/scarlett_os/player.py
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3

4
"""Scarlett Player module. Uses Gstreamer to output audio."""
1✔
5

6
# NOTE: THIS IS THE CLASS THAT WILL BE REPLACING scarlett_player.py eventually.
7
# It is cleaner, more object oriented, and will allows us to run proper tests.
8
# Also threading.RLock() and threading.Semaphore() works correctly.
9

10
#
11
# There are a LOT of threads going on here, all of them managed by Gstreamer.
12
# If pyglet ever needs to run under a Python that doesn't have a GIL, some
13
# locks will need to be introduced to prevent concurrency catastrophes.
14
#
15
# At the moment, no locks are used because we assume only one thread is
16
# executing Python code at a time.  Some semaphores are used to block and wake
17
# up the main thread when needed, these are all instances of
18
# threading.Semaphore.  Note that these don't represent any kind of
19
# thread-safety.
20

21
from __future__ import with_statement, division, absolute_import
1✔
22

23
import os
1✔
24
import sys
1✔
25

26
import signal
1✔
27
import threading
1✔
28
import logging
1✔
29
import pprint
1✔
30

31
from scarlett_os.internal.gi import gi
1✔
32
from scarlett_os.internal.gi import GObject
1✔
33
from scarlett_os.internal.gi import GLib
1✔
34
from scarlett_os.internal.gi import Gst
1✔
35
from scarlett_os.internal.gi import Gio
1✔
36

37
from scarlett_os.exceptions import IncompleteGStreamerError
1✔
38
from scarlett_os.exceptions import MetadataMissingError
1✔
39
from scarlett_os.exceptions import NoStreamError
1✔
40
from scarlett_os.exceptions import FileReadError
1✔
41
from scarlett_os.exceptions import UnknownTypeError
1✔
42
from scarlett_os.exceptions import InvalidUri
1✔
43
from scarlett_os.exceptions import UriReadError
1✔
44

45
import queue
1✔
46
from urllib.parse import quote
1✔
47

48
from scarlett_os.utility.gnome import trace
1✔
49
from scarlett_os.utility.gnome import abort_on_exception
1✔
50
from scarlett_os.utility.gnome import _IdleObject
1✔
51

52
from scarlett_os.internal.path import uri_is_valid
1✔
53
from scarlett_os.internal.path import isReadable
1✔
54
from scarlett_os.internal.path import path_from_uri
1✔
55
from scarlett_os.internal.path import get_parent_dir
1✔
56
from scarlett_os.internal.path import mkdir_if_does_not_exist
1✔
57
from scarlett_os.internal.path import fname_exists
1✔
58
from scarlett_os.internal.path import touch_empty_file
1✔
59

60
from scarlett_os.common.configure.ruamel_config import ConfigManager
1✔
61
from scarlett_os.internal.debugger import dump
1✔
62

63
# from scarlett_os.logger import setup_logger
64
#
65
# setup_logger()
66

67
# Alias
68
gst = Gst
1✔
69

70
# global pretty print for debugging
71
pp = pprint.PrettyPrinter(indent=4)
1✔
72

73
logger = logging.getLogger(__name__)
1✔
74

75

76
# Constants
77
QUEUE_SIZE = 10
1✔
78
BUFFER_SIZE = 10
1✔
79
SENTINEL = "__GSTDEC_SENTINEL__"
1✔
80
SEMAPHORE_NUM = 0
1✔
81

82

83
# Managing the Gobject main loop thread.
84

85
_shared_loop_thread = None
1✔
86
_loop_thread_lock = threading.RLock()
1✔
87

88

89
def get_loop_thread():
1✔
90
    """Get the shared main-loop thread.
91
    """
92
    global _shared_loop_thread
93
    with _loop_thread_lock:
1✔
94
        if not _shared_loop_thread:
1✔
95
            # Start a new thread.
96
            _shared_loop_thread = MainLoopThread()
1✔
97
            _shared_loop_thread.start()
1✔
98
        return _shared_loop_thread
1✔
99

100

101
# NOTE: doc updated via
102
# https://github.com/Faham/emophiz/blob/15612aaf13401201100d67a57dbe3ed9ace5589a/emotion_engine/dependencies/src/sensor_lib/SensorLib.Tobii/Software/tobiisdk-3.0.2-Win32/Win32/Python26/Modules/tobii/sdk/mainloop.py
103

104

105
class MainLoopThread(threading.Thread):
1✔
106
    """A daemon thread encapsulating a Gobject main loop.
107

108
    A mainloop is used by all asynchronous objects to defer handlers and callbacks to.
109
    The function run() blocks until the function quit() has been called (and all queued handlers have been executed).
110
    The run() function will then execute all the handlers in order.
111
    """
112

113
    def __init__(self):
1✔
114
        super(MainLoopThread, self).__init__()
1✔
115
        self.loop = GObject.MainLoop()
1✔
116
        self.daemon = True
1✔
117

118
    def run(self):
1✔
119
        self.loop.run()
1✔
120

121

122
class ScarlettPlayer(_IdleObject):
1✔
123
    # Anything defined here belongs to the class itself
124
    #
125
    # :param path: (str) path to sound file.
126
    # :handler_error: (callable)
127
    # :callback: (callable)
128

129
    DEFAULT_SRC = "uridecodebin"
1✔
130

131
    DEFAULT_SINK = "pulsesink"
1✔
132

133
    def __init__(self, path, handle_error, callback):
1✔
134
        self.running = False
1✔
135
        self.finished = False
1✔
136
        self.handle_error = False if handle_error is None else handle_error
1✔
137
        self.callback = callback
1✔
138
        # self.delay_start = False if delay_start is None else delay_start
139
        # self.stopme = threading.Event()
140
        self._lock = threading.Lock()
1✔
141

142
        # Set up the Gstreamer pipeline.
143
        # NOTE: Need to add .new for some reason to pass tests ...
144
        # source: https://github.com/hadware/gstreamer-python-player/issues/1
145
        self.pipeline = Gst.Pipeline.new("main-pipeline")
1✔
146
        self.ready_sem = threading.Semaphore(SEMAPHORE_NUM)
1✔
147

148
        # Register for bus signals.
149
        bus = self.pipeline.get_bus()
1✔
150
        bus.add_signal_watch()
1✔
151
        bus.connect("message::eos", self._message)
1✔
152
        bus.connect("message::error", self._message)
1✔
153
        bus.connect("message::state-changed", self._on_state_changed)
1✔
154

155
        # 1. Create pipeline's elements
156
        self.source = Gst.ElementFactory.make("uridecodebin", "input_stream")
1✔
157
        self.audioconvert = Gst.ElementFactory.make("audioconvert", None)
1✔
158
        self.splitter = Gst.ElementFactory.make("tee", "splitter")
1✔
159

160
        if not self.source or not self.audioconvert or not self.splitter:
1✔
161
            logger.error("ERROR: Not all elements could be created.")
1✔
162
            raise IncompleteGStreamerError()
1✔
163

164
        # TODO: Add a test to check for False / None type before moving forward. Throw AttributeError.
165

166
        # 2. Set properties
167
        uri = "file://" + quote(os.path.abspath(path))
1✔
168

169
        # Make sure GST likes the uri
170
        if not uri_is_valid(uri):
1✔
171
            logger.error(
1✔
172
                "Error: " "Something is wrong with uri " "provided. uri: {}".format(uri)
173
            )
174
            raise InvalidUri()
1✔
175

176
        # Make sure we can actually read the uri
177
        if not isReadable(path_from_uri(uri)):
1✔
178
            logger.error("Error: Can't read uri:" " {}".format(path_from_uri(uri)))
1✔
179
            raise UriReadError()
1✔
180

181
        self.source.set_property("uri", uri)
1✔
182

183
        # 3. Add them to the pipeline
184
        self.pipeline.add(self.source)
1✔
185
        self.pipeline.add(self.audioconvert)
1✔
186
        self.pipeline.add(self.splitter)
1✔
187

188
        self.audioconvert.link(self.splitter)
1✔
189

190
        self.source.connect("source-setup", self._source_setup_cb)
1✔
191

192
        # 4.a. uridecodebin has a "sometimes" pad (created after prerolling)
193
        self.source.connect("pad-added", self._decode_src_created)
1✔
194
        self.source.connect("no-more-pads", self._no_more_pads)
1✔
195
        self.source.connect("unknown-type", self._unknown_type)
1✔
196

197
        #######################################################################
198
        # QUEUE A
199
        #######################################################################
200
        self.queueA = Gst.ElementFactory.make("queue", None)
1✔
201
        # BOSSJONESTEMP # self.audioconvert =
202
        # Gst.ElementFactory.make('audioconvert', None)
203
        self.appsink = Gst.ElementFactory.make("appsink", None)
1✔
204
        self.appsink.set_property(
1✔
205
            "caps", Gst.Caps.from_string("audio/x-raw, format=(string)S16LE")
206
        )
207
        # TODO set endianness?
208
        # Set up the characteristics of the output. We don't want to
209
        # drop any data (nothing is real-time here); we should bound
210
        # the memory usage of the internal queue; and, most
211
        # importantly, setting "sync" to False disables the default
212
        # behavior in which you consume buffers in real time. This way,
213
        # we get data as soon as it's decoded.
214
        self.appsink.set_property("drop", False)
1✔
215
        self.appsink.set_property("max-buffers", BUFFER_SIZE)
1✔
216
        self.appsink.set_property("sync", False)
1✔
217

218
        # The callback to receive decoded data.
219
        self.appsink.set_property("emit-signals", True)
1✔
220
        self.appsink.connect("new-sample", self._new_sample)
1✔
221

222
        self.caps_handler = self.appsink.get_static_pad("sink").connect(
1✔
223
            "notify::caps", self._notify_caps
224
        )
225
        print("caps_handler: {}".format(self.caps_handler))
1✔
226

227
        self.pipeline.add(self.queueA)
1✔
228
        self.pipeline.add(self.appsink)
1✔
229

230
        self.queueA.link(self.appsink)
1✔
231

232
        # link tee to queueA
233
        tee_src_pad_to_appsink_bin = self.splitter.get_request_pad("src_%u")
1✔
234
        logger.debug(
1✔
235
            "Obtained request pad "
236
            "Name({}) Type({}) for audio branch.".format(
237
                self.splitter.name, self.splitter
238
            )
239
        )
240
        queueAsinkPad = self.queueA.get_static_pad("sink")
1✔
241
        logger.debug(
1✔
242
            "Obtained sink pad for element ({}) for tee -> queueA.".format(
243
                queueAsinkPad
244
            )
245
        )
246
        tee_src_pad_to_appsink_bin.link(queueAsinkPad)
1✔
247

248
        #######################################################################
249
        # QUEUE B
250
        #######################################################################
251

252
        self.queueB = Gst.ElementFactory.make("queue", None)
1✔
253
        self.pulsesink = Gst.ElementFactory.make(ScarlettPlayer.DEFAULT_SINK, None)
1✔
254

255
        self.pipeline.add(self.queueB)
1✔
256
        self.pipeline.add(self.pulsesink)
1✔
257

258
        self.queueB.link(self.pulsesink)
1✔
259

260
        self.queueB_sink_pad = self.queueB.get_static_pad("sink")
1✔
261

262
        # link tee to queueB
263
        tee_src_pad_to_appsink_bin = self.splitter.get_request_pad("src_%u")
1✔
264
        logger.debug(
1✔
265
            "Obtained request pad Name({}) Type({}) for audio branch.".format(
266
                self.splitter.name, self.splitter
267
            )
268
        )
269
        queueAsinkPad = self.queueB.get_static_pad("sink")
1✔
270
        logger.debug(
1✔
271
            "Obtained sink pad for element ({}) for tee -> queueB.".format(
272
                queueAsinkPad
273
            )
274
        )
275
        tee_src_pad_to_appsink_bin.link(queueAsinkPad)
1✔
276

277
        # recursively print elements
278
        self._listElements(self.pipeline)
1✔
279

280
        #######################################################################
281

282
        # Set up the queue for data and run the main thread.
283
        self.queue = queue.Queue(QUEUE_SIZE)
1✔
284
        # False if handle_error is None else handle_error
285
        # if loop_thread:
286
        #     self.thread = loop_thread
287
        # else:
288
        #     self.thread = get_loop_thread()
289

290
        self.thread = get_loop_thread()
1✔
291

292
        # This wil get filled with an exception if opening fails.
293
        self.read_exc = None
1✔
294
        self.dot_exc = None
1✔
295

296
        # Return as soon as the stream is ready!
297
        self.running = True
1✔
298
        self.got_caps = False
1✔
299

300
        # if not self.delay_start:
301

302
        # if self.stopme.isSet():
303
        #         return
304

305
        self.pipeline.set_state(Gst.State.PLAYING)
1✔
306
        # 8/8/2018 (Only enable this if we turn on debug mode)
307
        if os.environ.get("SCARLETT_DEBUG_MODE"):
1!
308
            self.on_debug_activate()
×
309

310
        self.ready_sem.acquire()
1✔
311

312
        if self.read_exc:
1!
313
            # An error occurred before the stream became ready.
314
            self.close(True)
×
315
            raise self.read_exc  # pylint: disable=raising-bad-type
×
316

317
    # def abort(self):
318
    #     self.stopme.set()
319

320
    def _source_setup_cb(self, discoverer, source):
1✔
321
        logger.debug("Discoverer object: ({})".format(discoverer))
1✔
322
        logger.debug("Source object: ({})".format(source))
1✔
323

324
    def _on_state_changed(self, bus, msg):
1✔
325
        # states = msg.parse_state_changed()
326
        # # To state is PLAYING
327
        # if msg.src.get_name() == "pipeline" and states[1] == 4:
328
        #     # NOTE: For right now, lets not try to write anything 8/8/2018
329
        #     # TODO: Modify the creation of this path, it should be programatically created
330
        #     # FIXME: This needs to use dynamic paths, it's possible that we're having issues because of order of operations
331
        #     # FIXME: STATIC PATH 7/3/2018
332
        #     dotfile = (
333
        #         "/home/pi/dev/bossjones-github/scarlett_os/_debug/generator-player.dot"
334
        #     )
335
        #     pngfile = "/home/pi/dev/bossjones-github/scarlett_os/_debug/generator-player-pipeline.png"  # NOQA
336

337
        #     parent_dir = get_parent_dir(dotfile)
338

339
        #     mkdir_if_does_not_exist(parent_dir)
340

341
        #     if os.access(dotfile, os.F_OK):
342
        #         os.remove(dotfile)
343
        #     if os.access(pngfile, os.F_OK):
344
        #         os.remove(pngfile)
345

346
        #     if not fname_exists(dotfile):
347
        #         touch_empty_file(dotfile)
348

349
        #     if not fname_exists(pngfile):
350
        #         touch_empty_file(pngfile)
351

352
        #     # FIXME: This needs to use dynamic paths, it's possible that we're having issues because of order of operations
353
        #     # FIXME: STATIC PATH 7/3/2018
354
        #     Gst.debug_bin_to_dot_file(
355
        #         msg.src, Gst.DebugGraphDetails.ALL, "generator-player"
356
        #     )
357

358
        #     cmd = "/usr/bin/dot -Tpng -o {pngfile} {dotfile}".format(
359
        #         pngfile=pngfile, dotfile=dotfile
360
        #     )
361
        #     os.system(cmd)
362
        #     print("pipeline dot file created in " + os.getenv("GST_DEBUG_DUMP_DOT_DIR"))
363
        pass
1✔
364

365
    def _listElements(self, bin, level=0):
1✔
366
        try:
1✔
367
            iterator = bin.iterate_elements()
1✔
368
            # print iterator
369
            while True:
1✔
370
                elem = iterator.next()
1✔
371
                if elem[1] is None:
1✔
372
                    break
1✔
373
                logger.debug(level * "** " + str(elem[1]))
1✔
374
                # uncomment to print pads of element
375
                self._iteratePads(elem[1])
1✔
376
                # call recursively
377
                self._listElements(elem[1], level + 1)
1✔
378
        except AttributeError:
1✔
379
            pass
1✔
380

381
    def _iteratePads(self, element):
1✔
382
        try:
1✔
383
            iterator = element.iterate_pads()
1✔
384
            while True:
1✔
385
                pad = iterator.next()
1✔
386
                if pad[1] is None:
1✔
387
                    break
1✔
388
                logger.debug("pad: {}".format(str(pad[1])))
1✔
389
        except AttributeError:
×
390
            pass
×
391

392
    # NOTE: This function generates the dot file, checks that graphviz in installed and
393
    # then finally generates a png file, which it then displays
394
    def on_debug_activate(self):
1✔
395
        # FIXME: STATIC PATH 7/3/2018
396
        # dotfile = (
397
        #     "/home/pi/dev/bossjones-github/scarlett_os/_debug/generator-player.dot"
398
        # )
399
        # pngfile = "/home/pi/dev/bossjones-github/scarlett_os/_debug/generator-player-pipeline.png"  # NOQA
400

401
        # parent_dir = get_parent_dir(dotfile)
402

403
        # mkdir_if_does_not_exist(parent_dir)
404

405
        # if os.access(dotfile, os.F_OK):
406
        #     os.remove(dotfile)
407
        # if os.access(pngfile, os.F_OK):
408
        #     os.remove(pngfile)
409

410
        # if not fname_exists(dotfile):
411
        #     touch_empty_file(dotfile)
412

413
        # if not fname_exists(pngfile):
414
        #     touch_empty_file(pngfile)
415

416
        # # FIXME: This needs to use dynamic paths, it's possible that we're having issues because of order of operations
417
        # # FIXME: STATIC PATH 7/3/2018
418
        # Gst.debug_bin_to_dot_file(
419
        #     self.pipeline, Gst.DebugGraphDetails.ALL, "generator-player"
420
        # )
421
        # cmd = "/usr/bin/dot -Tpng -o {pngfile} {dotfile}".format(
422
        #     pngfile=pngfile, dotfile=dotfile
423
        # )
424
        # os.system(cmd)
425
        pass
×
426

427
    # Gstreamer callbacks.
428

429
    def _notify_caps(self, pad, args):
1✔
430
        """The callback for the sinkpad's "notify::caps" signal.
431
        """
432
        # The sink has started to receive data, so the stream is ready.
433
        # This also is our opportunity to read information about the
434
        # stream.
435
        logger.debug("pad: {}".format(pad))
×
436
        logger.debug("pad name: {} parent: {}".format(pad.name, pad.get_parent()))
×
437
        logger.debug("args: {}".format(args))
×
438
        self.got_caps = True
×
439
        info = pad.get_current_caps().get_structure(0)
×
440

441
        # Stream attributes.
442
        self.channels = info.get_int("channels")[1]
×
443
        self.samplerate = info.get_int("rate")[1]
×
444

445
        # Query duration.
446
        success, length = pad.get_peer().query_duration(Gst.Format.TIME)
×
447
        if success:
×
448
            self.duration = length / 1000000000
×
449
            logger.debug("FILE DURATION: {}".format(self.duration))
×
450
        else:
451
            self.read_exc = MetadataMissingError("duration not available")
×
452

453
        # Allow constructor to complete.
454
        self.ready_sem.release()
×
455

456
    _got_a_pad = False
1✔
457

458
    def _decode_src_created(self, element, pad):
1✔
459
        """The callback for GstElement's "pad-added" signal.
460
        """
461
        # Decoded data is ready. Connect up the decoder, finally.
462
        name = pad.query_caps(None).to_string()
×
463
        logger.debug("pad: {}".format(pad))
×
464
        logger.debug("pad name: {} parent: {}".format(pad.name, pad.get_parent()))
×
465
        if name.startswith("audio/x-raw"):
×
466
            nextpad = self.audioconvert.get_static_pad("sink")
×
467
            if not nextpad.is_linked():
×
468
                self._got_a_pad = True
×
469
                pad.link(nextpad)
×
470

471
    def _no_more_pads(self, element):
1✔
472
        """The callback for GstElement's "no-more-pads" signal.
473
        """
474
        # Sent when the pads are done adding (i.e., there are no more
475
        # streams in the file). If we haven't gotten at least one
476
        # decodable stream, raise an exception.
477
        if not self._got_a_pad:
×
478
            logger.error(
×
479
                "If we haven't gotten at least one decodable stream, raise an exception."
480
            )
481
            self.read_exc = NoStreamError()
×
482
            self.ready_sem.release()  # No effect if we've already started.
×
483

484
    def _new_sample(self, sink):
1✔
485
        """The callback for appsink's "new-sample" signal.
486
        """
487
        if self.running:
×
488
            # FIXME: logger.debug("sink: {}".format(sink))
489
            # FIXME: logger.debug("sink name: {} parent: {}".format(sink.name, sink.get_parent()))
490
            # New data is available from the pipeline! Dump it into our
491
            # queue (or possibly block if we're full).
492
            buf = sink.emit("pull-sample").get_buffer()
×
493
            self.queue.put(buf.extract_dup(0, buf.get_size()))
×
494
        return Gst.FlowReturn.OK
×
495

496
    def _unknown_type(self, uridecodebin, decodebin, caps):
1✔
497
        """The callback for decodebin's "unknown-type" signal.
498
        """
499
        # This is called *before* the stream becomes ready when the
500
        # file can't be read.
501
        streaminfo = caps.to_string()
×
502
        if not streaminfo.startswith("audio/"):
×
503
            # Ignore non-audio (e.g., video) decode errors.
504
            return
×
505
        logger.error("Ignore non-audio (e.g., video) decode errors.")
×
506
        logger.error("streaminfo: {}".format(streaminfo))
×
507
        self.read_exc = UnknownTypeError(streaminfo)
×
508
        self.ready_sem.release()
×
509

510
    def _message(self, bus, message):
1✔
511
        """The callback for GstBus's "message" signal (for two kinds of
512
        messages).
513
        """
514
        if not self.finished:
×
515
            if message.type == Gst.MessageType.EOS:
×
516
                # The file is done. Tell the consumer thread.
517
                self.queue.put(SENTINEL)
×
518
                if not self.got_caps:
×
519
                    logger.error(
×
520
                        "If the stream ends before _notify_caps was called, this is an invalid file."
521
                    )
522
                    # If the stream ends before _notify_caps was called, this
523
                    # is an invalid file.
524
                    self.read_exc = NoStreamError()
×
525
                    self.ready_sem.release()
×
526

527
            elif message.type == Gst.MessageType.ERROR:
×
528
                gerror, debug = message.parse_error()
×
529
                if "not-linked" in debug:
×
530
                    logger.error("not-linked")
×
531
                    self.read_exc = NoStreamError()
×
532
                elif "No such file" in debug:
×
533
                    self.read_exc = IOError("resource not found")
×
534
                else:
535
                    self.read_exc = FileReadError(debug)
×
536
                self.ready_sem.release()
×
537

538
    # Iteration.
539
    def next(self):
1✔
540
        # Wait for data from the Gstreamer callbacks.
541
        val = self.queue.get()
1✔
542
        if val == SENTINEL:
1!
543
            # End of stream.
544
            raise StopIteration
×
545
        return val
1✔
546

547
    # For Python 3 compatibility.
548
    __next__ = next
1✔
549

550
    def __iter__(self):
1✔
551
        return self
×
552

553
    # Cleanup.
554
    def close(self, force=False):
1✔
555
        """Close the file and clean up associated resources.
556

557
        Calling `close()` a second time has no effect.
558
        """
559
        if self.running or force:
1✔
560
            self.running = False
1✔
561
            self.finished = True
1✔
562

563
            # Unregister for signals, which we registered for above with
564
            # `add_signal_watch`. (Without this, GStreamer leaks file
565
            # descriptors.)
566
            # FIXME: For some reason we need to keep this so that a stack trace happens and breaks everything
567
            # out of endless ide_add loop
568
            # Traceback (most recent call last):
569
            #     File "/home/pi/dev/bossjones-github/scarlett_os/scarlett_os/utility/generators.py", line 108, in __generator_executer
570
            #       result = next(self._generator)
571
            #     File "/home/pi/dev/bossjones-github/scarlett_os/scarlett_os/tasker.py", line 412, in player_generator_func
572
            #       p.close(force=True)
573
            #     File "/home/pi/dev/bossjones-github/scarlett_os/scarlett_os/player.py", line 531, in close
574
            #       self.pipeline
575
            # AttributeError: 'ScarlettPlayer' object has no attribute 'pipeline'
576
            print("[2018-debugging]: dump(self)  - BEGIN")
1✔
577
            print(self)
1✔
578
            # dump(self)
579
            print("[2018-debugging]: dump(self)  - END")
1✔
580
            # FIXME: 9/2/2018 -
581
            # We might need to unref our bus object. Calling too many times seems to cause problems.
582
            # Hi, I got an error while calling audio_open() several times(with .mp3 files). Looks like it is due to accumulated refs of bus objects in GstAudioFile. I gave it a monkey patch and apparently it seems fine. However, since my files are not gst-encoded, I'm not sure whether this would cause any problem with actual gst decoding tasks.
583
            # In my case, I inserted following lines in GstAudioFile.close():
584
            # bus = self.pipeline.get_bus()
585
            # bus.unref()
586
            # bus.remove_signal_watch()
587
            # SOURCE: https://github.com/beetbox/audioread/issues/28 ( khlukekim on Jan 28, 2016 )
588
            self.pipeline.get_bus().remove_signal_watch()
1✔
589

590
            # Stop reading the file.
591
            self.source.set_property("uri", None)
1✔
592
            # Block spurious signals.
593
            if self.caps_handler:
1!
594
                print("self.caps_handler = {}".format(self.caps_handler))
1✔
595
                self.appsink.get_static_pad("sink").disconnect(self.caps_handler)
1✔
596
                self.caps_handler = None
1✔
597
            else:
598
                print("self.caps_handler = None ... doing nothing")
×
599

600
            # Make space in the output queue to let the decoder thread
601
            # finish. (Otherwise, the thread blocks on its enqueue and
602
            # the interpreter hangs.)
603
            try:
1✔
604
                self.queue.get_nowait()
1✔
605
            except queue.Empty:
1✔
606
                pass
1✔
607

608
            # Halt the pipeline (closing file).
609
            if hasattr(self, "pipeline"):
1!
610
                print("Halt the self.pipeline (closing file).")
1✔
611
                self.pipeline.set_state(Gst.State.NULL)
1✔
612
            else:
613
                print("self.pipeline already halted, don't set_state ... doing nothing")
×
614

615
            if hasattr(self, "pipeline"):
1!
616
                print(
1✔
617
                    "Delete the pipeline object. This seems to be necessary on Python "
618
                    "2, but not Python 3 for some reason: on 3.5, at least, the "
619
                    "pipeline gets dereferenced automatically."
620
                )
621
                del self.pipeline
1✔
622
            else:
623
                print("self.pipeline already deleted... doing nothing")
×
624

625
    def __del__(self):
1✔
626
        logger.info("delete time")
1✔
627
        self.close()
1✔
628

629
    # Context manager.
630
    def __enter__(self):
1✔
631
        logger.info("enter time")
1✔
632
        return self
1✔
633

634
    def __exit__(self, exc_type, exc_val, exc_tb):
1✔
635
        logger.info("exit time")
1✔
636
        self.close()
1✔
637
        return self.handle_error
1✔
638

639

640
# Smoke test.
641
if __name__ == "__main__":
642
    if os.environ.get("SCARLETT_DEBUG_MODE"):
643
        import faulthandler
644

645
        faulthandler.register(signal.SIGUSR2, all_threads=True)
646

647
        from scarlett_os.internal.debugger import init_debugger
648
        from scarlett_os.internal.debugger import set_gst_grapviz_tracing
649

650
        init_debugger()
651
        set_gst_grapviz_tracing()
652
        # Example of how to use it
653

654
    from scarlett_os.logger import setup_logger
655

656
    setup_logger()
657

658
    wavefile = [
659
        "/home/pi/dev/bossjones-github/scarlett_os/static/sounds/pi-listening.wav"
660
    ]
661
    # ORIG # for path in sys.argv[1:]:
662
    for path in wavefile:
663
        path = os.path.abspath(os.path.expanduser(path))
664
        with ScarlettPlayer(path, False, False) as f:
665
            print(f.channels)
666
            print(f.samplerate)
667
            print(f.duration)
668
            for s in f:
669
                pass
670
                # READ IN BLOCKS # print(len(s), ord(s[0]))
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc