Commit 070ffe17 authored by Gottfried Gaisbauer's avatar Gottfried Gaisbauer
Browse files

finally removed dead code, controller is gone. added logging. improved stability

parent 0a8175ac
......@@ -2,32 +2,40 @@
import signal
import sys
import threading
from libraries.base.config import ConfigReader
from modules.controller.controller import AuraController
from modules.base.config import ConfigReader
from modules.scheduling.scheduler import AuraScheduler
from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator
from modules.communication.redis.adapter import ServerRedisAdapter
from modules.web.routes import Routes
from libraries.base.common import AuraCommon
class Aura():
class Aura(AuraCommon):
server = None
config = None
messenger = None
controller = None
# ------------------------------------------------------------------------------------------ #
def __init__(self):
self.config = ConfigReader()
self.config.loadConfig()
AuraCommon.__init__(self, name="AuraEngine")
server = object
self.controller = AuraController(self.config)
# self.controller = AuraController(self.config)
# create scheduler and ls_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.messenger.set_controller(self.controller)
self.messenger.set_config(self.config)
self.messenger.config = self.config
self.messenger.scheduler = self.scheduler
self.messenger.liquidsoapcommunicator = self.liquidsoapcommunicator
def receive_signal(signum, stack):
print("received signal")
......@@ -35,13 +43,20 @@ class Aura():
signal.signal(signal.SIGUSR1, receive_signal)
# addition initialization
self.scheduler.fetch_new_programme()
def join_comm(self):
# start listener thread
self.messenger.start()
def start_web_service(self):
r = Routes()
#r.app.run()
try:
Routes()
except OSError as e:
self.messenger.halt()
self.logger.critical("AuraEngine already running? Exception: " + e.strerror + ". Exiting...")
sys.exit(0)
# # ## ## ## ## ## # #
......
[station]
station_name="Radio FRO"
station_logo="/etc/aura/stationlogo.jpg"
station_fallback_pool="/var/audio/station_fallback_pool"
[user]
#Change this settings
daemongroup="gg"
daemonuser="gg"
[configfile]
scheduler_config_file="/etc/aura/scheduler.xml"
# SOUND CARD SETTINGS
[soundcard]
line_in_count=1
line_out_count=1
input_device[0]="hw:0" # make it comma separated!!
output_device[0]="hw:0"
# DATABASE SETTINGS
db_user="aura"
db_name="aura"
db_pass="aura"
db_host="localhost"
# ALSA SETTINGS
# if you have no idea what to do here => set use_alsa to "n", then pulseaudio is used
use_alsa="y"
# alsa_buffer => int
alsa_buffer="16000"
......@@ -34,39 +28,25 @@ frame_duration="0.4"
# frame_size => int
frame_size=""
[database]
db_user="aura"
db_name="aura"
db_pass="aura"
db_host="localhost"
[socket]
socketdir="/home/gg/PycharmProjects/engine/modules/liquidsoap"
[logging]
logdir="/var/log/aura"
# possible values: debug, info, warning, error, critical
loglevel="info"
# channelnames for mixing
[liquidsoap]
# leave this alone if you do not know what you are doing
http_channels="http,http2"
line_in_channels="live,live2"
filesystem_channels="filesystem"
adminmail="gogo@servus.at"
playlistdir="/var/audio/playlists/"
#calendarurl="http://localhost/index.php?option=com_jimtawl&view=calendar&format=json&from=#datefrom#&to=#dateto#"
#calendarurl="http://bermudafunk-kalender.critmass.de/index.php?option=com_jimtawl&view=calendar&format=json&from=#datefrom#&to=#dateto#"
calendarurl="http://localhost:8000/api/v1/playout"
importerurl=""
# how many days in future should the calendar be stored in database - default=7
#calendar_precache_days=7
audiobase="/var/audio/rec"
altaudiobase="/var/audio/preprod"
# hardware settings
# SOUNDCARD FROM STWST:
# HOME CARD
# was player_input_device:
# recinput="soundcard"
# altrecinput="soundcard"
# altrecorder_device="soundcard"
# recorder_device="soundcard"
# track_sensitive => fallback_folder track sensitivity
# max_blank => maximum time of blank from source
# min_noise => minimum duration of noise on source to switch back over
......@@ -75,6 +55,27 @@ fallback_max_blank="5"
fallback_min_noise="30"
fallback_threshold="-40"
[mail]
mail_server="mail.servus.at"
mail_user="m_gottfried"
mail_pass="n0idontw4ntthi5"
# multiple adminmails => space separated
admin_mail="gogo@servus.at gottfried@servus.at"
from_mail="engine@au.ra"
[dataurls]
# calendarurl="http://localhost/index.php?option=com_jimtawl&view=calendar&format=json&from=#datefrom#&to=#dateto#"
# calendarurl="http://bermudafunk-kalender.critmass.de/index.php?option=com_jimtawl&view=calendar&format=json&from=#datefrom#&to=#dateto#"
calendarurl="http://localhost:8000/api/v1/playout"
importerurl=""
[folder]
audiobase="/var/audio/rec"
altaudiobase="/var/audio/preprod"
playlistdir="/var/audio/playlists/"
install_dir="/home/gg/PycharmProjects/engine"
[stream]
stream="y"
stream_type="harbor"
#stream_type="icecast"
......@@ -90,16 +91,3 @@ stream_genre="mixed"
stream_description="Test Stream"
stream_admin_user="admin"
stream_admin_password="ahZ4caeg"
# ZeroMessagingQueue SETTINGS
communication="zmq"
zmqhostip="127.0.0.1"
zmqport="9099"
loglevel="info"
webservice_mode="apache"
#servername=""
#serviceport=""
install_dir="/home/gg/PycharmProjects/engine"
debug="y"
......@@ -7,77 +7,24 @@ import redis
from argparse import ArgumentParser
# own libs
from libraries.base.config import ConfigReader
from modules.tools.padavan import Padavan
from modules.cli_tool.padavan import Padavan
from libraries.exceptions.auraexceptions import PlaylistException
from libraries.base.common import AuraCommon
class Guru:
config = ConfigReader()
config.loadConfig()
class Guru(AuraCommon):
# ------------------------------------------------------------------------------------------ #
def __init__(self):
try:
parser = ArgumentParser()
# options
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")
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False,
help="Just the result will outputed to stout")
# getter
parser.add_argument("-gam", "--get-active-mixer", action="store_true", dest="get_active_mixer", default=False,
help="Which mixer is activated?")
parser.add_argument("-pms", "--print-mixer-status", action="store_true", dest="get_mixer_status", default=False,
help="Prints all mixer sources and their states")
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
parser.add_argument("-am", "--select-mixer", action="store", dest="select_mixer", default=-1, metavar="MIXERNUM",
help="Which mixer should be activated?", type=int)
parser.add_argument("-dm", "--de-select-mixer", action="store", dest="deselect_mixer", default=-1, metavar="MIXERNUM",
help="Which mixer should be activated?", type=int)
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)
#parser.add_argument("-as", "--add-source", action="store", dest="add_source", default=False,
# help="Add new source to LiquidSoap mixer [Experimental]")
# playlist manipulation
parser.add_argument("-fnp", "--fetch-new-programmes", action="store_true", dest="fetch_new_programme",
default=False, help="Fetch new programmes from calendarurl in comba.ini")
parser.add_argument("-spe", "--swap-playlist-entries", action="store", dest="swap_playlist_entries", default=0, metavar=("FROM", "TO"), nargs=2,
help="Swaps two Playlistentries")
parser.add_argument("-dpe", "--delete-playlist-entry", action="store", dest="delete_playlist_entry", default=0, metavar="INDEX",
help="Delete Playlistentry at INDEX")
parser.add_argument("-ipe", "--insert-playlist-entry", action="store", dest="insert_playlist_entry", default=0, metavar=("FROMTIME", "SOURCE"), nargs=2,
help="Add a new Playlistentry at a given index. Set fromtime with this format: 2017-12-31T13:30:00") # , type=valid_playlist_entry)
parser.add_argument("-pmq", "--print-message-queue", action="store_true", dest="print_message_queue", default=False,
help="Prints message queue")
# send a redis message
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
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?")
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?")
parser.add_argument("-np", "--now-playing", action="store", dest="now_playing", default=False, metavar="NOWPLAYINGSOURCE",
help="Which source is now playing")
parser.add_argument("-ip", "--init-player", action="store_true", dest="init_player", default=False,
help="Reset liquidsoap volume and mixer activations?")
AuraCommon.__init__(self, name="AuraGuru")
args = parser.parse_args()
self.create_parser()
self.init_argument_parser()
if len(sys.argv) == 1:
raise ValueError("No Argument passed!")
def init_argument_parser(self):
try:
parser = self.create_parser()
args = parser.parse_args()
except ValueError as e:
parser.print_help()
......@@ -124,10 +71,81 @@ class Guru:
exectime = end-start
print("execution time: "+str(exectime)+"s")
def create_parser(self):
parser = ArgumentParser()
# options
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")
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False,
help="Just the result will outputed to stout")
# getter
parser.add_argument("-gam", "--get-active-mixer", action="store_true", dest="get_active_mixer", default=False,
help="Which mixer is activated?")
parser.add_argument("-pms", "--print-mixer-status", action="store_true", dest="get_mixer_status", default=False,
help="Prints all mixer sources and their states")
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
parser.add_argument("-am", "--select-mixer", action="store", dest="select_mixer", default=-1,
metavar="MIXERNUM",
help="Which mixer should be activated?", type=int)
parser.add_argument("-dm", "--de-select-mixer", action="store", dest="deselect_mixer", default=-1,
metavar="MIXERNUM",
help="Which mixer should be activated?", type=int)
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)
# parser.add_argument("-as", "--add-source", action="store", dest="add_source", default=False,
# help="Add new source to LiquidSoap mixer [Experimental]")
# playlist manipulation
parser.add_argument("-fnp", "--fetch-new-programmes", action="store_true", dest="fetch_new_programme",
default=False, help="Fetch new programmes from calendarurl in comba.ini")
parser.add_argument("-spe", "--swap-playlist-entries", action="store", dest="swap_playlist_entries", default=0,
metavar=("FROM", "TO"), nargs=2,
help="Swaps two Playlistentries")
parser.add_argument("-dpe", "--delete-playlist-entry", action="store", dest="delete_playlist_entry", default=0,
metavar="INDEX",
help="Delete Playlistentry at INDEX")
parser.add_argument("-ipe", "--insert-playlist-entry", action="store", dest="insert_playlist_entry", default=0,
metavar=("FROMTIME", "SOURCE"), nargs=2,
help="Add a new Playlistentry at a given index. Set fromtime with this format: 2017-12-31T13:30:00") # , type=valid_playlist_entry)
parser.add_argument("-pmq", "--print-message-queue", action="store_true", dest="print_message_queue",
default=False,
help="Prints message queue")
# send a redis message
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
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?")
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?")
parser.add_argument("-np", "--now-playing", action="store_true", dest="now_playing", default=False,
help="Which source is now playing")
parser.add_argument("-ip", "--init-player", action="store_true", dest="init_player", default=False,
help="Reset liquidsoap volume and mixer activations?")
if len(sys.argv) == 1:
raise ValueError("No Argument passed!")
return parser
def valid_playlist_entry(argument):
from datetime import datetime
import argparse
try:
......
import logging
from modules.base.config import ConfigReader
class AuraCommon:
logger = None
config = None
def __init__(self, name):
self.read_config()
self.create_logger(name)
def read_config(self):
self.config = ConfigReader()
self.config.load_config()
def create_logger(self, name):
lvl = self.config.get("loglevel")
# create logger
self.logger = logging.getLogger(name)
self.logger.setLevel(lvl)
fh = logging.FileHandler(self.config.get("logdir") + "/aura.log")
fh.setLevel(lvl)
ch = logging.StreamHandler()
ch.setLevel(lvl)
formatter = logging.Formatter("%(asctime)s:%(name)s:%(levelname)s - %(message)s - [%(filename)s:%(lineno)s-%(funcName)s()]")
fh.setFormatter(formatter)
ch.setFormatter(formatter)
self.logger.addHandler(fh)
self.logger.addHandler(ch)
\ No newline at end of file
from xml.dom.minidom import parse
import datetime
from datetime import timedelta
import simplejson
from xml.etree import ElementTree
class NotTextNodeError(BaseException):
pass
class AuraSchedulerConfig():
def __init__(self, xmlpath):
self.jobs = {}
self.filename = xmlpath
self.playperiods = []
self.recordperiods = []
self.hasinstance = False
self.until = None
# -----------------------------------------------------------------------#
def getPlayPeriods(self):
if not self.hasinstance:
self.getJobs()
return self.playperiods
# -----------------------------------------------------------------------#
def getRecordPeriods(self):
if not self.hasinstance:
self.getJobs()
return self.recordperiods
# -----------------------------------------------------------------------#
def getJobs(self):
self.hasinstance = True
self.loadXml()
for job in self.jobs:
if 'job' not in job:
continue;
if 'until' not in job:
job['until'] = ''
if 'day' not in job:
job['day'] = 'all'
# self.jobs.sort(cmp=lambda x,y: cmp(x['time'], y['time']))
# self.jobs.sort(cmp=lambda x,y: cmp(x['day'], y['day']))
self.jobs.sort(key=lambda job: job['time'])
self.jobs.sort(key=lambda job: job['day'])
for index, job in enumerate(self.jobs):
if job['job'] == 'play_playlist':
job['duration'] = self._calcDuration(job['time'], job['until'])
self.playperiods.append({'from': job['time'],'until': job['until'], 'duration': job['duration']})
day = None
if 'day' in job:
day = job['day']
self.addPlaylistLoadJob(job['time'], job['until'], day)
if job['job'] == 'start_recording':
job['duration'] = self._calcDuration(job['time'], job['until'])
self.recordperiods.append({'from': job['time'],'until': job['until'], 'duration': job['duration']})
return self.jobs
# -----------------------------------------------------------------------#
def addPlaylistLoadJob(self, playTime, untilTime, day=None):
job = {}
playStart = datetime.datetime.strptime('1901-01-01T' + playTime,'%Y-%m-%dT%H:%M');
loadTime = playStart - timedelta(minutes=3)
loadTime = loadTime.strftime('%H:%M')
job['time'] = loadTime
job['from'] = playTime
job['until'] = untilTime
job['job'] = 'load_playlist'
if day and not day == 'all' and loadTime > playTime:
day = int(day)
day = 6 if day == 0 else day - 1
job['day'] = str(day)
self.jobs.append(job)
# -----------------------------------------------------------------------#
def storeJsonToXml(self, json):
try:
jobs = simplejson.loads(json)
except:
return False
xml = '<?xml version="1.0" encoding="UTF-8"?>'+"\n"
xml += '<Config>'+"\n";
xml += ' <Jobs multiple="true">'+"\n";
xmlend = ' </Jobs>'+"\n";
xmlend += '</Config>';
for job in jobs:
xml+= ' <job>'+"\n";
for key in job.keys():
xml+= ' <'+key+'>'+str(job[key])+'</'+key+'>'+"\n"
if not job.has_key('params'):
xml+= ' <params></params>'+"\n"
if not job.has_key('day'):
xml+= ' <day>all</day>'+"\n"
xml+= ' </job>'+"\n"
# validate xml
try:
x = ElementTree.fromstring(xml+xmlend)
except:
return False
else:
try:
file = open(self.filename, "w")
file.write(xml+xmlend)
file.close()
except:
return False
else:
return True
# -----------------------------------------------------------------------#
def loadXml(self):
dom = parse(self.filename)
config = self.nodeToDic(dom)
self.jobs = config['Config']['Jobs']
# -----------------------------------------------------------------------#
def getTextFromNode(self, node):
t = ""
for n in node.childNodes:
if n.nodeType == n.TEXT_NODE:
t += n.nodeValue
else:
raise NotTextNodeError
return t
# -----------------------------------------------------------------------#
def nodeToDic(self, node):
dic = {}
for n in node.childNodes:
if n.nodeType != n.ELEMENT_NODE:
continue
if n.getAttribute("multiple") == "true":
# node with multiple children:
# put them in a list
l = []
for c in n.childNodes:
if c.nodeType != n.ELEMENT_NODE:
continue
l.append(self.nodeToDic(c))
dic.update({n.nodeName: l})
continue
try:
text = self.getTextFromNode(n)
except NotTextNodeError:
# 'normal' node
dic.update({str(n.nodeName): self.nodeToDic(n)})
continue
# text node
dic.update({str(n.nodeName): str(text)})
continue
return dic
# -----------------------------------------------------------------------#
def in_timeperiod(self, now, job):
if 'until' not in job or not job['until']:
print("not in timeperiod")
return False
(hour1, minute1) = job['time'].split(':')
(hour2, minute2) = job['until'].split(':')
if job['time'] > job['until']:
print("in time period. time greater than until")
return datetime.time(hour=int(hour1), minute=int(minute1)) \
<= now.time()
else:
print("in time period. until greater than time")
return datetime.time(hour=int(hour1), minute=int(minute1)) \
<= now.time() \
<= datetime.time(hour=int(hour2), minute=int(minute2))
# -----------------------------------------------------------------------#
def _calcDuration(self, timestring1, timestring2):
"""Berechnet Zeit in Sekunden aus zwei Time-Strings
"""
ftr = [3600, 60, 1]
sec1 = sum([a * b for a, b in zip(ftr, map(int, timestring1.split(':')))])
sec2 = sum([a * b for a, b in zip(ftr, map(int, timestring2.split(':')))])
offset = 0 if sec2 > sec1 else 86400
return (sec2 + offset) - sec1
# -----------------------------------------------------------------------#