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
Showing
with 1885 additions and 0 deletions
#
# 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 os
import os.path
import sys
import logging
from pathlib import Path
from configparser import ConfigParser
class AuraConfig:
"""
AuraConfig Class
Holds the Aura Configuration as in the file `engine.ini`.
"""
ini_path = ""
logger = None
def __init__(self, ini_path="/etc/aura/engine.ini"):
"""
Initializes the configuration, defaults to `/etc/aura/engine.ini`.
If this file doesn't exist it uses `./configuration/engine.ini` from
the project directory.
Args:
ini_path(String): The path to the configuration file `engine.ini`
"""
config_file = Path(ini_path)
if not config_file.is_file():
ini_path = "%s/configuration/engine.ini" % Path(__file__).parent.parent.parent.absolute()
self.ini_path = ini_path
self.logger = logging.getLogger("AuraEngine")
self.load_config()
def set(self, key, value):
"""
Setter for some specific config property.
Args:
key (String): key
default (*): value
"""
try:
self.__dict__[key] = int(value)
except:
self.__dict__[key] = str(value)
def get(self, key, default=None):
"""
Getter for some specific config property.
Args:
key (String): key
default (*): value
"""
if key not in self.__dict__:
if default:
self.set(key, default)
else:
self.logger.warning("Key " + key + " not found in configfile " + self.ini_path + "!")
return None
if key == "loglevel":
loglvl = self.__dict__[key]
if loglvl == "debug":
return logging.DEBUG
elif loglvl == "info":
return logging.INFO
elif loglvl == "warning":
return logging.WARNING
elif loglvl == "error":
return logging.ERROR
else:
return logging.CRITICAL
if key == "debug":
return self.__dict__[key].count("y")
return self.__dict__[key]
def load_config(self):
"""
Set config defaults and load settings from file
"""
if not os.path.isfile(self.ini_path):
self.logger.critical(self.ini_path + " not found :(")
sys.exit(1)
# Read the file
f = open(self.ini_path, 'r')
ini_str = f.read()
f.close()
# Parse the values
config_parser = ConfigParser()
try:
config_parser.read_string(ini_str)
except Exception as e:
self.logger.critical("Cannot read " + self.ini_path + "! Reason: " + str(e))
sys.exit(0)
for section in config_parser.sections():
for key, value in config_parser.items(section):
v = config_parser.get(section, key).replace('"', '').strip()
self.set(key, v)
# Custom overrides and defaults
self.set("install_dir", os.path.realpath(__file__ + "../../../.."))
self.set("use_test_data", False)
self.set("api_prefix", "/api/v1")
def get_database_uri(self):
"""
Retrieves the database connection string.
"""
db_name = self.get("db_name")
db_user = self.get("db_user")
db_pass = self.get("db_pass")
db_host = self.get("db_host")
db_charset = self.get("db_charset", "utf8")
return "mysql://" + db_user + ":" + db_pass + "@" + db_host + "/" + db_name + "?charset=" + db_charset
\ No newline at end of file
#
# 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 logging
from libraries.base.config import AuraConfig
class AuraLogger():
config = None
logger = None
def __init__(self, config):
self.config = config
self.__create_logger("AuraEngine")
def __create_logger(self, name):
"""
Creates the logger instance for AuraEngine
:param name: LoggerName
:return:
"""
lvl = self.config.get("loglevel")
# create logger
self.logger = logging.getLogger(name)
self.logger.setLevel(lvl)
if not self.logger.hasHandlers():
# create file handler for logger
file_handler = logging.FileHandler(self.config.get("logdir") + "/engine.log")
file_handler.setLevel(lvl)
# create stream handler for logger
stream_handler = logging.StreamHandler()
stream_handler.setLevel(lvl)
# set format of log
datepart = "%(asctime)s:%(name)s:%(levelname)s"
message = " - %(message)s - "
filepart = "[%(filename)s:%(lineno)s-%(funcName)s()]"
formatter = logging.Formatter(datepart + message + filepart)
# set log of handlers
file_handler.setFormatter(formatter)
stream_handler.setFormatter(formatter)
# add handlers to the logger
self.logger.addHandler(file_handler)
self.logger.addHandler(stream_handler)
self.logger.critical("ADDED HANDLERS")
else:
self.logger.critical("REUSED LOGGER")
\ No newline at end of file
This diff is collapsed.
#
# 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 redis
import time
import datetime
import json
import re
import uuid
class RedisStateStore(object):
"""Store and get Reports from redis"""
def __init__(self, config, **redis_kwargs):
"""The default connection parameters are: host='localhost', port=6379, db=0"""
self.db = redis.Redis(host=config.get("redis_host"), port=config.get("redis_port"), db=config.get("redis_db"))
self.channel = '*'
self.section = '*'
self.separator = '_'
self.daily = False
# ------------------------------------------------------------------------------------------ #
def set_channel(self, channel):
"""
Kanal setzen
@type channel: string
@param channel: Kanal
"""
self.channel = channel
# ------------------------------------------------------------------------------------------ #
def set_section(self, section):
"""
Sektion setzen
@type section: string
@param section: Sektion
"""
self.section = section
# ------------------------------------------------------------------------------------------ #
def set_alive_state(self):
"""
Alive Funktion - alle 20 Sekunden melden, dass man noch am Leben ist
"""
self.set_state('alive', 'Hi', 21)
# ------------------------------------------------------------------------------------------ #
def get_alive_state(self, channel):
"""
Alive Status eines Channels ermitteln
@type channel: string
@param channel: der Channel
@rtype: string/None
@return: Ein String, oder None, bei negativem Ergebnis
"""
return self.get_state('alive', channel)
# ------------------------------------------------------------------------------------------ #
def set_state(self, name, value, expires=None, channel=None):
"""
Setzt einen Status
@type name: string
@param name: Name des state
@type value: string
@param value: Wert
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
key = self.__create_key__(channel + 'State', name)
if value == "":
self.db.delete(key)
else:
# publish on channel
message = json.dumps({'eventname':name, 'value': value})
self.db.publish(channel + 'Publish', message)
# store in database
self.db.set(key, value)
if(expires):
self.db.expire(key, 21)
# ------------------------------------------------------------------------------------------ #
def get_state(self, name, channel):
"""
Holt einen Status
@type name: string
@param name: Name des state
@type channel: string
@param channel: Kanal (optional)
"""
key = self.__create_key__(channel + 'State', name)
return self.db.get(key)
# ------------------------------------------------------------------------------------------ #
def queue_add_event(self, eventtime, name, value, channel=None):
"""
Kündigt einen Event an
@type eventtime: string
@param eventtime: Datum und Zeit des events
@type name: string
@param name: Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
timeevent = datetime.datetime.strptime(eventtime[0:16],"%Y-%m-%dT%H:%M")
expire = int(time.mktime(timeevent.timetuple()) - time.time()) + 60
self.__set_event__(name, eventtime, value, 'Evqueue', 'evqueue', expire, channel)
# ------------------------------------------------------------------------------------------ #
def queue_remove_events(self, name=None, channel=None):
"""
Löscht Events
@type name: string
@param name: Name des Events
@type channel: string
@param channel: Kanal (optional)
"""
query = channel + 'Evqueue_' if channel else '*Evqueue_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
for delkey in keys:
self.db.delete(delkey)
# ------------------------------------------------------------------------------------------ #
def fire_event(self, name, value, channel=None):
"""
Feuert einen Event
@type name: string
@param name: Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
eventtime = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M")
self.__set_event__(name, eventtime, value, 'Event', 'events', 60, channel)
# ------------------------------------------------------------------------------------------ #
def __set_event__(self, name, eventtime, value, type, namespace, expire, channel=None):
"""
Feuert einen Event
@type eventtime: string
@param eventtime: Datum und Zeit des events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
timeevent = datetime.datetime.strptime(eventtime[0:16],"%Y-%m-%dT%H:%M")
key = self.__create_key__(channel + type, eventtime, name)
value['starts'] = eventtime[0:16]
value['eventchannel'] = channel
value['eventname'] = name
self.db.hset(key, namespace, value)
self.db.expire(key, expire)
# ------------------------------------------------------------------------------------------ #
def get_event_queue(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
query = channel + 'Evqueue_' if channel else '*Evqueue_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
keys.sort()
entries = self.__get_entries__(keys, 'evqueue')
return entries
# ------------------------------------------------------------------------------------------ #
def get_events(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
query = channel + 'Event_' if channel else '*Event_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
keys.sort()
entries = self.__get_entries__(keys, 'events')
return entries
# ------------------------------------------------------------------------------------------ #
def get_next_event(self, name=None, channel=None):
"""
Holt den aktuellsten Event
@type channel: string
@param channel: Kanal (optional)
@rtype: dict/boolean
@return: ein Event oder False
"""
events = self.get_event_queue(name, channel)
if len(events) > 0:
result = events.pop(0)
else:
result = False
return result
# ------------------------------------------------------------------------------------------ #
def store(self, level, value):
"""
Hash speichern
@type level: string
@param level: der errorlevel
@type value: dict
@param value: Werte als dict
"""
microtime = str(time.time())
value['microtime'] = microtime
value['level'] = level
key = self.__create_key__(self.channel, self.section, level, microtime, str(uuid.uuid1()))
self.db.hset(key, self.channel, value)
self.db.expire(key, 864000)
# ------------------------------------------------------------------------------------------ #
def __get_keys__(self, level ='*'):
"""
Redis-Keys nach Suchkriterium ermitteln
@type level: string
@param level: einen Errorlevel filtern
@rtype: list
@return: Die Keys auf die das Suchkriterium zutrifft
"""
key = self.__create_key__(self.channel, self.section, level)
microtime = str(time.time())
search = microtime[0:4] + '*' if self.daily else '*'
return self.db.keys(key + self.separator + '*')
# ------------------------------------------------------------------------------------------ #
def __create_key__(self, *args):
"""
Key erschaffen - beliebig viele Argumente
@rtype: string
@return: Der key
"""
return self.separator.join(args)
def get_entries(self, level ='*'):
"""
Liste von Hashs nach Suchkriterium erhalten
@type level: string
@param level: einen Errorlevel filtern
@rtype: list
@return: Redis Hashs
"""
def tsort(x,y):
if float(x.split('_',4)[3]) > float(y.split('_',4)[3]):
return 1
elif float(x.split('_',4)[3]) < float(y.split('_',4)[3]):
return -1
else:
return 0
keys = self.__get_keys__(level)
keys.sort(tsort)
entries = self.__get_entries__(keys, self.channel)
entries = sorted(entries, key=lambda k: k['microtime'], reverse=True)
return entries
# ------------------------------------------------------------------------------------------ #
def __get_entries__(self, keys, channel):
entries = []
for key in keys:
entry = self.db.hget(key,channel)
entry = json.dumps(entry.decode('utf-8'))
if not (entry is None):
try:
entry = entry.decode('utf-8').replace('None','"None"')
entry = re.sub("########[^]]*########", lambda x:x.group(0).replace('\"','').replace('\'',''),entry.replace("\\\"","########").replace("\\'","++++++++").replace("'",'"').replace('u"','"').replace('"{','{').replace('}"','}')).replace("########","\"")
entry = json.loads(entry)
entry['key'] = key
entries.append(entry)
except:
pass
return entries
# ------------------------------------------------------------------------------------------ #
def publish(self, channel, message):
subscriber_count = self.db.execute_command('PUBSUB', 'NUMSUB', channel)
if channel.lower().find("reply") < 0 and subscriber_count[1] == 0:
raise Exception("No subscriber! Is Aura daemon running?")
self.db.publish(channel, message)
#
# 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 enum import Enum
class TerminalColors(Enum):
HEADER = "\033[95m"
RED = "\033[31m"
GREEN = "\033[32m"
ORANGE = "\033[33m"
BLUE = "\033[34m"
PINK = "\033[35m"
CYAN = "\033[36m"
WARNING = "\033[31m"
FAIL = "\033[41m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
ENDC = "\033[0m"
class RedisChannel(Enum):
STANDARD = "aura"
DPE_REPLY = "delete_playlist_entry_reply"
FNP_REPLY = "fetch_new_programme_reply"
GAP_REPLY = "get_act_programme_reply"
GCS_REPLY = "get_connection_status_reply"
GNF_REPLY = "get_next_file_reply"
IPE_REPLY = "insert_playlist_entry_reply"
IP_REPLY = "init_player_reply"
TS_REPLY = "track_service_reply"
MPE_REPLY = "move_playlist_entry_reply"
PMQ_REPLY = "print_message_queue_reply"
RDB_REPLY = "recreate_database_reply"
SNF_REPLY = "get_next_file_reply"
class ScheduleEntryType(Enum):
# enumeration with names of liquidsoap inputs
FILESYSTEM = "fs"
STREAM = "http"
LIVE_0 = "aura_linein_0"
LIVE_1 = "aura_linein_1"
LIVE_2 = "aura_linein_2"
LIVE_3 = "aura_linein_3"
LIVE_4 = "aura_linein_4"
class FallbackType(Enum):
SHOW = "show" # the first played when the show playlist fails
TIMESLOT = "timeslot" # the second played when timeslot fallback fails
STATION = "station" # the last played when everything else fails
class TimerType(Enum):
SWITCH = "switch"
FADEIN = "fadein"
FADEOUT = "fadeout"
#
# 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/>.
#
class NoProgrammeLoadedException(Exception):
pass
class LQConnectionError(Exception):
pass
class RedisConnectionException(Exception):
pass
class PlaylistException(Exception):
pass
class MailingException(Exception):
pass
class DiskSpaceException(Exception):
pass
#
# 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 json
import logging
class ExceptionLogger:
logger = None
error_data = None
job_result = None
def __init__(self):
self.logger = logging.getLogger("AuraEngine")
# ------------------------------------------------------------------------------------------ #
def __get_error__(self, job, errornumber, data):
"""
Privat: Ermittelt Fehlermeldung, Job-Name (Klassenmethode) und Fehlercode für den Job aus error/controller_error.js
@type errornumber: string
@param errornumber: Die interne Fehlernummer der aufrufenden Methode
"""
if data is None:
data = {}
if type(data) == type(str()):
data = json.loads(data)
has_data = isinstance(data, (dict)) and len(data) > 0
if job in self.error_data:
err_msg = self.error_data[job][errornumber]
err_id = self.error_data[job]['id'] + str(errornumber)
if has_data:
for key in data.keys():
err_msg = err_msg.replace('::' + key + '::', str(data[key]))
data['message'] = err_msg
data['job'] = job
data['code'] = err_id
return data
# ------------------------------------------------------------------------------------------ #
def success(self, job, data=None, errnum='00', value=''):
"""
Erfolgsmeldung loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
@type section: string
@param section: Gültigkeitsbereich
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'success',
'job': error['job'],
'value': value}
self.logger.debug(job + " successfully done " + str(self.job_result))
# ------------------------------------------------------------------------------------------ #
def info(self, job, data=None, errnum='01', value=''):
"""
Info loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
@type section: string
@param section: Gültigkeitsbereich
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'info',
'job': error['job'],
'value': value}
self.logger.info(str(self.job_result))
# ------------------------------------------------------------------------------------------ #
def warning(self, job, data=None, errnum='01', value=''):
"""
Warnung loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'warning',
'job': error['job'],
'value': value}
self.logger.warning(str(self.job_result))
# ------------------------------------------------------------------------------------------ #
def error(self, job, data=None, errnum='01', value=''):
"""
Error loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'error',
'job': error['job'],
'value': value}
self.logger.error(str(self.job_result))
# ------------------------------------------------------------------------------------------ #
def fatal(self, job, data=None, errnum='01', value='', section='execjob'):
"""
Fatal error loggen
@type errnum: string
@param errnum: Errornummer der aufrufenden Funktion
@type value: string
@param value: Optionaler Wert
@type section: string
@param section: Gültigkeitsbereich
"""
error = self.__get_error__(job, errnum, data)
self.job_result = {'message': error['message'],
'code': error['code'],
'success': 'fatal',
'job': error['job'],
'value': value,
'section': section}
self.logger.critical(str(self.job_result))
\ No newline at end of file
#
# Aura Engine
#
# Playout Daemon for autoradio project
#
#
# Copyright (C) 2020 David Trattnig <david.trattnig@subsquare.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/>.
#
# Meta
__version__ = '0.0.1'
__license__ = "GNU General Public License (GPL) Version 3"
__version_info__ = (0, 0, 1)
__author__ = 'David Trattnig <david.trattnig@subsquare.at>'
import datetime
import time
class SimpleUtil:
"""
A container for simple utility methods.
"""
@staticmethod
def fmt_time(timestamp):
"""
Formats a UNIX timestamp to a String displaying time in the format '%H:%M:%S'.
Args:
(Integer) timestamp: Unix epoch
Returns:
(String): Displaying the time
"""
return datetime.datetime.fromtimestamp(timestamp).strftime('%H:%M:%S')
@staticmethod
def timestamp(date_and_time=datetime.datetime.now()):
"""
Transforms the given `datetime` into a UNIX epoch timestamp.
If no parameter is passed, the current timestamp is returned.
Args:
(Datetime) date_and_time: the date and time to transform.
Returns:
(Integer): timestamp in seconds.
"""
return time.mktime(date_and_time.timetuple())
@staticmethod
def strike(text):
"""
Creates a strikethrough version of the given text.
Args:
(String) text: the text to strike.
Returns:
(String): the striked text.
"""
result = ""
for c in text:
result += c + '\u0336'
return result
\ No newline at end of file
#
# 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 json
from libraries.enum.auraenumerations import RedisChannel, TerminalColors
from modules.communication.redis.adapter import ClientRedisAdapter, ServerRedisAdapter
from modules.communication.redis.messenger import RedisMessenger
from libraries.database.broadcasts import AuraDatabaseModel
class Padavan:
args = None
config = None
lsc = None
zmqclient = None
redisclient = None
stringreply = ""
# ------------------------------------------------------------------------------------------ #
def __init__(self, args, config):
self.args = args
self.config = config
# ------------------------------------------------------------------------------------------ #
def meditate(self):
if self.args.fetch_new_programme:
self.fetch_new_programme()
elif self.args.get_active_mixer:
self.get_active_mixer()
elif self.args.get_mixer_status:
self.get_mixer_status()
elif self.args.get_act_programme:
self.get_act_programme()
elif self.args.get_connection_status:
self.get_connection_status()
elif self.args.shutdown:
self.shutdown()
elif self.args.redis_message:
self.redis_message(self.args.redis_message[0], self.args.redis_message[1])
elif self.args.select_mixer != -1:
self.select_mixer(self.args.select_mixer)
elif self.args.deselect_mixer != -1:
self.select_mixer(self.args.deselect_mixer, False)
elif self.args.set_volume:
self.set_volume(self.args.set_volume[0], self.args.set_volume[1])
elif self.args.print_message_queue:
self.print_message_queue()
elif self.args.get_file_for:
self.get_next_file(self.args.get_file_for)
elif self.args.set_file_for:
self.set_next_file(self.args.set_file_for[0], self.args.set_file_for[1])
elif self.args.now_playing:
print("")
elif self.args.init_player:
self.init_player()
elif self.args.adapt_trackservice_title:
self.adapt_trackservice_title(self.args.adapt_trackservice_title)
elif self.args.recreatedb:
self.recreatedb()
# else:
# raise Exception("")
# init liquid => faster exec time, when loading at runtime just what is needed
# ------------------------------------------------------------------------------------------ #
def init_liquidsoap_communication(self):
# import
from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator
# init liquidsoap communication
self.lsc = LiquidSoapCommunicator(self.config)
# enable connection
self.lsc.enable_transaction()
# ------------------------------------------------------------------------------------------ #
def destroy_liquidsoap_communication(self):
# enable connection
self.lsc.disable_transaction()
# ------------------------------------------------------------------------------------------ #
def init_redis_communication(self, with_server=False):
self.redisclient = ClientRedisAdapter(self.config)
if with_server:
self.redisserver = ServerRedisAdapter(self.config)
# ------------------------------------------------------------------------------------------ #
def send_redis(self, channel, message):
self.init_redis_communication()
self.redisclient.publish(channel, message)
# ------------------------------------------------------------------------------------------ #
def send_and_wait_redis(self, channel, message, reply_channel):
self.init_redis_communication(True)
self.redisclient.publish(channel, message)
return self.redisserver.listen_for_one_message(reply_channel.value)
# ------------------------------------------------------------------------------------------ #
def shutdown(self):
self.send_redis("aura", "shutdown")
self.stringreply = "Shutdown message sent!"
# ------------------------------------------------------------------------------------------ #
def fetch_new_programme(self):
json_reply = self.send_and_wait_redis("aura", "fetch_new_programme", RedisChannel.FNP_REPLY)
if json_reply != "":
actprogramme = json.loads(json_reply)
self.print_programme(actprogramme)
else:
print("No programme fetched")
# ------------------------------------------------------------------------------------------ #
def get_act_programme(self):
json_reply = self.send_and_wait_redis("aura", "get_act_programme", RedisChannel.GAP_REPLY)
actprogramme = json.loads(json_reply)
self.print_programme(actprogramme)
# ------------------------------------------------------------------------------------------ #
def get_connection_status(self):
json_reply = self.send_and_wait_redis("aura", "get_connection_status", RedisChannel.GCS_REPLY)
connection_status = json.loads(json_reply)
self.print_connection_status(connection_status)
# ------------------------------------------------------------------------------------------ #
def print_programme(self, programme):
cnt = 1
for show in programme:
for entry in show["playlist"]:
self.stringreply += str(cnt) + \
" --- schedule id #" + str(show["schedule_id"]) + "." + str(entry["entry_num"]) + \
" - show: " + show["show_name"] + \
" - starts @ " + entry["entry_start"] + \
" - plays " + str(entry["source"]) + "\n"
cnt = cnt + 1
# ------------------------------------------------------------------------------------------ #
def print_connection_status(self, connection_status):
if connection_status["pv"]:
self.stringreply = "Connection to pv: " + TerminalColors.GREEN.value + " " + str(connection_status["pv"]) + TerminalColors.ENDC.value
else:
self.stringreply = "Connection to pv: " + TerminalColors.RED.value + " " + str(connection_status["pv"]) + TerminalColors.ENDC.value
if connection_status["db"]:
self.stringreply += "\nConnection to db: " + TerminalColors.GREEN.value + " " + str(connection_status["db"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to db: " + TerminalColors.RED.value + " " + str(connection_status["db"]) + TerminalColors.ENDC.value
if connection_status["lqs"]:
self.stringreply += "\nConnection to lqs: " + TerminalColors.GREEN.value + " " + str(connection_status["lqs"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to lqs: " + TerminalColors.RED.value + " " + str(connection_status["lqs"]) + TerminalColors.ENDC.value
if connection_status["lqsr"]:
self.stringreply += "\nConnection to lqsr: " + TerminalColors.GREEN.value + " " + str(connection_status["lqsr"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to lqsr: " + TerminalColors.RED.value + " " + str(connection_status["lqsr"]) + TerminalColors.ENDC.value
if connection_status["tank"]:
self.stringreply += "\nConnection to tank: " + TerminalColors.GREEN.value + " " + str(connection_status["tank"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to tank: " + TerminalColors.RED.value + " " + str(connection_status["tank"]) + TerminalColors.ENDC.value
if connection_status["redis"]:
self.stringreply += "\nConnection to redis: " + TerminalColors.GREEN.value + " " + str(connection_status["redis"]) + TerminalColors.ENDC.value
else:
self.stringreply += "\nConnection to redis: " + TerminalColors.RED.value + " " + str(connection_status["redis"]) + TerminalColors.ENDC.value
# ------------------------------------------------------------------------------------------ #
def init_player(self):
"""
Initializes the player on Liquidsaop startup.
"""
self.stringreply = self.send_and_wait_redis("aura", "init_player", RedisChannel.IP_REPLY)
def adapt_trackservice_title(self, info):
"""
Updates the tracks-service with the info on currently played fallback track.
"""
self.stringreply = self.send_and_wait_redis("aura", "adapt_trackservice_title " + info, RedisChannel.GNF_REPLY)
# ------------------------------------------------------------------------------------------ #
def recreatedb(self):
print("YOU WILL GET PROBLEMS DUE TO DATABASE BLOCKING IF aura.py IS RUNNING! NO CHECKS IMPLEMENTED SO FAR!")
x = AuraDatabaseModel()
x.recreate_db()
self.stringreply = "Database recreated!"
# ------------------------------------------------------------------------------------------ #
def redis_message(self, channel, message):
self.send_redis(channel, message)
self.stringreply = "Message '"+message+"' sent to channel '"+channel+"'"
# ------------------------------------------------------------------------------------------ #
def print_message_queue(self):
self.stringreply = self.send_and_wait_redis("aura", "print_message_queue", RedisChannel.PMQ_REPLY)
# LIQUIDSOAP #
# ------------------------------------------------------------------------------------------ #
def select_mixer(self, mixername, activate=True):
# init lqs
self.init_liquidsoap_communication()
# select mixer and return the feedback
self.stringreply = self.lsc.channel_activate(mixername, activate)
# disable connection
self.destroy_liquidsoap_communication()
# ------------------------------------------------------------------------------------------ #
def set_volume(self, mixernumber, volume):
# init lqs and enable comm
self.init_liquidsoap_communication()
self.stringreply = self.lsc.set_volume(mixernumber, volume)
# disable connection
self.destroy_liquidsoap_communication()
# ------------------------------------------------------------------------------------------ #
def get_active_mixer(self):
self.init_liquidsoap_communication()
am = self.lsc.get_active_mixer()
if len(am) == 0:
self.destroy_liquidsoap_communication()
raise Exception("Guru recognized a problem: No active source!!!")
self.stringreply = str(am)
# disable connection
self.destroy_liquidsoap_communication()
# ------------------------------------------------------------------------------------------ #
def get_mixer_status(self):
self.init_liquidsoap_communication()
status = self.lsc.get_mixer_status()
for k, v in status.items():
self.stringreply += "source: " + k + "\t status: " + v + "\n"
# disable connection
self.destroy_liquidsoap_communication()
# REDIS #
# ------------------------------------------------------------------------------------------ #
def get_next_file(self, type):
# redis = RedisMessenger()
# next_file = redis.get_next_file_for(type)
# if next_file == "":
# next_file = "/var/audio/blank.flac"
# self.stringreply = next_file
#self.send_redis("aura", "set_next_file " + type)
next_file = self.send_and_wait_redis("aura", "get_next_file " + type, RedisChannel.GNF_REPLY)
self.stringreply = next_file
# ------------------------------------------------------------------------------------------ #
def set_next_file(self, type, file):
#from modules.communication.redis.messenger import RedisMessenger
#redis = RedisMessenger()
#redis.set_next_file_for(type, file)
self.send_redis("aura", "set_next_file " + type + " " + file)
self.stringreply = "Set "+file+" for fallback '"+type+"'"
#
# 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 socket
import urllib.parse
import configparser
import logging
from multiprocessing import Lock
from libraries.exceptions.auraexceptions import LQConnectionError
from libraries.enum.auraenumerations import TerminalColors
"""
LiquidSoapClient Class
Connects to a LiquidSoap instance over a socket and sends commands to it
"""
class LiquidSoapClient:
mutex = None
logger = None
debug = False
socket_path = ""
disable_logging = True
def __init__(self, config, socket_filename):
"""
Constructor
@type socket_path: string
@param socket_path: Der Pfad zum Socket des Liquidsoap-Scripts
"""
self.logger = logging.getLogger("AuraEngine")
self.socket_path = config.get('socketdir') + '/' + socket_filename
self.logger.debug("LiquidSoapClient using socketpath: " + self.socket_path)
# init
self.mutex = Lock()
self.connected = False
self.can_connect = True
self.message = ''
self.socket = None
self.metareader = configparser.ConfigParser()
# ------------------------------------------------------------------------------------------ #
def connect(self):
"""
Verbindung herstellen
"""
try:
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.connect(self.socket_path)
except socket.error as e:
msg = "Cannot connect to socketpath " + self.socket_path + ". Reason: "+str(e)
self.logger.critical(TerminalColors.RED.value+msg+TerminalColors.ENDC.value)
self.can_connect = False
self.connected = False
# raise e
else:
self.can_connect = True
self.connected = True
return True
# AttributeError('characters_written')
# ------------------------------------------------------------------------------------------ #
def is_connected(self):
return self.connected
# ------------------------------------------------------------------------------------------ #
def write(self, data):
"""
Auf den Socket schreiben
@type data: string
@param data: Der String der gesendet wird
"""
if self.connected:
self.socket.sendall(data.decode("UTF-8"))
# ------------------------------------------------------------------------------------------ #
def read_all(self, timeout=2):
"""
Vom Socket lesen, bis dieser "END" sendet
@type timeout: int
@param timeout: Ein optionales Timeout
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
# make socket non blocking
# self.client.setblocking(0)
data = ''
try:
# set timeout
self.socket.settimeout(timeout)
# acquire the lock
self.mutex.acquire()
while True:
data += self.socket.recv(1).decode("utf-8")
# receive as long as we are not at the END or recv a Bye! from liquidsoap
if data.find("END\r\n") != -1 or data.find("Bye!\r\n") != -1:
data.replace("END\r\n", "")
break
# release the lock
self.mutex.release()
except Exception as e:
self.logger.error(TerminalColors.RED.value+str(e)+TerminalColors.ENDC.value)
self.mutex.release()
return data
# ------------------------------------------------------------------------------------------ #
def read(self):
"""
read from socket and store return value in self.message
@rtype: string
@return: The answer of liquidsoap server
"""
if self.connected:
ret = self.read_all().splitlines()
try:
last = ret.pop() # pop out end
if len(ret) > 1:
self.message = str.join(" - ", ret)
elif len(ret) == 1:
self.message = ret[0]
if last == "Bye!":
self.message = last
except Exception as e:
self.logger.error(str(e))
return self.message
# ------------------------------------------------------------------------------------------ #
def close(self):
"""
Quit senden und Verbindung schließen
"""
if self.connected:
message = "quit\r"
self.socket.sendall(message.decode("UTF-8"))
self.socket.close()
self.connected = False
# ------------------------------------------------------------------------------------------ #
def command(self, namespace, command, param=""):
"""
Kommando an Liquidosap senden
@type command: string
@param command: Kommando
@type namespace: string
@param namespace: Namespace/Kanal der angesprochen wird
@type param: mixed
@param param: ein optionaler Parameter
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
param = (param.strip() if param.strip() == "" else " " + urllib.parse.unquote(param.strip()))
if self.connected:
# print namespace + '.' + command + param + "\n"
if namespace is "":
message = str(command) + str(param) + str("\n")
else:
message = str(namespace) + str(".") + str(command) + str(param) + str("\n")
try:
if not self.disable_logging:
self.logger.info("LiquidSoapClient sending to LiquidSoap Server: " + message[0:len(message)-1])
# send all the stuff over the socket to liquidsoap server
self.socket.sendall(message.encode())
if not self.disable_logging:
self.logger.debug("LiquidSoapClient waiting for reply from LiquidSoap Server")
# wait for reply
self.read()
if not self.disable_logging:
self.logger.info("LiquidSoapClient got reply: " + self.message)
except BrokenPipeError as e:
self.logger.error(TerminalColors.RED.value+"Detected a problem with liquidsoap connection while sending: " + message + ". Reason: " + str(e) + "! Trying to reconnect."+TerminalColors.RED.value)
self.connect()
raise
except Exception as e:
self.logger.error("Unexpected error: " + str(e))
raise
return self.message
else:
msg = "LiquidsoapClient not connected to LiquidSoap Server"
self.logger.error(msg)
raise LQConnectionError(msg)
# ------------------------------------------------------------------------------------------ #
def help(self):
"""
get liquidsoap server help
@rtype: string
@return: the response of the liquidsoap server
"""
if self.connected:
self.command('help', '')
return self.message
# ------------------------------------------------------------------------------------------ #
def version(self):
"""
Liquidsoap get version
@rtype: string
@return: the response of the liquidsoap server
"""
if self.connected:
message = 'version'
self.command(message, '')
return self.message
# ------------------------------------------------------------------------------------------ #
def uptime(self):
"""
Liquidsoap get uptime
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
if self.connected:
self.command('uptime', '')
return self.message
# ------------------------------------------------------------------------------------------ #
def byebye(self):
"""
Liquidsoap say byebye
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
if self.connected:
self.command("", "quit")
return self.message
\ No newline at end of file
This diff is collapsed.
#
# 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 logging
import datetime
import threading
from libraries.enum.auraenumerations import ScheduleEntryType, TerminalColors
"""
LiquidSoapInitThread class.
Starts the LiquidSoap player including the current show.
"""
class LiquidSoapInitThread(threading.Thread):
logger = None
active_entry = None
liquidsoapcommunicator = None
def __init__(self, liquidsoapcommunicator, active_entry):
"""
Initialize the thread.
"""
threading.Thread.__init__(self)
self.logger = logging.getLogger("AuraEngine")
self.liquidsoapcommunicator = liquidsoapcommunicator
self.active_entry = active_entry
def run(self):
"""
Starts the LiquidSoap player including the current show.
"""
try:
# Sleep needed, because the socket is created too slow by liquidsoap
time.sleep(1)
self.logger.info("Waited 1s for liquidsoap. Jez soit a si gspian")
self.liquidsoapcommunicator.enable_transaction()
# Wait another second. lqs really starts slow.. be prepared you liquidsoap you!
time.sleep(1)
self.set_start_parameters()
self.set_active_show()
self.liquidsoapcommunicator.disable_transaction()
# The rest of the system now can use liquidsoap connection
self.liquidsoapcommunicator.is_liquidsoap_running = True
except Exception as e:
self.logger.critical(TerminalColors.RED.value+"Liquidsoap connection ERROR! Restart LQ Server! Reason: "+str(e)+TerminalColors.ENDC.value)
def set_active_show(self):
"""
Sets and resumes the show which should be playing at the time of starting
the LiquidSoap player.
"""
if self.active_entry is not None:
channel = self.active_entry.type
self.logger.info("LiquidSoapInitThread sets activechannel '%s' for entry '%s'" % (channel, str(self.active_entry)))
if channel == ScheduleEntryType.FILESYSTEM:
# TODO For some reason LiquidSoap cue-points don't work. Actually,
# this would be the ideal approach to seek. Investigate some solution!
# if seconds_to_seek < 0:
# seconds_to_seek = 0
# self.liquidsoapcommunicator.activate(self.active_entry, seconds_to_seek)
self.liquidsoapcommunicator.activate(self.active_entry)
# rest_of_playlist = self.active_entry.get_next_entries()
# for entry in rest_of_playlist:
# self.logger.info("ACTIVATING NEXT ENTRY: %s" %entry)
# self.liquidsoapcommunicator.activate(entry)
# Have to seek? Calc how many seconds were missed
now_unix = time.mktime(datetime.datetime.now().timetuple())
seconds_to_seek = now_unix - self.active_entry.start_unix
# And seek these seconds forward
if seconds_to_seek > 0:
# Without plenty of timeout (10s) the seek doesn't work
seconds_to_seek += 10
time.sleep(10)
response = self.liquidsoapcommunicator.playlist_seek(seconds_to_seek)
self.logger.info("LiquidSoap seek response: " + response)
# Finally make something hearable :-)
if channel:
# Activate HTTP stream if needed
self.liquidsoapcommunicator.http_start_stop(channel == ScheduleEntryType.STREAM)
# Finally set the volume up
self.liquidsoapcommunicator.channel_volume(channel.value, self.active_entry.volume)
else:
self.logger.error("Channel is NULL or empty! Cannot set ")
else:
self.logger.warning("No active entry in the scheduler! Is a programme loaded?")
def set_start_parameters(self):
"""
Set initial parameters for the LiquidSoap player startup.
"""
# Reset channels and reload them
channels = self.liquidsoapcommunicator.reload_channels()
# For all available channels
for c in channels:
# Set volume to zero
self.liquidsoapcommunicator.channel_volume(c, "0")
# And activate this channel
self.liquidsoapcommunicator.channel_activate(c, True)
# Setting init params like a blank file..
install_dir = self.liquidsoapcommunicator.config.get("install_dir")
self.liquidsoapcommunicator.playlist_push(install_dir + "/configuration/blank.flac")
# .. or the radio fro stream (it is overwritten as soon as one http overtake is planned)
#self.liquidsoapcommunicator.set_http_url("http://stream.fro.at/fro-128.ogg")
This diff is collapsed.
#
# 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 smtplib
from email.message import EmailMessage
from libraries.exceptions.auraexceptions import MailingException
class AuraMailer():
"""
Service to send emails to Aura administrators.
"""
config = None
def __init__(self, config):
"""
Constructor to initialize service with Aura `config`.
Args:
config (AuraConfig): The configuration with the mail server details
"""
self.config = config
self.admin_mails = config.get("admin_mail")
#
# PUBLIC METHODS
#
def send_admin_mail(self, subject, body):
"""
Sends an email to the administrator as defined in the configuration.
Args:
subject (String): The email's subject
body (String): The email's body text
"""
admin_mails = self.admin_mails.split()
for mail_to in admin_mails:
self.__send(mail_to, subject, body)
#
# PRIVATE METHODS
#
def __send(self, mail_to, subject, body):
"""
Sends an email to the given address.
Args:
subject (String): The email's subject
body (String): The email's body text
"""
# read config
mail_server = self.config.get("mail_server")
mail_port = self.config.get("mail_server_port")
mail_user = self.config.get("mail_user")
mail_pass = self.config.get("mail_pass")
from_mail = self.config.get("from_mail")
# check settings
if mail_server == "":
raise MailingException("Mail Server not set")
if mail_port == "":
raise MailingException("Mailserver Port not set")
if mail_user == "":
raise MailingException("Mail user not set")
if mail_pass == "":
raise MailingException("No Password for mailing set")
if from_mail == "":
raise MailingException("From Mail not set")
# stuff the message together and ...
msg = EmailMessage()
msg.set_content(body)
mailsubject_prefix = self.config.get("mailsubject_prefix")
if mailsubject_prefix == "":
msg["Subject"] = subject
else:
msg["Subject"] = mailsubject_prefix + " " + subject
msg["From"] = from_mail
msg["To"] = mail_to
# ... send the mail
try:
server = smtplib.SMTP(mail_server, int(mail_port))
server.starttls()
server.login(mail_user, mail_pass)
server.send_message(msg)
server.quit()
except Exception as e:
raise MailingException(str(e))
This diff is collapsed.
This diff is collapsed.
__author__ = 'gg'
#!/usr/bin/liquidsoap
#
# 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/>.
#
icecast_vorbis_metadata = false
inputs = ref []
# load settings from ini file
%include "settings.liq"
# include some functions
%include "library.liq"
# include fallback functions
%include "fallback.liq"
#################
# create inputs #
#################
# enable play from filesystem
%include "in_filesystem.liq"
# enable stream overtakes
%include "in_stream.liq"
# enabled line in from soundcard
%include "in_soundcard.liq"
# fill the mixer
mixer = mix(id="mixer", list.append([input_fs, input_http], !inputs))
# output source with fallbacks
stripped_stream = strip_blank(track_sensitive=false, max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold, mixer)
# enable fallback
output_source = fallback(id="fallback", track_sensitive=false, [stripped_stream, timeslot_fallback, show_fallback, mksafe(station_fallback)])
##################
# create outputs #
##################
# create soundcard output
%include "out_soundcard.liq"
# recording output
%include "out_filesystem.liq"
# stream output
%include "out_stream.liq"
# enable socket functions
%include "serverfunctions.liq"
########################
# start initialization #
########################
system('#{list.assoc("install_dir", ini)}/guru.py --init-player --quiet')
This diff is collapsed.
__author__ = 'michel'
import os
import sys