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 2256 additions and 0 deletions
#!/usr/bin/liquidsoap
%include "readini.liq"
ini = read_ini("/etc/aura/aura.ini")
%include "settings.liq"
%include "fallback.liq"
%include "serverfunctions.liq"
# create sources
#input_fs = single(id="fs", "/var/audio/fallback/output.flac")
input_fs = request.queue(id="fs")
input_http = input.http(id="http", "http://stream.fro.at/fro-128.ogg")
linein_alsa = input.alsa(id="live", device="hw:0", bufferize = false)
# create fallbacks
timeslot_fallback = fallback_create(skip=true, "timeslot_fallback", create_timeslot_fallback)
station_fallback = fallback_create(skip=true, "station_fallback", create_station_fallback)
show_fallback = fallback_create(skip=true, "show_fallback", create_show_fallback)
# fill the mixer
mixer = mix(id="mixer", [input_fs, input_http, linein_alsa])
# output source with station_fallback
output_source = fallback(track_sensitive=false, [mixer, timeslot_fallback, show_fallback, station_fallback])
clock.unify([output_source, mixer, show_fallback, timeslot_fallback, station_fallback])
# create output
output.alsa(id="lineout", device="hw:0", bufferize = false, output_source)
system('#{list.assoc("install_dir", ini)}/guru.py --init-player')
Source diff could not be displayed: it is too large. Options to address this: view the blob.
input_fs = single(id="fs", "/var/audio/fallback/output.flac")
input_http = input.http(id="http", "http://stream.fro.at/fro128.mp3")
linein_alsa_1 = input.alsa(id="linein", device = input_device_0, bufferize = false)
mixer = mix(id="mixer", [input_fs, input_http, linein_alsa_1])
output.alsa(id="lineout", device = output_device_0, bufferize = false, mixer)
%include "readini.liq"
inifile = '/etc/aura/aura.ini'
ini = read_ini(inifile)
set("log.file.path", "./<script>.log")
set("server.telnet", true)
set("server.telnet.bind_addr", "0.0.0.0")
set("server.telnet.port", 1234)
use_alsa = list.assoc("use_alsa", ini) == "y"
frame_duration = float_of_string(list.assoc("frame_duration", ini))
frame_size = int_of_string(list.assoc("frame_size", ini))
alsa_buffer = int_of_string(list.assoc("alsa_buffer", ini))
alsa_buffer_length = int_of_string(list.assoc("alsa_buffer_length", ini))
alsa_periods = int_of_string(list.assoc("alsa_periods", ini))
if use_alsa then
if frame_duration > 0.0 then
print("setting frame.duration to #{frame_duration}")
set("frame.duration", frame_duration)
end
if frame_size > 0 then
print("setting frame.size to #{frame_size}")
set("frame.size", frame_size)
end
if alsa_buffer > 0 then
print("setting alsa.buffer to #{alsa_buffer}")
set("alsa.alsa_buffer", alsa_buffer)
end
if alsa_buffer > 0 then
print("setting alsa.buffer_length to #{alsa_buffer_length}")
set("alsa.buffer_length", alsa_buffer_length)
end
if alsa_periods > 0 then
print("setting alsa.periods to #{alsa_periods}")
set("alsa.periods", alsa_periods) # assertion error when setting periods other than 0 => alsa default
end
end
# First, we create a list referencing the dynamic sources:
dyn_sources = ref []
# This is our icecast output.
# It is a partial application: the source needs to be given!
out = output.icecast(%mp3,
host="test",
password="hackme",
fallible=true)
# Now we write a function to create
# a playlist source and output it.
def create_playlist(uri) =
# The playlist source
s = playlist(uri)
# The output
output = out(s)
# We register both source and output
# in the list of sources
dyn_sources :=
list.append( [(uri,s),(uri,output)],
!dyn_sources )
"Done!"
end
# And a function to destroy a dynamic source
def destroy_playlist(uri) =
# We need to find the source in the list,
# remove it and destroy it. Currently, the language
# lacks some nice operators for that so we do it
# the functional way
# This function is executed on every item in the list
# of dynamic sources
def parse_list(ret, current_element) =
# ret is of the form: (matching_sources, remaining_sources)
# We extract those two:
matching_sources = fst(ret)
remaining_sources = snd(ret)
# current_element is of the form: ("uri", source) so
# we check the first element
current_uri = fst(current_element)
if current_uri == uri then
# In this case, we add the source to the list of
# matched sources
(list.append( [snd(current_element)],
matching_sources),
remaining_sources)
else
# In this case, we put the element in the list of remaining
# sources
(matching_sources,
list.append([current_element],
remaining_sources))
end
end
# Now we execute the function:
result = list.fold(parse_list, ([], []), !dyn_sources)
matching_sources = fst(result)
remaining_sources = snd(result)
# We store the remaining sources in dyn_sources
dyn_sources := remaining_sources
# If no source matched, we return an error
if list.length(matching_sources) == 0 then
"Error: no matching sources!"
else
# We stop all sources
list.iter(source.shutdown, matching_sources)
# And return
"Done!"
end
end
def backup_pool () =
result = get_process_lines("python ./helpers/next_song_from_pool.py")
log("next song: #{result}")
request.create(list.hd(result))
end
backup_pool = request.dynamic(backup_pool)
# Now we register the telnet commands:
server.register(namespace="dynamic_playlist",
description="Start a new dynamic playlist.",
usage="start <uri>",
"start",
create_playlist)
server.register(namespace="dynamic_playlist",
description="Stop a dynamic playlist.",
usage="stop <uri>",
"stop",
destroy_playlist)
server.register(namespace="dyn",
description="load next song from pool",
usage="next <folder>",
"next",
output.dummy(blank())
set("frame.size", 1881)
#set("decoding.buffer_length", 20.)
#set("frame.channels", 2)
#set("frame.samplerate", 44100)
set("server.telnet", true)
set("server.telnet.port", 1234)
set("alsa.alsa_buffer", 2048)
#set("alsa.periods", 8)
#set("alsa.buffer_length", 4096)
# works, but every now and then buffer overruns in alsa.in on node
# more or less the same with or w/o clock_safe set. maybe a bit better with clock_safe=false
input_alsa = input.alsa(bufferize=false, clock_safe=false)
output.alsa(bufferize=false, clock_safe=false, input_alsa)
#output.alsa(bufferize = false, input.alsa(bufferize = false))
# works
#url = "http://stream.fro.at/fro64.mp3"
#i = strip_blank(input.http(id="stream", url))
#input_http = mksafe(i)
#output.alsa(bufferize=false, input_http)
# works
#input_fs = fallback(track_sensitive=false,
# [ single("/var/audio/fallback/music.flac"), playlist.safe("/var/audio/fallback/NightmaresOnWax/Smokers Delight") ] )
#ignore(input_fs)
#output.alsa(bufferize=false, input_fs)
#output.alsa(device="hw:1,0", bufferize=false, input_alsa)
#output.alsa(device="hw:1,0", bufferize=false, input_http)
#output.pulseaudio(input_http)
#output.alsa(device="hw:0,0", bufferize=false, input_alsa)
#output.pulseaudio(input_fs)
#mixer = mix(id = "mixer", [input_alsa])
#final_stream = fallback(id="station_fallback", [mixer, playlist.safe("/var/audio/fallback/NightmaresOnWax/Smokers Delight")])
#output.alsa(device="hw:1,0", bufferize=false, final_stream)
#ignore(mixer)
#ignore(input_fs)
#ignore(input_http)
#ignore(input_alsa)
#set("frame.size", 3763)
#set("decoding.buffer_length", 20.)
#set("frame.channels", 2)
#set("frame.samplerate", 44100)
set("server.telnet", true)
set("server.telnet.port", 1234)
input_alsa = input.alsa(bufferize=false)
#output.alsa(device="hw:0,0", bufferize=false, input_alsa)
#output.alsa(bufferize = false, input.alsa(bufferize = false))
url = "http://stream.fro.at/fro64.mp3"
#url = "http://mp3stream1.apasf.apa.at:8000/listen.pls"
input_http = strip_blank(id="http_strip", input.http(id="stream", url))
#input_http = mksafe(i)
ignore(input_http)
input_fs = strip_blank(id="fs_strip", single("/var/audio/fallback/music.flac"))
#fallback(track_sensitive=false,
# [ single("/var/audio/fallback/music.flac"), playlist.safe("/var/audio/fallback/NightmaresOnWax/Smokers Delight") ] )
ignore(input_fs)
#output.alsa(device="hw:0,0", bufferize=false, input_alsa)
#output.alsa(device="hw:0,0", bufferize=false, input_http)
#output.pulseaudio(input_http)
#output.alsa(device="hw:0,0", bufferize=false, input_fs)
#output.pulseaudio(input_fs)
mixer = mix(id = "mixer", [input_fs, input_http, input_alsa])
#final_stream = fallback(id="station_fallback", [mixer, playlist.safe("/var/audio/fallback/NightmaresOnWax/Smokers Delight")])
output.alsa(bufferize=false, mixer)
ignore(mixer)
#ignore(input_fs)
#ignore(input_http)
#ignore(input_alsa)
%include "readini.liq"
inifile = '/etc/aura/aura.ini'
ini = read_ini(inifile)
def get_task_number (taskname) =
tasks = [
('seek_track', '10'),
('load', '11'),
('move', '12'),
('insert', '13'),
('remove', '14'),
('push', '15'),
('data', '16'),
('skip', '17'),
('pause', '19'),
('play', '18'),
('flush', '16'),
]
if list.mem_assoc(taskname, tasks) then
tasks[taskname]
else
log("Warning: task #{taskname} does not exist")
"00"
end
end
def get_namespace_number(namespace) =
channelnames = ref string.split(separator=',', list.assoc("channels", ini))
namespaces = [
('player', '10'),
('common', '11'),
('playlist', '11'),
('request', '12'),
('mixer', '13'),
]
def addNamespace (ret, el)
number = string_of((list.length(ret) + 10))
list.append(ret,[("ch#{el}", "#{number}")])
end
namespaces = list.fold(addNamespace, namespaces, !channelnames)
if list.mem_assoc(namespace, namespaces) then
namespaces[namespace]
else
log("Warning: namespace #{namespace} does not exist")
log(json_of(namespaces))
"00"
end
end
def create_protocol () =
#pwd = list.hd(get_process_lines('pwd'))
#def getdirname() =
# if dirname(argv(0)) == "." then
# ""
# else
# '/'^dirname(argv(0))
# end
#end
#cur = getdirname()
add_protocol(temporary=true, "silence", fun(arg,delay)-> begin system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/silence.sh 0.01 > /var/tmp/quiet.wav; qwavheaderdump -q -F /var/tmp/quiet.wav > /dev/null' ) ['/var/tmp/quiet.wav'] end)
end
#create_protocol()
def message (~value="", namespace, success, message, code, error, ~level="error", ~type='internal') =
level = if success then 'success' else level end
namespace = if namespace == 'common' then
'playlist'
else
namespace
end
namespace_number = get_namespace_number(namespace)
code = "10#{namespace_number}#{code}"
result = [('section', '#{namespace}'),
('success', '#{success}'),
('message', '#{message}'),
('value', '#{value}'),
('code', '#{code}#{error}'),
('level', '#{level}'),
('type', '#{type}'),
]
json_of(compact=true,result)
end
##Sorgt für unterbrechungsfreien Wechsel zwischen den Tracks
def crossfade(a,b)
add(normalize=false,
[ fade.initial(duration=2., b),
sequence(merge=true,
[ blank(duration=0.), fallback([]) ]),
fade.final(duration=2.,a) ])
end
def foldplaylist(l, el)=
# fold metadata to list
def tolist (ret, el) =
tmp = string.split(separator='=',el)
if list.length(tmp) > 1 then # found the separator in current el
# get the key -> value pair
key = list.nth(tmp,0)
value = list.nth(tmp,1)
# append to list
list.append(ret,[(key,value)])
else
# nothing to do
ret
end
end
# extract metadata part from annotation
extracted = string.extract(pattern="annotate:[,](.*):/",string.replace(pattern='"',(fun (s) -> ""),el))
# split extracted string by comma separator and create a list
list.append(l,[list.fold(tolist, [], string.split(separator=',',extracted["1"]))])
end
# The server seeking function
def seek_track(source, t) =
t = float_of_string(default=0.,t)
ret = source.seek(source,t)
log("Seeked #{ret} seconds.")
success = ret > 0.
error = if success then '00' else '01' end
errors = [('00', 'Seeked #{ret} seconds on #{source.id(source)}'), ('01', 'Seek failed')]
message(value=string_of(ret), source.id(source), success, errors[error], get_task_number ('seek_track'), error, level="warning", type='unknown')
#"Seeked #{ret} seconds."
end
def reload_recorded(~skip, ~uri) =
print('param skip: #{skip} +++ param uri: #{uri}')
print("reload_recorded not implemented")
"nana"
end
#!/usr/bin/liquidsoap
#set("log.stdout", true)
inst = if argv(1) != "" then string_of(argv(1)) else 'playd' end
instance = ref inst
%include "readini.liq"
inifile = '/etc/aura/aura.ini'
ini = read_ini(inifile)
# send alive
ignore(system('#{list.assoc("install_dir", ini)}/modules/liquidsoap/helpers/message.py -c #{!instance} -t sayAlive'))
# send alive frequently
exec_at(freq=20., pred={true}, {ignore(system('#{list.assoc("install_dir", ini)}/modules/liquidsoap/helpers/message.py -c #{!instance} -t sayAlive'))})
# set current playlist
ignore(system('#{list.assoc("install_dir", ini)}/modules/liquidsoap/helpers/message.py -c #{!instance} --task=setState -n playlistcurrent -v ""'))
# load data from ini file
#daemongroup = list.assoc("daemongroup", ini)
#daemonuser = list.assoc("daemonuser", ini)
socketdir = list.assoc("socketdir", ini)
# ALSA settings
use_alsa = list.assoc("use_alsa", ini)
alsa_buffer = int_of_string(list.assoc("alsa_buffer", ini))
alsa_periods = int_of_string(list.assoc("alsa_periods", ini))
# set player i/o devices
player_input = list.assoc("input_device", ini)
#player_second_input = list.assoc("player_second")
player_output = list.assoc("output_device", ini)
# fallback settings
fallback_audio_folder = list.assoc("fallback_audio_folder", ini)
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))
# channel names from config
channelnames = ref string.split(separator=',', list.assoc("channels", ini))
# alsa settings
# buffer - decrease latency: eg. alsa_buffer="2024"
set("alsa.alsa_buffer", alsa_buffer)
set("alsa.periods", alsa_periods)
# enable socketserver
set("server.socket", true)
set("server.socket.path", socketdir^"/<script>.sock")
set("server.telnet", true)
set("server.telnet.port", 1234)
# enable daemon
#set("init.daemon", true)
#set("init.daemon.change_user.group", daemongroup)
#set("init.daemon.change_user.user", daemonuser)
#set("init.daemon.pidfile.path", socketdir^"/<script>.pid")
#set("init.daemon.pidfile.perms", 0o666)
# set logging
set("log.file",true)
set("log.file.path", list.assoc("logdir", ini)^"/<script>.log")
set("log.file.perms",0o640)
set("log.level", 10)
# allowed mime types
set("playlists.mime_types.xml",["text/xml","application/xml","application/xspf+xml"])
# load functions
# dir = list.assoc("install_dir", ini)
%include "library.liq"
%include "playlist.liq"
# Der input wie oben definiert
def get_input()
def get_input()
if use_alsa == "y" then
if player_input == "soundcard" then
print("autodetect device")
input.alsa(id="sound_input", fallible=true, clock_safe=false)
else
print("manual set device: "^player_input)
input.alsa(id="sound_input", fallible=true, clock_safe=false, device=player_input)
end
else
input.pulseaudio(id="sound_input")
end
end
def get_fallback()
if fallback_audio_folder != "" then
print("fallbackfolder chosen: "^fallback_audio_folder)
playlist.safe("/var/audio/fallback/music.flac")
#playlist.safe(fallback_audio_folder)
else
blank(duration=20.0)
end
end
if player_input == "" then
blank(duration=0.1) # wait...
else
# start silence detector on input alsa and set a fallback
fallback(track_sensitive=false,
[ strip_blank(max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold, get_input()),
get_fallback() ]
)
end
end
playlistrunning = ref false
playlist_recorded = playlist.xml(id='playlist', on_done=fun() -> begin ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n playlistcurrent -v ""')) ignore(server.execute('mixer.select 0 false')) end, 'none')
# Die Source aus der Playlist
recorded = snd(playlist_recorded)
# Skippen erlauben
add_skip_command(recorded)
# Funktion zum laden einer neuen Playlist
playlist_funcs = fst(playlist_recorded)
# Flush functions
flush_recorded = snd(fst(playlist_funcs))
# Reload function
reload_recorded = fst(fst(playlist_funcs))
#up funtction
move_recorded = fst(fst(snd(playlist_funcs)))
#insert funtction
insert_recorded = fst(snd(fst(snd(playlist_funcs))))
# remove function
remove_recorded = snd(snd(fst(snd(playlist_funcs))))
#push function
push_recorded = snd(snd(snd(playlist_funcs)))
#show playlist function
data_recorded = fst(snd(snd(playlist_funcs)))
# create playlist source with smart crossfade
playlist = fallback(id="common", track_sensitive=false, [
switch(transitions =
[crossfade,crossfade],
[
( {!playlistrunning == true}, recorded ), # play the playlist
( {!playlistrunning == false}, get_input() )
]
),
if fallback_audio_folder != "" then
playlist.safe(fallback_audio_folder)
else
blank(duration=20.0)
end
])
# method to dynamicaly create a channel
def addChannel (ret, el, ~base="ch")
equeue = request.equeue(id = base^el) # create the equeue request
# add a seek function to the channel
server.register(namespace="ch"^el,
description="Seek to a relative position",
usage="seek <duration>",
"seek",fun (x) -> begin seek_track(equeue, x) end)
# append and return the list
list.append(ret,[equeue])
end
channels = addChannel([], '1', base='auto')
channels = addChannel(channels, '2', base='auto')
channels = list.fold(addChannel, channels, !channelnames)
mixer = mix(id = "mixer", list.append([playlist], channels))
#ignore(s) # testing
# User may load a XML-Playlist
#server.register(namespace='playlist',
# description="Load Playlist",
# usage="load <uri>",
# "load",fun (p) -> begin
# reload_recorded(skip=0, uri=p)
# end
#)
# Register the seek function
server.register(namespace='playlist',
description="Seek to a relative position",
usage="seek <duration>",
"seek",fun (x) -> begin seek_track(recorded, x) end
)
# The play function
server.register(namespace='playlist',
description="Play recorded files",
usage="play",
"play",fun (x) -> if !playlistrunning == true then
message('playlist', false, 'Playlist already playing', get_task_number ('play'), '01', level="info", type="user")
else
ignore(server.execute('mixer.select 0 true'))
playlistrunning := true
message('playlist', true, 'Start playing' , get_task_number ('play'), '00')
end
)
# Flush current playlist
server.register(namespace='playlist',
description="Flush recorded playlist",
usage="flush",
"flush",fun (s) -> begin flush_recorded() end)
# The remove function
server.register(namespace='playlist',
description="Remove item from playlist",
usage="remove <pos>",
"remove",fun (p) -> begin remove_recorded(int_of_string(p)) end)
# Let the user move up or down a track
server.register(namespace='playlist',
description="Move an item up or down",
usage="move <from> <to>",
"move",fun (p) -> begin
params=string.split(separator=" ",p)
src=if list.length(params)>0 then (int_of_string(list.nth(params,0))+0) else 0 end
target=if list.length(params)>1 then (int_of_string(list.nth(params,1))+0) else 0 end
move_recorded(src, target)
end
)
# Insert an entry
server.register(namespace='playlist',
description="Add an entry",
usage="insert <pos> <uri> [<title> <time>]",
"insert",fun (p) -> begin
params=string.split(separator=" ",p)
pos=if list.length(params)>0 then list.nth(params,0) else '' end
uri=if list.length(params)>1 then list.nth(params,1) else '' end
title=if list.length(params)>2 then list.nth(params,2) else '' end
time=if list.length(params)>3 then list.nth(params,3) else '' end
insert_recorded(pos=pos, title=title,time=time,uri)
end
)
# Insert a track on top of playlist
server.register(namespace='playlist',
description="Push an item to top and play immediately",
usage="push <pos>",
"push",fun (p) -> begin
params=string.split(separator=" ",p)
pos=if list.length(params)>0 then int_of_string(list.nth(params,0)) else 0 end
push_recorded(pos)
end
)
# Show metadata
server.register(namespace='playlist',
description="Show remaining playlist data in json format",
usage="data",
"data",fun (s) -> begin
playlist = data_recorded()
json_of(compact=true, playlist)
end
)
# Pause/stop playlist
server.register(namespace='playlist',
description="Pause playing recorded files",
usage="pause",
"pause", fun (x) -> if !playlistrunning == false then
message('playlist', false, 'Playlist already stopped', get_task_number ('pause'), '01', level="info", type="user")
else
playlistrunning := false
ignore(server.execute('playlist.skip'))
ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n playlistcurrent -v ""'))
message('playlist', true, 'Playlist stopped', get_task_number ('pause'), '00')
end
)
# get remaining time
server.register(namespace='playlist',
description="Remaining time",
usage = "remaining",
"remaining", fun(x) -> string_of(source.remaining(recorded))
)
#streamm = single("/var/audio/fallback/music.flac")
#ignore(streamm) # testing
# Alsa output
if use_alsa == "y" then
if player_output == 'soundcard' then
output.alsa(id="player", fallible=true, mixer)
else
output.alsa(id="player", fallible=true, device=player_output, mixer)
end
else
output.pulseaudio(id="player", mixer)
end
# %include "stream.liq"
# Liest Playlist im XML-Format (XSPF)
%include "readini.liq"
%include "library.liq"
inst = if argv(1) != "" then string_of(argv(1)) else 'playd' end
instance = ref inst
error_message = ref ""
def playlist.xml(~id="",~skip=0,~on_done={()},uri)
# A reference to the playlist
playlist = ref []
# A reference to the uri
playlist_uri = ref uri
# A reference to know if the source
# has been stopped
has_stopped = ref false
# The next function - see request.dynamic
def next () =
file =
if list.length(!playlist) > 0 then
ret = list.hd(!playlist) # get first entry
playlist := list.tl(!playlist) # remove first entry from playlist
ret
else
# Playlist finished?
if not !has_stopped then
on_done () # call on_done method
end
has_stopped := true
""
end
if file == '' then
request.create("silence:waiting") #
else
track = file
extracted = string.extract(pattern="annotate:.*:(/.*)",string.replace(pattern='"',(fun (s) -> ""), track))
wavfile = extracted["1"]
ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n playlistcurrent -v "#{wavfile}"'))
request.create(track)
end
end
# Instanciate the source
source = request.dynamic(id=id,next)
# flush function
def flush () =
log(label=id,"Flush "^string_of(list.length(!playlist))^" Items")
playlist := [] # playlist is empty now
ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n playlistcurrent -v ""'))
message(value=json_of(!playlist), 'playlist', true, "Playlist flushed", get_task_number ('flush'), "00", type='internal')
end
# Get its id.
id = source.id(source)
# The load function
def load_playlist (~skip=1) =
playlist_tmp = request.create.raw(!playlist_uri) # temporary try to create the playlist
result =
if file.exists(!playlist_uri) and request.resolve(playlist_tmp) then # playlist should exist and be resolvable
playlist_tmp = request.filename(playlist_tmp) # create a temporary request
entries = playlist.parse(playlist_tmp) # parse the playlist request
# create a annotation from playlist track
def annotate_track(el) =
meta = fst(el)
# remove protocol from filepath
filepath = string.replace(pattern='"',(fun (s) -> ""),string.replace(pattern='file://',(fun (s) -> ""),string.escape(snd(el))))
# fold metadata
s = list.fold(fun (cur, el) -> begin "#{cur},#{fst(el)}=#{string.escape(snd(el))}" end, "", meta)
# return annotation
"annotate:#{s}:#{filepath}"
end
# map entries
error_message := if list.length(entries) > 0 then "OK" else "empty" end
list.map(annotate_track,entries)
else
error_message := if file.exists(!playlist_uri) then "!resolve" else "!exist" end
log(label=id,"Couldn't read playlist: request resolution failed.")
# return an empty list
[]
end
request.destroy(playlist_tmp) # destroy the temporary request
playlist := result
end
# Move function
def move (fromPos, toPos) =
countx = ref -1 # counter
fromx = ref (fromPos - 1) # position of item
tox = ref (toPos - 1) # position to move
msg = ref ""
msg := message('playlist', true, "Item #{fromPos} moved to #{toPos} in Playlist", get_task_number ('move'), "00", type='internal')
# check if we can move
if (toPos) > list.length(!playlist)
or (fromPos) > list.length(!playlist)
or toPos < 1
or fromPos < 1 then
msg := message('playlist', false, "Moving item #{fromPos} to position #{toPos} is impossible.", get_task_number ('move'), "01", type='user', level="warning")
elsif toPos != fromPos then
# get the item to move
itemToMove = ref list.nth(!playlist, !fromx)
# returns the resorted list
def resort (ret, el) =
countx := !countx + 1
if !countx == !fromx then # das ist das Item, das verschoben wird - es wird uebersprungen
ret
elsif !countx == !tox then # Wir haben die Position erreicht, an der das zu verschiebende Item platziert wird
if !fromx < !tox then # aber entry wurde bereits uebersprungen
list.append(ret,[el, !itemToMove]) # also muss hinter dem aktuellen Element eingefuegt werden
else
list.append(ret,[!itemToMove, el]) # ...andernfalls davor
end
else
list.append(ret,[el]) #Liste um den aktuellen Eintrag erweitern
end
end
playlist := list.fold(resort, [], !playlist)
end
!msg
end
# method to delete an item from the list
def remove (index) =
countx = ref 0
delx = ref (index - 1)
def delete (ret, el) =
countx := !countx + 1
if !countx == (!delx +1) then
ret # the position to be removed
else
list.append(ret,[el]) # append evereything else
end
end
playlist := list.fold(delete, [], !playlist)
message(value=json_of(!playlist), 'playlist', true, "Item #{index} removed from Playlist", get_task_number ('remove'), "00", type='internal')
end
# method to insert an item
def insert (~title='Unknown', ~time='00:00', ~pos='', uri) =
el = 'annotate:title="#{title}",time="#{time}",location="#{uri}":#{uri}'
playlist := list.append(!playlist,[el]) #Item erst mal an die Playlist anhaengen
if int_of_string(pos) > 0 then # Eine Position ist angegeben
result = move(list.length(!playlist), int_of_string(pos)) # Item dorthin verschieben
result_list = of_json(default=[('success','false')], result) # Die Ausgabe von "move" als assoziatives Array
if list.assoc('success', result_list) != "true" then # War move nicht erfolgreich?
result # ... dann geben wir die Ausgabe von move zurueck
else # ... andernfalls Erfolgsmeldung
message(value=json_of(!playlist), 'playlist', true, "#{title} item inserted into #{pos} in Playlist", get_task_number ('insert'), "00", type='internal')
end
elsif int_of_string(uri) > 0 then # uri ist ein int? Da hat der User was falsch gemacht...
message('playlist', true, "Syntax error: playlist.insert <pos> <uri> [<title> <time>]", get_task_number ('insert'), "01", type='user', level="info")
else # da auch, da ist pos 0 oder negativ
message('playlist', true, "Cannot insert #{title} at position #{pos}. Put it on the end.", get_task_number ('insert'), "02", type='user', level="info")
end
end
# returns the remaining list
def getplaylist () =
list.fold(foldplaylist, [], !playlist)
end
def push (index) =
"Not implemented yet"
end
# The reload function
def reload(~blank=false, ~skip=0, ~uri="") =
if uri != "" then
playlist_uri := uri
end
log(label=id,"Reloading playlist with URI #{!playlist_uri}")
has_stopped := false
load_playlist(skip=skip)
#if !error_message == 'OK' then
# message('playlist', true, 'Playlist #{!playlist_uri} loaded', get_task_number ('load'), '00', level="success")
#elsif !error_message == '!exist' then
# message('playlist', false, "Playlist #{!playlist_uri} does not exist", get_task_number ('load'), '01', level="warning", type="external")
#elsif !error_message == 'empty' then
# message('playlist', false, "Playlist #{!playlist_uri} is empty or wrong format", get_task_number ('load'), '02', level="warning", type="external")
#else
# message('playlist', false, "Playlist #{!playlist_uri} doesn't resolve.", get_task_number ('load'), '03', level="warning", type="external")
#end
end
log(label=id,"Loading playlist with URI #{!playlist_uri}")
# Load the playlist
load_playlist(skip=skip)
# Return
(((reload,flush),((move,(insert,remove)),(getplaylist,push))),source)
end
input_fs = single(id="fs", "/var/audio/fallback/output.flac")
input_http = input.http(id="http", "http://stream.fro.at/fro128.mp3")
linein_pulse_1 = input.pulseaudio(id="linein", device = input_device_0)
mixer = mix(id="mixer", [input_fs, input_http, linein_pulse_1])
out := output.pulseaudio(id="lineout", mixer)
inst = if argv(1) != "" then string_of(argv(1)) else 'record' end
instance = ref inst
%include "readini.liq"
ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} -t sayAlive'))
exec_at(freq=20., pred={true}, {ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} -t sayAlive'))})
audiobase= if !instance == 'record' then list.assoc("audiobase", ini) else list.assoc("altaudiobase", ini) end
filenamepattern = ref audiobase^"/%Y-%m-%d/%Y-%m-%d-%H-%M.wav"
daemongroup = list.assoc("daemongroup", ini)
daemonuser = list.assoc("daemonuser", ini)
socketdir = list.assoc("socketdir", ini)
recinput = if !instance == 'record' then list.assoc("recinput", ini) else list.assoc("altrecinput", ini) end
recorder_device = if !instance == 'record' then list.assoc("recorder_device", ini) else list.assoc("altrecorder_device", ini) end
set("init.daemon",true)
set("init.daemon.change_user.group",daemongroup)
set("init.daemon.change_user.user",daemonuser)
set("server.socket",true)
set("server.socket.path",socketdir^"/"^!instance^".sock")
set("init.daemon.pidfile.path",socketdir^"/"^!instance^".pid")
set("init.daemon",true)
set("log.file",true)
set("log.file.path",list.assoc("logdir", ini)^"/"^!instance^".log")
set("log.file.perms",0o660)
set("log.level",4)
# Der aktuelle Dateiname für die Aufnahme
recordingfile = ref ""
#wir definieren eine Referenz für die Stop-Funktion, die aber bisher noch nichts tun kann
stop_f = ref (fun () -> ())
#bewahre uns davor, dass zweimal gleichzeitig die gleiche Date aufgenommen wird
block_dump = ref false
# Stop dump - wir definieren die Funktion, die stop_f ausführt
def stop_dump() =
f = !stop_f
f ()
end
#Der input wie oben definiert
def get_input()
if recinput == 'soundcard' then
## input ist Alsa
if recorder_device != '' then
input.alsa(device=recorder_device)
else
input.alsa()
end
elsif recinput == 'nosound' then
mksafe(empty())
else
## input ein via config definierter Stream
mksafe(input.http(recinput))
end
end
#Wav header fixen und ggf. die Aufzeichnung beenden
def on_close(filename)
# es darf wieder aufgenommen werden
block_dump := false
# Korrekten WAV-Header schreiben
system("qwavheaderdump -F #{filename}")
# event dumpend feuern
ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n dumpend -v #{filename}'))
log('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n dumpend -v #{filename}')
# Naechsten Dateinamen vormerken
recordingfile := list.hd(get_process_lines("date +#{!filenamepattern}"))
ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n dumpfile -v #{!recordingfile}'))
end
#Funktion gibt Auskunft welches File aktuell ist und wieviel Prozent bereits aufgenommen werden
def currecording()
curfile = !recordingfile
if curfile != "" then
procent_done = list.hd(get_process_lines("echo $(($(stat -c%s "^curfile^")/3174777))"))
"#{curfile},#{procent_done}"
else
""
end
end
#Funktion zum Start der Aufzeichnung
def start_dump() =
log('start dump')
# don't record twice
if !block_dump == false then
block_dump := true
# Der Input - Alsa oder Stream
input=get_input()
record = output.file(
id="recorder",
# Wav Stereo erzeugen
%wav(stereo=true),
perm = 0o664,
# die aktuell aufnehmende Datei in 'recordingfile' festhalten
on_start={ begin
recordingfile := list.hd(get_process_lines("date +#{!filenamepattern}"))
log('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n dumpfile -v #{!recordingfile}')
ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n dumpfile -v #{!recordingfile}'))
end},
# Beim Stoppen den Status zurücksetzen
on_stop={ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n dumpfile -v ""'))},
# Dateipfad
!filenamepattern,
# Funktion die beim Schließen der Datei aufgerufen wird
on_close=on_close,
# Alle 30 Minuten eine neue Datei
reopen_when={ if !instance == 'record' then int_of_float(gettimeofday()/60.) mod 30 == 0 else false end },
# Der Input
input
)
# Die Stopfunkton zeigt nun auf die Shutdown-Funktion der aktuellen Source
stop_f := fun () -> begin
log('stop recording')
# Aufnahme sauber beenden
ignore(server.execute('recorder.stop'))
# Source zerstören
source.shutdown(record)
# Variable zurücksetzen
recordingfile := ""
end
end
end
# Der Server wird durch 3 Funktionen bereichert
# Der User darf die Aufzeichnung manuell starten
server.register(namespace="record",
description="Start recording.",
usage="start",
"start",
fun (s) -> begin start_dump() "OK" end)
# Der User darf die Aufzeichnung manuell stoppen
server.register(namespace="record",
description="Stop recording.",
usage="stop",
"stop",
fun (s) -> begin stop_dump() "OK" end)
if !instance != 'record' then
# Der User darf einen Dateinamen für die Aufnahme definieren
server.register(namespace="record",
description="Define filename for output.",
usage="setfilename",
"setfilename",
fun (s) -> begin filenamepattern := audiobase^"/"^string_of(s) "OK" end)
end
# Der USer kann sich über den Fortschritt der Aufnahme informieren
server.register(namespace="record",
description="Show current file.",
usage="curfile",
"curfile",
fun (s) -> currecording() )
output.dummy(blank(id="serve"))
%include "/home/gg/PycharmProjects/aura/modules/liquidsoap/readini.liq"
ini = read_ini("/etc/aura/aura.ini")
set("log.file",true)
set("log.file.path",list.assoc("logdir", ini)^"/<script>.log")
#set("log.file.path", "<script>.log")
set("log.file.perms",0o660)
set("log.level",3)
#fallback_audio_folder = list.assoc("fallback_audio_folder", ini)
#fallback_audio_folder = "/var/audio/fallback"
# track_sensitive => fallback_folder track sensitivity
# max_blank => maximum time of blank from source
# min_noise => minimum duration of noise on source to switch back over
# threshold => power in dB under which the stream is considered silent
#stream = fallback(track_sensitive=false,
# [ strip_blank(id="", max_blank=10., min_noise=10., threshold=0., once(input.alsa(id="sound_input", fallible=true, clock_safe=false))),
# playlist.safe(fallback_audio_folder) ])
stream = fallback(track_sensitive=false,
[ strip_blank(id="defaultstripper", max_blank=10., min_noise=10., threshold=0., single("/var/audio/fallback/music.flac")),
playlist.safe("/var/audio/fallback/NightmaresOnWax/Smokers Delight") ] )
#stream = single("/var/audio/fallback/music.flac")
output.alsa(id="player", device="hw:0,0", stream)
#output.alsa(fallible=true, input.alsa(id="sound_input", fallible=true, clock_safe=false))
#Create stream?
stream = list.assoc("stream", ini)
stream_type = list.assoc("stream_type", ini)
stream_bitrate = int_of_string(list.assoc("stream_bitrate", ini))
stream_port = int_of_string(list.assoc("stream_port", ini))
stream_mountpoint = list.assoc("stream_mountpoint", ini)
stream_user = list.assoc("stream_user", ini)
stream_password = list.assoc("stream_password", ini)
stream_host = list.assoc("stream_host", ini)
stream_url = list.assoc("stream_url", ini)
stream_name = list.assoc("stream_name", ini)
stream_genre = list.assoc("stream_genre", ini)
stream_description = list.assoc("stream_description", ini)
if stream != "" then
if stream_type == "icecast" then
if stream_bitrate == 24 then
ignore(output.icecast(%mp3(bitrate = 24, samplerate = 22050), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 32 then
ignore(output.icecast(%mp3(bitrate = 32, samplerate = 22050), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 48 then
ignore(output.icecast(%mp3(bitrate = 48, samplerate = 22050), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 64 then
ignore(output.icecast(%mp3(bitrate = 64, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 80 then
ignore(output.icecast(%mp3(bitrate = 80, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 96 then
ignore(output.icecast(%mp3(bitrate = 96, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 112 then
ignore(output.icecast(%mp3(bitrate = 112, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 128 then
ignore(output.icecast(%mp3(bitrate = 128, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 160 then
ignore(output.icecast(%mp3(bitrate = 160, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 192 then
ignore(output.icecast(%mp3(bitrate = 192, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 224 then
ignore(output.icecast(%mp3(bitrate = 224, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 256 then
ignore(output.icecast(%mp3(bitrate = 256, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 320 then
ignore(output.icecast(%mp3(bitrate = 320, samplerate = 44100), mount=stream_mountpoint, host=stream_host, port=stream_port, name=stream_name, url=stream_url, genre=stream_genre, description=stream_description, user=stream_user, password=stream_password, icy_metadata="true", fallible=true, s))
end
elsif stream_type == "harbor" then
if stream_bitrate == 24 then
ignore(output.harbor(%mp3(bitrate = 24, samplerate = 22050), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 32 then
ignore(output.harbor(%mp3(bitrate = 32, samplerate = 22050), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 48 then
ignore(output.harbor(%mp3(bitrate = 48, samplerate = 22050), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 64 then
ignore(output.harbor(%mp3(bitrate = 64, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 80 then
ignore(output.harbor(%mp3(bitrate = 80, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 96 then
ignore(output.harbor(%mp3(bitrate = 96, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 112 then
ignore(output.harbor(%mp3(bitrate = 112, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 128 then
ignore(output.harbor(%mp3(bitrate = 128, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 160 then
ignore(output.harbor(%mp3(bitrate = 160, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 192 then
ignore(output.harbor(%mp3(bitrate = 192, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 224 then
ignore(output.harbor(%mp3(bitrate = 224, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 256 then
ignore(output.harbor(%mp3(bitrate = 256, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
elsif stream_bitrate == 320 then
ignore(output.harbor(%mp3(bitrate = 320, samplerate = 44100), user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, s))
end
end
end
\ No newline at end of file
set("log.file.path", "./test.log")
#input_alsa = input.alsa(bufferize=false) #, clock_safe=false)
#output.alsa(bufferize=false, input_alsa)
input_pulse = input.pulseaudio() #, clock_safe=false)
output.pulseaudio(input_pulse)
#!/usr/bin/liquidsoap
%include "/etc/aura/readini.liq"
%include "/etc/aura/library.liq"
%include "/etc/aura/playlist.liq"
inifile = '/etc/aura/aura.ini'
ini = read_ini(inifile)
socketdir = list.assoc("socketdir", ini)
# set player i/o devices
live_input_device = list.assoc("live_input_device", ini)
output_device = list.assoc("output_device", ini)
# fallback settings
station_fallback_pool = list.assoc("station_fallback_pool", ini)
ignore(station_fallback_pool)
fallback_audio_folder = list.assoc("fallback_audio_folder", ini)
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))
# channel names from config
channelnames = ref string.split(separator=',', list.assoc("channels", ini))
set("server.socket", true)
set("server.socket.path", socketdir^"/<script>.sock")
set("server.telnet", true)
set("server.telnet.port", 1234)
# alsa settings
#set("alsa.alsa_buffer", 2048)
#set("alsa.periods", 0)
#set("alsa.buffer_length", 1)
playlistrunning = ref false
# dynamic list of sources
sources = ref []
playlist_recorded = playlist.xml(
id='playlist', on_done=fun() -> begin ignore(system('#{list.assoc("install_dir", ini)}/modules/soundengine/helpers/message.py -c #{!instance} --task=setState -n playlistcurrent -v ""')) ignore(server.execute('mixer.select 0 false')) end, 'none')
# Die Source aus der Playlist
recorded = snd(playlist_recorded)
# Skippen erlauben
add_skip_command(recorded)
# User may load a XML-Playlist
server.register(namespace='playlist',
description="Load Playlist",
usage="load <uri>",
"load",fun (p) -> begin
reload_recorded(skip=0, uri=p)
end
)
def get_station_fallback_pool()
#playlist.safe("/var/audio/fallback/NightmaresOnWax/Smokers Delight")
playlist.safe(station_fallback_pool)
#if station_fallback_pool != "" then
# print("fallbackfolder chosen: "^station_fallback_pool)
# playlist.safe(station_fallback_pool)
#else
# print("no fallbackfolder chosen. blanking 20s.")
# blank(duration=20.0)
#end
end
def get_live_input()
if live_input_device == "soundcard" then
print("autodetect input device")
#strip_blank(id="input_alsa_strip", max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold,
input.alsa(id="input_alsa", bufferize=false) #)
else
print("manually set device: "^live_input_device)
#strip_blank(id="input_alsa_strip", max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold,
input.alsa(id="input_alsa", bufferize=false, device=live_input_device) #)
end
end
def get_input_fs()
fallback(track_sensitive=false,
[ single("/var/audio/fallback/music.flac"), get_station_fallback_pool() ] )
end
def get_input_stream()
url = "http://stream.fro.at/fro128.mp3"
#url = "http://mp3stream1.apasf.apa.at:8000/listen.pls"
mksafe(strip_blank(id="stream_strip", input.http(id="stream", url)))
end
# create playlist source with smart crossfade
#filesystem_input = fallback(id="filesystem_input", track_sensitive=false, [
# get_input(),
# get_fallback()])
# filesystem_input
#fs_input = get_input_fs()
fs_input = fallback(track_sensitive=false,
[ single("/var/audio/fallback/music.flac"), get_station_fallback_pool() ] )
# create a source for stream overtakes
stream_input = get_input_stream()
#url = "http://stream.fro.at/fro64.mp3"
#url = "http://mp3stream1.apasf.apa.at:8000/listen.pls"
#stream_input = strip_blank(id="httpsttrip", input.http(id="stream", url))
# and the studio input
#live_input = get_live_input()
live_input = input.alsa(id="input_alsa", bufferize=false)
# API HOWTO:
# mixer.inputs
# stream.stop
# stream.url [url]
# stream.start
# mixer.select [streamovertake] true
def addChannel(source)
sources := list.append([source], !sources)
end
addChannel(fs_input)
addChannel(stream_input)
addChannel(live_input)
mixer = mix(id = "mixer", !sources)
#final_stream = fallback(id="station_fallback", [mixer, playlist.safe("/var/audio/fallback/NightmaresOnWax/Smokers Delight")]
#server.register(namespace='mixer',
# description="Load Playlist",
# usage="select <num>",
# "load",fun (p) -> begin
# reload_recorded(skip=0, uri=p)
# end
#)
ignore(mixer)
ignore(fs_input)
ignore(live_input)
ignore(addChannel)
ignore(stream_input)
ignore(channelnames)
ignore(output_device)
ignore(playlistrunning)
ignore(fallback_max_blank)
ignore(fallback_min_noise)
ignore(fallback_threshold)
ignore(fallback_audio_folder)
print("output_device: "^output_device)
output.alsa(id="alsaout", bufferize=false, device=output_device, mixer)
#ret = server.execute('help')
#print(ret)
import os
import sys
import threading
import simplejson
import queue
import traceback
import urllib
import logging
from mutagen.flac import FLAC
from datetime import datetime, timedelta
from libraries.database.broadcasts import Schedule, ScheduleEntry
from libraries.enum.scheduleentrytype import ScheduleEntryType
from modules.communication.redis.messenger import RedisMessenger
class AuraCalendarService(threading.Thread):
messenger = RedisMessenger()
until = ""
audiobase = ""
playlistdir = ""
xmlplaylist = range(0)
queue = None
config = None
debug = False
_stop_event = None
logger = None
url = dict()
data = dict()
"""
Fetching playlist data, write it into the database and notify service
"""
def __init__(self, config, datefrom="", dateto=""):
threading.Thread.__init__(self)
self.config = config
self.logger = logging.getLogger("AuraEngine")
self.messenger.set_channel("aura")
self.messenger.set_section("calendar")
self.dateto = dateto
self.datefrom = str(datefrom)
self.queue = queue.Queue()
self._stop_event = threading.Event()
self.__set_url__("calendar")
self.__set_url__("importer")
# ------------------------------------------------------------------------------------------ #
def set_date_from(self, date):
self.datefrom = str(date).replace(" ", "T")
# ------------------------------------------------------------------------------------------ #
def set_date_to(self, date):
self.dateto = str(date).replace(" ", "T")
# ------------------------------------------------------------------------------------------ #
def set_until_time(self, timestring):
self.until = timestring
# ------------------------------------------------------------------------------------------ #
def set_audio_path(self, path):
self.audiobase = path
# ------------------------------------------------------------------------------------------ #
def set_playlist_store(self, path):
self.playlistdir = path
# ------------------------------------------------------------------------------------------ #
def get_duration(self, start, end):
return self.__calc_duration__(start, end)
# ------------------------------------------------------------------------------------------ #
def get_queue(self):
return self.queue
# ------------------------------------------------------------------------------------------ #
def get_uri(self):
if not self.playlistdir:
return False
if not self.datefrom:
return False
if not self.__calc_date_to__():
return
hostname = self.get("servername");
port = self.get("serviceport");
date_from = self.datefrom[0:16] + ":00";
date_to = self.dateto[0:16] + ":00";
uri = "http://" + hostname + ":" + port + "/playlist/" + date_from + "/" + date_to
return uri
# ------------------------------------------------------------------------------------------ #
def run(self):
"""
Fetch calendar data and store it in the database
"""
# fetch upcoming schedules from ENGINE
self.__fetch_schedule_data__()
# fetch playlist and fallbacks to the schedules from TANK
self.__fetch_schedule_entry_data__()
for schedule in self.fetched_schedule_data:
if "start" not in schedule:
self.logger.warning("No start of schedule given. skipping the schedule "+str(schedule))
continue
if "end" not in schedule:
self.logger.warning("No end of schedule given. skipping the schedule "+str(schedule))
continue
# store the schedule
schedule_db = self.store_schedule(schedule)
# store playlists to play
self.store_schedule_playlist(schedule_db, schedule["playlist"], "playlist_id")
self.store_schedule_playlist(schedule_db, schedule["schedule_fallback"], "schedule_fallback_id", True)
self.store_schedule_playlist(schedule_db, schedule["show_fallback"], "show_fallback_id", True)
self.store_schedule_playlist(schedule_db, schedule["station_fallback"], "station_fallback", True)
# release the mutex
self.queue.put("fetching_finished")
# terminate the thread
return
def store_schedule(self, schedule):
schedule_db = Schedule.query.filter(Schedule.schedule_id == schedule["schedule_id"]).first()
havetoadd = False
if not schedule_db:
self.logger.debug("no schedule with given schedule id in database => create new")
schedule_db = Schedule()
havetoadd = True
# calc duration
duration = self.__calc_duration__(schedule["start"], schedule["end"])
schedule["duration"] = timedelta(seconds=duration).__str__()
schedule_db.show_id = schedule["show_id"]
schedule_db.schedule_id = schedule["schedule_id"]
schedule_db.schedule_start = schedule["start"]
schedule_db.schedule_end = schedule["end"]
schedule_db.show_name = schedule["show_name"]
schedule_db.show_hosts = schedule["show_hosts"]
schedule_db.is_repetition = schedule["is_repetition"]
schedule_db.rtr_category = schedule["show_rtrcategory"]
schedule_db.languages = schedule["show_languages"]
schedule_db.type = schedule["show_type"]
schedule_db.category = schedule["show_categories"]
schedule_db.topic = schedule["show_topics"]
schedule_db.musicfocus = schedule["show_musicfocus"]
schedule_db.playlist_id = schedule["playlist_id"]
schedule_db.schedule_fallback_id = schedule["schedule_fallback_id"]
schedule_db.show_fallback_id = schedule["show_fallback_id"]
schedule_db.station_fallback_id = schedule["station_fallback_id"]
schedule_db.store(add=havetoadd, commit=True)
return schedule_db
# ------------------------------------------------------------------------------------------ #
def store_schedule_playlist(self, schedule_db, playlist, playlist_id_name, isfallbackplaylist=False):
entrynum = 0
if "entries" in playlist:
lastentry = None
for entry in playlist["entries"]:
lastentry = self.store_playlist_entry(schedule_db, playlist, entry, lastentry, entrynum, isfallbackplaylist)
entrynum = entrynum + 1
else:
self.logger.warning("No scheduleentries for playlist #" + playlist_id_name + " in schedule #" + str(schedule_db.schedule_id) + " found")
# ------------------------------------------------------------------------------------------ #
def store_playlist_entry(self, schedule_db, playlist, entry, lastentry, entrynum, isfallbackplaylist=False):
schedule_entry_db = ScheduleEntry.select_one(playlist["playlist_id"], entrynum)
havetoadd = False
if not schedule_entry_db:
self.logger.debug("no scheduleentry with id " + str(playlist["playlist_id"]) + " and pos " + str(entrynum) + " in database => creating a new one")
schedule_entry_db = ScheduleEntry()
havetoadd = True
schedule_entry_db.playlist_id = playlist["playlist_id"]
schedule_entry_db.entry_num = entrynum
schedule_entry_db.schedule_id = schedule_db.schedule_id
schedule_entry_db.source = entry["source"]
schedule_entry_db.is_fallback = isfallbackplaylist
schedule_entry_db.entry_start = schedule_db.schedule_start + timedelta(seconds=self.get_length(lastentry))
schedule_entry_db.calc_unix_times()
schedule_entry_db.define_clean_source()
self.logger.debug("Storing entries... playlist_id: " + str(playlist["playlist_id"]) + " schedule_id: " + str(schedule_db.schedule_id) + " num: " + str(entrynum))
schedule_entry_db.store(add=havetoadd, commit=True)
return schedule_entry_db
# ------------------------------------------------------------------------------------------ #
def __calc_date_to__(self):
if self.dateto:
return True
if not self.until:
return False
if not self.datefrom:
return False
date_start = datetime.strptime(self.datefrom.replace("T"," "), "%Y-%m-%d %H:%M")
time_start = date_start.strftime("%H:%M")
day_offset = 1 if (time_start > self.until) else 0
end_date = date_start + timedelta(day_offset)
self.dateto = end_date.strftime("%F") + "T" + self.until
return True
# ------------------------------------------------------------------------------------------ #
@staticmethod
def __calc_duration__(start, end):
"""
Berechnet Zeit in Sekunden aus Differenz zwischen Start und Enddatum
@type start: datetime
@param start: Startzeit
@type end: datetime
@param end: Endzeit
@rtype: int
@return: Zeit in Sekunden
"""
sec1 = int(datetime.strptime(start[0:16].replace(" ","T"),"%Y-%m-%dT%H:%M").strftime("%s"));
sec2 = int(datetime.strptime(end[0:16].replace(" ","T"),"%Y-%m-%dT%H:%M").strftime("%s"));
return (sec2 - sec1);
# ------------------------------------------------------------------------------------------ #
def __fetch_schedule_entry_data__(self):
# store fetched entries => do not have to fetch playlist_id more than once
fetched_entries=[]
try:
for schedule in self.fetched_schedule_data:
# retrieve playlist and the fallbacks for every schedule
# if a playlist is already fetched, it is not fetched again
schedule["playlist"] = self.__fetch_schedule_entries__(schedule, "playlist_id", fetched_entries)
schedule["schedule_fallback"] = self.__fetch_schedule_entries__(schedule, "schedule_fallback_id", fetched_entries)
schedule["show_fallback"] = self.__fetch_schedule_entries__(schedule, "show_fallback_id", fetched_entries)
schedule["station_fallback"] = self.__fetch_schedule_entries__(schedule, "station_fallback_id", fetched_entries)
self.logger.info("fetched for schedule_id: "+str(schedule["schedule_id"]))
self.logger.info("playlist: "+str(schedule["playlist"]))
self.logger.info("schedule_fallback: "+str(schedule["schedule_fallback"]))
self.logger.info("show_fallback: "+str(schedule["show_fallback"]))
self.logger.info("station_fallback: "+str(schedule["station_fallback"]))
except Exception as e:
self.logger.error(str(e))
# ------------------------------------------------------------------------------------------ #
def __fetch_schedule_entries__(self, schedule, id_name, fetched_schedule_entries):
servicetype = "importer"
use_testdata = False
html_response = self.__fetch_data__(servicetype)
if not html_response:
use_testdata = True
for entry in fetched_schedule_entries:
if entry["playlist_id"] == schedule[id_name]:
self.logger.debug("playlist #" + str(schedule[id_name]) + " already fetched")
return entry
if use_testdata:
# HARDCODED Testdata
if schedule[id_name] == 0 or schedule[id_name] is None:
# this happens when playlist id is not filled out in pv
html_response = '{"playlist_id": 0}'
elif schedule[id_name] % 4 == 0: # playlist with two files
html_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"file:///var/audio/fallback/NightmaresOnWax/DJ-Kicks/01 - Type - Slow Process.flac"}]}'
elif schedule[id_name] % 3 == 0: # playlist with jingle and then http stream
html_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"linein://0"}]}'
elif schedule[id_name] % 2 == 0: # playlist with jingle and then linein
html_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"http://stream.fro.at:80/fro-128.ogg"}]}'
else: # pool playlist
html_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"pool:///chillout"}]}'
try:
schedule_entries = simplejson.loads(html_response)
except Exception as e:
self.logger.critical("Cannot fetch schedule entries from importer")
sys.exit()
if "entries" in schedule_entries:
for entry in schedule_entries["entries"]:
if entry["source"].startswith("file"):
e = entry["source"][7:]
if not os.path.isfile(e):
self.logger.warning("File", e, "does not exist!")
fetched_schedule_entries.append(schedule_entries)
return schedule_entries
# ------------------------------------------------------------------------------------------ #
def __fetch_schedule_data__(self):
servicetype = "calendar"
use_testdata = False
html_response = self.__fetch_data__(servicetype)
if not html_response:
use_testdata = True
# if an error occours => use testdata
if use_testdata:
html_response = '[{"schedule_id":1,"schedule_start":"' + (datetime.now() + timedelta(hours=0)).strftime('%Y-%m-%d %H:00:00') + '","schedule_end":"' + (datetime.now() + timedelta(hours=1)).strftime('%Y-%m-%d %H:00:00') + '","show_id":9,"show_name":"FROzine","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":2,"schedule_fallback_id":12,"show_fallback_id":92,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"},{"schedule_id":2,"schedule_start":"' + (datetime.now()+timedelta(hours=1)).strftime('%Y-%m-%d %H:00:00') + '","schedule_end":"' + (datetime.now()+timedelta(hours=2)).strftime('%Y-%m-%d %H:00:00') + '","show_id":10,"show_name":"FROMat","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":4,"schedule_fallback_id":22,"show_fallback_id":102,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"},{"schedule_id":3,"schedule_start":"' + (datetime.now()+timedelta(hours=2)).strftime('%Y-%m-%d %H:00:00') + '","schedule_end":"' + (datetime.now() + timedelta(hours=3)).strftime('%Y-%m-%d %H:00:00') + '","show_id":11,"show_name":"Radio für Senioren","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":6,"schedule_fallback_id":32,"show_fallback_id":112,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"}]'
try:
self.fetched_schedule_data = simplejson.loads(html_response)
except Exception as e:
self.logger.critical("Cannot fetch schedule entries from PV")
sys.exit()
# check data
self.logger.critical("Hardcoded Response && no JSON data checks. I believe what i get here")
return self.fetched_schedule_data
# ------------------------------------------------------------------------------------------ #
def __fetch_data__(self, type):
# open an url and read the data
try:
if type not in self.data:
if self.url[type] == "":
return False
request = urllib.request.Request(self.url[type])
else:
request = urllib.request.Request(self.url[type], self.data[type])
response = urllib.request.urlopen(request)
html_response = response.read()
except (urllib.error.URLError, IOError, ValueError) as e:
self.logger.error("Cannot connect to " + self.config.get("calendarurl") + "! reason: " + str(e.reason))
return False
return html_response
# ------------------------------------------------------------------------------------------ #
def get_length(self, entry):
if entry is None or entry.source == ScheduleEntryType.STREAM or entry.type == ScheduleEntryType.LIVE:
return 0
audio_file = FLAC(entry.cleansource)
return audio_file.info.length
# ------------------------------------------------------------------------------------------ #
def __get_error_data__(self):
"""
Basisdaten als dict liefern
"""
return {"from": str(self.datefrom), "dateto": str(self.dateto), "path": "self.playlistpath", "url": self.config.get("calendarurl")}
# ------------------------------------------------------------------------------------------ #
def __set_url__(self, type):
url = self.config.get(type+"url")
pos = url.find("?")
if pos > 0:
self.url[type] = url[0:pos]
self.data[type] = url[pos:]
else:
self.url[type] = url
# ------------------------------------------------------------------------------------------ #
def stop(self):
self._stop_event.set()
# ------------------------------------------------------------------------------------------ #
def get_calendar_data(self):
return self.fetched_schedule_data
# -*- coding: utf-8 -*-
#
# scheduler.py
#
# Copyright 2018 Radio FRO <https://fro.at>, Radio Helsinki <https://helsinki.at>, Radio Orange <https://o94.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.0.1'
__license__ = "GNU General Public License (GPL) Version 3"
__version_info__ = (0, 0, 1)
__author__ = 'Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>'
"""
Aura Scheduler
Is holding the eventqueue
"""
import time
import simplejson
import datetime
import decimal
import traceback
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
from libraries.exceptions.exception_logger import ExceptionLogger
from libraries.enum.scheduleentrytype import ScheduleEntryType
def alchemyencoder(obj):
"""JSON encoder function for SQLAlchemy special classes."""
if isinstance(obj, datetime.date):
return obj.isoformat()
elif isinstance(obj, decimal.Decimal):
return float(obj)
elif isinstance(obj, sqlalchemy.orm.state.InstanceState):
return ""
elif isinstance(obj, Schedule):
return simplejson.dumps([obj._asdict()], default=alchemyencoder)
else:
return str(obj)
class AuraScheduler(ExceptionLogger, threading.Thread):
"""
Aura Scheduler Class
Gets data from pv and importer, stores and fires events
"""
redismessenger = RedisMessenger()
liquidsoapcommunicator = None
schedule_entries = None
message_timer = []
job_result = {}
programme = None
client = None
active_entry = None
logger = None
config = None
def __init__(self, config):
"""
Constructor
@type config: ConfigReader
@param config: read aura.ini
"""
self.config = config
self.logger = logging.getLogger("AuraEngine")
# init threading
threading.Thread.__init__(self)
# init messenger.. probably not needed anymore
self.redismessenger.set_channel('scheduler')
self.redismessenger.set_section('execjob')
# load schedulerconfig...
self.schedulerconfig = self.config.get("scheduler_config_file")
# load error messages
error_file = self.config.get("install_dir") + "/errormessages/scheduler_error.js"
self.error_data = simplejson.load(open(error_file))
# init database ?
self.init_database()
self.redismessenger.send('Scheduler started', '0000', 'success', 'initApp', None, 'appinternal')
# start loading new programm every hour
self.start()
# ------------------------------------------------------------------------------------------ #
def init_database(self):
# check if tables do exist. if not create them
try:
ScheduleEntry.select_all()
except sqlalchemy.exc.ProgrammingError as e:
if e.__dict__["code"] == "f405": # error for no such table
AuraDatabaseModel.recreate_db()
else:
raise
def run(self):
while True:
self.fetch_new_programme()
time.sleep(3600)
# ------------------------------------------------------------------------------------------ #
def get_active_entry(self):
now_unix = time.mktime(datetime.datetime.now().timetuple())
actsource = ""
lastentry = None
# load programme if necessary
if self.programme is None:
self.logger.debug("want to get active channel, but have to load programme first")
self.load_programme_from_db()
# get active source
for entry in self.programme:
# check if lastentry is set and if act entry is in the future
if lastentry is not None and entry.entry_start_unix > now_unix:
# return lastentry if so
return entry # actsource = entry.source
# break
lastentry = entry
return None
# ------------------------------------------------------------------------------------------ #
def load_programme_from_db(self, silent=False):
self.programme = ScheduleEntry.select_all()
self.logger.info("i am the scheduler and i am holding the following stuff")
# now in unixtime
now_unix = time.mktime(datetime.datetime.now().timetuple())
# switch to check if its the first stream in loaded programme
first_stream_in_programme = False
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/50000 # testing purpose
# 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)
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)
elif entry.type == ScheduleEntryType.FILESYSTEM:
self.add_or_update_timer(entry, diff, self.liquidsoapcommunicator.activate)
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))
# ------------------------------------------------------------------------------------------ #
def add_or_update_timer(self, entry, diff, func):
# check if something is planned at given time
planned_timer = self.is_something_planned_at_time(entry.entry_start)
# if something is planned on entry.entry_start
if planned_timer:
planned_entry = planned_timer.entry
# check if the playlist_id's are different
if planned_entry.playlist_id != entry.playlist_id:
# if not stop the old timer and remove it from the list
self.stop_timer(planned_timer)
# and create a new one
self.create_timer(entry, diff, func)
# if the playlist id's do not differ => do nothing, they are the same
# if nothing is planned at given time, create a new timer
else:
self.create_timer(entry, diff, func)
# ------------------------------------------------------------------------------------------ #
def stop_timer(self, timer):
# stop timer
timer.cancel()
# and remove it from message queue
self.message_timer.remove(timer)
# ------------------------------------------------------------------------------------------ #
def create_timer(self, entry, diff, func):
t = CallFunctionTimer(diff, func, [entry])
self.message_timer.append(t)
t.start()
# ------------------------------------------------------------------------------------------ #
def is_something_planned_at_time(self, given_time):
for t in self.message_timer:
if t.entry.entry_start == given_time:
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 = ""
if self.programme is None or len(self.programme) == 0:
self.fetch_new_programme()
try:
programme_as_string = simplejson.dumps([p._asdict() for p in self.programme], default=alchemyencoder)
except Exception as e:
self.logger.error("Cannot transform programme into JSON String. Reason: " + str(e))
traceback.print_exc()
return programme_as_string
# ------------------------------------------------------------------------------------------ #
def print_message_queue(self):
message_queue = ""
for t in self.message_timer:
message_queue += t.get_info()+"\n"
return message_queue
# ------------------------------------------------------------------------------------------ #
def swap_playlist_entries(self, indexes):
from_entry = None
to_entry = None
from_idx = indexes["from_index"]
to_idx = indexes["to_index"]
# find the entries
for p in self.programme:
if p.programme_index == int(from_idx):
from_entry = p
if p.programme_index == int(to_idx):
to_entry = p
# break out of loop, if both entries found
if from_entry is not None and to_entry is not None:
break
# check if entries are found
if from_entry is None or to_entry is None:
return "From or To Entry not found!"
# swap sources
swap = from_entry.source
from_entry.source = to_entry.source
to_entry.source = swap
# store to database
from_entry.store(add=False, commit=False)
to_entry.store(add=False, commit=True)
# and return the programme with swapped entries
return self.get_act_programme_as_string()
# ------------------------------------------------------------------------------------------ #
def delete_playlist_entry(self, index):
found = False
for p in self.programme:
if p.programme_index == int(index):
p.delete(True)
self.load_programme_from_db()
found = True
break
if not found:
self.logger.warning("Nothing to delete")
return self.get_act_programme_as_string()
# ------------------------------------------------------------------------------------------ #
def insert_playlist_entry(self, fromtime_source):
fromtime = fromtime_source["fromtime"]
source = fromtime_source["source"]
entry = ScheduleEntry()
entry.entry_start = fromtime
entry.source = source
entry.playlist_id = 0
entry.schedule_id = 0
entry.entry_num = ScheduleEntry.select_next_manual_entry_num()
entry.store(add=True, commit=True)
self.load_programme_from_db()
return self.get_act_programme_as_string()
# ------------------------------------------------------------------------------------------ #
def fetch_new_programme(self):
acs = AuraCalendarService(self.config)
queue = acs.get_queue()
# start fetching thread
acs.start()
# wait for the end
response = queue.get()
if response == "fetching_finished":
self.load_programme_from_db()
return self.get_act_programme_as_string()
else:
self.logger.warning("Got an unknown response from AuraCalendarService: " + response)
# ------------------------------------------------------------------------------------------ #
def set_next_file_for(self, playlistname):
print(playlistname)
return ""
# ------------------------------------------------------------------------------------------ #
def start_recording(self, data):
"""
Aufnahme starten
"""
result = self.client.recorder_start()
if self.__check_result__(result):
self.success("start_recording", result, "00")
else:
self.error("start_recording", result, "01")
# ------------------------------------------------------------------------------------------ #
def stop_recording(self, data):
"""
Aufnahme anhalten
"""
result = self.client.recorder_stop()
if self.__check_result__(result):
self.success("stop_recording", result, "00")
else:
self.error("stop_recording", result, "01")
class CallFunctionTimer(threading.Timer):
logger = None
entry = None
debug = False
diff = None
def __init__(self, diff, func, param):
threading.Timer.__init__(self, diff, func, param)
self.diff = diff
self.func = func
self.entry = param[0]
self.logger = logging.getLogger("AuraEngine")
msg = "MessageTimer starting @ " + str(self.entry.entry_start) + " source '" + str(self.entry.source) + "' In seconds: " + str(self.diff)
self.logger.debug(msg)
from flask import request
from libraries.database.database import app
from libraries.database.broadcasts import TrackService
class Routes:
error = None
def __init__(self):
app.run()
@staticmethod
@app.route('/')
@app.route('/index')
def index():
return "Welcome to Aura. Please use the CLI Tool guru.py to manipulate the server. Web service is in planned..."
# request: http://localhost:5000/trackservice?from=2018-01-17T13:30:00&to=2018-01-17T16:00:00
@staticmethod
@app.route("/trackservice", methods=["GET"])
def trackservice():
from_time = request.args.get("from")
to_time = request.args.get("to")
now = request.args.get("now")
if now == "":
entry = TrackService.now_playing()
return "from: " + str(from_time) + " to: " + str(to_time) + " now: " + str(now)
\ No newline at end of file
[Unit]
Description=Aura Engine Playout Server
After=network.target
[Service]
Type=simple
User=gg
WorkingDirectory=/home/gg/PycharmProjects/engine
ExecStart=/home/gg/PycharmProjects/engine/aura.py
Restart=always
[Install]
WantedBy=multi-user.target
#!/usr/bin/python3
from libraries.database.broadcasts import *
import simplejson
import sqlalchemy
def alchemyencoder(obj):
"""JSON encoder function for SQLAlchemy special classes."""
if isinstance(obj, datetime.date):
return obj.isoformat()
elif isinstance(obj, decimal.Decimal):
return float(obj)
elif isinstance(obj, sqlalchemy.orm.state.InstanceState):
return ""
#elif isinstance(obj, Schedule):
# return simplejson.dumps([obj._asdict()], default=alchemyencoder)
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)
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
select_with_relationship()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()