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 1537 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 simplejson
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 = simplejson.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
#
# engine
#
# Playout Daemon for autoradio project
#
# Copyright 2014 BFR <info@freie-radios.de>
# Copyright (C) 2017-2018 Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; Version 3 of the License
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, the license can be downloaded here:
#
# http://www.gnu.org/licenses/gpl.html
# Meta
__version__ = '0.1.1'
__license__ = "GNU General Public License (GPL) Version 3"
__version_info__ = (0, 1, 1)
__author__ = 'Michael Liebler <michael-liebler@radio-z.net>'
"""
Aura Config Reader
"""
import os
import sys
import socket
import logging
from configparser import ConfigParser
class ConfigReader(object):
ini_path = ""
logger = None
def __init__(self):
self.logger = logging.getLogger("AuraEngine")
def set(self, key, value):
"""
Set a property
@type key: string
@param key: The Key
@type value: mixed
@param value: Beliebiger Wert
"""
try:
self.__dict__[key] = int(value)
except:
self.__dict__[key] = str(value)
# ------------------------------------------------------------------------------------------ #
def get(self, key, default=None):
"""
get a loaded property
@type key: string
@param key: Der Key
@type default: mixed
@param default: Beliebiger Wert
"""
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
:return:
"""
self.ini_path = self.get('configpath', '/etc/aura/engine.ini')
if not os.path.isfile(self.ini_path):
self.logger.critical(self.ini_path + " not found :(")
sys.exit(1)
# INI einlesen
f = open(self.ini_path, 'r')
ini_str = f.read()
f.close()
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)
self.set("install_dir", os.path.realpath(__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 simplejson
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.swap_playlist_entries:
self.swap_playlist_entries(self.args.swap_playlist_entries[0], self.args.swap_playlist_entries[1])
elif self.args.delete_playlist_entry:
self.delete_playlist_entry(self.args.delete_playlist_entry[0])
elif self.args.insert_playlist_entry:
self.insert_playlist_entry(self.args.insert_playlist_entry[0], self.args.insert_playlist_entry[1]) #, self.args.insert_playlist_entry[2])
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()
if with_server:
self.redisserver = ServerRedisAdapter()
# ------------------------------------------------------------------------------------------ #
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 = simplejson.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 = simplejson.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 = simplejson.loads(json_reply)
self.print_connection_status(connection_status)
# ------------------------------------------------------------------------------------------ #
def print_programme(self, programme):
for entry in programme:
self.stringreply += "idx: " + str(entry["programme_index"]) + \
" --- schedule id #" + str(entry["schedule_id"]) + "." + str(entry["entry_num"]) + \
" - show: " + entry["schedule"]["show_name"] + \
" - starts @ " + entry["entry_start"] + \
" - plays " + str(entry["source"]) + "\n"
# ------------------------------------------------------------------------------------------ #
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 swap_playlist_entries(self, from_index, to_index):
json_reply = self.send_and_wait_redis("aura", "swap_playlist_entries " + str(from_index) + " " + str(to_index), RedisChannel.MPE_REPLY)
actprogramme = simplejson.loads(json_reply)
self.print_programme(actprogramme)
# ------------------------------------------------------------------------------------------ #
def delete_playlist_entry(self, index):
json_reply = self.send_and_wait_redis("aura", "delete_playlist_entry " + str(index), RedisChannel.DPE_REPLY)
actprogramme = simplejson.loads(json_reply)
self.print_programme(actprogramme)
# ------------------------------------------------------------------------------------------ #
def insert_playlist_entry(self, fromtime, source):
json_reply = self.send_and_wait_redis("aura", "insert_playlist_entry " + fromtime + " " + source, RedisChannel.IPE_REPLY)
actprogramme = simplejson.loads(json_reply)
self.print_programme(actprogramme)
# ------------------------------------------------------------------------------------------ #
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)
# ------------------------------------------------------------------------------------------ #
def set_next_file(self, type, file):
from modules.communication.redis.messenger import RedisMessenger
redis = RedisMessenger()
redis.set_next_file_for(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 simplejson
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"] = 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 simplejson.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
class LiquidSoapInitThread(threading.Thread):
logger = None
socket = None
active_entry = None
liquidsoapcommunicator = None
# ------------------------------------------------------------------------------------------ #
def __init__(self):
threading.Thread.__init__(self)
self.logger = logging.getLogger("AuraEngine")
# ------------------------------------------------------------------------------------------ #
def run(self):
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")
# enable lqs transaction
self.liquidsoapcommunicator.enable_transaction()
# wait another second. lqs really starts slow.. be prepared you liquidsoap you!
time.sleep(1)
# set some parameters
self.set_start_parameters()
# set active
self.set_active_show()
# disable lqs transaction again
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))
def set_active_show(self):
if self.active_entry is not None:
self.logger.info("LiquidSoapInitThread sets activechannel: " + str(self.active_entry))
channel = self.active_entry.type
# have to seek?
if channel == ScheduleEntryType.FILESYSTEM:
# calc how many seconds were missed
now_unix = time.mktime(datetime.datetime.now().timetuple())
seconds_to_seek = now_unix - self.active_entry.entry_start_unix
# and seek these seconds forward
if seconds_to_seek > 0:
self.liquidsoapcommunicator.playlist_seek(seconds_to_seek)
# finally make something hearable :-)
if channel != "" and channel is not None:
# 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):
# 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)
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_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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
__author__ = 'gg'
#
# 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/>.
#
set("log.file.path", "./<script>.log")
set("server.telnet", true)
set("server.telnet.bind_addr", "0.0.0.0")
set("server.telnet.port", 1234)
# ALSA / pulse settings
# durch ausprobieren herausgefunden für asus xonar dgx 5.1
# chip: CMI8788
# driver: snd_oxygen
set("frame.duration", 0.30)
set("alsa.alsa_buffer", 8192) # 7168) # 6144) # 8192) # 10240) #15876
set("alsa.buffer_length", 25)
set("alsa.periods", 0) # assertion error when setting periods other than 0 => alsa default
input_linein = input.alsa(id="linein", bufferize = false)
input_fs = single(id="fs", "/var/audio/fallback/output.flac")
input_http = input.http(id="http", "http://stream.fro.at/fro-128.ogg")
mixer = mix(id="mixer", [input_fs, input_http, input_linein])
output.alsa(id="lineout", bufferize = false, mixer)
This diff is collapsed.
This diff is collapsed.
__author__ = 'michel'
import os
import sys
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.