Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • aura/engine
  • hermannschwaerzler/engine
  • sumpfralle/aura-engine
3 results
Show changes
Commits on Source (217)
Showing with 1409 additions and 0 deletions
.idea/
*.pyc
*.log
configuration/engine.ini
.vscode/tags
\ No newline at end of file
image: python:3.7
stages:
- test
before_script:
- apt-get -qq update
- apt-cache search libmariadb
- apt-get install -y python3-virtualenv virtualenv redis-server redis-tools libev4 libev-dev # mariadb-server libmariadbclient-dev
- /usr/bin/virtualenv venv
- . venv/bin/activate
- python3 -V
- pip3 install -r requirements.txt
- mkdir /etc/aura
- mkdir /var/log/aura
- pwd
- cp configuration/sample.engine.ini configuration/engine.ini
simple_guru_help:
stage: test
script:
- python3 guru.py -h
#print_connection_status:
# stage: test
# script:
# - python3 guru.py -pcs
\ No newline at end of file
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Aura Engine - Run",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/aura.py",
"console": "integratedTerminal"
},
{
"name": "Aura Engine API - Run",
"type": "python",
"request": "launch",
"stopOnEntry": false,
"program": "${workspaceFolder}/api.py",
"env": {
"FLASK_APP": "${workspaceRoot}/app.py"
},
"args": [
"run"
],
"debugOptions": [
"RedirectOutput"
]
},
{
"name": "Aura Engine - Recreate Database",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/aura.py",
"args": ["--recreate-database"],
"console": "integratedTerminal"
}
]
}
\ No newline at end of file
{
"python.pythonPath": "/usr/bin/python3.7"
}
\ No newline at end of file
Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
David Trattnig <david.trattnig@subsquare.at>
This diff is collapsed.
# Aura Engine
<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/images/aura-engine.png" width="250" align="right" />
Aura Engine is a play-out engine as part of [Aura Radio Software Suite](#About),
specifically build for the requirements of community radios.
<!-- TOC -->
- [Aura Engine](#aura-engine)
- [Features](#features)
- [Architecture](#architecture)
- [Requirements](#requirements)
- [Installation](#installation)
- [Start the Engine](#start-the-engine)
- [Logging](#logging)
- [About](#about)
- [Resources](#resources)
<!-- /TOC -->
## Features
- Play audio from multiple sources
- Dynamic switching of sources
- Record output to filesystem
- Stream output to an Icecast Server
- Multichannel Line-out
- Blank Detenction / Silence Detecter
- Auto Pilot a.k.a. Fallback Handling
Read more on the [Engine Features](docs/engine-features.md) page.
## Architecture
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.
To learn more, checkout the [Engine Developer Guide](docs/developer-guide.md) or visit
the [Aura Meta](https://gitlab.servus.at/autoradio/meta) repository.
## Requirements
**Hardware Requirements:** This depends on how many audio sources and targets you are
going to use, but for the most common scenarios any current hardware should be sufficient.
For the audio devices it is required to use an interface which has supported ALSA drivers.
**Operating System:** Any linux system with ALSA, PulseAudio or Jack2 support should work.
It is tested and coded on Debian Stretch and Ubuntu 18.0 with Python 3.6+.
## Installation
```bash
git clone https://gitlab.servus.at/autoradio/engine
```
**Install System Packages:**
```bash
sudo apt install \
git \
python3.7 python3.7-pip python3.7-dev \
redis-server \
liquidsoap liquidsoap-plugin-icecast \
mariadb-server libmariadbclient-dev \
quelcom \
liquidsoap-plugin-alsa liquidsoap-plugin-pulseaudio \
liquidsoap-plugin-all
```
**Install Python Packages:**
```bash
./install.sh
```
**Setup Database:**
```bash
mysql -u root -p
CREATE DATABASE aura_engine CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'aura'@'localhost' IDENTIFIED BY 'secure-password';
GRANT ALL PRIVILEGES ON aura_engine.* TO 'aura'@'localhost';
```
**Configuration:**
Run
```bash
sh init.sh
```
This creates the folder */var/audio* and copies some default configuration
to */etc/aura/engine.ini*
After that, you have to edit the settings in */etc/aura/engine.ini*. Ensure to take
your time to carefully review those settings!
Read more about detailed settings in the [Configuration Guide](docs/configuration-guide.md).
## Start the Engine
To start the AuRa Engine execute:
```bash
systemctl start aura-lqs
systemctl start aura-engine
```
and on system boot run following:
```bash
systemctl enable aura-lqs
systemctl enable aura-engine
```
The first service starts the LiquidSoap Engine, while the latter boots the actual AuRa Engine.
## Logging
You can access the service logs using one of:
```
journalctl -u aura-lqs
journalctl -u aura-engine
```
## About
<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/images/aura-logo.png" width="150" />
Aura Engine is the play-out engine of the Aura Radio Software Suite. Aura stands for Automated Radio and is a swiss army knife for community radios. 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/autoradio/meta) repository or on the specific project pages.
| [<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/images/aura-steering.png" width="150" align="left" />](https://gitlab.servus.at/autoradio/pv) | [<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/images/aura-dashboard.png" width="150" align="left" />](https://gitlab.servus.at/autoradio/dashboard) | [<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/images/aura-tank.png" width="150" align="left" />](https://gitlab.servus.at/autoradio/tank) | [<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/images/aura-engine.png" width="150" align="left" />](https://gitlab.servus.at/autoradio/engine) |
|---|---|---|---|
| [Steering](https://gitlab.servus.at/autoradio/pv) | [Dashboard](https://gitlab.servus.at/autoradio/dashboard) | [Tank](https://gitlab.servus.at/autoradio/tank) | [Engine](https://gitlab.servus.at/autoradio/engine) |
## Resources ##
* **Python**: https://docs.python.org/
* **Redis**: https://redis.io/
* **Liquidsoap**: https://www.liquidsoap.info/doc-1.4.0/
* **Jack Audio**: https://jackaudio.org/
#!/usr/bin/python3.7
#
# Aura Engine
#
# Copyright (C) 2017-2020
# David Trattnig <david.trattnig@subsquare.at>
# Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
# 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.
# 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.
# 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/>.
import os
import sys
import signal
import logging
import unittest
from pathlib import Path
from flask import request, render_template, Flask, Response
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.declarative import declarative_base
from libraries.base.logger import AuraLogger
from libraries.base.config import AuraConfig
# from modules.monitoring.diskspace_watcher import DiskSpaceWatcher
def configure_flask():
app.config["SQLALCHEMY_DATABASE_URI"] = config.get_database_uri()
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
config = AuraConfig()
app = Flask(__name__)
configure_flask()
DB = SQLAlchemy(app)
Base = declarative_base()
class Aura:
logger = None
config = None
server = None
messenger = None
controller = None
scheduler = None
# ------------------------------------------------------------------------------------------ #
def __init__(self):
self.config = config
AuraLogger(self.config)
self.logger = logging.getLogger("AuraEngine")
def startup(self):
from modules.scheduling.scheduler import AuraScheduler
from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator
from modules.communication.redis.adapter import ServerRedisAdapter
# Check if the database has to be re-created
if self.config.get("recreate_db") is not None:
AuraScheduler(self.config)
# Create scheduler and Liquidsoap communicator
self.liquidsoapcommunicator = LiquidSoapCommunicator(self.config)
self.scheduler = AuraScheduler(self.config)
# Give both a reference of each other
self.liquidsoapcommunicator.scheduler = self.scheduler
self.scheduler.liquidsoapcommunicator = self.liquidsoapcommunicator
# Create the Redis adapter
self.messenger = ServerRedisAdapter(self.config)
self.messenger.scheduler = self.scheduler
self.messenger.liquidsoapcommunicator = self.liquidsoapcommunicator
# TODO Check if it's working / needed.
#self.diskspace_watcher = DiskSpaceWatcher(self.config, self.logger, self.liquidsoapcommunicator)
#self.diskspace_watcher.start()
# And finally wait for redis message / start listener thread
self.messenger.start()
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
aura = Aura()
if len(sys.argv) >= 2:
if "--use-test-data" in sys.argv:
aura.config.set("use_test_data", True)
if "--recreate-database" in sys.argv:
aura.config.set("recreate_db", True)
aura.startup()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()
File added
###################
# engine Settings #
###################
[database]
db_user="aura_engine"
db_name="aura_engine"
db_pass="***change-me***"
db_host="localhost"
[monitoring]
# how often should i check the diskspace. defaults to 600s = 10m
diskspace_check_interval=20
# under which value should i start sending admin mails. possible values k, M, G, T or no metric prefix. defaults to 2G
diskspace_warning_value=1G
# under which value should i stop recording. defaults to 200M
diskspace_critical_value=100M
[mail]
mail_server=""
mail_server_port=""
mail_user=""
mail_pass=""
# if you want to send multiple adminmails, make them space separated
admin_mail="david@subsquare.at gogo@servus.at"
# with from mailadress should be used
from_mail="monitor@aura.py"
# The beginning of the subject. With that you can easily apply filter rules with any mail client
mailsubject_prefix="[AURA Engine]"
[dataurls]
# The URL to get the Calendar via PV/Steering
calendarurl="http://localhost:8000/api/v1/playout"
# The URL to get show details via PV/Steering
api_show_url="http://localhost:8000/api/v1/shows/${ID}/"
# The URL to get playlist details via Tank
importerurl="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
# how often should the calendar be fetched in seconds (This determines the time of the last change before a specific show)
fetching_frequency=3600
# sets the time how long we have to fade in and out, when we select another mixer input
# values are in seconds
# this is solved on engine level because it is kind of tough with liquidsoap
[fading]
fade_in_time="0.5"
fade_out_time="2.5"
#######################
# LiquidSoap Settings #
#######################
# all these settings from here to the bottom require a restart of the liquidsoap server
[user]
# the user and group under which this software will run
daemongroup="david"
daemonuser="david"
[socket]
socketdir="/home/david/code/aura/aura-engine/modules/liquidsoap"
[logging]
logdir="/var/log/aura"
# possible values: debug, info, warning, error, critical
loglevel="info"
# track_sensitive => fallback_folder track sensitivity
# max_blank => maximum time of blank from source (float)
# min_noise => minimum duration of noise on source to switch back over (float)
# threshold => power in dB under which the stream is considered silent (float)
fallback_max_blank="10."
fallback_min_noise="1."
fallback_threshold="-60."
[soundcard]
# choose your weapon
# if you are starving for pain in the ass choose alsa
# if you don't care about latency choose pulseaudio
# if you want low latency and a bit of experimenting, choose jack
soundsystem="alsa"
# you can define up to 5 inputs and outputs
# it is tested with
# - ALSA with ONE input and ONE output
# - pulseaudio with ONE input and ONE output (should work with multiple ins/outs)
# - jack with multiple inputs and outputs
#
# boundaries:
# - if you use jack, you have to kill liquidsoap. somehow liquidsoap cannot disconnect from jackd when shutting down
#
# with alsa you have to write the devicenames like hw:0
# with pulse and jack => an non empty value means it is used
# devices with empty string are ignored and not used
input_device_0="hw:0"
input_device_1=""
input_device_2=""
input_device_3=""
input_device_4=""
# same same, but different
output_device_0="default"
output_device_1=""
output_device_2=""
output_device_3=""
output_device_4=""
# if you are using alsa, you most probably have to tweak these values
# out of the box you will hear alot of cracklings and artifacts
# alsa_buffer => int
alsa_buffer="8192"
# alsa_buffer_length => int
alsa_buffer_length="25"
# alsa_periods => int
alsa_periods="0"
# frame_duration => double
frame_duration=""
# frame_size => int
frame_size=""
#####################
# Recorder Settings #
#####################
# you can define up to 5 recorder types.
# aac, flac, mp3, ogg, opus and wav is supported
[recording]
# flac example
# enable this recorder. everything else than 'y' is considered as disabled
rec_0="n"
# first set a folder
rec_0_folder="/var/audio/rec/flac"
# after how many minutes the recording will be cut
rec_0_duration="30"
# file (or encoding-) type
rec_0_encoding="flac"
# bitrate (with encoding types without bitrate like flac or wav it is substituted. 32 => very poor quality. 320 => very high quality)
rec_0_bitrate="128"
# channels: everything else than 2 is considered as mono
rec_0_channels="2"
# aac example
rec_1="n"
rec_1_folder="/var/audio/rec/aac"
rec_1_duration="30"
rec_1_encoding="aac"
rec_1_bitrate="64"
rec_1_channels="2"
# mp3 example
rec_2="n"
rec_2_folder="/var/audio/rec/mp3"
rec_2_duration="30"
rec_2_encoding="mp3"
rec_2_bitrate="32"
rec_2_channels="2"
# ogg example
rec_3="n"
rec_3_folder="/var/audio/rec/ogg"
rec_3_duration="30"
rec_3_encoding="ogg"
rec_3_bitrate="320"
rec_3_channels="2"
# opus example
rec_4="n"
rec_4_folder="/var/audio/rec/opus"
rec_4_duration="30"
rec_4_encoding="opus"
rec_4_bitrate="32"
rec_4_channels="2"
# wav example
#rec_4="n"
#rec_4_folder="/var/audio/rec/wav"
#rec_4_duration="30"
#rec_4_filetype="wav"
#rec_4_bitrate="320"
#rec_4_channels="2"
###################
# Stream Settings #
###################
# You can define up to outgoing 5 streams
# aac, flac, mp3, ogg and opus is supported
[stream]
# defines enabled or not
stream_0="y"
# possible values: aac, flac, mp3, ogg, opus (depending on what liquidsoap-plugins you installed)
stream_0_encoding="aac"
# bitrate (with encoding types without bitrate like flac or ogg it is substituted. 32 => very poor quality. 320 => very high quality)
stream_0_bitrate="128"
# how many channels? everything else than 2 is considered as mono
stream_0_channels="2"
# to where we are streaming..?
stream_0_host="localhost"
# and which port?
stream_0_port="8888"
# the name of the mountpoint
stream_0_mountpoint="aura-test-0.aac"
# username
stream_0_user="source"
# and the password
stream_0_password="hack-me"
# stream url
stream_0_url="http://www.fro.at"
# the name of the stream
stream_0_name="AURA Test Stream 0"
# the genre of the stream
stream_0_genre="mixed"
# description of the stream
stream_0_description="Test Stream 0"
stream_1="y"
stream_1_encoding="flac"
stream_1_bitrate="128"
stream_1_channels="2"
stream_1_host="localhost"
stream_1_port="8888"
stream_1_mountpoint="aura-test-1.flac"
stream_1_user="source"
stream_1_password="hack-me"
stream_1_url="http://www.fro.at"
stream_1_name="AURA Test Stream 1"
stream_1_genre="mixed"
stream_1_description="Test Stream 1"
stream_2="y"
stream_2_encoding="mp3"
stream_2_bitrate="64"
stream_2_channels="2"
stream_2_host="localhost"
stream_2_port="8888"
stream_2_mountpoint="aura-test-2.mp3"
stream_2_user="source"
stream_2_password="hack-me"
stream_2_url="http://www.fro.at"
stream_2_name="AURA Test Stream 2"
stream_2_genre="mixed"
stream_2_description="Test Stream 2"
stream_3="y"
stream_3_encoding="ogg"
stream_3_bitrate="64"
stream_3_channels="2"
stream_3_host="localhost"
stream_3_port="8888"
stream_3_mountpoint="aura-test-3.ogg"
stream_3_user="source"
stream_3_password="hack-me"
stream_3_url="http://www.fro.at"
stream_3_name="AURA Test Stream 3"
stream_3_genre="mixed"
stream_3_description="Test Stream 3"
stream_4="y"
stream_4_encoding="opus"
stream_4_bitrate="64"
stream_4_channels="2"
stream_4_host="localhost"
stream_4_port="8888"
stream_4_mountpoint="aura-test-4.opus"
stream_4_user="source"
stream_4_password="hack-me"
stream_4_url="http://www.fro.at"
stream_4_name="AURA Test Stream 4"
stream_4_genre="mixed"
stream_4_description="Test Stream 4"
<Config>
<Jobs multiple="true">
<job>
<time>00:00</time>
<until>23:00</until>
<job>play_playlist</job>
<params>no_stop</params>
</job>
<job>
<job>start_recording</job>
<until>00:00</until>
<day>all</day>
<time>00:00</time>
<params>no_stop</params>
</job>
<job>
<daysolder>4</daysolder>
<job>clean_cached</job>
<day>1</day>
<time>00:03</time>
<params></params>
</job>
<job>
<time>01:00</time>
<day>all</day>
<job>precache</job>
<params></params>
</job>
</Jobs>
</Config>
# Aura Engine Configuration Guide
This page goes into detail on what is possible to configure within the engine.
## Soundcard
Configure your audio device in the `[soundcard]` section of `engine.ini`.
You can configure up to **five** line IN and OUT stereo channels. Your hardware should
support that. When you use JACK, you will see the additional elements popping up when
viewing your connections (with e.g. Patchage).
**Pulse Audio:** When using Ubuntu, Pulse Audio is selected by default. This is convenient,
as you won't have the need to adapt any Engine setting to get audio playing initially.
**ALSA:** When you use ALSA, you will have to play around with ALSA settings. In the folder
`./modules/liquidsoap` is a scipt called alsa_settings_tester.liq. You can start it
with 'liquidsoap -v --debug alsa_settings_tester.liq'. Changing and playing with
settings may help you to find correct ALSA settings.
**Jack Audio**: Beside ALSA the sound servers such as
is supported.
Install the JACK daemon and GUI:
```bash
sudo apt-get install jackd qjackctl
```
Please ensure to enable "*realtime process priority*" when installing JACK to keep latency low.
Now, you are able to configure your hardware settings using following command:
```bash
qjackctl
```
Next you need to install the JACK plugin for Liquidsoap:
```bash
sudo apt install \
liquidsoap-plugin-jack
```
## Recordings
You can configure up to **five** recorders in the `[recording]`.
## Streams
You can configure up to **five** streams in the `[streams]`.
## Fallbacks
Configure fallback handling in the `[fallback]` section.
## Database
Configure your engine database in the `[database]` section.
## Monitoring
Configure monitoring parameters in the `[monitoring]` section.
## Web User Interface
Configure the web interface in the `[web-ui]` section.
## Administrator Emails
Configure the email server for admin mails in the `[mail]` section.
## API Endpoints
Configure connections to the other Aura components in the `[dataurls]` section.
## Fading
Configure fading parameters in the `[fading]` section.
## Logging
Configure log handling in the `[logging]` section.
## User
Configure the executing system user in the `[user]` section.
## Socket
Configure socket connectivity in the `[socket]` section.
## Redis
Configure Redis connectivity in the `[redis]` section.
## Frequently Asked Questions
### Which Audio Interface / Soundcard is compatible with Aura?
Basically any audio device which is supported by Linux Debian/Ubuntu and has ALSA drivers.
Engine has been tested with following audio interfaces:
- ASUS Xonar DGX,
- Roland Duo-Capture Ex
- Onboard Soundcard (HDA Intel ALC262)
- Native Instruments Komplete Audio 6
### ALSA Settings
#### Install: I don't want to install all Liquidsoap Audio plugins. How?
Depending on what stream you are going to send, and what recordings you are going to use,
install one or some of the following:
```bash
sudo apt install \
liquidsoap-plugin-aac # for aac support
liquidsoap-plugin-flac # for flac support
liquidsoap-plugin-lame liquidsoap-plugin-mad # for mp3 support
liquidsoap-plugin-opus # for opus support
liquidsoap-plugin-vorbis # for ogg support
```
#### In the Liquidsoap Logs I get 'Error when starting output output_lineout_0: Failure("Error while setting open_pcm: Device or resource busy")!'. What does it mean?
You probably have set a wrong or occupied device ID.
#### How can I find the audio device IDs, required for settings in engine.ini?
* **ALSA**: You can get the device numbers or IDs by executing:
cat /proc/asound/cards
* **Pulse Audio**: You might not need this for Pulse Audio, but still, to see all available devices use:
pactl list
**If you cannot find correct ALSA settings**
Well, this is - at least for me - a hard one. I could not manage to find correct ALSA settings for the above mentioned soundcards. The best experience i had with the ASUS Xonar DGX, but still very problematic (especially the first couple of minutes after starting liquidsoap). Since i enabled JACK support i only use that. It is also a bit of trial and error, but works pretty much out of the box.
**If you experience 'hangs' or other artefacts on the output signal**
* reduce the quality (especially, when hangs are on the stream) or
* install the realtime kernel with
```bash
apt install linux-image-rt-amd64
reboot
```
or
* invest in better hardware
\ No newline at end of file
# Aura Engine Development Guide
This page gives insights on extending Aura Engine internals or through the API.
## Architecture
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.
To get the basic architectural overview, visit the [Aura Meta](https://gitlab.servus.at/autoradio/meta) repository.
## Required Data Sources
The AURA Project "**Dashboard**" provides the GUI to organize shows, schedules/timelsots
and organize uploads in form of playlists. Those playlists can be organized in timeslots
using a fancy calendar interface.
These data-sources need to be configurated in the "engine.ini" configuration file:
# The URL to get the Calendar via PV/Steering
calendarurl="http://localhost:8000/api/v1/playout"
# The URL to get show details via PV/Steering
api_show_url="http://localhost:8000/api/v1/shows/${ID}/"
The AURA Project "**Tank**" on the other hand delivers information on the tracks, playlists
to be played and its meta-data:
# The URL to get playlist details via Tank
importerurl="http://localhost:8040/api/v1/shows/${SLUG}/playlists"
More information you can find here: <https://gitlab.servus.at/autoradio/meta/blob/master/api-definition.md>
## Provided API Endpoints
**Soundserverstate:** Returns true and false values of the internal In- and Outputs
/api/v1/soundserver_state
**Trackservice:**
/api/v1/trackservice/<selected_date>
/api/v1/trackservice/
## Components
**aura.py**: It is the server which is connected to the external programme source (e.g. aura steering and tank), to liquidsoap and is listening for redis pubsub messages. This precious little server is telling liquidsoap what to play and when.
**Guru**: The commandline tool for interacting with the server. Also provides the communication from Liquidsoap to the Python (Command-)Server.
**Liquidsoap**: The heart of AuRa Engine. It uses the built in mixer, to switch between different sources. It records everything and streams everything depending on your settings in aura.ini.
## Running the Engine
Aura Engine is consisting of two components: The Python Engine and Liquidsoap Core.
It's important to start both components in the correct order:
1. Run the Python engine
./run.sh
2. Run the Liquidsoap core
./run.sh lqs
# Aura Engine Features
This page gives a more detailed overview of the Aura Engine features.
## Input
### Play audio from multiple sources
It's possible to air playlists with music or recordings stored on the **filessystem**,
via external **streams** or live from a **studio**.
### Dynamic switching of sources
TODO extend: * switch the soundserver at the correct time to a given source for a specific show
## Output
### Record output to filesystem
TODO extend: * record what is broadcasted
### Stream output to an Icecast Server
TODO extend: * stream to an icecast server
### Multichannel Line-out
TODO extend: * play to line-out
## Blank Detenction / Silence Detecter
The engine offers a simple way to detect scenarios where no music is on air.
It possible to configure the sensitivity of the Silence Detector and automatically
transition play-out to a Fallback Playlist (see Auto Pilot).
## Auto Pilot a.k.a. Fallback Handling
In case there is no schedule delivered by Steering, engine provides multiple
fallback handling scenarios. The available fallbacks are evaluated in following order:
1. **Timeslot Fallback**:
2. **Show Fallback**:
3. **Station Fallback**:
3.1 Station Fallback via remote playlist
3.2 Station Fallback via local music folder
Note: All filenames in the music folder should be ASCII encoded, otherwise this may cause engine failures.
{
}
\ No newline at end of file
{
"allData": {
"id": "01",
"00": "Global Metadata delivered",
"01": "Could not get Data from Sound Engine"
},
"channel_insert": {
"id": "02",
"00": "On Channel ::channel:: insert ::uri:: at position ::pos::",
"02": "On Channel ::channel:: could not insert ::uri:: at position ::pos::"
},
"channel_move": {
"id": "03",
"00": "On Channel ::channel:: moved Item from ::fromPos:: to position ::toPos::",
"01": "Warning: Position ::fromPos:: out of range",
"02": "Warning: Cannot move to same position",
"03": "On Channel ::channel:: could not move from position ::fromPos:: to position ::toPos::"
},
"channel_off": {
"id": "04",
"00": "Channel ::channel:: off",
"01": "Could not activate Channel ::channel::"
},
"channel_on": {
"id": "05",
"00": "Channel ::channel:: on",
"01": "Could not deactivate Channel ::channel::"
},
"channel_queue": {
"id": "06",
"00": "Channel Queue for ::channel:: delivered",
"01": "Could not get channel queue from channel ::channel::",
"02": "Could not get channel queue from channel ::channel::",
"03": "Could not get channel queue from channel ::channel::"
},
"channel_remove": {
"id": "07",
"00": "Removed item on position ::pos:: from channel ::channel::",
"01": "Could not remove item on position ::pos:: from channel ::channel::",
"02": "Warning: position ::pos:: out of range'"
},
"channel_seek": {
"id": "08",
"00": "Seeked channel ::channel:: ::duration:: seconds",
"01": "Could not seek channel ::channel:: ::duration:: seconds"
},
"channel_skip": {
"id": "09",
"00": "Skipped channel ::channel::",
"01": "0 Channels listed",
"02": "Could not get channels from sound engine",
"03": "Could not skip ::channel::"
},
"channel_volume": {
"id": "10",
"00": "Volume ::volume::% set on channel ::channel::",
"01": "Could not set volume to ::volume::% on channel ::channel::",
"02": "0 Channels listed",
"03": "Could not get channels from sound engine"
},
"currentData": {
"id": "11",
"00": "Current track metadata delivered",
"01": "Nothing seems to be on air",
"02": "Could not detect metadata"
},
"help": {
"id": "12",
"00": "none",
"01": "Could not open help file"
},
"listChannels": {
"id": "13",
"00": "Listed Channels",
"01": "0 Channels listed",
"02": "Could not get channels from sound engine"
},
"message": {
"id": "14",
"00": "none"
},
"playlist_data": {
"id": "15",
"00": "Playlist data delivered"
},
"playlist_flush": {
"id": "16",
"00": "Flushed playlist",
"01": "Could not flush playlist"
},
"playlist_insert": {
"id": "17",
"00": "Insert track ::uri:: on position ::pos::"
},
"playlist_load": {
"id": "18",
"00": "Load Playlist ::uri::",
"01": "Could not load Playlist ::uri::",
"02": "Playlist is not well formed XML"
},
"playlist_move": {
"id": "19",
"00": "Moved playlist track from position ::fromPos:: to ::toPos::"
},
"playlist_pause": {
"id": "20",
"00": "Playlist paused",
"01": "Playlist already paused"
},
"playlist_stop": {
"id": "21",
"00": "Playlist stopped",
"01": "Playlist already stopped"
},
"playlist_play": {
"id": "22",
"00": "Playlist started",
"01": "Playlist already playing",
"02": "0 Channels listed",
"03": "Could not get channels from sound engine"
},
"playlist_push": {
"id": "23",
"00": "Playlist: pushed ::uri::",
"01": "Could not push ::uri::"
},
"playlist_remove": {
"id": "24",
"00": "Removed track on position ::pos:: from playlist",
"01": "Could not remove track on position ::pos:: from playlist"
},
"playlist_seek": {
"id": "25",
"00": "Seeked playlist ::duration:: seconds",
"01": "Could not seek playlist ::duration:: seconds"
},
"playlist_skip": {
"id": "26",
"00": "Could not skip playlist"
},
"recorder_data": {
"id": "27",
"00": "Delivered recorder data",
"01": "Could not deliver recorder data"
},
"recorder_start": {
"id": "28",
"00": "Recorder started",
"01": "Could not start recorder"
},
"recorder_stop": {
"id": "29",
"00": "Recorder stopped",
"01": "Could not stop recorder"
},
"scheduler_reload": {
"id": "30",
"00": "Reload signal was sent to scheduler",
"01": "Could not find the scheduler process"
},
"sendLqcCommand": {
"id": "31",
"01": "Soundengine not running",
"02": "Recorder not running"
},
"get_channel_state": {
"id": "32",
"00": "Channels ::channel:: state",
"01": "Could not get channel state from channel ::channel::"
},
"setPassword": {
"id": "33",
"00": "Successfull set password",
"01": "Not enough access rights for this operation"
},
"addUser": {
"id": "34",
"00": "Successfull add user ::username::",
"01": "Not enough access rights for this operation"
},
"delUser": {
"id": "35",
"00": "Successfull removed user ::username::",
"01": "Not enough access rights for this operation"
},
"scheduler_data": {
"id": "36",
"00": "Successfull delivered scheduler config",
"01": "Scheduler config seems to be broken"
},
"scheduler_store": {
"id": "37",
"00": "Successfull stored scheduler config",
"01": "Not enough access rights for this operation",
"02": "Could not store a valid scheduler XML"
},
"getUserlist": {
"id": "38",
"00": "Userlist was successfully delivered",
"01": "Not enough access rights for this operation"
},
"get_act_programme": {
"id": "39",
"00": "Successfully fetched the program",
"01": "Cannot fetch actual program"
}
}
\ No newline at end of file
{
"exec_job": {
"id": "01",
"00": "Execute job ::job::",
"01": "Fatal: Could not execute job ::job::. Command ::exec:: results in Exception ::Exception::. Stopped watcher"
},
"schedule_job": {
"id": "02",
"00": "Scheduled job ::job:: for ::scheduled_for:: at ::scheduled_at::",
"01": "Could not execute job"
},
"load_playlist": {
"id": "03",
"00": "Load playlist ::uri::",
"01": "Could not load playlist ::uri::. File does not exist!",
"02": "Controller failed to load playlist ::uri::. Message was '::message::'"
},
"play_playlist": {
"id": "04",
"00": "Started playlist",
"01": "Controller failed to start playlist. Message was '::message::'"
},
"stop_playlist": {
"id": "05",
"00": "Started playlist",
"01": "Controller failed to start playlist. Message was '::message::'"
},
"start_recording": {
"id": "06",
"00": "Started recording",
"01": "Controller failed to start recording. Message was '::message::'"
},
"stop_recording": {
"id": "07",
"00": "Stopped recording",
"01": "Controller failed to stop recording. Message was '::message::'"
},
"precache": {
"id": "08",
"00": "Precached playlists",
"01": "Could not precache playlist."
},
"clean_cached": {
"id": "09",
"00": "Cleaned cache",
"01": "Could not clean cache"
},
"on_start": {
"id": "10",
"00": "Do initial jobs",
"01": "Could not do initial jobs"
},
"lookup_prearranged": {
"id": "11",
"00": "Lookup for prearranged tracks",
"01": "No system channel available"
},
"start_prearranged": {
"id": "12",
"00": "Started preaarranged tracks"
},
"end_prearranged": {
"id": "13",
"00": "Stopped preaarranged tracks"
}
}
\ No newline at end of file
#!/usr/bin/python3.7
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
import time
import sys
import redis
from pathlib import Path
from argparse import ArgumentParser
# own libs
from modules.cli_tool.padavan import Padavan
from libraries.exceptions.auraexceptions import PlaylistException
from libraries.base.config import AuraConfig
class Guru():
"""
Command Line Interface (CLI) for Aura Engine.
"""
# config_path = "%s/configuration/engine.ini" % Path(__file__).parent.absolute()
config = AuraConfig()
parser = None
args = None
# ------------------------------------------------------------------------------------------ #
def __init__(self):
self.init_argument_parser()
self.handle_arguments()
def handle_arguments(self):
if self.args.stoptime:
start = time.time()
if not self.args.quiet:
print("Guru thinking...")
try:
p = Padavan(self.args, self.config)
p.meditate()
except PlaylistException as pe:
# typically there is no next file found
if not self.args.quiet:
print(pe)
else:
print("")
exit(4)
except redis.exceptions.TimeoutError:
print("Timeout when waiting for redis message. Is AURA daemon running? Exiting...")
exit(3)
if not self.args.quiet:
print("...result: ")
if p.stringreply != "":
#print(p.stringreply)
if p.stringreply[len(p.stringreply)-1] == "\n":
print(p.stringreply[0:len(p.stringreply) - 1])
else:
print(p.stringreply[0:len(p.stringreply)])
if self.args.stoptime:
end = time.time()
exectime = end-start
print("execution time: "+str(exectime)+"s")
def init_argument_parser(self):
try:
self.create_parser()
self.args = self.parser.parse_args()
except (ValueError, TypeError) as e:
if self.parser is not None:
self.parser.print_help()
print()
print(e)
exit(1)
def create_parser(self):
self.parser = ArgumentParser()
# options
self.parser.add_argument("-sep", "--stop-execution-time", action="store_true", dest="stoptime", default=False, help="Prints the execution time at the end of the skript")
self.parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Just the result will outputed to stout")
self.parser.add_argument("-rd", "--recreate-database", action="store_true", dest="recreatedb", default=False, help="Do you want to recreate the database?")
# getter
self.parser.add_argument("-pcs", "--print-connection-status", action="store_true", dest="get_connection_status", default=False, help="Prints the status of the connection to liquidsoap, pv and tank")
self.parser.add_argument("-gam", "--get-active-mixer", action="store_true", dest="get_active_mixer", default=False, help="Which mixer is activated?")
self.parser.add_argument("-pms", "--print-mixer-status", action="store_true", dest="get_mixer_status", default=False, help="Prints all mixer sources and their states")
self.parser.add_argument("-pap", "--print-act-programme", action="store_true", dest="get_act_programme", default=False, help="Prints the actual Programme, the controller holds")
# liquid manipulation
self.parser.add_argument("-am", "--select-mixer", action="store", dest="select_mixer", default=-1, metavar="MIXERNAME", help="Which mixer should be activated?")
self.parser.add_argument("-dm", "--de-select-mixer", action="store", dest="deselect_mixer", default=-1, metavar="MIXERNAME", help="Which mixer should be activated?")
self.parser.add_argument("-vm", "--volume", action="store", dest="set_volume", default=0, metavar=("MIXERNUM", "VOLUME"), nargs=2, help="Set volume of a mixer source", type=int)
# shutdown server
self.parser.add_argument("-sd", "--shutdown", action="store_true", dest="shutdown", default=False, help="Shutting down aura server")
# playlist in/output
self.parser.add_argument("-fnp", "--fetch-new-programmes", action="store_true", dest="fetch_new_programme", default=False, help="Fetch new programmes from api_calendar_url in engine.ini")
self.parser.add_argument("-pmq", "--print-message-queue", action="store_true", dest="print_message_queue", default=False, help="Prints message queue")
# send a redis message
self.parser.add_argument("-rm", "--redis-message", action="store", dest="redis_message", default=False, metavar=("CHANNEL", "MESSAGE"), nargs=2, help="Send a redis message to the Listeners")
# calls from liquidsoap
self.parser.add_argument("-gnf", "--get-next-file-for", action="store", dest="get_file_for", default=False, metavar="PLAYLISTTYPE", help="For which type you wanna GET a next audio file?")
self.parser.add_argument("-snf", "--set-next-file-for", action="store", dest="set_file_for", default=False, metavar=("PLAYLISTTYPE", "FILE"), nargs=2, help="For which type you wanna SET a next audio file?")
self.parser.add_argument("-np", "--now-playing", action="store_true", dest="now_playing", default=False, help="Which source is now playing")
self.parser.add_argument("-ip", "--init-player", action="store_true", dest="init_player", default=False, help="Reset liquidsoap volume and mixer activations?")
self.parser.add_argument("-ts", "--adapt-trackservice-title", action="store", dest="adapt_trackservice_title", default=False, metavar="TITLE", help="Update trackservice entry due to fallback")
if len(sys.argv) == 1:
raise ValueError("No Argument passed!")
def valid_playlist_entry(argument):
from datetime import datetime
try:
index = int(argument[0])
fromtime = datetime.strptime(argument[1], "%Y-%m-%d")
source = argument[2]
return index, fromtime, source
except:
msg = "Not a valid date: '{0}'.".format(argument[0])
print(msg)
raise
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
Guru()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()
#!/usr/bin/python3.7
#
# engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This file is part of engine.
#
# engine is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# engine 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with engine. If not, see <http://www.gnu.org/licenses/>.
#
from libraries.database.broadcasts import AuraDatabaseModel
AuraDatabaseModel.recreate_db(systemexit=True)
python3.7 $(which pip3) install -r requirements.txt
\ No newline at end of file