Commit b1f590f2 authored by Gottfried Gaisbauer's avatar Gottfried Gaisbauer
Browse files

enabled fading between sources. has to be tested a bit more..

parent a79e03fd
......@@ -25,7 +25,7 @@ mail_pass=""
admin_mail="gogo@servus.at gottfried@servus.at"
# with from mailadress should be used
from_mail="monitor@aura.py"
# The beginning of the subject. With that you can easily apply filter rules
# The beginning of the subject. With that you can easily apply filter rules with any mail client
mailsubject_prefix="[AURA]"
[dataurls]
......@@ -36,13 +36,20 @@ importerurl="http://localhost:8008/api/v1/groups/_public/playlists/"
# how often should the calendar be fetched in seconds (This determines the time of the last change before a specific show)
fetching_frequency=3600
# sets the time how long we have to fade in and out, when we select another mixer input
# values are in seconds
# this is solved on engine level because it is kind of tough with liquidsoap
[fading]
fade_in_time="0.5"
fade_out_time="2.5"
#######################
# LiquidSoap Settings #
#######################
# all these settings from here to the bottom require a restart of the liquidsoap server
[liquidsoap]
[user]
# the user and group under which this software will run
daemongroup="gg"
daemonuser="gg"
......@@ -60,8 +67,8 @@ loglevel="info"
# min_noise => minimum duration of noise on source to switch back over (float)
# threshold => power in dB under which the stream is considered silent (float)
fallback_max_blank="5."
fallback_min_noise="0."
fallback_threshold="-40."
fallback_min_noise="1."
fallback_threshold="-50."
[soundcard]
# choose your weapon
......
......@@ -300,26 +300,13 @@ class Padavan:
redis = RedisMessenger()
next_file = redis.get_next_file_for(type)
# "annotate:file_id='3541',length='400.0',title='Titel',artist='Artist',album='Album',canal='reggae':" +
#print(next_file)
if next_file == "":
next_file = "/var/audio/blank.flac"
# if type == "timeslot":
# next_file = ""
# elif type == "show":
# next_file = ""
# else:
# next_file = "/var/audio/fallback/music.flac"
if type == "timeslot":
next_file = ""
if type == "show":
next_file = ""
#print("stringreply: "+next_file)
self.stringreply = next_file
#self.send_redis("aura", "set_next_file " + type)
self.send_redis("aura", "set_next_file " + type)
# ------------------------------------------------------------------------------------------ #
def set_next_file(self, type, file):
......
......@@ -43,6 +43,7 @@ class LiquidSoapClient:
logger = None
debug = False
socket_path = ""
enable_logging = True
def __init__(self, config, socket_filename):
"""
......@@ -195,17 +196,20 @@ class LiquidSoapClient:
message = str(namespace) + str(".") + str(command) + str(param) + str("\n")
try:
self.logger.info("LiquidSoapClient sending to LiquidSoap Server: " + message[0:len(message)-1])
if self.enable_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())
self.logger.debug("LiquidSoapClient waiting for reply from LiquidSoap Server")
if self.enable_logging:
self.logger.debug("LiquidSoapClient waiting for reply from LiquidSoap Server")
# wait for reply
self.read()
self.logger.info("LiquidSoapClient got reply: " + self.message)
if self.enable_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()
......
......@@ -42,7 +42,7 @@ from libraries.exceptions.exception_logger import ExceptionLogger
"""
class LiquidSoapCommunicator(ExceptionLogger):
lqcr = None
# lqcr = None
client = None
logger = None
transaction = 0
......@@ -52,9 +52,10 @@ class LiquidSoapCommunicator(ExceptionLogger):
auramailer = None
is_liquidsoap_running = False
connection_attempts = 0
active_channel = None
# ------------------------------------------------------------------------------------------ #
def __init__(self, config, logger=True):
def __init__(self, config):
"""
Constructor
"""
......@@ -143,11 +144,15 @@ class LiquidSoapCommunicator(ExceptionLogger):
return inputstate
# ------------------------------------------------------------------------------------------ #
def get_mixer_volume(self, channel):
return False
# ------------------------------------------------------------------------------------------ #
def get_recorder_status(self):
self.enable_transaction(self.lqcr)
recorder_state = self.__send_lqc_command__(self.lqcr, "record", "status")
self.disable_transaction(self.lqcr)
self.enable_transaction(self.client)
recorder_state = self.__send_lqc_command__(self.client, "record", "status")
self.disable_transaction(self.client)
return recorder_state
......@@ -217,6 +222,31 @@ class LiquidSoapCommunicator(ExceptionLogger):
if returnvalue == "off":
self.__send_lqc_command__(self.client, "recorder", str(num), "start")
# ------------------------------------------------------------------------------------------ #
def fade_in(self, new_entry, seconds):
target_volume = new_entry.volume
step = seconds / target_volume
self.logger.info("Starting to fading " + new_entry.type.value + " in. step is " + str(step) + ". target volume is " + str(target_volume))
for i in range(target_volume):
self.channel_volume(new_entry.type.value, i + 1)
time.sleep(step)
return True
# ------------------------------------------------------------------------------------------ #
def fade_out(self, old_entry, seconds):
step = abs(seconds) / old_entry.volume
self.logger.info("Starting to fading " + old_entry.type.value + " out. step is " + str(step))
for i in range(old_entry.volume):
self.channel_volume(old_entry.type.value, old_entry.volume-i-1)
time.sleep(step)
return True
# ------------------------------------------------------------------------------------------ #
def activate(self, new_entry):
# grab the actual active entry
......@@ -252,11 +282,18 @@ class LiquidSoapCommunicator(ExceptionLogger):
# push to fs or stream
if entry.type == ScheduleEntryType.FILESYSTEM:
self.playlist_push(entry.source)
if entry.type == ScheduleEntryType.STREAM:
self.active_channel = entry.type
elif entry.type == ScheduleEntryType.STREAM:
self.set_http_url(entry.source)
self.http_start_stop(True)
self.active_channel = entry.type
# else: # live
# nothing to do when we are live => just leave it as is
self.active_channel = entry.type
# set active channel to wanted volume
if not activate_different_channel:
self.channel_volume(entry.type.value, entry.volume)
......@@ -265,7 +302,7 @@ class LiquidSoapCommunicator(ExceptionLogger):
def activate_different_channel(self, entry, active_type):
self.logger.info(TerminalColors.PINK.value + "LiquidSoapCommunicator is activating " + entry.type.value + " & deactivating " + active_type.value + "!" + TerminalColors.ENDC.value)
# reuse of this function, because activate_same_channel and activate_different_channel are doing pretty the same except setting of the volume
# reuse of this function, because activate_same_channel and activate_different_channel are doing pretty the same except setting of the volume to zero
self.activate_same_channel(entry, True)
# set other channels to zero volume
......@@ -352,6 +389,11 @@ class LiquidSoapCommunicator(ExceptionLogger):
try:
channels = self.get_all_channels()
index = channels.index(channel)
except ValueError as e:
self.logger.error("Cannot set volume of channel " + channel + ". Reason: " + str(e))
return
try:
if len(channel) < 1:
self.logger.warning("Cannot set volume of channel " + channel + "! There are no channels.")
......@@ -364,7 +406,7 @@ class LiquidSoapCommunicator(ExceptionLogger):
self.logger.warning("Setting volume of channel " + channel + " gone wrong! Liquidsoap message: " + message)
return message
except (AttributeError, ValueError) as e: #(LQConnectionError, AttributeError):
except AttributeError as e: #(LQConnectionError, AttributeError):
self.disable_transaction(force=True)
self.logger.error("Ran into exception when setting volume of channel " + channel + ". Reason: " + str(e))
......
......@@ -54,18 +54,8 @@ mixer = mix(id="mixer", list.append([input_fs, input_http], !inputs))
# output source with fallbacks
stripped_stream = strip_blank(track_sensitive=false, max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold, mixer)
#stripped_stream = strip_blank(mixer)
#stripped_stream = mixer
ignore(fallback_max_blank)
ignore(fallback_min_noise)
ignore(fallback_threshold)
ignore(timeslot_fallback)
ignore(show_fallback)
ignore(station_fallback)
# enable fallback
# output_source = mixer
output_source = fallback(id="fallback", track_sensitive=false, [stripped_stream, timeslot_fallback, show_fallback, mksafe(station_fallback)])
##################
......
......@@ -116,7 +116,8 @@ def fallback_create(~skip=true, name, requestor)
# Tell the system when a new track
# is played
source = on_metadata(fun (meta) ->
system('#{list.assoc("install_dir", ini)}/guru.py --fallback-metadata-change name'),
log("ON_METADATA_DISABLED"),
# system('#{list.assoc("install_dir", ini)}/guru.py --fallback-metadata-change name'),
source)
log("channel created")
......@@ -126,7 +127,6 @@ def fallback_create(~skip=true, name, requestor)
end
def create_dynamic_playlist(next)
log("dynamic playlist with song #{next} is created")
request.create(list.hd(next))
end
......@@ -137,21 +137,18 @@ def create_playlist() =
end
def create_station_fallback() =
log("requesting next song for STATION fallback")
result = get_process_lines('#{list.assoc("install_dir", ini)}/guru.py --get-next-file-for station --quiet')
log("next song for STATION fallback is: #{result}")
create_dynamic_playlist(result)
end
def create_show_fallback() =
log("requesting next song for SHOW fallback")
result = get_process_lines('#{list.assoc("install_dir", ini)}/guru.py --get-next-file-for show --quiet')
log("next song for SHOW fallback is: #{result}")
create_dynamic_playlist(result)
end
def create_timeslot_fallback() =
log("requesting next song for TIMESLOT fallback")
result = get_process_lines('#{list.assoc("install_dir", ini)}/guru.py --get-next-file-for timeslot --quiet')
log("next song for TIMESLOT fallback is: #{result}")
create_dynamic_playlist(result)
......
......@@ -23,4 +23,4 @@
#
# a hard one
input_fs = request.queue(id="fs")
\ No newline at end of file
input_fs = request.equeue(id="fs")
\ No newline at end of file
......@@ -61,7 +61,7 @@ server.register(namespace="auraengine",
)
# return a state of the inputs/outputs of the soundserver as JSON
server.register(namespace="auraengine",
server.register(namespace = "auraengine",
description="returns enabled lineouts/lineins, connected outgoing streams, and recorder. Also returns fallbacksettings.",
usage="state",
"state",
......@@ -123,3 +123,25 @@ server.register(namespace = source.id(input_fs),
"Seeked #{ret} seconds."
end
)
def fadeTo(source_number) =
if source_number == "" then
print(source_number)
"Usage: mixer.fadeto <source nb> #{source_number}"
else
r = server.execute("mixer.select #{source_number} true")
print(r)
"Donee!"
end
end
# enable fadeTo for the mixer
server.register(namespace = "mixer",
description = "is fading from one mixer input to another",
usage = "fadeto <source number>",
"fadeto",
fadeTo
)
ignore(fade_in_time)
ignore(fade_out_time)
\ No newline at end of file
......@@ -55,6 +55,10 @@ fallback_max_blank = float_of_string(list.assoc("fallback_max_blank", ini))
fallback_min_noise = float_of_string(list.assoc("fallback_min_noise", ini))
fallback_threshold = float_of_string(list.assoc("fallback_threshold", ini))
# FADING SETTINGS
fade_in_time = list.assoc("fade_in_time", ini) #int_of_string(list.assoc("fade_in_time", ini))
fade_out_time = list.assoc("fade_out_time", ini) #int_of_string(list.assoc("fade_out_time", ini))
# RECORDER SETTINGS
#rec_0_filetype = list.assoc("rec_0_filetype", ini)
#rec_1_filetype = list.assoc("rec_1_filetype", ini)
......
......@@ -317,11 +317,11 @@ class AuraCalendarService(threading.Thread):
if rand_id % 4 == 0: # playlist with two files
json_response = '{"playlist_id":' + str(rand_id) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"file:///var/audio/fallback/NightmaresOnWax/DJ-Kicks/02 - Only Child - Breakneck.flac"}]}'
elif rand_id % 3 == 0: # playlist with jingle and then http stream
elif rand_id % 3 == 0: # playlist with jingle and then linein
json_response = '{"playlist_id":' + str(rand_id) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"linein://1"}]}'
elif rand_id % 2 == 0: # playlist with jingle and then linein
elif rand_id % 2 == 0: # playlist with jingle and then http stream
json_response = '{"playlist_id":' + str(rand_id) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"http://chill.out.airtime.pro:8000/chill_a"}]}'
else: # pool playlist
else: # pool playlist
json_response = '{"playlist_id":' + str(rand_id) + ',"entries":[{"source":"pool:///hiphop"}]}'
elif schedule[id_name] % 4 == 0: # playlist with two files
......
......@@ -42,7 +42,6 @@ import sqlalchemy
import logging
import threading
# Die eigenen Bibliotheken
from modules.communication.redis.messenger import RedisMessenger
from modules.scheduling.calendar import AuraCalendarService
from libraries.database.broadcasts import Schedule, ScheduleEntry, AuraDatabaseModel
......@@ -135,11 +134,11 @@ class AuraScheduler(ExceptionLogger, threading.Thread):
# ------------------------------------------------------------------------------------------ #
def run(self):
# set seconds to wait
seconds_to_wait = int(self.config.get("fetching_frequency"))
#while True:
while not self.exit_event.is_set():
# set seconds to wait
seconds_to_wait = int(self.config.get("fetching_frequency"))
# calc next time
next_time = datetime.datetime.now() + datetime.timedelta(seconds=seconds_to_wait)
......@@ -192,38 +191,77 @@ class AuraScheduler(ExceptionLogger, threading.Thread):
# switch to check if its the first stream in loaded programme
first_stream_in_programme = False
# fading times
fade_in_time = float(self.config.get("fade_in_time"))
fade_out_time = float(self.config.get("fade_out_time"))
# old entry for fading out
old_entry = None
for entry in self.programme:
# since we get also programmes from act hour, filter these out
if entry.entry_start_unix > now_unix:
# when do we have to start?
diff = entry.entry_start_unix - now_unix
diff = diff/100000 # testing purpose
diff = diff/100 # testing purpose
self.enable_fading(fade_in_time, fade_out_time, old_entry, entry, diff)
# create the activation threads and run them after <diff> seconds
if entry.source.startswith("linein"):
self.add_or_update_timer(entry, diff, self.liquidsoapcommunicator.activate)
self.add_or_update_timer(diff, self.liquidsoapcommunicator.activate, [entry])
elif entry.type == ScheduleEntryType.STREAM:
if first_stream_in_programme:
self.liquidsoapcommunicator.next_stream_source(entry.source)
first_stream_in_programme = False
self.add_or_update_timer(entry, diff, self.liquidsoapcommunicator.activate)
self.add_or_update_timer(diff, self.liquidsoapcommunicator.activate, [entry])
elif entry.type == ScheduleEntryType.FILESYSTEM:
self.add_or_update_timer(entry, diff, self.liquidsoapcommunicator.activate)
self.add_or_update_timer(diff, self.liquidsoapcommunicator.activate, [entry])
else:
self.logger.warning("Scheduler cannot understand source '" + entry.source + "' from " + str(entry))
self.logger.warning(" Not setting any activation Thread!")
self.logger.info(str(entry))
# store the old entry for fading out
old_entry = entry
self.logger.warning(self.print_message_queue())
# ------------------------------------------------------------------------------------------ #
def enable_fading(self, fade_in_time, fade_out_time, old_entry, new_entry, diff):
# enable fading when entry types are different
if old_entry is not None:
if old_entry.type != new_entry.type:
# enable fadeout if enabled
if fade_out_time != 0 and old_entry is not None:
params = [old_entry, -fade_out_time]
self.add_or_update_timer(diff, self.liquidsoapcommunicator.fade_out, params)
# same for fadein
if fade_in_time != 0:
params = [new_entry, fade_in_time]
self.add_or_update_timer(diff, self.liquidsoapcommunicator.fade_in, params)
# ------------------------------------------------------------------------------------------ #
def add_or_update_timer(self, entry, diff, func):
def add_or_update_timer(self, diff, func, parameters):
length = len(parameters)
if length == 2:
entry = parameters[0]
fadingtime = parameters[1]
elif length == 1:
entry = parameters[0]
fadingtime = 0
# check if something is planned at given time
planned_timer = self.is_something_planned_at_time(entry.entry_start)
if fadingtime < 0:
planned_timer = self.is_something_planned_at_time(entry.entry_start + datetime.timedelta(seconds=fadingtime))
else:
planned_timer = self.is_something_planned_at_time(entry.entry_start)
# if something is planned on entry.entry_start
if planned_timer:
......@@ -235,13 +273,13 @@ class AuraScheduler(ExceptionLogger, threading.Thread):
self.stop_timer(planned_timer)
# and create a new one
self.create_timer(entry, diff, func)
self.create_timer(diff, func, parameters)
# if the playlist id's do not differ => do nothing, they are the same
# if the playlist id's do not differ => reuse the old timer and do nothing, they are the same
# if nothing is planned at given time, create a new timer
else:
self.create_timer(entry, diff, func)
self.create_timer(diff, func, parameters)
# ------------------------------------------------------------------------------------------ #
def stop_timer(self, timer):
......@@ -251,8 +289,8 @@ class AuraScheduler(ExceptionLogger, threading.Thread):
self.message_timer.remove(timer)
# ------------------------------------------------------------------------------------------ #
def create_timer(self, entry, diff, func):
t = CallFunctionTimer(diff, func, [entry])
def create_timer(self, diff, func, parameters):
t = CallFunctionTimer(diff, func, parameters)
self.message_timer.append(t)
t.start()
......@@ -263,14 +301,6 @@ class AuraScheduler(ExceptionLogger, threading.Thread):
return t
return False
# ------------------------------------------------------------------------------------------ #
def find_entry_in_timers(self, entry):
# check if a playlist id is already planned
for t in self.message_timer:
if t.entry.playlist_id == entry.playlist_id and t.entry.entry_start == entry.entry_start:
return t
return False
# ------------------------------------------------------------------------------------------ #
def get_act_programme_as_string(self):
programme_as_string = ""
......@@ -290,7 +320,7 @@ class AuraScheduler(ExceptionLogger, threading.Thread):
def print_message_queue(self):
message_queue = ""
for t in self.message_timer:
message_queue += t.get_info()+"\n"
message_queue += str(t)+"\n"
return message_queue
......@@ -418,8 +448,8 @@ class AuraScheduler(ExceptionLogger, threading.Thread):
# ------------------------------------------------------------------------------------------ #
class CallFunctionTimer(threading.Timer):
logger = None
param = None
entry = None
debug = False
diff = None
def __init__(self, diff, func, param):
......@@ -427,9 +457,20 @@ class CallFunctionTimer(threading.Timer):
self.diff = diff
self.func = func
self.param = param
self.entry = param[0]
self.logger = logging.getLogger("AuraEngine")
self.logger.debug(str(self))
msg = "CallFunctionTimer starting @ " + str(self.entry.entry_start) + " source '" + str(self.entry.source) + "' In seconds: " + str(self.diff)
self.logger.debug(msg)
# ------------------------------------------------------------------------------------------ #
def __str__(self):
if len(self.param) >= 2:
# fading in
if self.param[1] > 0:
return "CallFunctionTimer starting @ " + str(self.entry.entry_start) + " fading in source '" + str(self.entry.source) + "' in seconds: " + str(self.diff)
# fading out
else:
return "CallFunctionTimer starting @ " + str(self.entry.entry_start + datetime.timedelta(seconds=self.param[1])) + " fading out source '" + str(self.entry.source) + "' in seconds: " + str(self.diff+self.param[1])
else:
return "CallFunctionTimer starting @ " + str(self.entry.entry_start) + " switching to source '" + str(self.entry.source) + "' in seconds: " + str(self.diff)
......@@ -28,6 +28,7 @@ from libraries.database.broadcasts import *
import simplejson
import logging
import sqlalchemy
import decimal
from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator
from modules.monitoring.diskspace_watcher import DiskSpaceWatcher
......@@ -46,11 +47,6 @@ def alchemyencoder(obj):
else:
return str(obj)
def select_with_relationship():
se = TrackServiceScheduleEntry.select_all()
for e in se:
print(e._asdict())
# programme_as_string = simplejson.dumps([se[0]._asdict()], default=alchemyencoder)
# print(programme_as_string)
......@@ -65,13 +61,28 @@ def select_act_programme():
for p in programme:
print(p)
def fadeout(lsc):
entry = ScheduleEntry.select_act_programme()
lsc.fade_out(entry, 2)
def fadein(lsc):
entry = ScheduleEntry.select_act_programme()
lsc.fade_in(entry, 1)
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
#select_with_relationship()
#start_diskspace_watcher()
select_act_programme()
#select_act_programme()
config = AuraConfig()
config.read_config()
lsc = LiquidSoapCommunicator(config.config)
fadeout(lsc)
fadein(lsc)
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment