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
Select Git revision
  • dev-old
  • dev-old-david
  • develop
  • lars-tests
  • master
  • master-old
  • topic/filesystem-fallbacks
  • topic/tank_connection
  • topic/tank_connection_david
  • user/equinox/docker
10 results

Target

Select target project
  • aura/engine
  • hermannschwaerzler/engine
  • sumpfralle/aura-engine
3 results
Select Git revision
  • 122-synchronized-ci
  • feat-use-docker-main-tag
  • fix-aura-sysuser
  • fix-broken-pipe-153
  • fix-docker-release
  • fix-push-latest-with-tag
  • fix-streamchannel-retries
  • gitlab-templates
  • improve-test-coverage-137
  • improve-test-coverage-143
  • main
  • orm-less-scheduling
  • remove-mailer
  • update-changelog-alpha3
  • virtual-timeslots-131
  • 1.0.0-alpha1
  • 1.0.0-alpha2
  • 1.0.0-alpha3
  • 1.0.0-alpha4
  • 1.0.0-alpha5
20 results
Show changes
Showing
with 2929 additions and 0 deletions
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if bitrate == 24 or bitrate == 32 or bitrate == 48 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 7, bits_per_sample=8), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 7, bits_per_sample=8), filenamepattern, mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 7, bits_per_sample=8), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 7, bits_per_sample=8), filenamepattern, mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 7, bits_per_sample=8), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 7, bits_per_sample=8), filenamepattern, mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 6, bits_per_sample=16), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 6, bits_per_sample=16), filenamepattern, mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 5, bits_per_sample=16), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 5, bits_per_sample=16), filenamepattern, mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 4, bits_per_sample=16), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 4, bits_per_sample=16), filenamepattern, mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 3, bits_per_sample=32), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 3, bits_per_sample=32), filenamepattern, mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 2, bits_per_sample=32), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 2, bits_per_sample=32), filenamepattern, mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_filesystem_stereo(%flac(samplerate=44100, channels = 2, compression = 1, bits_per_sample=32), filenamepattern, !source))
else
ignore(output_filesystem_mono(%flac(samplerate=44100, channels = 1, compression = 1, bits_per_sample=32), filenamepattern, mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if bitrate == 24 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 24, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 24, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 32 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 32, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 32, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 48 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 48, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 48, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 64, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 64, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 96, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 96, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 128, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 128, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 160, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 160, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 192, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 192, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 224, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 224, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 256, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 256, stereo = false), filenamepattern, mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_filesystem_stereo(%mp3(bitrate = 320, stereo = true), filenamepattern, !source))
else
ignore(output_filesystem_mono(%mp3(bitrate = 320, stereo = false), filenamepattern, mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if bitrate == 24 or bitrate == 32 or bitrate == 48 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=-0.1, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=-0.1, channels = 1), filenamepattern, mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=0, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=0, channels = 1), filenamepattern, mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=0.2, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=0.2, channels = 1), filenamepattern, mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=0.4, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=0.4, channels = 1), filenamepattern, mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=0.5, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=0.5, channels = 1), filenamepattern, mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=0.6, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=0.6, channels = 1), filenamepattern, mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=0.7, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=0.7, channels = 1), filenamepattern, mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=0.8, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=0.8, channels = 1), filenamepattern, mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_filesystem_stereo(%vorbis(quality=0.9, channels = 2), filenamepattern, !source))
else
ignore(output_filesystem_mono(%vorbis(quality=0.9, channels = 1), filenamepattern, mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if bitrate == 24 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 24, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 24, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 32 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 32, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 32, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 48 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 48, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 48, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 64, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 64, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 96, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 96, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 128, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 128, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 160, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 160, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 192, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 192, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 224, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 224, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 256, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 256, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_filesystem_stereo(%opus(bitrate = 320, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, !source))
else
ignore(output_filesystem_mono(%opus(bitrate = 320, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), filenamepattern, mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if bitrate <= 128 then
if stereo then
ignore(output_filesystem_stereo(%wav(stereo=true, channels=2, samplesize=8, header=true, duration=30.), filenamepattern, !source))
else
ignore(output_filesystem_mono(%wav(stereo=false, channels=1, samplesize=8, header=true, duration=30.), filenamepattern, mean(!source)))
end
else
if stereo then
ignore(output_filesystem_stereo(%wav(stereo=true, channels=2, samplesize=16, header=true, duration=30.), filenamepattern, !source))
else
ignore(output_filesystem_mono(%wav(stereo=false, channels=1, samplesize=16, header=true, duration=30.), filenamepattern, mean(!source)))
end
end
\ No newline at end of file
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if bitrate == 24 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 24, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 24, channels = 1), mean(!source)))
end
elsif bitrate == 32 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 32, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 32, channels = 1), mean(!source)))
end
elsif bitrate == 48 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 48, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 48, channels = 1), mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 64, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 64, channels = 1), mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 96, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 96, channels = 1), mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 128, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 128, channels = 1), mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 160, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 160, channels = 1), mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 192, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 192, channels = 1), mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 224, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 224, channels = 1), mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 256, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 256, channels = 1), mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_icecast_stereo(%aac(bitrate = 320, channels = 2), !source))
else
ignore(output_icecast_mono(%aac(bitrate = 320, channels = 1), mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if not icecast_vorbis_metadata then
source := add(normalize=false, [amplify(0.00001, noise()), !source])
end
if bitrate == 24 or bitrate == 32 or bitrate == 48 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 7, bits_per_sample=8), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 7, bits_per_sample=8), mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 7, bits_per_sample=8), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 7, bits_per_sample=8), mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 7, bits_per_sample=8), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 7, bits_per_sample=8), mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 6, bits_per_sample=16), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 6, bits_per_sample=16), mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 5, bits_per_sample=16), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 5, bits_per_sample=16), mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 4, bits_per_sample=16), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 4, bits_per_sample=16), mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 3, bits_per_sample=32), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 3, bits_per_sample=32), mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 2, bits_per_sample=32), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 2, bits_per_sample=32), mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_icecast_stereo(%flac(samplerate=44100, channels = 2, compression = 1, bits_per_sample=32), !source))
else
ignore(output_icecast_mono(%flac(samplerate=44100, channels = 1, compression = 1, bits_per_sample=32), mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if bitrate == 24 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 24, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 24, stereo = false), mean(!source)))
end
elsif bitrate == 32 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 32, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 32, stereo = false), mean(!source)))
end
elsif bitrate == 48 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 48, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 48, stereo = false), mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 64, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 64, stereo = false), mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 96, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 96, stereo = false), mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 128, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 128, stereo = false), mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 160, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 160, stereo = false), mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 192, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 192, stereo = false), mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 224, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 224, stereo = false), mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 256, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 256, stereo = false), mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_icecast_stereo(%mp3(bitrate = 320, stereo = true), !source))
else
ignore(output_icecast_mono(%mp3(bitrate = 320, stereo = false), mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if not icecast_vorbis_metadata then
source := add(normalize=false, [amplify(0.00001, noise()), !source])
end
if bitrate == 24 or bitrate == 32 or bitrate == 48 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=-0.1, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=-0.1, channels = 1), mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=0, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=0, channels = 1), mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=0.2, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=0.2, channels = 1), mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=0.4, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=0.4, channels = 1), mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=0.5, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=0.5, channels = 1), mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=0.6, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=0.6, channels = 1), mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=0.7, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=0.7, channels = 1), mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=0.8, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=0.8, channels = 1), mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_icecast_stereo(%vorbis(quality=0.9, channels = 2), !source))
else
ignore(output_icecast_mono(%vorbis(quality=0.9, channels = 1), mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if bitrate == 24 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 24, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 24, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 32 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 32, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 32, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 48 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 48, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 48, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 64 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 64, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 64, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 96 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 96, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 96, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 128 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 128, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 128, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 160 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 160, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 160, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 192 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 192, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 192, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 224 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 224, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 224, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 256 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 256, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 256, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
elsif bitrate == 320 then
if stereo then
ignore(output_icecast_stereo(%opus(bitrate = 320, channels = 2, signal="music", application="audio", complexity=10, vbr="constrained"), !source))
else
ignore(output_icecast_mono(%opus(bitrate = 320, channels = 1, signal="music", application="audio", complexity=10, vbr="constrained"), mean(!source)))
end
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#################
# Read INI File #
#################
debug = false
def read_ini(file)
# read ini file
settings_file_content = get_process_lines("cat "^file )
# one entry stored as ["setting_name", "setting"]
settings_map = list.map(string.split(separator="="), settings_file_content)
def filter_pair(setting_pair) =
if debug then
print(" +++ IN FILTER_PAIR +++")
end
# get head of settings_pair
setting_name = list.hd(default="", setting_pair)
if debug then
print(" -- setting_name:")
print(setting_name)
end
# settings in ini are with '"', so read them with high comma
setting_unfiltered = list.nth(default="", setting_pair, 1)
if debug then
print(" -- setting_unfiltered:")
print(setting_unfiltered)
end
# filter high comma out. why the hell comes an array ["1", setting] returned?
# the filter patterns are perl regex
setting = string.extract(pattern='"(.*)"', setting_unfiltered)
if debug then
print(" -- setting ( after string.extract):")
print(setting['1'])
end
filtered_pair = [(setting_name, setting['1'])]
if debug then
print(" -- filter_pair returning: --")
print(filtered_pair)
end
# return filtered_pair
filtered_pair
end
def filter_map(filled_list, next_element) =
# if debug then
# print(" +++ IN FILTER_MAP +++")
# print(" .. length of filled_list: ")
# print(list.length(filled_list))
# print(" .. next_element")
# print(next_element)
# end
# the normal case: settingname and its setting
if list.length(next_element) >= 2 then
if debug then
print(" ===> LENGTH list to insert in settings_list is equal TWO! <===")
print(" -- next_element")
print(next_element)
end
setting_pair = filter_pair(next_element)
# add settings_pair to liquidsoap list
#list.add(setting_pair, filled_list)
list.append(setting_pair, filled_list)
else
if list.length(next_element) >= 1 then
if debug then
print(" ===> LENGTH of list to insert in settings_list is equal ONE! <===")
print(" -- next_element")
print(next_element)
end
#
list.append([(list.hd(default="",next_element), "")], filled_list)
else
if debug then
print(" ===> LENGTH of list to insert in settings_list is greater then two or less than one <===")
print(" -- next_element")
print(next_element)
end
# return the list as is
filled_list
end
end
end
# get install dir
pwd = get_process_lines("pwd") # returns an array
absolute_path = list.hd(default="", pwd) # grabs head of array (pwd only returns one line which is our desired result)
install_dir = '"' ^ path.dirname(path.dirname(absolute_path)) ^ '"' # pre and append " for filter_pair function. otherwise this setting would be lost through regex and fold function below would be imho much more difficult to read
# add installdir to the loaded settings from ini file
#settings_map = list.add(["install_dir", install_dir], settings_map)
# list.fold param description:
# f(a,b,c)
# a) a function which is fold
# b) the list which is to be filled (optional with already containing elements)
# c) from this list every element is taken, function applied and inserted into b
# fold function without pre and appended "
settings = list.fold(filter_map, [("install_dir", install_dir)], settings_map)
#settings = list.fold(filter_map, [], settings_map)
if debug then
print(settings)
end
settings
end
if debug then
ini = read_ini("/etc/aura/engine.ini")
print(ini)
print(list.assoc(default="", "install_dir", ini))
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def icy_update(v) =
# Parse the argument
l = string.split(separator=",",v)
def split(l,v) =
v = string.split(separator="=",v)
if list.length(v) >= 2 then
list.append(l,[(list.nth(v,0,default=""),list.nth(v,1,default=""))])
else
l
end
end
meta = list.fold(split,[],l)
# Update metadata
if s0_enable == true then
icy.update_metadata(
mount=s0_mount,
user=s0_user,
password=s0_pass,
host=s0_host,
port=s0_port,
meta
)
end
if s1_enable == true then
icy.update_metadata(
mount=s1_mount,
user=s1_user,
password=s1_pass,
host=s1_host,
port=s1_port,
meta
)
end
if s2_enable == true then
icy.update_metadata(
mount=s2_mount,
user=s2_user,
password=s2_pass,
host=s2_host,
port=s2_port,
meta
)
end
if s3_enable == true then
icy.update_metadata(
mount=s3_mount,
user=s3_user,
password=s3_pass,
host=s3_host,
port=s3_port,
meta
)
end
if s4_enable == true then
icy.update_metadata(
mount=s4_mount,
user=s4_user,
password=s4_pass,
host=s4_host,
port=s4_port,
meta
)
end
"Done!"
end
server.register("update", namespace="metadata",
description="Update metadata",
usage="update title=foo,album=bar, ...",
icy_update)
# shutdown server function
#server.register(namespace='server',
# description="shutdown server",
# usage="stop",
# "stop",
# fun(x,y) -> shutdown )
#
# to reduce complexity of lqs => query 'mixer.inputs' over socket and parse it in python
#server.register(namespace="auraengine",
# "enabled_lineins",
# fun (s) -> begin
# log("auraengine.enabled_lineins")
# "0:#{!linein_0_enabled}, 1:#{!linein_1_enabled}, 2:#{!linein_2_enabled}, 3:#{!linein_3_enabled}, 4:#{!linein_4_enabled}"
# end
#)
#server.register(namespace="auraengine",
# "enabled_lineouts",
# fun(s) -> begin
# log("auraengine.enabled_lineouts")
# "0:#{!lineout_0_enabled}, 1:#{!lineout_1_enabled}, 2:#{!lineout_2_enabled}, 3:#{!lineout_3_enabled}, 4:#{!lineout_4_enabled}"
# end
#)
# are outgoing streams connected?
server.register(namespace="auraengine",
description="returns if outgoing streams are connected",
usage="out_streams_connected",
"out_streams_connected",
fun (s) -> begin
log("streams.connection_status")
"0:#{!s0_connected}, 1:#{!s1_connected}, 2:#{!s2_connected}, 3:#{!s3_connected}, 4:#{!s4_connected}"
end
)
# return a state of the inputs/outputs of the soundserver as JSON
server.register(namespace = "auraengine",
description="returns enabled lineouts/lineins, connected outgoing streams, and recorder. Also returns fallbacksettings.",
usage="state",
"state",
fun(s) -> begin
log("auraengine.state")
ret = '{'
ret = ret^'"streams": {'
ret = ret^'"stream_0": {"enabled": #{s0_enable}, "connected": #{!s0_connected}},'
ret = ret^'"stream_1": {"enabled": #{s1_enable}, "connected": #{!s1_connected}},'
ret = ret^'"stream_2": {"enabled": #{s2_enable}, "connected": #{!s2_connected}},'
ret = ret^'"stream_3": {"enabled": #{s3_enable}, "connected": #{!s3_connected}},'
ret = ret^'"stream_4": {"enabled": #{s4_enable}, "connected": #{!s4_connected}}'
ret = ret^'},'
ret = ret^'"recorder": {'
ret = ret^'"recorder_0": {"enabled": #{r0_enable}, "recording": #{!r0_is_recording}},'
ret = ret^'"recorder_1": {"enabled": #{r1_enable}, "recording": #{!r1_is_recording}},'
ret = ret^'"recorder_2": {"enabled": #{r2_enable}, "recording": #{!r2_is_recording}},'
ret = ret^'"recorder_3": {"enabled": #{r3_enable}, "recording": #{!r3_is_recording}},'
ret = ret^'"recorder_4": {"enabled": #{r4_enable}, "recording": #{!r4_is_recording}}'
ret = ret^'},'
ret = ret^'"linein": {'
ret = ret^'"linein_0": {"enabled": #{a0_in != ""}},'
ret = ret^'"linein_1": {"enabled": #{a1_in != ""}},'
ret = ret^'"linein_2": {"enabled": #{a2_in != ""}},'
ret = ret^'"linein_3": {"enabled": #{a3_in != ""}},'
ret = ret^'"linein_4": {"enabled": #{a4_in != ""}}'
ret = ret^'},'
ret = ret^'"lineout": {'
ret = ret^'"lineout_0": {"enabled": #{a0_out != ""}},'
ret = ret^'"lineout_1": {"enabled": #{a1_out != ""}},'
ret = ret^'"lineout_2": {"enabled": #{a2_out != ""}},'
ret = ret^'"lineout_3": {"enabled": #{a3_out != ""}},'
ret = ret^'"lineout_4": {"enabled": #{a4_out != ""}}'
ret = ret^'}'
ret = ret^'}'
ret
# outgoing streams enabled?
#ret = "stream_0_enabled:#{!s0_enable}, stream_1_enabled:#{!s1_enable}, stream_2_enabled:#{!s2_enable}, stream_3_enabled:#{!s3_enable}, stream_4_enabled:#{!s4_enable}, "
# outgoing recorder enabled
#ret = ret^"recorder_0_enabled:#{r0_enable}, recorder_1_enabled:#{r1_enable}, recorder_2_enabled:#{r2_enable}, recorder_3_enabled:#{r3_enable}, recorder_4_enabled:#{r4_enable}, "
#ret = ret^"linein_0_enabled:#{a0_in != ''}, linein_1_enabled:#{a1_in != ''}, linein_2_enabled:#{a2_in != ''}, linein_3_enabled:#{a3_in != ''}, linein_4_enabled:#{a4_in != ''}, "
#ret = ret^"lineout_0_enabled:#{a0_out != ''}, lineout_1_enabled:#{a1_out != ''}, lineout_2_enabled:#{a2_out != ''}, lineout_3_enabled:#{a3_out != ''}, lineout_4_enabled:#{a4_out != ''}, "
#ret = ret^"fallback_max_blank:#{fallback_max_blank}, fallback_min_noise:#{fallback_min_noise}, fallback_threshold:#{fallback_threshold}"
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)
# Activate a source by selecting it and setting the volume to 100 (or vice versa)
def activate(p) =
params=string.split(separator=" ", p)
if list.length(params) < 2 then
print(p)
"Usage: mixer.activate <source nb> <true|false>"
else
source_number = list.nth(default="0", params, 0)
source_enable = list.nth(default="false", params, 1)
if source_enable == "true" then
r = server.execute("mixer.select #{source_number} true")
print(r)
r = server.execute("mixer.volume #{source_number} 100")
print(r)
else
r = server.execute("mixer.volume #{source_number} 0")
print(r)
r = server.execute("mixer.select #{source_number} false")
print(r)
end
"Done!"
end
end
server.register(namespace = "mixer",
description = "is selecting a source and setting the volume to 100",
usage = "activate <source nb> <true|false>",
"activate",
activate
)
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# READ INI FILE
%include "readini.liq"
default_config = "../../configuration/engine.ini"
config = list.hd(default=default_config, get_process_lines("ls /etc/aura/engine.ini"))
log("Config file used: #{config}")
ini = read_ini(config)
engine_config_folder = string.split(separator="/engine.ini", config)
engine_config_folder = list.nth(default="../../configuration/", engine_config_folder, 0)
# ALLOW LIQUIDSOAP RUN AS ROOT
lqs_allow_root = list.assoc(default="false", "liquidsoap_as_root", ini)
if lqs_allow_root == "true" then
set("init.allow_root", true)
end
# BASICS
set("console.colorize","always")
# TELNET SETTINGS
set("server.telnet", true)
set("server.telnet.bind_addr", "0.0.0.0")
set("server.telnet.port", 1234)
# LOG FILE SETTINGS
log_dir = list.assoc(default="", "logdir", ini)
set("log.file.path", "#{log_dir}/engine-lqs.log")
# SOCKET SETTINGS
set("server.socket", true)
set("server.socket.path", "./<script>.sock")
#set("request.grace_time",2.)
# SOUND CARD SETTINGS
#print(ini)
#a0_in = ini['input_device_0'][0]
a0_in = list.assoc(default="", "input_device_0", ini)
a1_in = list.assoc(default="", "input_device_1", ini)
a2_in = list.assoc(default="", "input_device_2", ini)
a3_in = list.assoc(default="", "input_device_3", ini)
a4_in = list.assoc(default="", "input_device_4", ini)
a0_out = list.assoc(default="", "output_device_0", ini)
a1_out = list.assoc(default="", "output_device_1", ini)
a2_out = list.assoc(default="", "output_device_2", ini)
a3_out = list.assoc(default="", "output_device_3", ini)
a4_out = list.assoc(default="", "output_device_4", ini)
input_stream_buffer = float_of_string(list.assoc(default="3.0", "input_stream_buffer", ini))
# FALLBACK SETTINGS
fallback_station_playlist_path = "#{engine_config_folder}/playlists/station-fallback-playlist.m3u"
fallback_station_playlist_path = list.assoc(default=fallback_station_playlist_path, "fallback_music_playlist", ini)
fallback_station_dir = list.assoc(default="/var/audio/station", "fallback_music_folder", ini)
fallback_station_dir_reload = int_of_string(list.assoc(default="300", "fallback_music_folder_reload", ini))
fallback_max_blank = float_of_string(list.assoc(default="", "fallback_max_blank", ini))
fallback_min_noise = float_of_string(list.assoc(default="", "fallback_min_noise", ini))
fallback_threshold = float_of_string(list.assoc(default="", "fallback_threshold", ini))
# FADING SETTINGS
fade_in_time = list.assoc(default="", "fade_in_time", ini) #int_of_string(list.assoc(default="", "fade_in_time", ini))
fade_out_time = list.assoc(default="", "fade_out_time", ini) #int_of_string(list.assoc(default="", "fade_out_time", ini))
# RECORDER SETTINGS
#rec_0_filetype = list.assoc(default="", "rec_0_filetype", ini)
#rec_1_filetype = list.assoc(default="", "rec_1_filetype", ini)
#rec_2_filetype = list.assoc(default="", "rec_2_filetype", ini)
#rec_3_filetype = list.assoc(default="", "rec_3_filetype", ini)
#rec_4_filetype = list.assoc(default="", "rec_4_filetype", ini)
# ALSA / pulse settings
soundsystem = list.assoc(default="", "soundsystem", ini)
use_alsa = soundsystem == "alsa"
use_jack = soundsystem == "jack"
if use_alsa then
frame_duration = float_of_string(list.assoc(default="", "frame_duration", ini))
frame_size = int_of_string(list.assoc(default="", "frame_size", ini))
alsa_buffer = int_of_string(list.assoc(default="", "alsa_buffer", ini))
alsa_buffer_length = int_of_string(list.assoc(default="", "alsa_buffer_length", ini))
alsa_periods = int_of_string(list.assoc(default="", "alsa_periods", ini))
if frame_duration > 0.0 then
print("setting frame.duration to #{frame_duration}s")
set("frame.duration", frame_duration)
end
if frame_size > 0 then
print("setting frame.size to #{frame_size}")
set("frame.audio.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
print("**************************************************************************************")
print(" AURA ENGINE - LIQUIDSOAP SETTINGS ")
print("**************************************************************************************")
print(" Engine Configuration Folder: #{engine_config_folder}")
print(" Station Fallback Playlist: #{fallback_station_playlist_path}")
print(" Station Fallback Directory: #{fallback_station_dir}")
print("**************************************************************************************")
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Crossfade between tracks,
# taking the respective volume levels
# into account in the choice of the
# transition.
# @category Source / Track Processing
# @param ~start_next Crossing duration, if any.
# @param ~fade_in Fade-in duration, if any.
# @param ~fade_out Fade-out duration, if any.
# @param ~width Width of the volume analysis window.
# @param ~conservative Always prepare for
# a premature end-of-track.
# @param s The input source.
def crossfade (~start_next=5.,~fade_in=3.,
~fade_out=3., ~width=2.,
~conservative=false,s)
high = -20.
medium = -32.
margin = 4.
fade.out = fade.out(type="sin",duration=fade_out)
fade.in = fade.in(type="sin",duration=fade_in)
add = fun (a,b) -> add(normalize=false,[b,a])
log = log(label="crossfade")
def transition(a,b,ma,mb,sa,sb)
list.iter(fun(x)->
log(level=4,"Before: #{x}"),ma)
list.iter(fun(x)->
log(level=4,"After : #{x}"),mb)
if
# If A and B and not too loud and close,
# fully cross-fade them.
a <= medium and
b <= medium and
abs(a - b) <= margin
then
log("Transition: crossed, fade-in, fade-out.")
add(fade.out(sa),fade.in(sb))
elsif
# If B is significantly louder than A,
# only fade-out A.
# We don't want to fade almost silent things,
# ask for >medium.
b >= a + margin and a >= medium and b <= high
then
log("Transition: crossed, fade-out.")
add(fade.out(sa),sb)
elsif
# Do not fade if it's already very low.
b >= a + margin and a <= medium and b <= high
then
log("Transition: crossed, no fade-out.")
add(sa,sb)
elsif
# Opposite as the previous one.
a >= b + margin and b >= medium and a <= high
then
log("Transition: crossed, fade-in.")
add(sa,fade.in(sb))
# What to do with a loud end and
# a quiet beginning ?
# A good idea is to use a jingle to separate
# the two tracks, but that's another story.
else
# Otherwise, A and B are just too loud
# to overlap nicely, or the difference
# between them is too large and
# overlapping would completely mask one
# of them.
log("No transition: just sequencing.")
sequence([sa, sb])
end
end
cross(width=width, duration=start_next,
conservative=conservative,
transition,s)
end
# Custom crossfade to deal with jingles.
# def smarter_crossfade (~start_next=5.,~fade_in=3.,~fade_out=3.,
# ~default=(fun (a,b) -> sequence([a, b])),
# ~high=-15., ~medium=-32., ~margin=4.,
# ~width=2.,~conservative=false,s)
# fade.out = fade.out(type="sin",duration=fade_out)
# fade.in = fade.in(type="sin",duration=fade_in)
# add = fun (a,b) -> add(normalize=false,[b, a])
# log = log(label="smarter_crossfade")
# def transition(a,b,ma,mb,sa,sb)
# list.iter(fun(x)-> log(level=4,"Before: #{x}"),ma)
# list.iter(fun(x)-> log(level=4,"After : #{x}"),mb)
# if ma["type"] == "jingles" or mb["type"] == "jingles" then
# log("Old or new file is a jingle: sequenced transition.")
# sequence([sa, sb])
# elsif
# # If A and B are not too loud and close, fully cross-fade them.
# a <= medium and b <= medium and abs(a - b) <= margin
# then
# log("Old <= medium, new <= medium and |old-new| <= margin.")
# log("Old and new source are not too loud and close.")
# log("Transition: crossed, fade-in, fade-out.")
# add(fade.out(sa),fade.in(sb))
# elsif
# # If B is significantly louder than A, only fade-out A.
# # We don't want to fade almost silent things, ask for >medium.
# b >= a + margin and a >= medium and b <= high
# then
# log("new >= old + margin, old >= medium and new <= high.")
# log("New source is significantly louder than old one.")
# log("Transition: crossed, fade-out.")
# add(fade.out(sa),sb)
# elsif
# # Opposite as the previous one.
# a >= b + margin and b >= medium and a <= high
# then
# log("old >= new + margin, new >= medium and old <= high")
# log("Old source is significantly louder than new one.")
# log("Transition: crossed, fade-in.")
# add(sa,fade.in(sb))
# elsif
# # Do not fade if it's already very low.
# b >= a + margin and a <= medium and b <= high
# then
# log("new >= old + margin, old <= medium and new <= high.")
# log("Do not fade if it's already very low.")
# log("Transition: crossed, no fade.")
# add(sa,sb)
# # What to do with a loud end and a quiet beginning ?
# # A good idea is to use a jingle to separate the two tracks,
# # but that's another story.
# else
# # Otherwise, A and B are just too loud to overlap nicely,
# # or the difference between them is too large and overlapping would
# # completely mask one of them.
# log("No transition: using default.")
# default(sa, sb)
# end
# end
# #smart_cross(width=width, duration=start_next, conservative=conservative, transition, s)
# smart_crossfade(duration=start_next, fade_in=fade_in, fade_out=fade_out, width=width, conservative=conservative, transition, s)
# end
# create a pool
def fallback_create(~skip=true, name, requestor)
log("Creating channel #{name}")
# Create the request.dynamic source
# Set conservative to true to queue several songs in advance
#source = request.dynamic(conservative=true, length=50., id="pool_"^name, requestor, timeout=60.)
source = request.dynamic(length=50., id="pool_"^name, requestor, timeout=60.)
# Apply normalization using replaygain information
source = amplify(1., override="replay_gain", source)
# Skip blank when asked to
source =
if skip then
skip_blank(max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold, source)
else
source
end
# Tell the system when a new track is played
def do_meta(meta) =
filename = meta["filename"]
# artist = meta["artist"]
# title = meta["title"]
system('#{list.assoc(default="", "install_dir", ini)}/guru.py --on_play "#{filename}"')
end
source = on_metadata(do_meta, source)
log("channel created")
# Finally apply a smart crossfading
#smarter_crossfade(source)
crossfade(source)
end
def create_dynamic_playlist(next)
request.create(list.hd(default="", next))
end
def create_playlist() =
log("requesting next song for PLAYLIST")
result = get_process_lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for "playlist" --quiet')
create_dynamic_playlist(result)
end
def create_station_fallback() =
result = get_process_lines('#{list.assoc(default="", "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() =
result = get_process_lines('#{list.assoc(default="", "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() =
result = get_process_lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for timeslot --quiet')
log("next song for TIMESLOT fallback is: #{result}")
create_dynamic_playlist(result)
end
# 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)
\ No newline at end of file
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# %include "readini.liq"
# ini = read_ini("/etc/aura/engine.ini")
# %include "settings.liq"
# TELNET SETTINGS
# set("server.telnet", true)
# set("server.telnet.bind_addr", "0.0.0.0")
# set("server.telnet.port", 2345)
inst = if argv(1) != "" then string_of(argv(1)) else 'record' end
instance = ref inst
audiobase = if !instance == 'record' then list.assoc(default="", "audiobase", ini) else list.assoc(default="", "altaudiobase", ini) end
rec_filetype = list.assoc(default="", "rec_filetype", ini)
filenamepattern = ref audiobase^"/%Y-%m-%d/%Y-%m-%d-%H-%M.flac"
# 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
is_record_active = ref false
# Stop dump - wir definieren die Funktion, die stop_f ausführt
def stop_dump() =
f = !stop_f
f ()
end
def on_start()
recordingfile := list.hd(default="", get_process_lines("date +#{!filenamepattern}"))
end
# Wav header fixen und ggf. die Aufzeichnung beenden
def on_close(filename)
# es darf wieder aufgenommen werden
is_record_active := false
# if list.assoc(default="", "rec_filetype", ini) == 'wav'
# # Korrekten WAV-Header schreiben
# system("qwavheaderdump -F #{filename}")
# Naechsten Dateinamen vormerken
recordingfile := list.hd(default="", get_process_lines("date +#{!filenamepattern}"))
end
# Der input wie oben definiert
def get_input()
output_source
# input.alsa()
end
def get_output()
input = get_input()
d = int_of_string(list.assoc(default="", "rec_duration", ini))
if rec_filetype == 'flac' then
log("output file type is FLAC")
output.file(
id="recorder",
%flac(samplerate=44100, channels=2, compression=5, bits_per_sample=16),
perm = 0o664,
on_start=on_start,
!filenamepattern,
on_close=on_close,
reopen_when={ if !instance == 'record' then int_of_float(gettimeofday()/60.) mod 30 == 0 else false end },
input
)
else
# record in WAV
log("output file type is WAV")
output.file(
id="recorder",
%wav(stereo=true, channels=2, samplesize=16, header=true),
perm = 0o664,
on_start=on_start,
!filenamepattern,
on_close=on_close,
reopen_when={ if !instance == 'record' then int_of_float(gettimeofday()/60.) mod d == 0 else false end },
input
)
end
end
# Funktion gibt Auskunft welches File aktuell ist und wieviel Prozent bereits aufgenommen werden
def currecording()
curfile = !recordingfile
if curfile != "" then
percent_done = default="", get_process_lines("echo $(($(stat -c%s "^curfile^")/3174777))"))
"#{curfile}, #{percent_done}%"
else
"Nothing is being recorded now"
end
end
#Funktion zum Start der Aufzeichnung
def start_dump() =
log('start dump')
# don't record twice is_record_active
if !is_record_active == false then
is_record_active := true
log('starting to record')
record = get_output()
log('record defined')
# 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
else
log("recorder already active")
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"))
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# define file name pattern
filenamepattern = ref audiobase^"/%Y-%m-%d/%Y-%m-%d-%H-%M.flac"
# Der aktuelle Dateiname für die Aufnahme
recordingfile = ref ""
# bewahre uns davor, dass zweimal gleichzeitig die gleiche Date aufgenommen wird
is_record_active = ref false
#wir definieren eine Referenz für die Stop-Funktion, die aber bisher noch nichts tun kann
stop_f = ref (fun () -> ())
def start_wav_output(recfile, filenamepattern, recorder_number)
duration = int_of_string(list.assoc(default="", "rec_"^recorder_number^"_duration", ini))
channels = int_of_string(list.assoc(default="", "rec_"^recorder_number^"_channels", ini))
samplesize = int_of_string(list.assoc(default="", "rec_"^recorder_number^"_samplesize", ini))
ignore(duration)
ignore(channels)
ignore(samplesize)
# def on_start()
# recfile := list.hd(default="", get_process_lines("date +#{!filenamepattern}"))
# end
# def on_close(filename)
# recordingfile := list.hd(default="", get_process_lines("date +#{!filenamepattern}"))
# end
print(channels)
print(samplesize)
#out_wav = output.file(id="recorder", perm = 0o664, on_start=on_start, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 })
#out_wav(%wav(stereo=true, channels=2, samplesize=8, header=true, !filenamepattern, output_source)
#ignore(out_wav)
output.dummy(id="wav_dummy_recording", blank())
# if channels == 2 then
# output.file(id="recorder", %wav(stereo=true, channels=2, samplesize=8, header=true), perm = 0o664, on_start=on_start, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 }, !filenamepattern, audio_to_stereo(output_source))
## out_wav(output_source)
# else
# output.file(id="recorder", %wav(stereo=true, channels=1, samplesize=8, header=true), perm = 0o664, on_start=on_start, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 }, !filenamepattern, output_source)
# out_wav(output_source)
# end
#if channels == 2 then
# if samplesize < 12 then
# ignore(out_wav(%wav(stereo=true, channels=2, samplesize=8, header=true), !filenamepattern, output_source))
# else
# ignore(out_wav(%wav(stereo=true, channels=2, samplesize=16, header=true), !filenamepattern, output_source))
# end
#else
# if samplesize < 12 then
# ignore(out_wav(%wav(stereo=true, channels=1, samplesize=8, header=true), !filenamepattern, output_source))
# else
# ignore(out_wav(%wav(stereo=true, channels=1, samplesize=16, header=true), !filenamepattern, output_source))
# end
#end
end
def start_flac_output(recorder_number, filenamepattern, duration)
# duration = int_of_string(list.assoc(default="", "rec_"^recorder_number^"_samplerate", ini))
# samplerate = list.assoc(default="", "rec_"^recorder_number^"_samplerate", ini)
channels = int_of_string(list.assoc(default="", "rec_"^recorder_number^"_channels", ini))
# compression = int_of_string(list.assoc(default="", "rec_"^recorder_number^"_compression", ini))
# bits_per_sample = int_of_string(list.assoc(default="", "rec_"^recorder_number^"_bits_per_sample", ini))
#output.file(id="recorder", %flac(samplerate=44100, channels=1, compression=7, bits_per_sample=32), perm = 0o664, on_start=on_start, !filenamepattern, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 }, output_source)
recfile = ref ''
def on_start()
recfile := list.hd(default="", get_process_lines("date +#{filenamepattern}"))
end
def on_close(filename)
recfile := list.hd(default="", get_process_lines("date +#{filenamepattern}"))
end
# dumbass liquidsoap cannot handle one output definition for mono and stereo
output_filesystem_mono = output.file(id="recorder", perm = 0o664, on_start=on_start, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 })
output_filesystem_stereo = output.file(id="recorder", perm = 0o664, on_start=on_start, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 })
ignore(output_filesystem_mono)
ignore(output_filesystem_stereo)
if channels == 2 then
output.dummy(id="flac_dummy_recording_stereo", blank())
# output_stereof(%flac(samplerate=44100, channels=1, compression=1, bits_per_sample=16), !filenamepattern, output_source)
else
output.dummy(id="flac_dummy_recording_mono", blank())
# output_icecast_monof(%flac(samplerate=44100, channels=1, compression=1, bits_per_sample=16), !filenamepattern, output_source)
end
end
def enable_stop_function(record)
# 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
def set_recorder_output(rec_filetype, recorder_number)
# flac output
if rec_filetype == 'flac' then
log("output file type is FLAC")
record = start_flac_output(recorder_number)
enable_stop_function(record)
# WAV output
else
log("output file type is WAV")
record = start_wav_output(recorder_number)
enable_stop_function(record)
end
end
# shows current file and how many bytes were written so far
def currecording()
curfile = !recordingfile
if curfile != "" then
bytes_written = list.hd(default="", get_process_lines("echo $(($(stat -c%s "^curfile^")))"))
"#{curfile}, #{bytes_written}B"
else
""
end
end
\ No newline at end of file
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def get_icecast_mp3_stream(number)
stream_bitrate = int_of_string(list.assoc(default="", "stream_#{number}_bitrate", ini))
stream_mountpoint = list.assoc(default="", "stream_#{number}_mountpoint", ini)
stream_host = list.assoc(default="", "stream_#{number}_host", ini)
stream_port = int_of_string(list.assoc(default="", "stream_#{number}_port", ini))
stream_name = list.assoc(default="", "stream_#{number}_name", ini)
stream_url = list.assoc(default="", "stream_#{number}_url", ini)
stream_genre = list.assoc(default="", "stream_#{number}_genre", ini)
stream_description = list.assoc(default="", "stream_#{number}_description", ini)
stream_user = list.assoc(default="", "stream_#{number}_user", ini)
stream_password = list.assoc(default="", "stream_#{number}_password", ini)
if stream_bitrate == 24 then
icecast_stream = 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, buffer(output_source))
clock.assign_new(id="stream_#{number}",
[icecast_stream]
)
icecast_stream
else
icecast_stream = output.icecast(%mp3(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, buffer(output_source))
clock.assign_new(id="stream_#{number}",
[icecast_stream]
)
icecast_stream
end
end
def get_icecast_ogg_stream(number)
stream_quality = float_of_string(list.assoc(default="", "stream_#{number}_quality", ini))
stream_mountpoint = list.assoc(default="", "stream_#{number}_mountpoint", ini)
stream_host = list.assoc(default="", "stream_#{number}_host", ini)
stream_port = int_of_string(default="", list.assoc("stream_#{number}_port", ini))
stream_name = list.assoc(default="", "stream_#{number}_name", ini)
stream_url = list.assoc(default="", "stream_#{number}_url", ini)
stream_genre = list.assoc(default="", "stream_#{number}_genre", ini)
stream_description = list.assoc(default="", "stream_#{number}_description", ini)
stream_user = list.assoc(default="", "stream_#{number}_user", ini)
stream_password = list.assoc(default="", "stream_#{number}_password", ini)
if stream_quality >= 0.5 then
icecast_stream = output.icecast(%vorbis(quality = 1.0), 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, buffer(output_source))
clock.assign_new(id="stream_#{number}",
[icecast_stream]
)
icecast_stream
else
icecast_stream = output.icecast(%vorbis(quality = 0.1), 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, buffer(output_source))
clock.assign_new(id="stream_#{number}",
[icecast_stream]
)
icecast_stream
end
end
def get_harbour_mp3_stream(number)
stream_bitrate = int_of_string(list.assoc(default="", "stream_#{number}_bitrate", ini))
stream_user = list.assoc(default="", "stream_#{number}_user", ini)
stream_password = list.assoc(default="", "stream_#{number}_password", ini)
stream_port = int_of_string(list.assoc(default="", "stream_#{number}_port", ini))
stream_url = list.assoc(default="", "stream_#{number}_url", ini)
stream_mountpoint = list.assoc(default="", "stream_#{number}_mountpoint", ini)
if stream_bitrate == 24 then
harbourstream = 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, buffer(output_source))
clock.assign_new(id="stream_#{number}",
[harbourstream]
)
harbourstream
else
harbourstream = output.harbor(%mp3, user=stream_user, password=stream_password, id="stream", port=stream_port, url=stream_url, mount=stream_mountpoint, icy_metadata="true", fallible=true, buffer(output_source))
clock.assign_new(id="stream_#{number}",
[harbourstream]
)
harbourstream
end
end
def get_stream(number)
stream = list.assoc(default="", "stream_#{number}", ini)
stream_type = list.assoc(default="", "stream_#{number}_type", ini)
stream_format = list.assoc(default="", "stream_#{number}_format", ini)
# is stream enabled?
if stream == "y" then
log("activating stream #{number}")
if stream_type == "icecast" then
log("its an icecast stream")
if stream_format == "mp3" then
log("filled with mp3")
get_icecast_mp3_stream(number)
elsif stream_format == "vorbis" then
log("filled with ogg")
get_icecast_ogg_stream(number)
else
output.dummy(id="no_valid_stream_format_DUMMY_ICECAST", blank())
end
elsif stream_type == "harbor" then
log("its an harbor stream")
if stream_format == "mp3" then
get_harbour_mp3_stream(number)
else
output.dummy(id="no_valid_stream_format_DUMMY_HARBOUR", blank())
end
else
output.dummy(id="no_valid_stream_type_DUMMY", blank())
end
else
output.dummy(id="no_stream_enabled_DUMMY", blank())
end
end
def set_streams()
stream_0 = get_stream(0)
#stream_1 = get_stream(1)
#stream_2 = get_stream(2)
#stream_3 = get_stream(3)
#stream_4 = get_stream(4)
end
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import datetime
import urllib
import logging
import json
import requests
import threading
import platform
from enum import Enum
from socket import socket, AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST
import meta
from modules.cli.redis.adapter import ClientRedisAdapter
from modules.base.utils import SimpleUtil as SU
from modules.base.mail import AuraMailer
# Exceptions
class EngineMalfunctionException(Exception):
pass
# Status Codes
class MonitorResponseCode(Enum):
OK = "OK"
INVALID_STATE = "INVALID-STATE"
class AuraMonitor:
"""
Engine Monitoring is in charge of:
- Checking the overall status of all components and external API endpoints
- Checking the vital parts, which are minimal requirements for running the engine
- Sending a heartbeat to a defined server via socket
"""
logger = None
engine = None
mailer = None
status = None
already_invalid = None
engine_id = None
heartbeat_server = None
heartbeat_port = None
heartbeat_frequency = None
heartbeat_socket = None
heartbeat_running = None
def __init__(self, config, engine):
"""
Initialize Monitoring
"""
self.logger = logging.getLogger("AuraEngine")
self.config = config
self.engine = engine
self.mailer = AuraMailer(self.config)
self.status = dict()
self.status["engine"] = dict()
self.status["lqs"] = dict()
self.status["redis"] = dict()
self.status["api"] = dict()
self.status["api"]["steering"] = dict()
self.status["api"]["tank"] = dict()
self.status["api"]["engine"] = dict()
self.already_invalid = False
# Register as an engine plugin
self.engine.plugins["monitor"] = self
# Heartbeat settings
self.heartbeat_running = False
self.heartbeat_server = config.get("heartbeat_server")
self.heartbeat_port = config.get("heartbeat_port")
self.heartbeat_frequency = config.get("heartbeat_frequency")
self.heartbeat_socket = socket(AF_INET, SOCK_DGRAM)
self.engine_id = self.get_engine_id()
#
# EVENTS
#
def on_boot(self):
"""
Called when the engine is booting.
"""
# Start Monitoring
is_valid = self.has_valid_status(False)
status = self.get_status()
self.logger.info("Status Monitor:\n%s" % json.dumps(status, indent=4))
if not is_valid:
self.logger.info("Engine Status: " + SU.red(status["engine"]["status"]))
self.post_health(status, False)
raise EngineMalfunctionException
else:
self.logger.info("Engine Status: " + SU.green("[OK]"))
self.post_health(status, True)
def on_sick(self, data):
"""
Called when the engine is in some unhealthy state.
"""
self.post_health(data, False)
def on_resurrect(self, data):
"""
Called when the engine turned healthy again after being sick.
"""
self.post_health(data, True)
#
# PUBLIC METHODS
#
def get_status(self):
"""
Retrieves the current monitoring status.
"""
return self.status
def has_valid_status(self, update_vitality_only):
"""
Checks if the current status is valid to run engine. By default it
does not request new status information, rather using the cached one.
To request new data either call `get_status()` before or use the
`update_vital` parameter.
Args:
update_vitality_only (Boolean): Refreshes only the vital parts required for the heartbeat
"""
is_valid = False
if update_vitality_only:
self.update_vitality_status()
else:
self.update_status()
try:
if self.status["lqs"]["active"] \
and self.status["lqs"]["mixer"]["in_filesystem_0"] \
and self.status["redis"]["active"] \
and self.status["audio_source"]["exists"]:
self.status["engine"]["status"] = MonitorResponseCode.OK.value
is_valid = True
else:
self.status["engine"]["status"] = MonitorResponseCode.INVALID_STATE.value
except Exception as e:
self.logger.error("Exception while validating engine status: " + str(e))
self.status["engine"]["status"] = MonitorResponseCode.INVALID_STATE.value
return is_valid
#
# PRIVATE METHODS
#
def post_health(self, data, is_healthy):
"""
Post unhealthy state info to Engine API.
"""
body = dict()
body["log_time"] = datetime.datetime.now()
body["is_healthy"] = is_healthy
body["details"] = json.dumps(data, default=str)
json_data = json.dumps(body, default=str)
url = self.config.get("api_engine_store_health")
url = url.replace("${ENGINE_NUMBER}", str(self.config.get("api_engine_number")))
headers = {'content-type': 'application/json'}
r = requests.post(url, data=json_data, headers=headers)
if r.status_code == 204:
self.logger.info("Successfully posted healthy=%s state to Engine API!" % is_healthy)
else:
self.logger.error("HTTP %s | Error while pushing health state to Engine API: %s" % (r.status_code, str(r.json())))
def update_status(self):
"""
Requests the current status of all components
"""
self.status["engine"]["version"] = meta.__version__
self.engine.player.connector.enable_transaction()
self.status["lqs"]["version"] = self.engine.version()
self.status["lqs"]["uptime"] = self.engine.uptime()
self.status["lqs"]["io"] = self.get_io_state()
self.status["lqs"]["mixer"] = self.engine.player.mixer.mixer_status()
self.status["lqs"]["mixer_fallback"] = self.engine.player.mixer_fallback.mixer_status()
self.engine.player.connector.disable_transaction()
self.status["api"]["steering"]["url"] = self.config.get("api_steering_status")
self.status["api"]["steering"]["available"] = self.validate_url_connection(self.config.get("api_steering_status"))
self.status["api"]["tank"]["url"] = self.config.get("api_tank_status")
self.status["api"]["tank"]["available"] = self.validate_url_connection(self.config.get("api_tank_status"))
self.status["api"]["tank"]["status"] = self.get_url_response(self.config.get("api_tank_status"))
self.status["api"]["engine"]["url"] = self.config.get("api_engine_status")
self.status["api"]["engine"]["available"] = self.validate_url_connection(self.config.get("api_engine_status"))
self.update_vitality_status()
def update_vitality_status(self):
"""
Refreshes the vital status info which are required for the engine to survive.
"""
self.engine.player.connector.enable_transaction()
self.status["lqs"]["active"] = self.engine.is_active()
self.engine.player.connector.disable_transaction()
self.status["redis"]["active"] = self.validate_redis_connection()
self.status["audio_source"] = self.validate_directory(self.config.get("audio_source_folder"))
# After first update start the Heartbeat Monitor
if not self.heartbeat_running:
self.heartbeat_running = True
if self.config.get("heartbeat_frequency") > 0:
self.heartbeat()
def heartbeat(self):
"""
Every `heartbeat_frequency` seconds the current vitality status is checked. If it's okay,
a heartbeat is sent to the configured server.
"""
if self.has_valid_status(True):
self.heartbeat_socket.sendto(str.encode("OK"), (self.heartbeat_server, self.heartbeat_port))
# Engine resurrected into normal state
if self.already_invalid:
self.already_invalid = False
status = json.dumps(self.get_status())
self.logger.info(SU.green("OK - Engine turned back into some healthy state!")+"\n"+str(status))
self.mailer.send_admin_mail( \
"OK - Engine turned back into some HEALTHY STATE!", \
"Things seem fine again at '%s':\n\n%s" % (self.engine_id, status))
# Route call of event via event dispatcher to provide ability for additional hooks
self.engine.event_dispatcher.on_resurrect(status)
else:
# Engine turned into invalid state
if not self.already_invalid:
self.already_invalid = True
status = json.dumps(self.get_status())
self.logger.critical(SU.red("Engine turned into some INVALID STATE!")+"\n"+str(status))
self.mailer.send_admin_mail( \
"ERROR - Engine turned into some INVALID STATE!", \
"There's an issue with Aura Engine '%s':\n\n%s" % (self.engine_id, status))
# Route call of event via event dispatcher to provide ability for additional hooks
self.engine.event_dispatcher.on_sick(status)
threading.Timer(self.config.get("heartbeat_frequency"), self.heartbeat).start()
def get_io_state(self):
"""
Retrieves all input and outputs provided by the engine.
"""
ios = self.engine.engine_state()
try:
ios = ios.replace('"connected":', '"connected": ""')
ios = json.loads(ios, strict=False)
return ios
except Exception as e:
self.logger.warn("Got invalid JSON from Liquidsoap - " + str(e))
return MonitorResponseCode.INVALID_STATE.value
def validate_url_connection(self, url):
"""
Checks if connection to passed URL is successful.
"""
try:
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
response.read()
except Exception:
return False
return True
def validate_redis_connection(self):
"""
Checks if the connection to Redis is successful.
"""
try:
cra = ClientRedisAdapter(self.config)
cra.publish("aura", "status")
except:
return False
return True
def validate_directory(self, dir_path):
"""
Checks if a given directory is existing and holds content
"""
status = dict()
status["path"] = dir_path
status["exists"] = os.path.exists(dir_path) and os.path.isdir(dir_path)
status["has_content"] = False
if status["exists"]:
status["has_content"] = any([True for _ in os.scandir(dir_path)])
return status
def get_url_response(self, url):
"""
Fetches JSON data from the given URL.
Args:
url (String): The API endpoint to call
Returns:
(dict[]): A Python object representing the JSON structure
"""
data = None
try:
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
data = response.read()
return json.loads(data, strict=False)
except (urllib.error.URLError, IOError, ValueError) as e:
self.logger.error("Error while connecting to URL '%s' - %s" % (url, e))
return MonitorResponseCode.INVALID_STATE.value
def get_engine_id(self):
"""
Retrieves a String identifier consisting of IP and Hostname to differentiate
the engine in mails and status broadcasts.
"""
host = platform.node()
return "%s (%s)" % (self.get_ip(), host)
def get_ip(self):
"""
Returns the IP of the Engine instance.
"""
try:
s = socket(AF_INET, SOCK_DGRAM)
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
s.connect(('<broadcast>', 0))
return s.getsockname()[0]
except:
self.logger.critical(SU.red("Error while accessing network via <broadcast>!"))
return "<UNKNOWN NETWORK>"
\ No newline at end of file
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import logging
import requests
from modules.base.utils import SimpleUtil as SU
from modules.core.resources import ResourceUtil
class TrackServiceHandler():
"""
Sends the trackservice entry and studio clock information to the `engine-api` REST endpoint.
"""
logger = None
config = None
soundsystem = None
def __init__(self, config, soundsystem):
"""
Initialize.
"""
self.logger = logging.getLogger("AuraEngine")
self.config = config
self.soundsystem = soundsystem
def on_play(self, entry):
"""
Some track started playing.
"""
self.store_trackservice(entry)
self.store_clock_info(entry)
def store_trackservice(self, entry):
"""
Posts the given `PlaylistEntry` to the Engine API Playlog.
"""
data = dict()
diff = (entry.entry_start_actual - entry.entry_start).total_seconds()
self.logger.info("There's a difference of %s seconds between planned and actual start of the entry" % diff)
data["track_start"] = entry.entry_start_actual
if entry.meta_data:
data["track_artist"] = entry.meta_data.artist
data["track_album"] = entry.meta_data.album
data["track_title"] = entry.meta_data.title
data["track_duration"] = entry.duration
data["track_num"] = entry.entry_num
content_class = ResourceUtil.get_content_class(entry.get_content_type())
data["track_type"] = content_class.numeric
data["playlist_id"] = entry.playlist.playlist_id
data["schedule_id"] = entry.playlist.schedule.schedule_id
data["show_id"] = entry.playlist.schedule.show_id
data["show_name"] = entry.playlist.schedule.show_name
data["log_source"] = self.config.get("api_engine_number")
data = SU.clean_dictionary(data)
self.logger.info("Posting schedule update to Engine API...")
url = self.config.get("api_engine_store_playlog")
headers = {'content-type': 'application/json'}
body = json.dumps(data, indent=4, sort_keys=True, default=str)
response = requests.post(url, data=body, headers=headers)
self.logger.info("Engine API response: %s" % response.status_code)
def store_clock_info(self, entry):
"""
Posts the current and next show information to the Engine API.
"""
current_playlist = self.soundsystem.scheduler.get_active_playlist()
current_schedule = current_playlist.schedule
next_schedule = self.soundsystem.scheduler.get_next_schedules(1)
if next_schedule: next_schedule = next_schedule[0]
data = dict()
data["engine_source"] = self.config.get("api_engine_number")
if current_playlist:
data["current_playlist"] = dict()
data["current_playlist"]["playlist_id"] = current_playlist.playlist_id
data["current_playlist"]["entries"] = []
for e in current_playlist.entries:
entry = dict()
entry["track_start"] = e.entry_start
if e.meta_data:
entry["track_artist"] = e.meta_data.artist
entry["track_album"] = e.meta_data.album
entry["track_title"] = e.meta_data.title
entry["track_num"] = e.entry_num
entry["track_duration"] = e.duration
content_class = ResourceUtil.get_content_class(e.get_content_type)
entry["track_type"] = content_class.numeric
entry = SU.clean_dictionary(entry)
data["current_playlist"]["entries"].append(entry)
if current_schedule:
cs = dict()
cs["schedule_id"] = current_schedule.schedule_id
cs["schedule_start"] = current_schedule.schedule_start
cs["schedule_end"] = current_schedule.schedule_end
cs["show_id"] = current_schedule.show_id
cs["show_name"] = current_schedule.show_name
cs["playlist_id"] = current_schedule.playlist_id
cs["fallback_type"] = current_schedule.fallback_state.id
cs = SU.clean_dictionary(cs)
data["current_schedule"] = cs
if next_schedule:
ns = dict()
ns["schedule_id"] = next_schedule.schedule_id
ns["schedule_start"] = next_schedule.schedule_start
ns["schedule_end"] = next_schedule.schedule_end
ns["show_id"] = next_schedule.show_id
ns["show_name"] = next_schedule.show_name
ns["playlist_id"] = next_schedule.playlist_id
ns["fallback_type"] = next_schedule.fallback_state.id
ns = SU.clean_dictionary(ns)
data["next_schedule"] = ns
data = SU.clean_dictionary(data)
self.logger.info("Posting clock info update to Engine API...")
url = self.config.get("api_engine_store_clock")
headers = {'content-type': 'application/json'}
body = json.dumps(data, indent=4, sort_keys=True, default=str)
response = requests.put(url, data=body, headers=headers)
self.logger.info("Engine API response: %s" % response.status_code)
\ No newline at end of file
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 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 Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import threading
import queue
import logging
from datetime import datetime
from modules.scheduling.types import PlaylistType
from modules.base.utils import SimpleUtil as SU
from modules.base.models import Schedule, Playlist, PlaylistEntry, PlaylistEntryMetaData
from modules.scheduling.calender_fetcher import CalendarFetcher
class AuraCalendarService(threading.Thread):
"""
The `AuraCalendarService` retrieves all current schedules and related
playlists including audio files from the configured API endpoints and
stores it in the local database.
To perform the API queries it utilizes the CalendarFetcher class.
Attributes:
#FIXME Review attributes not needed.
"""
queue = None
config = None
logger = None
fetched_schedule_data = None
calendar_fetcher = None
stop_event = None
def __init__(self, config):
"""
Initializes the class.
Args:
config (AuraConfig): The configuration
"""
threading.Thread.__init__(self)
self.config = config
self.logger = logging.getLogger("AuraEngine")
self.queue = queue.Queue()
self.stop_event = threading.Event()
self.calendar_fetcher = CalendarFetcher(config)
def get_queue(self):
"""
Retrieves the queue of fetched data.
"""
return self.queue
def run(self):
"""
Fetch calendar data and store it in the database. Also handles local deletion of remotely
deleted schedules.
Returns
Schedule ([]): An arrar of retrieved schedules passed via `self.queue`
"""
result = []
now_unix = SU.timestamp()
scheduling_window_start = self.config.get("scheduling_window_start")
try:
fetched_schedule_data = self.calendar_fetcher.fetch()
self.logger.debug("Schedule data fetched from API: " + str(fetched_schedule_data))
# If nothing is fetched, return
if not fetched_schedule_data:
self.queue.put("fetching_aborted Nothing fetched")
return
# Check if existing schedules have been deleted
local_schedules = Schedule.select_programme(datetime.now())
for local_schedule in local_schedules:
# Only allow deletion of schedules which are deleted before the start of the scheduling window
if local_schedule.start_unix > now_unix:
if (local_schedule.start_unix - scheduling_window_start) > now_unix:
# Filter the local schedule from the fetched ones
existing_schedule = list(filter(lambda new_schedule: \
new_schedule["schedule_id"] == local_schedule.schedule_id, fetched_schedule_data))
if existing_schedule:
# self.logger.debug("Schedule #%s is still existing remotely!" % (local_schedule.schedule_id))
pass
else:
self.logger.info("Schedule #%s has been deleted remotely, hence also delete it locally [%s]" % \
(local_schedule.schedule_id, str(local_schedule)))
local_schedule.delete(commit=True)
self.logger.info("Deleted local schedule #%s from database" % local_schedule.schedule_id)
else:
msg = "Schedule #%s has been deleted remotely. Since the scheduling window has already started, it won't be deleted locally." % \
local_schedule.schedule_id
self.logger.error(SU.red(msg))
# Process fetched schedules
for schedule in fetched_schedule_data:
# Check schedule for validity
if "start" not in schedule:
self.logger.warning("No 'start' of schedule given. Skipping the schedule: %s " % str(schedule))
continue
if "end" not in schedule:
self.logger.warning("No 'end' of schedule given. Skipping the schedule: %s " % str(schedule))
continue
# Store the schedule
schedule_db = self.store_schedule(schedule)
# Store playlists to play
self.store_playlist(schedule_db, schedule_db.playlist_id, schedule["playlist"], PlaylistType.DEFAULT.id)
if schedule_db.schedule_fallback_id:
self.store_playlist(schedule_db, schedule_db.schedule_fallback_id, schedule["schedule_fallback"], PlaylistType.TIMESLOT.id)
if schedule_db.show_fallback_id:
self.store_playlist(schedule_db, schedule_db.show_fallback_id, schedule["show_fallback"], PlaylistType.SHOW.id)
if schedule_db.station_fallback_id:
self.store_playlist(schedule_db, schedule_db.station_fallback_id, schedule["station_fallback"], PlaylistType.STATION.id)
result.append(schedule_db)
# Release the mutex
self.queue.put(result)
except Exception as e:
# Release the mutex
self.logger.warning("Fetching aborted due to: %s" % str(e), e)
self.queue.put("fetching_aborted " + str(e))
# terminate the thread
return
def store_schedule(self, schedule):
"""
Stores the given schedule to the database.
Args:
schedule (Schedule): The schedule
"""
schedule_db = Schedule.select_show_on_datetime(schedule["start"])
havetoadd = False
if not schedule_db:
self.logger.debug("no schedule with given schedule id in database => create new")
schedule_db = Schedule()
havetoadd = True
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.funding_category = schedule["show_fundingcategory"]
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_playlist(self, schedule_db, playlist_id, fetched_playlist, fallbackplaylist_type=0):
"""
Stores the Playlist to the database.
"""
if not playlist_id or not fetched_playlist:
self.logger.debug("Playlist type %s with ID '%s' is not available!" % (fallbackplaylist_type, playlist_id))
return
playlist_db = Playlist.select_playlist_for_schedule(schedule_db.schedule_start, playlist_id)
havetoadd = False
if not playlist_db:
playlist_db = Playlist()
havetoadd = True
self.logger.debug("Storing playlist %d for schedule (%s)" % (playlist_id, str(schedule_db)))
playlist_db.playlist_id = playlist_id
playlist_db.schedule_start = schedule_db.schedule_start
playlist_db.show_name = schedule_db.show_name
playlist_db.fallback_type = fallbackplaylist_type
if "entries" in fetched_playlist:
playlist_db.entry_count = len(fetched_playlist["entries"])
else:
playlist_db.entry_count = 0
playlist_db.store(havetoadd, commit=True)
if playlist_db.entry_count > 0:
self.store_playlist_entries(schedule_db, playlist_db, fetched_playlist)
return playlist_db
def store_playlist_entries(self, schedule_db, playlist_db, fetched_playlist):
"""
Stores the playlist entries to the database.
"""
entry_num = 0
time_marker = playlist_db.start_unix
self.expand_entry_duration(schedule_db, fetched_playlist)
self.delete_orphaned_entries(playlist_db, fetched_playlist)
for entry in fetched_playlist["entries"]:
entry_db = PlaylistEntry.select_playlistentry_for_playlist(playlist_db.artificial_id, entry_num)
havetoadd = False
if not entry_db:
entry_db = PlaylistEntry()
havetoadd = True
entry_db.entry_start = datetime.fromtimestamp(time_marker)
entry_db.artificial_playlist_id = playlist_db.artificial_id
entry_db.entry_num = entry_num
entry_db.duration = SU.nano_to_seconds(entry["duration"])
if "uri" in entry:
# FIXME Refactor mix of uri/filename/file/source
entry_db.uri = entry["uri"]
entry_db.source = entry["uri"]
if "filename" in entry:
entry_db.source = entry["filename"]
entry_db.store(havetoadd, commit=True)
if "file" in entry:
self.store_playlist_entry_metadata(entry_db, entry["file"]["metadata"])
entry_num = entry_num + 1
time_marker += entry_db.duration
def delete_orphaned_entries(self, playlist_db, fetched_playlist):
"""
Deletes all playlist entries which are beyond the current playlist's `entry_count`.
Such entries might be existing due to a remotely changed playlist, which now has
less entries than before.
"""
new_last_idx = len(fetched_playlist["entries"])
existing_last_idx = PlaylistEntry.count_entries(playlist_db.artificial_id)-1
if existing_last_idx < new_last_idx:
return
for entry_num in range(new_last_idx, existing_last_idx+1, 1):
PlaylistEntry.delete_entry(playlist_db.artificial_id, entry_num)
self.logger.info(SU.yellow("Deleted playlist entry %s:%s" % (playlist_db.artificial_id, entry_num)))
entry_num += 1
def expand_entry_duration(self, schedule_db, fetched_playlist):
"""
If some playlist entry doesn't have a duration assigned, its duration is expanded to the
remaining duration of the playlist (= schedule duration minus playlist entries with duration).
If there's more than one entry without duration, such entries are removed from the playlist.
"""
total_seconds = (schedule_db.schedule_end - schedule_db.schedule_start).total_seconds()
total_duration = SU.seconds_to_nano(total_seconds)
actual_duration = 0
missing_duration = []
idx = 0
for entry in fetched_playlist["entries"]:
if not "duration" in entry:
missing_duration.append(idx)
else:
actual_duration += entry["duration"]
idx += 1
if len(missing_duration) == 1:
fetched_playlist["entries"][missing_duration[0]]["duration"] = total_duration - actual_duration
self.logger.info("Expanded duration of playlist entry #%s:%s" % (fetched_playlist["id"], missing_duration[0]))
elif len(missing_duration) > 1:
# This case should actually never happen, as TANK doesn't allow more than one entry w/o duration anymore
for i in reversed(missing_duration[1:-1]):
self.logger.error(SU.red("Deleted Playlist Entry without duration: %s" % \
str(fetched_playlist["entries"][i])))
del fetched_playlist["entries"][i]
def store_playlist_entry_metadata(self, entry_db, metadata):
"""
Stores the meta-data for a PlaylistEntry.
"""
metadata_db = PlaylistEntryMetaData.select_metadata_for_entry(entry_db.artificial_id)
havetoadd = False
if not metadata_db:
metadata_db = PlaylistEntryMetaData()
havetoadd = True
metadata_db.artificial_entry_id = entry_db.artificial_id
if "artist" in metadata:
metadata_db.artist = metadata["artist"]
else:
metadata_db.artist = ""
if "album" in metadata:
metadata_db.album = metadata["album"]
else:
metadata_db.album = ""
if "title" in metadata:
metadata_db.title = metadata["title"]
else:
metadata_db.title = ""
metadata_db.store(havetoadd, commit=True)
def stop(self):
self.stop_event.set()