engine.py 23.5 KB
Newer Older
1
#
David Trattnig's avatar
David Trattnig committed
2
# Aura Engine (https://gitlab.servus.at/aura/engine)
3
#
David Trattnig's avatar
David Trattnig committed
4
# Copyright (C) 2017-2020 - The Aura Engine Team.
5
#
David Trattnig's avatar
David Trattnig committed
6
7
8
9
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
10
#
David Trattnig's avatar
David Trattnig committed
11
12
13
14
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
15
#
David Trattnig's avatar
David Trattnig committed
16
17
18
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

19

20
21
import time
import logging
22

David Trattnig's avatar
David Trattnig committed
23
24
from contextlib             import suppress
from threading              import Thread
David Trattnig's avatar
David Trattnig committed
25

26
27
import meta

David Trattnig's avatar
David Trattnig committed
28
29
30
31
32
33
34
35
36
from src.base.config        import AuraConfig
from src.base.utils         import SimpleUtil as SU
from src.base.exceptions    import LQConnectionError, InvalidChannelException, LQStreamException, LoadSourceException
from src.resources          import ResourceClass, ResourceUtil                                   
from src.channels           import ChannelType, TransitionType, LiquidsoapResponse, EntryPlayState, ResourceType, ChannelRouter
from src.events             import EngineEventDispatcher
from src.control            import EngineControlInterface
from src.mixer              import Mixer, MixerType
from src.client.connector   import PlayerConnector
37

38

39

40

41
42
43
class Engine():
    """
    The Engine.
44
    """
David Trattnig's avatar
David Trattnig committed
45
    instance = None
46
    engine_time_offset = 0.0
47
    logger = None
48
    eci = None
49
    channels = None
50
    channel_router = None
51
    scheduler = None
52
    event_dispatcher = None
53
54
    plugins = None
    connector = None
55

56
57

    def __init__(self):
58
        """
59
        Constructor
60
        """
David Trattnig's avatar
David Trattnig committed
61
        if Engine.instance:
62
63
64
65
            raise Exception("Engine is already running!")        
        Engine.instance = self         
        self.logger = logging.getLogger("AuraEngine")            
        self.config = AuraConfig.config()
66
        Engine.engine_time_offset = float(self.config.get("lqs_delay_offset"))
67
        
68
        self.plugins = dict()                    
69
        self.channel_router = ChannelRouter(self.config, self.logger)
70
71
72
        self.start()


73
74
75

    def start(self):
        """
76
77
        Starts the engine. Called when the connection to the sound-system implementation
        has been established.
78
        """
79
80
        self.event_dispatcher = EngineEventDispatcher(self)
        self.eci = EngineControlInterface(self, self.event_dispatcher)
81
        self.connector = PlayerConnector(self.event_dispatcher)
82
        self.event_dispatcher.on_initialized()
83
84
85
86
        
        while not self.is_connected(): 
            self.logger.info(SU.yellow("Waiting for Liquidsoap to be running ..."))
            time.sleep(2)
87
        self.logger.info(SU.green("Engine Core ------[ connected ]-------- Liquidsoap"))
88

89
        self.player = Player(self.connector, self.event_dispatcher)
90
        self.event_dispatcher.on_boot()
91
        self.logger.info(EngineSplash.splash_screen("Engine Core", meta.__version__))
92
        self.event_dispatcher.on_ready()
David Trattnig's avatar
David Trattnig committed
93
94


95
96
97

    #
    #   Basic Methods
98
    #
99

100

101
    def is_connected(self):
102
        """
103
        Checks if there's a valid connection to Liquidsoap.
David Trattnig's avatar
David Trattnig committed
104
        """
105
        has_connection = False
106
107
        try:
            self.uptime()
108
            has_connection = True
109
110
111
112
        except LQConnectionError as e:
            self.logger.info("Liquidsoap is not running so far")
        except Exception as e:
            self.logger.error("Cannot check if Liquidsoap is running. Reason: " + str(e))
113

114
        return has_connection
115
116


117
118
119
120
    def engine_state(self):
        """
        Retrieves the state of all inputs and outputs.
        """
121
        state = self.connector.send_lqc_command("engine", "state")
122
        return state
David Trattnig's avatar
David Trattnig committed
123

David Trattnig's avatar
David Trattnig committed
124

125
    def version(self):
126
        """
127
        Get the version of Liquidsoap.
128
        """
129
        data = self.connector.send_lqc_command("version", "")
130
        return data
131
132


133
    def uptime(self):
David Trattnig's avatar
David Trattnig committed
134
        """
135
        Retrieves the uptime of Liquidsoap.
David Trattnig's avatar
David Trattnig committed
136
        """
137
138
139
        self.connector.enable_transaction()
        data = self.connector.send_lqc_command("uptime", "")
        self.connector.disable_transaction()
140
        return data
141

David Trattnig's avatar
David Trattnig committed
142

143
144
145
146
147
148
149
150
    @staticmethod
    def engine_time():
        """
        Liquidsoap is slow in executing commands, therefore it's needed to schedule
        actions by (n) seconds in advance, as defined in the configuration file by
        the property `lqs_delay_offset`. it's important to note that this method
        requires the class variable `EngineUtil.engine_time_offset` to be set on
        Engine initialization.
151

152
153
154
155
        Returns:
            (Integer):  the Unix epoch timestamp including the offset
        """
        return SU.timestamp() + Engine.engine_time_offset
156

157

David Trattnig's avatar
David Trattnig committed
158
159
160
161
162
163
164
165
    @staticmethod
    def get_instance():
        """
        Returns the one and only engine.
        """
        return Engine.instance


166
    def terminate(self):
David Trattnig's avatar
David Trattnig committed
167
        """
168
        Terminates the engine and all related processes.
David Trattnig's avatar
David Trattnig committed
169
        """
170
        if self.eci: self.eci.terminate()
171
172
173



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#
#   PLAYER
#


class Player:
    """
    Engine Player.
    """
    config = None
    logger = None
    connector = None
    channels = None
    channel_router = None
    event_dispatcher = None
    mixer = None
    mixer_fallback = None



194
    def __init__(self, connector, event_dispatcher):
David Trattnig's avatar
David Trattnig committed
195
        """
196
        Constructor
197

198
199
200
        Args:
            config (AuraConfig):    The configuration
        """
201
        self.config = AuraConfig.config()
202
203
        self.logger = logging.getLogger("AuraEngine")
        self.event_dispatcher = event_dispatcher        
204
        self.connector = connector
205
206
207
        self.channel_router = ChannelRouter(self.config, self.logger)
        self.mixer = Mixer(self.config, MixerType.MAIN, self.connector)
        self.mixer_fallback = Mixer(self.config, MixerType.FALLBACK, self.connector)
208
209
210



211
    def preload(self, entry):
David Trattnig's avatar
David Trattnig committed
212
        """
213
        Pre-Load the entry. This is required before the actual `play(..)` can happen.
David Trattnig's avatar
David Trattnig committed
214
215
216

        Be aware when using this method to queue a very short entry (shorter than ``) this may
        result in sitations with incorrect timing. In this case bundle multiple short entries as
217
        one queue using `preload_playlist(self, entries)`.
218

David Trattnig's avatar
David Trattnig committed
219
220
221
222
223
224
        It's important to note, that his method is blocking until loading has finished. If this 
        method is called asynchronously, the progress on the preloading state can be looked up in 
        `entry.state`.

        Args:
            entries ([Entry]):    An array holding filesystem entries
David Trattnig's avatar
David Trattnig committed
225
226
227
        """
        entry.status = EntryPlayState.LOADING
        self.logger.info("Loading entry '%s'" % entry)
228
        is_ready = False
229

David Trattnig's avatar
David Trattnig committed
230
        # LIVE
231
        if entry.get_content_type() in ResourceClass.LIVE.types:
232
            entry.channel = "linein_" + entry.source.split("line://")[1]
233
            is_ready = True
234
        else:
235
236
            channel_type = self.channel_router.type_for_resource(entry.get_content_type())
            entry.previous_channel, entry.channel = self.channel_router.channel_swap(channel_type)
237

238
239
240
        # QUEUE
        if entry.get_content_type() in ResourceClass.FILE.types:
            is_ready = self.queue_push(entry.channel, entry.source)
David Trattnig's avatar
David Trattnig committed
241
242
            
        # STREAM
243
        elif entry.get_content_type() in ResourceClass.STREAM.types:
244
            is_ready = self.stream_load_entry(entry)
David Trattnig's avatar
David Trattnig committed
245

246
        if is_ready:
247
            entry.status = EntryPlayState.READY
248

249
        self.event_dispatcher.on_queue([entry])
David Trattnig's avatar
David Trattnig committed
250
251
252



253
    def preload_group(self, entries, channel_type=ChannelType.QUEUE):
David Trattnig's avatar
David Trattnig committed
254
        """
255
        Pre-Load multiple filesystem entries at once. This call is required before the 
David Trattnig's avatar
David Trattnig committed
256
        actual `play(..)` can happen. Due to their nature, non-filesystem entries cannot be queued
257
        using this method. In this case use `preload(self, entry)` instead. This method also allows
David Trattnig's avatar
David Trattnig committed
258
259
260
261
262
263
264
        queuing of very short files, such as jingles.

        It's important to note, that his method is blocking until loading has finished. If this 
        method is called asynchronously, the progress on the preloading state can be looked up in 
        `entry.state`.

        Args:
265
266
            entries ([Entry]):          An array holding filesystem entries
            channel_type (ChannelType): The type of channel where it should be queued (optional)
David Trattnig's avatar
David Trattnig committed
267
        """
268
        channels = None
David Trattnig's avatar
David Trattnig committed
269
270
271

        # Validate entry type
        for entry in entries:
272
            if entry.get_content_type() != ResourceType.FILE:
David Trattnig's avatar
David Trattnig committed
273
274
                raise InvalidChannelException
        
275
276
        # Determine channel        
        channels = self.channel_router.channel_swap(channel_type)
David Trattnig's avatar
David Trattnig committed
277
278
279
280
281
282
283

        # Queue entries
        for entry in entries:
            entry.status = EntryPlayState.LOADING
            self.logger.info("Loading entry '%s'" % entry)

            # Choose and save the input channel
284
            entry.previous_channel, entry.channel = channels
David Trattnig's avatar
David Trattnig committed
285

286
            if self.queue_push(entry.channel, entry.source) == True:
David Trattnig's avatar
David Trattnig committed
287
288
                entry.status = EntryPlayState.READY
        
289
        self.event_dispatcher.on_queue(entries)
290
        return channels
David Trattnig's avatar
David Trattnig committed
291

292
293

    def play(self, entry, transition):
David Trattnig's avatar
David Trattnig committed
294
        """
295
        Plays a new `Entry`. In case of a new timeslot (or some intented, immediate transition),
296
        a clean channel is selected and transitions between old and new channel is performed.
David Trattnig's avatar
David Trattnig committed
297

298
        This method expects that the entry is pre-loaded using `preload(..)` or `preload_group(self, entries)`
David Trattnig's avatar
David Trattnig committed
299
300
        before being played. In case the pre-roll has happened for a group of entries, only the 
        first entry of the group needs to be passed.
David Trattnig's avatar
David Trattnig committed
301

David Trattnig's avatar
David Trattnig committed
302
        Args:
David Trattnig's avatar
David Trattnig committed
303
            entry (PlaylistEntry):          The audio source to be played
David Trattnig's avatar
David Trattnig committed
304
            transition (TransitionType):    The type of transition to use e.g. fade-in or instant volume level.
David Trattnig's avatar
David Trattnig committed
305
            queue (Boolean):                If `True` the entry is queued if the `ChannelType` does allow so; 
306
                otherwise a new channel of the same type is activated
David Trattnig's avatar
David Trattnig committed
307
308
        
        """
David Trattnig's avatar
David Trattnig committed
309
        with suppress(LQConnectionError):
310
311
312
313
314
            
            channel_type = self.channel_router.type_of_channel(entry.channel)
            mixer = self.mixer
            if channel_type == ChannelType.FALLBACK_QUEUE:
                mixer = self.mixer_fallback
David Trattnig's avatar
David Trattnig committed
315
316

            # Instant activation or fade-in
317
            self.connector.enable_transaction()
318
            if transition == TransitionType.FADE:
319
320
                mixer.channel_select(entry.channel.value, True)
                mixer.fade_in(entry.channel, entry.volume)
321
            else:
322
323
                mixer.channel_activate(entry.channel.value, True)
            self.connector.disable_transaction()
324

325
326
            # Update active channel for the current channel type            
            self.channel_router.set_active(channel_type, entry.channel)
David Trattnig's avatar
David Trattnig committed
327
328

            # Dear filesystem channels, please leave the room as you would like to find it!
329
330
331
332
            if entry.previous_channel and \
                entry.previous_channel in ChannelType.QUEUE.channels and \
                entry.previous_channel in ChannelType.FALLBACK_QUEUE.channels:

David Trattnig's avatar
David Trattnig committed
333
334
335
336
                def clean_up():
                    # Wait a little, if there is some long fade-out. Note, this also means,
                    # this channel should not be used for at least some seconds (including clearing time).
                    time.sleep(2)
337
338
339
                    self.connector.enable_transaction()
                    mixer.channel_activate(entry.previous_channel.value, False)
                    res = self.queue_clear(entry.previous_channel)
340
                    self.logger.info("Clear Queue Response: " + res)
341
                    self.connector.disable_transaction()
David Trattnig's avatar
David Trattnig committed
342
                Thread(target=clean_up).start()
343
            
344
            self.event_dispatcher.on_play(entry)
345
346
347
348



    def stop(self, entry, transition):
David Trattnig's avatar
David Trattnig committed
349
        """
350
        Stops the currently playing entry.
David Trattnig's avatar
David Trattnig committed
351
352

        Args:
353
354
            entry (Entry):                  The entry to stop playing
            transition (TransitionType):    The type of transition to use e.g. fade-out.
David Trattnig's avatar
David Trattnig committed
355
        """
David Trattnig's avatar
David Trattnig committed
356
        with suppress(LQConnectionError):
357
            self.connector.enable_transaction()
358

359
            if not entry.channel:
360
                self.logger.warn(SU.red("Trying to stop entry %s, but it has no channel assigned" % entry))
361
362
363
                return
            
            if transition == TransitionType.FADE:
364
                self.mixer.fade_out(entry.channel)
365
            else:
366
                self.mixer.channel_volume(entry.channel, 0)
367

368
            self.logger.info(SU.pink("Stopped channel '%s' for entry %s" % (entry.channel, entry)))
369
            self.connector.disable_transaction()
370
            self.event_dispatcher.on_stop(entry)
371

David Trattnig's avatar
David Trattnig committed
372

373

374
    def start_fallback_playlist(self, entries):
David Trattnig's avatar
David Trattnig committed
375
        """
376
        Sets any scheduled fallback playlist and performs a fade-in.
David Trattnig's avatar
David Trattnig committed
377
378

        Args:
379
            entries ([Entry]):    The playlist entries
David Trattnig's avatar
David Trattnig committed
380
        """
381
        self.preload_group(entries, ChannelType.FALLBACK_QUEUE)
382
        self.play(entries[0], TransitionType.FADE)
383
        self.event_dispatcher.on_fallback_updated(entries)
David Trattnig's avatar
David Trattnig committed
384

385

386

387
    def stop_fallback_playlist(self):
David Trattnig's avatar
David Trattnig committed
388
        """
389
        Performs a fade-out and clears any scheduled fallback playlist.
David Trattnig's avatar
David Trattnig committed
390
        """
391
        dirty_channel = self.channel_router.get_active(ChannelType.FALLBACK_QUEUE)
392

393
394
        self.logger.info(f"Fading out channel '{dirty_channel}'")
        self.connector.enable_transaction()
395
        self.mixer_fallback.fade_out(dirty_channel)
396
        self.connector.disable_transaction()  
397

398
399
400
401
402
403
404
405
406
407
408
409
        def clean_up():
            # Wait a little, if there is some long fade-out. Note, this also means,
            # this channel should not be used for at least some seconds (including clearing time).
            time.sleep(2)
            self.connector.enable_transaction()
            self.mixer_fallback.channel_activate(dirty_channel.value, False)
            res = self.queue_clear(dirty_channel)
            self.logger.info("Clear Fallback Queue Response: " + res)
            self.connector.disable_transaction()            
            self.event_dispatcher.on_fallback_cleaned(dirty_channel)
        Thread(target=clean_up).start()
        
410

411
412

    #
413
    #   Channel Type - Stream
414
415
    #

David Trattnig's avatar
David Trattnig committed
416
417
418
419

    def stream_load_entry(self, entry):
        """
        Loads the given stream entry and updates the entries's status codes.
David Trattnig's avatar
Docs.    
David Trattnig committed
420
421
422

        Args:
            entry (Entry):  The entry to be pre-loaded
David Trattnig's avatar
David Trattnig committed
423

David Trattnig's avatar
Docs.    
David Trattnig committed
424
425
        Returns:
            (Boolean):  `True` if successfull
David Trattnig's avatar
David Trattnig committed
426
427
428
429
430
431
432
433
434
435
436
        """
        self.stream_load(entry.channel, entry.source)
        time.sleep(1)

        retry_delay = self.config.get("input_stream_retry_delay") 
        max_retries =  self.config.get("input_stream_max_retries")
        retries = 0

        while not self.stream_is_ready(entry.channel, entry.source):
            self.logger.info("Loading Stream ...")
            if retries >= max_retries:
David Trattnig's avatar
David Trattnig committed
437
                raise LoadSourceException("Could not connect to stream while waiting for %s seconds!" % str(retries*retry_delay))
David Trattnig's avatar
David Trattnig committed
438
439
440
            time.sleep(retry_delay)
            retries += 1

441
        return True
David Trattnig's avatar
David Trattnig committed
442
443
444
445



    def stream_load(self, channel, url):
David Trattnig's avatar
David Trattnig committed
446
        """
David Trattnig's avatar
David Trattnig committed
447
448
        Preloads the stream URL on the given channel. Note this method is blocking
        some serious amount of time; hence it's worth being called asynchroneously.
David Trattnig's avatar
David Trattnig committed
449
450
451

        Args:
            channel (Channel): The stream channel
452
            url (String):      The stream URL
David Trattnig's avatar
David Trattnig committed
453
454
455

        Returns:
            (Boolean):  `True` if successful
David Trattnig's avatar
David Trattnig committed
456
457
        """
        result = None
458

459
460
        self.connector.enable_transaction()
        result = self.connector.send_lqc_command(channel, "stream_stop")
David Trattnig's avatar
David Trattnig committed
461
462
        
        if result != LiquidsoapResponse.SUCCESS.value:
David Trattnig's avatar
David Trattnig committed
463
            self.logger.error("%s.stop result: %s" % (channel, result))
David Trattnig's avatar
David Trattnig committed
464
            raise LQStreamException("Error while stopping stream!")
465

466
        result = self.connector.send_lqc_command(channel, "stream_set_url", url)
467

David Trattnig's avatar
David Trattnig committed
468
        if result != LiquidsoapResponse.SUCCESS.value:
David Trattnig's avatar
David Trattnig committed
469
            self.logger.error("%s.set_url result: %s" % (channel, result))
David Trattnig's avatar
David Trattnig committed
470
            raise LQStreamException("Error while setting stream URL!")
471

David Trattnig's avatar
David Trattnig committed
472
473
        # Liquidsoap ignores commands sent without a certain timeout
        time.sleep(2)
474

475
        result = self.connector.send_lqc_command(channel, "stream_start")
David Trattnig's avatar
David Trattnig committed
476
        self.logger.info("%s.start result: %s" % (channel, result))
477

478
        self.connector.disable_transaction()
David Trattnig's avatar
David Trattnig committed
479
480
481
482
        return result



David Trattnig's avatar
David Trattnig committed
483
    def stream_is_ready(self, channel, url):
David Trattnig's avatar
David Trattnig committed
484
        """
David Trattnig's avatar
David Trattnig committed
485
486
        Checks if the stream on the given channel is ready to play. Note this method is blocking
        some serious amount of time even when successfull; hence it's worth being called asynchroneously.
David Trattnig's avatar
David Trattnig committed
487
488
489

        Args:
            channel (Channel): The stream channel
490
            url (String):      The stream URL
David Trattnig's avatar
David Trattnig committed
491
492
493

        Returns:
            (Boolean):  `True` if successful
David Trattnig's avatar
David Trattnig committed
494
495
496
        """
        result = None

497
        self.connector.enable_transaction()
David Trattnig's avatar
David Trattnig committed
498

499
        result = self.connector.send_lqc_command(channel, "stream_status")
David Trattnig's avatar
David Trattnig committed
500
        self.logger.info("%s.status result: %s" % (channel, result))
David Trattnig's avatar
David Trattnig committed
501
502
503
504
505
506
507
508
509

        if not result.startswith(LiquidsoapResponse.STREAM_STATUS_CONNECTED.value):
            return False

        lqs_url = result.split(" ")[1]
        if not url == lqs_url:
            self.logger.error("Wrong URL '%s' set for channel '%s', expected: '%s'." % (lqs_url, channel, url))
            return False

510
        self.connector.disable_transaction()
David Trattnig's avatar
David Trattnig committed
511

David Trattnig's avatar
David Trattnig committed
512
513
514
        stream_buffer = self.config.get("input_stream_buffer")
        self.logger.info("Ready to play stream, but wait %s seconds until the buffer is filled..." % str(stream_buffer))
        time.sleep(round(float(stream_buffer)))
David Trattnig's avatar
David Trattnig committed
515
        return True
516
517


David Trattnig's avatar
David Trattnig committed
518

519
    #
520
    #   Channel Type - Queue 
521
    #
David Trattnig's avatar
David Trattnig committed
522

523

524
    def queue_push(self, channel, source):
525
        """
526
        Adds an filesystem URI to the given `ChannelType.QUEUE` channel.
527

528
        Args:
David Trattnig's avatar
David Trattnig committed
529
            channel (Channel): The channel to push the file to
530
            source (String):   The URI of the file
David Trattnig's avatar
David Trattnig committed
531

532
        Returns:
David Trattnig's avatar
David Trattnig committed
533
            (Boolean):  `True` if successful
534
        """
535
536
537
        if channel not in ChannelType.QUEUE.channels and \
            channel not in ChannelType.FALLBACK_QUEUE.channels:
                raise InvalidChannelException
538
      
539
540
541
        self.connector.enable_transaction()
        audio_store = self.config.get("audio_source_folder")
        extension = self.config.get("audio_source_extension")
542
543
        filepath = ResourceUtil.source_to_filepath(audio_store, source, extension)
        self.logger.info(SU.pink(f"{channel}.queue_push('{filepath}')"))
544
545
546
        result = self.connector.send_lqc_command(channel, "queue_push", filepath)
        self.logger.info("%s.queue_push result: %s" % (channel, result))
        self.connector.disable_transaction()
547

David Trattnig's avatar
David Trattnig committed
548
        # If successful, Liquidsoap returns a resource ID of the queued track
549
550
551
552
553
554
555
556
        resource_id = -1
        try:
            resource_id = int(result)
        except ValueError:
            self.logger.error(SU.red("Got an invalid resource ID: '%s'" % result))
            return False

        return resource_id >= 0
557
558
559



560
    def queue_seek(self, channel, seconds_to_seek):
561
        """
562
        Forwards the player of the given `ChannelType.QUEUE` channel by (n) seconds.
563

564
        Args:
David Trattnig's avatar
David Trattnig committed
565
            channel (Channel): The channel to push the file to
566
            seconds_to_seeks (Float):   The seconds to skip
David Trattnig's avatar
David Trattnig committed
567
568
569

        Returns:
            (String):   Liquidsoap response
570
        """
571
572
573
        if channel not in ChannelType.QUEUE.channels and \
            channel not in ChannelType.FALLBACK_QUEUE.channels:
                raise InvalidChannelException
574

575
576
577
578
        self.connector.enable_transaction()
        result = self.connector.send_lqc_command(channel, "queue_seek", str(seconds_to_seek))
        self.logger.info("%s.seek result: %s" % (channel, result))
        self.connector.disable_transaction()
579
580

        return result
581
582


David Trattnig's avatar
David Trattnig committed
583

584
    def queue_clear(self, channel):
585
        """
586
        Removes all tracks currently queued in the given `ChannelType.QUEUE` channel.
David Trattnig's avatar
David Trattnig committed
587
588
589
590
591
592

        Args:
            channel (Channel): The channel to push the file to

        Returns:
            (String):   Liquidsoap response
593
        """
594
595
596
        if channel not in ChannelType.QUEUE.channels and \
            channel not in ChannelType.FALLBACK_QUEUE.channels:
                raise InvalidChannelException
597

598
        self.logger.info(SU.pink("Clearing filesystem queue '%s'!" % channel))
599

600
601
602
603
        self.connector.enable_transaction()
        result = self.connector.send_lqc_command(channel, "queue_clear")
        self.logger.info("%s.clear result: %s" % (channel, result))
        self.connector.disable_transaction()
604

605
        return result
606

David Trattnig's avatar
David Trattnig committed
607
608


609
    #
610
    #   Channel Type - Playlist 
611
612
    #

613

614
    def playlist_set_uri(self, channel, playlist_uri):
615
        """
616
        Sets the URI of a playlist.
David Trattnig's avatar
David Trattnig committed
617
618

        Args:
619
620
            channel (Channel): The channel to push the file to
            playlist_uri (String): The path to the playlist
621

David Trattnig's avatar
David Trattnig committed
622
        Returns:
623
            (String):   Liquidsoap response
624
        """
625
        self.logger.info(SU.pink("Setting URI of playlist '%s' to '%s'" % (channel, playlist_uri)))
626

627
628
629
630
        self.connector.enable_transaction()
        result = self.connector.send_lqc_command(channel, "playlist_uri_set", playlist_uri)
        self.logger.info("%s.playlist_uri result: %s" % (channel, result))
        self.connector.disable_transaction()
631

632
        return result
633
634
635



636
    def playlist_clear_uri(self, channel):
637
        """
638
        Clears the URI of a playlist.
639

640
641
        Args:
            channel (Channel): The channel to push the file to
David Trattnig's avatar
David Trattnig committed
642

643
644
        Returns:
            (String):   Liquidsoap response
David Trattnig's avatar
David Trattnig committed
645
        """
646
        self.logger.info(SU.pink("Clearing URI of playlist '%s'" % (channel)))
647

648
649
650
651
        self.connector.enable_transaction()
        result = self.connector.send_lqc_command(channel, "playlist_uri_clear")
        self.logger.info("%s.playlist_uri_clear result: %s" % (channel, result))
        self.connector.disable_transaction()
652

653
        return result
David Trattnig's avatar
David Trattnig committed
654

655

David Trattnig's avatar
David Trattnig committed
656

657

David Trattnig's avatar
David Trattnig committed
658

659
660
661
662
class EngineSplash:
    
    @staticmethod
    def splash_screen(component, version):
663
        """
664
        Prints the engine logo and version info.
665
        """
666
667
668
669
670
671
672
673
674
        return """\n
             █████╗ ██╗   ██╗██████╗  █████╗     ███████╗███╗   ██╗ ██████╗ ██╗███╗   ██╗███████╗
            ██╔══██╗██║   ██║██╔══██╗██╔══██╗    ██╔════╝████╗  ██║██╔════╝ ██║████╗  ██║██╔════╝
            ███████║██║   ██║██████╔╝███████║    █████╗  ██╔██╗ ██║██║  ███╗██║██╔██╗ ██║█████╗  
            ██╔══██║██║   ██║██╔══██╗██╔══██║    ██╔══╝  ██║╚██╗██║██║   ██║██║██║╚██╗██║██╔══╝  
            ██║  ██║╚██████╔╝██║  ██║██║  ██║    ███████╗██║ ╚████║╚██████╔╝██║██║ ╚████║███████╗
            ╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝    ╚══════╝╚═╝  ╚═══╝ ╚═════╝ ╚═╝╚═╝  ╚═══╝╚══════╝
            %s v%s - Ready to play!
        \n""" % (component, version)       
675

676