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 1615 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/>.
#
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())
#
# 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.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):
self.stringreply = self.send_and_wait_redis("aura", "init_player", RedisChannel.IP_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 urllib
import logging
import json
from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator
#from libraries.database.broadcasts import ScheduleEntry
from libraries.base.config import AuraConfig
# ------------------------------------------------------------------------------------------ #
class ConnectionTester(AuraConfig):
# ------------------------------------------------------------------------------------------ #
def __init__(self):
super(ConnectionTester, self).__init__()
# ------------------------------------------------------------------------------------------ #
def get_connection_status(self):
status = dict()
status["db"] = False # self.test_db_conn()
status["pv"] = self.test_pv_conn()
status["lqs"] = self.test_lqs_conn()
status["lqsr"] = False # self.test_lqsr_conn()
status["tank"] = self.test_tank_conn()
status["redis"] = self.test_redis_conn()
return json.dumps(status)
# ------------------------------------------------------------------------------------------ #
# def test_db_conn(self):
# try:
# ScheduleEntry.select_all()
# except:
# return False
#
# return True
# ------------------------------------------------------------------------------------------ #
def test_lqs_conn(self):
try:
lsc = LiquidSoapCommunicator(self.config)
lsc.get_mixer_status()
return True
except Exception as e:
return False
# ------------------------------------------------------------------------------------------ #
def test_lqsr_conn(self):
try:
lsc = LiquidSoapCommunicator(self.config)
lsc.get_recorder_status()
return True
except Exception as e:
return False
# ------------------------------------------------------------------------------------------ #
def test_pv_conn(self):
return self.test_url_connection(self.config.get("calendarurl"))
# ------------------------------------------------------------------------------------------ #
def test_tank_conn(self):
# test load of playlist 1
return self.test_url_connection(self.config.get("importerurl")+"1")
# ------------------------------------------------------------------------------------------ #
def test_redis_conn(self):
from modules.communication.redis.adapter import ClientRedisAdapter
try:
cra = ClientRedisAdapter()
cra.publish("aura", "status")
except:
return False
return True
def test_url_connection(self, url):
try:
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
response.read()
except Exception as e:
return False
return True
\ 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 socket
import urllib.parse
import configparser
import logging
from multiprocessing import Lock
from libraries.exceptions.auraexceptions import LQConnectionError
"""
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(msg)
self.can_connect = False
self.connected = False
# raise e
else:
self.can_connect = True
self.connected = True
return True
# ------------------------------------------------------------------------------------------ #
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(str(e))
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("Detected a problem with liquidsoap connection while sending: " + message + ". Reason: " + str(e) + "! Trying to reconnect.")
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
"""
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("Liquidsoap connection ERROR! Restart LQ Server! Reason: "+str(e), e)
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")
#
# 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 modules.communication.liquidsoap.client import LiquidSoapClient
class LiquidSoapPlayerClient(LiquidSoapClient):
# ------------------------------------------------------------------------------------------ #
def mixer(self, command, *args):
if command == "status":
return self.mixerstatus(*args)
if command == "inputs":
return self.mixerinputs()
if command == "volume":
return self.mixervolume(*args)
if command == "select":
if len(args) == 2:
return self.mixerselect(args[0], args[1])
return "LiquidSoapPlayerClient does not understand mixer."+command+str(args)
# ------------------------------------------------------------------------------------------ #
def recorder(self, num, command, *args):
if command == "status":
return self.recorderstatus(num)
if command == "start":
return self.recorderstart(num)
if command == "stop":
return self.recorderstop(num)
return "LiquidSoapPlayerClient does not understand mixer." + command + str(args)
# ------------------------------------------------------------------------------------------ #
def http(self, command, *args):
if command == "url":
return self.set_http_url(*args)
return "LiquidSoapPlayerClient does not understand http." + command + str(args)
# ------------------------------------------------------------------------------------------ #
def fs(self, command, *args):
if command == "push":
return self.fs_push(*args)
if command == "seek":
return self.fs_seek(*args)
if command == "clear":
return self.fs_clear(*args)
return "LiquidSoapPlayerClient does not understand fs." + command + str(args)
# ------------------------------------------------------------------------------------------ #
def uptime(self, command=""): # no command will come
return self.command("uptime", "")
# ------------------------------------------------------------------------------------------ #
def auraengine(self, command, *args):
if command == "state":
return self.auraengine_state()
return "LiquidSoapPlayerClient does not understand auraengine." + command + str(args)
# ------------------------------------------------------------------------------------------ #
def auraengine_state(self):
self.command('auraengine', 'state')
return self.message
# ------------------------------------------------------------------------------------------ #
def fs_push(self, uri):
self.command('fs', 'push', uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def fs_clear(self):
self.command('fs', 'clear')
return self.message
def fs_seek(self, uri):
self.command('fs', 'seek', uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def set_http_url(self, uri):
self.command('http', 'url', uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def mixerinputs(self):
# send command
self.command("mixer", "inputs")
# convert to list and return it
return self.message.strip().split(' ')
# ------------------------------------------------------------------------------------------ #
def mixerstatus(self, pos=""):
"""
Get state of a source in the mixer
@type pos: string
@param pos: Mixerposition
@rtype: string
@return: Response from LiquidSoap
"""
self.command("mixer", "status", str(pos))
return self.message
# ------------------------------------------------------------------------------------------ #
def mixerselect(self, pos, activate):
"""
Kanal/Source aktivieren
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command("mixer", "select", str(pos) + " " + str(activate).lower())
return self.message
# ------------------------------------------------------------------------------------------ #
def mixervolume(self, pos, volume):
"""
set channel volume
:param pos:
:param volume:
:return:
"""
self.command("mixer", "volume", str(pos) + " " + str(volume))
return self.message
# ------------------------------------------------------------------------------------------ #
def recorderstatus(self, num):
"""
get status of a recorder
:return:
"""
self.command("recorder_" + str(num), "status")
return self.message
# ------------------------------------------------------------------------------------------ #
def recorderstart(self, num):
"""
get status of a recorder
:return:
"""
self.command("recorder_" + str(num), "start")
return self.message
# ------------------------------------------------------------------------------------------ #
def recorderstop(self, num):
"""
get status of a recorder
:return:
"""
self.command("recorder_" + str(num), "stop")
return self.message
# ------------------------------------------------------------------------------------------ #
def skip(self, namespace="playlist", pos=""):
"""
Source skippen
@type namespace: string
@param namespace: Namespace der Source
@type pos: string
@param pos: Die Position - optional - Position des Channels vom Mixer benötigt
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('skip', namespace, pos)
return self.message
# ------------------------------------------------------------------------------------------ #
def remove(self, pos, namespace="playlist"):
"""
Track aus der secondary_queue oder der Playlist entfernen
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('remove', namespace, str(pos))
return self.message
# ------------------------------------------------------------------------------------------ #
def insert(self, uri, pos='0', namespace="playlist"):
"""
Track einfügen
@type uri: string
@param uri: Uri einer Audiodatei
@type pos: string
@param pos: Die Position
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('insert', namespace, str(pos) + ' ' + uri)
return self.message
# ------------------------------------------------------------------------------------------ #
def move(self, fromPos, toPos, namespace="playlist"):
"""
Track von Position fromPos nach Position toPos verschieben
@type fromPos: string/int
@param fromPos: Position des zu verschiebenden Tracks
@type toPos: string
@param toPos: Die Position zu der verschoben werden soll
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('move', namespace, str(fromPos) + ' ' + str(toPos))
return self.message
# ------------------------------------------------------------------------------------------ #
def play(self, namespace="playlist"):
"""
Source abspielen - funktioniert nur bei Playlist
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('play', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def pause(self, namespace="playlist"):
"""
Source pausieren/stoppen - funktioniert nur bei Playlist
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('pause', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def flush(self, namespace="playlist"):
"""
Playlist leeren
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('flush', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def playlistData(self):
"""
Metadaten der Playlist ausgeben
@rtype: string
@return: Ein Json-String
"""
self.command('data', 'playlist')
return self.message
# ------------------------------------------------------------------------------------------ #
def seek(self, duration, namespace="playlist"):
"""
Aktuell laufenen Track des Kanals vorspulen
@type duration: string/int
@param duration: Dauer in Sekunden
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('seek', namespace, str(duration))
return self.message
# ------------------------------------------------------------------------------------------ #
def get_queue(self, namespace="ch1", queue='queue'):
"""
Queue eines Kanals ausgeben
@type namespace: string
@param namespace: Namespace der Source
@type queue: string
@param queue: Name des queues (queue, primary_queue, secondary_queue)
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command(queue, namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def loadPlaylist(self, uri, params="", namespace="playlist"):
"""
Playlist laden
@type uri: string
@param uri: Uri einer Playlist im XSPF-Format
@type params: string
@param params: obsolete
@type namespace: string
@param namespace: Namespace der Source - hier nur playlist
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('load', namespace, uri + params)
return self.message
# ------------------------------------------------------------------------------------------ #
def currentTrack(self, namespace="request"):
"""
Das oder die ID(s) der gerade abgespielten requests erhalten
@type namespace: string
@param namespace: Namespace der Source
@rtype: string
@return: Die Antwort des Liquidsoap-Servers (als String)
"""
self.command('on_air', namespace)
return self.message
# ------------------------------------------------------------------------------------------ #
def volume(self, pos, volume, namespace="mixer"):
"""
Lautstärke eines Kanals setzen
@type pos: int/string
@param pos: Die Position/ Nummer des Kanals (playlist=0)
@type volume: int/string
@param volume: Zahl von 1 -100
@type namespace: string
@param namespace: Namespace der Source (immer mixer)
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('volume', namespace, str(pos) + ' ' + str(volume))
return self.message
# ------------------------------------------------------------------------------------------ #
def playlist_remaining(self):
"""
Wie lange läuft der aktuelle Track der Playlist noch
@rtype: string
@return: Die Antwort des Liquidsoap-Servers
"""
self.command('remaining', 'playlist')
return self.message
# ------------------------------------------------------------------------------------------ #
def list_channels(self):
"""
Channels auflisten (Simple JSON)
"""
# Liquidsoap Kommando
channels = self.sendLqcCommand(self.lqc, 'mixer', 'inputs')
if not isinstance(channels, list):
self.error('02')
elif len(channels) < 1:
self.warning('01')
else:
self.success('00', channels)
self.notifyClient()
\ 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 smtplib
from email.message import EmailMessage
from libraries.exceptions.auraexceptions import MailingException
class AuraMailer():
config = None
# ------------------------------------------------------------------------------------------ #
def __init__(self, config):
self.config = config
self.admin_mails = config.get("admin_mail")
# ------------------------------------------------------------------------------------------ #
def send_admin_mail(self, subject, body):
admin_mails = self.admin_mails.split()
for mail_to in admin_mails:
self.__send(mail_to, subject, body)
# ------------------------------------------------------------------------------------------ #
def __send(self, mail_to, subject, body):
# 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'
This diff is collapsed.
This diff is collapsed.
__author__ = 'michel'
import os
import sys
#!/bin/bash
pack_int(){ printf "%08X\n" $1 | sed 's/\([0-9A-F]\{2\}\)\([0-9A-F]\{2\}\)\([0-9A-F]\{2\}\)\([0-9A-F]\{2\}\)/\\\\\\x\4\\\\\\x\3\\\\\\x\2\\\\\\x\1/I' | xargs printf; }
pack_short(){ printf "%04X\n" $1 | sed 's/\([0-9A-F]\{2\}\)\([0-9A-F]\{2\}\)/\\\\\\x\2\\\\\\x\1/I' | xargs printf; }
duration=1800
if [[ $# -eq 1 ]]; then
duration=$1
fi
channels=2
bps=16
sample=44100
Subchunk1Size=18
Subchunk2Size=$(echo "$duration*$sample*$channels*$bps/8" | bc)
ChunkSize=$((20 + $Subchunk1Size + $Subchunk2Size))
echo -n RIFF
pack_int $ChunkSize
echo -n "WAVEfmt "
pack_int $Subchunk1Size
pack_short 1
pack_short $channels
pack_int $sample
pack_int $((bps/8 * channels * sample))
pack_short $((bps/8 * channels))
pack_short $bps
pack_short 0
echo -n data
pack_int $Subchunk2Size
dd if=/dev/zero bs=1 count=$Subchunk2Size 2>/dev/null
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.