Commit 2b6ebefd authored by david's avatar david
Browse files

Merge branch 'master' into 'lars-debian-deployment'

# Conflicts:
#   docs/bare-metal-installation.md
parents 31c5f72c 9afa33f0
Pipeline #1161 passed with stage
in 1 minute and 10 seconds
......@@ -11,4 +11,4 @@ env.list
audio
python
__pycache__
config/engine.docker.ini
config/docker.engine.ini
# Contributing
When contributing to this repository, please first read about [contributing to AURA](https://gitlab.servus.at/aura/meta/-/blob/master/docs/development/contributions.md). Then discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.
Please note we have a [code of conduct](/aura/meta/-/blob/master/docs/development/code_of_conduct.md), please follow it in all your interactions with the project.
\ No newline at end of file
......@@ -31,4 +31,4 @@ VOLUME ["/srv/socket", "/srv/logs", "/var/audio/source", "/var/audio/playlist"]
# Start the Engine
EXPOSE 1337/tcp
ENTRYPOINT ["./run.sh"]
ENTRYPOINT ["./run.sh", "prod"]
......@@ -4,7 +4,7 @@
<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/assets/images/aura-engine.png" width="250" align="right" />
Aura Engine is a scheduling and play-out engine as part of [Aura Radio Software Suite](#About), specifically build for
Aura Engine is a scheduling and play-out engine as part of [Aura Radio Software Suite](#About), specifically built for
the requirements of community radio stations.
<!-- TOC -->
......@@ -25,9 +25,9 @@ the requirements of community radio stations.
## Functionality
In conjuction with other AURA components Engine provides several features:
In conjunction with other AURA components Engine provides several features:
- **Scheduler** to automatically broadcast your radio programme (see [AURA Dashboard](https://gitlab.servus.at/aura/dashboard) for an user interface to do scheduling)
- **Scheduler** to automatically broadcast your radio programme (see [AURA Dashboard](https://gitlab.servus.at/aura/dashboard) for a user interface to do scheduling)
- **Analog input and outputs** provided by [Engine Core](https://gitlab.servus.at/aura/engine-core)
- **Streaming to an [Icecast](https://icecast.org/) Server including [Icy Metadata](https://cast.readme.io/docs/icy)** provided by [Engine Core](https://gitlab.servus.at/aura/engine-core)
- **Autonomous playout** by caching the schedule information pulled from [Steering](https://gitlab.servus.at/aura/steering) in a local database. This allows Engine be keep running, independently from any network or service outages. This enables the application of (*High Availability* infrastructure scenarios)[https://gitlab.servus.at/aura/meta/-/blob/master/docs/administration/installation-guide.md#high-availability].
......@@ -41,7 +41,7 @@ In conjuction with other AURA components Engine provides several features:
Engine provide a scheduling functionality by polling external API endpoints frequently. Those API endpoints are provided by [Steering](https://gitlab.servus.at/aura/steering) to retrieve schedule information and [Tank](https://gitlab.servus.at/aura/tank) to retrieve playlist information. To define your schedule you'll also need [AURA Dashboard](https://gitlab.servus.at/aura/dashboard) which is an elegent web user interface to manage your shows, playlists and schedules.
Ideally any audio is scheduled some time before the actual, planned playout to avoid timing issues with buffering and preloading. Nonetheless, playlists can also be scheduled after a given calendar timeslot has started already. In such case the playout starts as soon it's preloaded.
Ideally any audio is scheduled some time before the actual, planned playout to avoid timing issues with buffering and preloading. Nonetheless, playlists can also be scheduled after a given calendar timeslot has started already. In such case the playout starts as soon it is preloaded.
If for some reason the playout is corrupted, stopped or too silent to make any sense, then this <u>triggers a fallback using the silence detector</u> (see chapter below).
......@@ -49,28 +49,28 @@ If for some reason the playout is corrupted, stopped or too silent to make any s
#### Versatile playlists
It's possible to schedules playlists with music or pre-recorded shows stored on the **file system**, via external **streams** or live from an **analog input** in the studio. All types of sources can be mixed in a single playlist.
It is possible to schedules playlists with music or pre-recorded shows stored on the **file system**, via external **streams** or live from an **analog input** in the studio. All types of sources can be mixed in a single playlist.
The switching between types of audio source is handled automatically, with configured fadings applied.
> Note: Any live sources or streams not specifing a length property, are automatically expanded to the left duration of the timeslot.
> Note: Any live sources or streams not specifying a length property, are automatically expanded to the left duration of the timeslot.
#### Default playlists
While a timeslot can have a specific playlist assigned, it's also possible to define default playlists
While a timeslot can have a specific playlist assigned, it is also possible to define default playlists
for schedules and shows:
- **Default Schedule Playlist**: This playlist is defined on the level of some recurrence rules (*Schedule*).
In case the timeslot doesn't have any specific playlist assigned, this playlist is broadcasted.
- **Default Show Playlist**: This playlist can be assigned to some show. If neither the specific timeslot
playlist nor the default schedule playlist is specificed the *default show playlist* is broadcasted.
playlist nor the default schedule playlist is specified the *default show playlist* is broadcasted.
If none of these playlists have been specified the *Auto DJ* feature of [Engine Core](https://gitlab.servus.at/aura/engine-core) takes over (optional).
### Heartbeat Monitoring
Instead of checking all status properties, the Heartbeat only validates the vital ones required to run the engine. If all of those are valid, a network socket request is sent to a defined server. This heartbeat is sent continiously based on the configured `heartbeat_frequency`. The service receiving this heartbeat ticks can decide what to do with that information. One scenario could be switching to another Engine instance or any other custom failover scenario. Under `contrib/heartbeat-monitor` you'll find some sample application digesting these heartbeat signals.
Instead of checking all status properties, the Heartbeat only validates the vital ones required to run the engine. If all of those are valid, a network socket request is sent to a defined server. This heartbeat is sent continuously based on the configured `heartbeat_frequency`. The service receiving this heartbeat ticks can decide what to do with that information. One scenario could be switching to another Engine instance or any other custom failover scenario. Under `contrib/heartbeat-monitor` you'll find some sample application digesting these heartbeat signals.
### Logging
......@@ -84,21 +84,34 @@ For production we recommend running Engine using Docker Compose. If you want to
## Using Docker
If you only want to run the single Engine Docker container, you can do this in a few, simple steps. Before getting started copy the default configuration file to `config/engine.docker.ini`:
If you only want to run a single Engine Docker container, you can do this in a few, simple steps.
Before getting started copy the default configuration file to `config/engine.docker.ini`:
```shell
cp config/sample-docker.engine.ini config/docker.engine.ini
```
You'll need update a few settings:
- The password `db_pass` for the local database holding scheduling information
- The app secret `api_tank_secret` for connecting to [AURA Tank](https://gitlab.servus.at/aura/tank)
- Also check the `ENV` variables defined in the `run.sh` script.
At the moment production deployment using Docker and Docker Compose is [*work in progress*](https://gitlab.servus.at/aura/meta/-/issues/56).
If you would like to run the local codebase, starting Engine in Docker requires you to do a build first:
```shell
cp config/sample-docker.engine.ini config/engine.docker.ini
./run.sh docker:build
```
You'll need to do a few configurations which are required:
- The password `db_pass` for the local database holding scheduling information
- The app secret `api_tank_secret` for connecting to [AURA Tank](https://gitlab.servus.at/aura/tank)
- Also check the `ENV` variables defined in the `run.sh` script.
After your build has finished start the Engine with following command.
Now start the engine with:
If no build is available it pulls the latest image from [Docker Hub](https://hub.docker.com/r/autoradio/engine).
```shell
./run.sh docker:engine
./run.sh docker:dev
```
## Read more
......@@ -112,7 +125,7 @@ Now start the engine with:
[<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/assets/images/aura-logo.png" width="150" />](https://gitlab.servus.at/aura/meta)
AURA stands for Automated Radio and is a swiss army knife for community radio stations. Beside the Engine it provides Steering (Admin Interface for the radio station), Dashboard (Collaborative scheduling and programme coordination), Tank (Audio uploading, pre-processing and delivery). Read more in the [Aura Meta](https://gitlab.servus.at/aura/meta) repository or on the specific project pages.
Automated Radio (AURA) is a open source software suite for community radio stations. Learn more about AURA in the [Meta repository](https://gitlab.servus.at/aura/meta).
| [<img src="https://gitlab.servus.at/aura/meta/-/raw/master/assets/images/aura-steering.png" width="150" align="left" />](https://gitlab.servus.at/aura/steering) | [<img src="https://gitlab.servus.at/aura/meta/-/raw/master/assets/images/aura-dashboard.png" width="150" align="left" />](https://gitlab.servus.at/aura/dashboard) | [<img src="https://gitlab.servus.at/aura/meta/-/raw/master/assets/images/aura-tank.png" width="150" align="left" />](https://gitlab.servus.at/aura/tank) | [<img src="https://gitlab.servus.at/aura/meta/-/raw/master/assets/images/aura-engine.png" width="150" align="left" />](https://gitlab.servus.at/aura/engine) |
|---|---|---|---|
......
......@@ -93,7 +93,7 @@ scheduling_window_end=60
preload_offset=15
# Sometimes it might take longer to get a stream connected. Here you can define a viable length.
# But note, that this may affect the preloading time (see `preload_offset`), hence affecting the
# overall playout, it's delays and possible fallbacks
# overall playout, its delays and possible fallbacks
input_stream_retry_delay=1
input_stream_max_retries=10
input_stream_buffer=3.0
......
......@@ -93,7 +93,7 @@ scheduling_window_end=60
preload_offset=15
# Sometimes it might take longer to get a stream connected. Here you can define a viable length.
# But note, that this may affect the preloading time (see `preload_offset`), hence affecting the
# overall playout, it's delays and possible fallbacks
# overall playout, its delays and possible fallbacks
input_stream_retry_delay=1
input_stream_max_retries=10
input_stream_buffer=3.0
......
......@@ -93,7 +93,7 @@ scheduling_window_end=60
preload_offset=15
# Sometimes it might take longer to get a stream connected. Here you can define a viable length.
# But note, that this may affect the preloading time (see `preload_offset`), hence affecting the
# overall playout, it's delays and possible fallbacks
# overall playout, its delays and possible fallbacks
input_stream_retry_delay=1
input_stream_max_retries=10
input_stream_buffer=3.0
......
[program:aura-engine]
user = engineuser
directory = /opt/aura/engine
command = /opt/aura/engine/run.sh engine
command = /opt/aura/engine/run.sh prod
priority = 666
autostart = true
......
......@@ -8,7 +8,7 @@ Requires=aura-engine-core.service
Type=simple
User=engineuser
WorkingDirectory=/opt/aura/engine
ExecStart=/opt/aura/engine/run.sh
ExecStart=/opt/aura/engine/run.sh prod
Restart=always
[Install]
......
#!/usr/bin/env python2.7
#!/usr/bin/env python3
# Copyright (c) 2001, Nicola Larosa
# All rights reserved.
......@@ -34,54 +34,72 @@ clients that sent at least one packet during the run, but have
not sent any packet since a time longer than the definition of the timeout.
Adjust the constant parameters as needed, or call as:
PyHBServer.py [timeout [udpport]]
PyHeartBeat.py [udpport [timeout]]
Set the environment variable "DEBUG" to "1" in order to emit more detailed
debug messages.
In addition "127.0.0.1" is marked as a previously active peer.
Manual heartbeat messages can be easily sent via "netcat":
echo foo | nc -q 1 -u localhost 43334
https://www.oreilly.com/library/view/python-cookbook/0596001673/ch10s13.html
"""
HBPORT = 43334
CHECKWAIT = 10
from socket import socket, gethostbyname, AF_INET, SOCK_DGRAM
import os
import socket
import sys
from threading import Lock, Thread, Event
from time import time, ctime, sleep
import sys
DEFAULT_HEARTBEAT_PORT = 43334
DEFAULT_WAIT_PERIOD = 10
DEBUG_ENABLED = os.getenv("DEBUG", "0") == "1"
class BeatDict:
"Manage heartbeat dictionary"
def __init__(self):
self.beatDict = {}
if __debug__:
self.beatDict['127.0.0.1'] = time( )
self.dictLock = Lock( )
if DEBUG_ENABLED:
self.beatDict["127.0.0.1"] = time()
self.dictLock = Lock()
def __repr__(self):
list = ''
self.dictLock.acquire( )
for key in self.beatDict.keys( ):
list = "%sIP address: %s - Last time: %s\n" % (
list, key, ctime(self.beatDict[key]))
self.dictLock.release( )
return list
result = ""
self.dictLock.acquire()
for key in self.beatDict.keys():
result += "IP address: %s - Last time: %s\n" % (
key,
ctime(self.beatDict[key]),
)
self.dictLock.release()
return result
def update(self, entry):
"Create or update a dictionary entry"
self.dictLock.acquire( )
self.beatDict[entry] = time( )
self.dictLock.release( )
self.dictLock.acquire()
self.beatDict[entry] = time()
self.dictLock.release()
def extractSilent(self, howPast):
"Returns a list of entries older than howPast"
silent = []
when = time( ) - howPast
self.dictLock.acquire( )
for key in self.beatDict.keys( ):
when = time() - howPast
self.dictLock.acquire()
for key in self.beatDict.keys():
if self.beatDict[key] < when:
silent.append(key)
self.dictLock.release( )
self.dictLock.release()
return silent
class BeatRec(Thread):
"Receive UDP packets, log them in heartbeat dictionary"
......@@ -90,52 +108,62 @@ class BeatRec(Thread):
self.goOnEvent = goOnEvent
self.updateDictFunc = updateDictFunc
self.port = port
self.recSocket = socket(AF_INET, SOCK_DGRAM)
self.recSocket.bind(('', port))
self.recSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.recSocket.settimeout(0.2)
self.recSocket.bind(("", port))
def __repr__(self):
return "Heartbeat Server on port: %d\n" % self.port
return f"Heartbeat Server on port: {self.port}"
def run(self):
while self.goOnEvent.isSet( ):
if __debug__:
print "Waiting to receive..."
while self.goOnEvent.isSet():
if DEBUG_ENABLED:
print("Waiting to receive...")
try:
data, addr = self.recSocket.recvfrom(6)
if __debug__:
print "Received packet from " + `addr`
except socket.timeout:
# no incoming message -> no timestamp update -> check again
pass
else:
if DEBUG_ENABLED:
print(f"Received packet from {addr}")
self.updateDictFunc(addr[0])
def main( ):
def main():
"Listen to the heartbeats and detect inactive clients"
global HBPORT, CHECKWAIT
if len(sys.argv)>1:
HBPORT=sys.argv[1]
if len(sys.argv)>2:
CHECKWAIT=sys.argv[2]
beatRecGoOnEvent = Event( )
beatRecGoOnEvent.set( )
beatDictObject = BeatDict( )
beatRecThread = BeatRec(beatRecGoOnEvent, beatDictObject.update, HBPORT)
if __debug__:
print beatRecThread
beatRecThread.start( )
print "PyHeartBeat server listening on port %d" % HBPORT
print "\n*** Press Ctrl-C to stop ***\n"
while 1:
if len(sys.argv) > 1:
heartbeat_port = int(sys.argv[1])
else:
heartbeat_port = DEFAULT_HEARTBEAT_PORT
if len(sys.argv) > 2:
wait_period = float(sys.argv[2])
else:
wait_period = DEFAULT_WAIT_PERIOD
beatRecGoOnEvent = Event()
beatRecGoOnEvent.set()
beatDictObject = BeatDict()
beatRecThread = BeatRec(beatRecGoOnEvent, beatDictObject.update, heartbeat_port)
if DEBUG_ENABLED:
print(beatRecThread)
beatRecThread.start()
print(f"PyHeartBeat server listening on port {heartbeat_port}")
print("\n*** Press Ctrl-C to stop ***\n")
while True:
try:
if __debug__:
print "Beat Dictionary"
print `beatDictObject`
silent = beatDictObject.extractSilent(CHECKWAIT)
if DEBUG_ENABLED:
print(f"Beat Dictionary: {beatDictObject}")
silent = beatDictObject.extractSilent(wait_period)
if silent:
print "Silent clients"
print `silent`
sleep(CHECKWAIT)
print(f"Silent clients: {' '.join(silent)}")
sleep(wait_period)
except KeyboardInterrupt:
print "Exiting."
beatRecGoOnEvent.clear( )
beatRecThread.join( )
print("Exiting.")
beatRecGoOnEvent.clear()
beatRecThread.join()
break
if __name__ == '__main__':
main( )
\ No newline at end of file
if __name__ == "__main__":
main()
......@@ -8,6 +8,7 @@
- [Setting up the database](#setting-up-the-database)
- [Configuration](#configuration)
- [Running Engine](#running-engine)
- [Starting dependencies](#starting-dependencies)
- [Daemonized Engine](#daemonized-engine)
- [Running with Systemd](#running-with-systemd)
- [Running with Supervisor](#running-with-supervisor)
......@@ -59,9 +60,9 @@ Create your base configuration from the sample configuration
```shell
# Development
cp config/sample.development.engine.ini config/engine.ini
cp config/sample-development.engine.ini config/engine.ini
# Production
cp config/sample.production.engine.ini config/engine.ini
cp config/sample-production.engine.ini config/engine.ini
```
### Setting up the database
......@@ -83,9 +84,9 @@ In your development environment edit following file to configure the engine:
./config/engine.ini
```
> Please note, if some configuration exists under `/etc/aura/engine.ini` the configuration by default is drawn from there.
> Please note, if some configuration exists under `/etc/aura/engine.ini` the configuration by default is drawn from there. This overrides any configuration located in `./engine/config`.
While the configuration has plenty of configuration options, you only need to set a few mandatory ones, given you are running the other components (such as 'engine-core', "engine-api" etc.) at the default settings too.
While the configuration file has plenty of options, you only need to set a few mandatory ones, given you are running the other components (such as 'engine-core', "engine-api" etc.) at their default settings too.
Required modifications are:
- The password `db_pass` for the local database holding scheduling information
......@@ -96,16 +97,42 @@ Required modifications are:
There's a convencience script `run.sh` to get engine started
```shell
engine$ ./run.sh
engine$ ./run.sh
```
Keep in mind you'll also need to start Engine Core separately
The script executes the *default target*, which is usually `dev` for development environments.
You can call this target explicitely too:
```shell
engine$ ./run.sh dev
```
Or run Engine in production mode:
```shell
engine$ ./run.sh prod
```
For details on the run script, consult the [AURA CLI documentation](https://gitlab.servus.at/aura/meta/-/blob/master/docs/administration/cli.md).
### Starting dependencies
You'll also need to start Engine Core separately.
> Note it should not matter in which order you start Engine and Engine Core.
```shell
engine-core$ ./run.sh
engine-core$ ./run.sh
```
In order to have a full engine experience also the other AURA Components are required to be running. For convencience in starting the full environment checkout how to run Aura Web using Docker Compose within the [Meta Repository](https://gitlab.servus.at/aura/tank)
Last but not least, Engine API is the target service to store playlogs, health information and details for the [Studio Clock](https://gitlab.servus.at/aura/dashboard-clock).
```shell
engine-api$ ./run.sh
```
In order to have the complete Engine experience, other AURA Components are required to be running too. Checkout the [Meta Repository](https://gitlab.servus.at/aura/meta) on how to run for example AURA Web using Docker Compose.
## Daemonized Engine
......@@ -124,7 +151,7 @@ systemctl daemon-reload
### Running with Supervisor
Now, given you are in the engine's home directory `/opt/aura/engine/`, simply type following to start the services:
Now, given you are in the engine's home directory like `/opt/aura/engine/`, simply type following to start the services:
```shell
supervisord
......@@ -138,8 +165,6 @@ Then you'll need to reload the supervisor configuration using `sudo`:
sudo supervisorctl reload
```
## Logging
All Engine logs can be found under `./logs`.
......
......@@ -5,7 +5,7 @@ This page gives insights on extending Aura Engine internals or through the API.
<!-- TOC -->
1. [Aura Engine Development Guide](#aura-engine-development-guide)
1. [AURA Componentes](#aura-componentes)
1. [AURA Components](#aura-componentes)
2. [Engine Components](#engine-components)
3. [Running for Development](#running-for-development)
4. [Testing](#testing)
......@@ -16,7 +16,7 @@ This page gives insights on extending Aura Engine internals or through the API.
<!-- /TOC -->
## AURA Componentes
## AURA Components
AURA Engine as part of the AURA Radio Suite uses an modulear architecture based on a REST API. All external information is retrieved using JSON data-structures.
......@@ -28,25 +28,19 @@ For example:
- Steering, to get the main incredient of an play-out engine: schedules (or "timeslots" in Steering terms),
which hold the actual information on playlists and their entries.
- Dashboard, to have a neat interface, being able to programm the timeslots
- Dashboard, to have a neat interface, being able to programme the timeslots
- Tank, to get the references to audio files and other audio sources. Plus the actual files.
If you need to test and develop against the Engine's API you'll also need to get the `engine-api` project running.
For a start it's recommended to create a general `aura` project folder. In there you start cloning all the sub-projects. After having all the sub-projects configured, and verified that they are working, take a look at the AURA `meta` project.
For a start it is recommended to create a general `aura` project folder. In there you start cloning all the sub-projects. After having all the sub-projects configured, and verified that they are working, take a look at the AURA `meta` project.
There's a convenience script to start all of the three main dependencies (Steering, Dashboard, Tank) all at once:
```bash
~/code/aura/meta$ ./run.sh aura local
```
## Engine Components
*...TBD...*
## Running for Development
Ensure you have following other projects up and running:
......@@ -57,7 +51,7 @@ Ensure you have following other projects up and running:
- engine-api
- dashboard-clock (optional)
The following steps espect you having done the bases configuration and set up a database as outlined in the [Native Installation](https://gitlab.servus.at/aura/engine/-/blob/master/docs/bare-metal-installation.md) document.
The following steps expect you having done the bases configuration and set up a database as outlined in the [Native Installation](https://gitlab.servus.at/aura/engine/-/blob/master/docs/bare-metal-installation.md) document.
If you don't have already, you'll need to create an virtual environment:
......@@ -122,11 +116,11 @@ point in time and the involved phase before:
in `engine.ini`). The actual start of the window is calcuated by (timeslot start - window start)
and the end by (timeslot end - window end)
During the scheduling window, the external API Endpoints are pulled continiously, to
During the scheduling window, the external API Endpoints are pulled continuously, to
check for updated timeslots and related playlists. Also, any changes to playlists and
its entries are respected within that window (see `fetching_frequency` in `engine.ini`).
> Important: It's vital that the the scheduling window is wider than the fetching frequency.
> Important: It is vital that the the scheduling window is wider than the fetching frequency.
Otherwise one fetch might never hit a scheduling window, hence not being able to schedule stuff.
> Note: If you delete any existing timeslot in Dashboard/Steering this is only reflected in Engine until the start
......@@ -148,7 +142,7 @@ point in time and the involved phase before:
or due to some severe connectivity issues to some external stream.
- **Play-out**: Finally the actual play-out is happening. The faders of the virtual mixers are pushed
all the way up, as soon it's "time to play" for one of the pre-loaded entries.
all the way up, as soon it is "time to play" for one of the pre-loaded entries.
Transitions between playlist entries with different types of sources (file, stream and analog
inputs) are performed automatically. At the end of each timeslot the channel is faded-out,
no matter if the total length of the playlist entries would require a longer timeslot.
......@@ -165,6 +159,12 @@ Build your own, local Docker image
./run.sh docker:build
```
Run the locally build image
```shell
./run.sh docker:dev
```
Releasing a new version to DockerHub
```shell
......
......@@ -4,3 +4,4 @@ Flask==1.1.2
Flask_SQLAlchemy==2.4.3
validators==0.12.1
http-parser==0.9.0
wheel==0.37.0
\ No newline at end of file
......@@ -26,6 +26,7 @@ import os
import sys
import signal
import logging
import threading
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
......@@ -41,7 +42,7 @@ def configure_flask():
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# FIXME Instatiate SQLAlchemy without the need for Flask
# FIXME Instantiate SQLAlchemy without the need for Flask
app = Flask(__name__)
configure_flask()
DB = SQLAlchemy(app)
......@@ -54,7 +55,6 @@ class EngineRunner:
logger = None
config = None
engine = None
lqs = None
def __init__(self):
......@@ -86,14 +86,13 @@ class EngineRunner:
"""
Shutdown of the engine. Also terminates the Liquidsoap thread.
"""
for thread in threading.enumerate():
self.logger.info(thread.name)
if self.engine:
self.engine.terminate()
if self.lqs:
self.lqs.terminate()
self.logger.info("Terminated Liquidsoap")
self.logger.info("Gracefully terminated Aura Engine! (signum:%s, frame:%s)" % (signum, frame))
self.logger.info(f"Gracefully terminated Aura Engine! (signum:{signum}, frame:{frame})")
sys.exit(0)
......
#!/bin/bash
mode="engine"
mode="dev"