diff --git a/README.md b/README.md index 4beba556b72fa319d4b06fd21724571b87f7faa8..349cc378cb9b54d961a7238d91890b1d5c3e4d98 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,53 @@ sudo apt install \ git \ python3 python3-pip \ redis-server \ - liquidsoap liquidsoap-plugin-alsa liquidsoap-plugin-flac liquidsoap-plugin-icecast liquidsoap-plugin-pulseaudio \ + liquidsoap liquidsoap-plugin-icecast \ mariadb-server libmariadbclient-dev \ quelcom ``` +##### Liquidsoap Plugins + +###### Soundcard +How liquidsoap is using your soundcard is depending on what you are going to use: + +with ALSA: +```bash +sudo apt install \ + liquidsoap-plugin-alsa liquidsoap-plugin-pulseaudio +``` + +With pulseaudio: +```bash +sudo apt install \ + liquidsoap-plugin-pulseaudio +``` + +with jack: +```bash +sudo apt install \ + liquidsoap-plugin-jack +``` + +###### File Formats + +Depending on what stream you are going to send, and what recordings you are going to use: +```bash +sudo apt install \ + liquidsoap-plugin-aac # for aac support + liquidsoap-plugin-flac # for flac support + liquidsoap-plugin-lame liquidsoap-plugin-mad # for mp3 support + liquidsoap-plugin-opus # for opus support + liquidsoap-plugin-vorbis # for ogg support +``` + +###### Simple + +```bash +sudo apt install \ + liquidsoap-plugin-all +``` + #### Python Packages @@ -61,7 +103,7 @@ GRANT ALL PRIVILEGES ON aura_engine.* TO 'aura'@'localhost'; ##### phpmyadmin / adminer way -Log into your phpmyadmin or adminer with correct privileges and create a database and a user for the aura engine. +Log into your phpmyadmin or adminer with correct privileges, create a database and a user for the aura engine. #### Files and Folders @@ -86,33 +128,62 @@ The commandline tool for interacting with the server. Also provides the communic #### Liquidsoap -The heart of AURA Engine. It uses the built in mixer, to switch between different sources. +The heart of AURA Engine. It uses the built in mixer, to switch between different sources. It records everything and streams everything depending on your settings in aura.ini. #### Find Help -LiquidSoap Reference: http://savonet.sourceforge.net/doc-svn/reference.html -Python3.5 Reference: https://docs.python.org/3.5/ +##### Liquidsoap +Reference: \ +http://savonet.sourceforge.net/doc-svn/reference.html +##### Python +Reference: \ +https://docs.python.org/3.5/ + +#### Interfaces + +##### From Aura Engine + +_Soundserverstate_ \ +Returns true and false values of the internal In- and Outputs \ +/api/v1/soundserver_state + +_Trackservice_ \ +/api/v1/trackservice/<selected_date> \ +/api/v1/trackservice/ + +##### To Aura Engine + +Interfaces are needed from pv/steering to engine and from tank to engine. More informations you can find here: https://gitlab.servus.at/autoradio/meta/blob/master/api-definition.md ### Hardware #### Soundcard -AURA Engine ist tested with a ASUS Xonar DGX and a Roland Duo-Capture Ex. Both work well with jack and pulseaudio. For good experience with ALSA, you may need better hardware. +AURA Engine is tested with a ASUS Xonar DGX, a Roland Duo-Capture Ex and also on an Onboard Soundcard (HDA Intel ALC262). Both work well with jack and pulseaudio. For a good experience with ALSA, you may need better hardware. #### Hard/Soft -When you use ALSA, you will have to play around with ALSA settings. In the folder ./modules/liquidsoap is a scipt called alsa_settings_tester.liq. You can start it with 'liquidsoap -v --debug alsa_settings_tester.liq'. Changing and playing with settings can help you to find correct ALSA settings. +When you use ALSA, you will have to play around with ALSA settings. In the folder ./modules/liquidsoap is a scipt called alsa_settings_tester.liq. You can start it with 'liquidsoap -v --debug alsa_settings_tester.liq'. Changing and playing with settings may help you to find correct ALSA settings. + +#### Line In + +You can configure up to **five** line ins. Your hardware should support that. When you use JACK, you will see the additional elements popping up when viewing your connections (with e.g. Patchage). + +#### Recordings + +You can configure up to **five** recorders. You find the settings in the main config file engine.ini. You can choose between different output formats. -### Recordings +#### Streams -You can configure up to five recorders. You find the settings in the main config file engine.ini. You can choose between different output formats. +You can configure up to **five** streams. You find the settings in the engine.ini. You can choose between different streaming formats. -### Streams +### Troubleshooting -You can configure up to five streams. You find the settings in the engine.ini. You can choose between different streaming formats. +**If you cannot find correct ALSA settings** \ +Well, this is - at least for me - a hard one. I could not manage to find correct ALSA settings for the above mentioned soundcards. The best experience i had with the ASUS Xonar DGX, but still very problematic (especially the first couple of minutes after starting liquidsoap). Since i enabled JACK support i only use that. It is also a bit of trial and error, but works pretty much out of the box. -If you experience 'hangs' on the stream - * reduce the quality or +**If you experience 'hangs' or other artefacts on the output signal** + * reduce the quality (especially, when hangs are on the stream) or * install the realtime kernel with ```bash apt install linux-image-rt-amd64 diff --git a/aura.py b/aura.py index ef5efc1de45d151f4848fdb013fd733fdb11c3cb..5b958258ba2e1c6f982e7489796ebe1e68c0385b 100755 --- a/aura.py +++ b/aura.py @@ -75,7 +75,7 @@ class Aura(AuraLogger): self.messenger.liquidsoapcommunicator = self.liquidsoapcommunicator self.diskspace_watcher = DiskSpaceWatcher(self.config, self.logger, self.liquidsoapcommunicator) - self.diskspace_watcher.run() + self.diskspace_watcher.start() def receive_signal(signum, stack): print("received signal") @@ -83,12 +83,14 @@ class Aura(AuraLogger): signal.signal(signal.SIGUSR1, receive_signal) - # wait for redis message + # and finally wait for redis message self.join_comm() # start the web service self.start_web_service() + + def join_comm(self): # start listener thread self.messenger.start() diff --git a/configuration/engine.ini b/configuration/engine.ini index 982481b0d7f4513b52dfb6215656fd14cb7a59d6..5258d1bbc5d7f4abdef77c60863cd6905056267a 100644 --- a/configuration/engine.ini +++ b/configuration/engine.ini @@ -23,13 +23,15 @@ mail_user="" mail_pass="" # if you want to send multiple adminmails, make them space separated admin_mail="gogo@servus.at gottfried@servus.at" -# +# with from mailadress should be used from_mail="monitor@aura.py" # The beginning of the subject. With that you can easily apply filter rules mailsubject_prefix="[AURA]" [dataurls] +# the url of pv/steering calendarurl="http://localhost:8000/api/v1/playout" +# the url of tank importerurl="http://localhost:8008/api/v1/groups/_public/playlists/" # how often should the calendar be fetched in seconds (This determines the time of the last change before a specific show) fetching_frequency=3600 @@ -38,7 +40,7 @@ fetching_frequency=3600 # LiquidSoap Settings # ####################### -# all these settings here require a restart of the liquidsoap server +# all these settings from here to the bottom require a restart of the liquidsoap server [liquidsoap] # the user and group under which this software will run @@ -78,7 +80,7 @@ soundsystem="jack" # with pulse and jack => an non empty value means it is used # devices with empty string are ignored and not used input_device_0="y" -input_device_1="" +input_device_1="y" input_device_2="" input_device_3="" input_device_4="" diff --git a/guru.py b/guru.py index 3e7560bf684ec7f34d8ce0c3ee047c429669ee07..a6f7f82b516ba255e8b43d84de4a7cdb24e8487f 100755 --- a/guru.py +++ b/guru.py @@ -72,6 +72,7 @@ class Guru(AuraConfig): print("...result: ") if p.stringreply != "": + print(p.stringreply) if p.stringreply[len(p.stringreply)-1] == "\n": print(p.stringreply[0:len(p.stringreply) - 1]) else: diff --git a/libraries/database/broadcasts.py b/libraries/database/broadcasts.py index 54ca121c76258de88235fac81fe5a3a8cd9af303..e0b3235459b8665a1b9356a0b4acf3ae23e6af7d 100644 --- a/libraries/database/broadcasts.py +++ b/libraries/database/broadcasts.py @@ -161,6 +161,7 @@ class ScheduleEntry(DB.Model, AuraDatabaseModel): @orm.reconstructor def reconstructor(self): self.calc_unix_times() + self.define_clean_source() self.set_entry_type() def define_clean_source(self): @@ -193,7 +194,16 @@ class ScheduleEntry(DB.Model, AuraDatabaseModel): if self.source.startswith("pool") or self.source.startswith("playlist") or self.source.startswith("file"): self.type = ScheduleEntryType.FILESYSTEM if self.source.startswith("live") or self.source.startswith("linein"): - self.type = ScheduleEntryType.LIVE + if self.cleansource == "0": + self.type = ScheduleEntryType.LIVE_0 + elif self.cleansource == "1": + self.type = ScheduleEntryType.LIVE_1 + elif self.cleansource == "2": + self.type = ScheduleEntryType.LIVE_2 + elif self.cleansource == "3": + self.type = ScheduleEntryType.LIVE_3 + elif self.cleansource == "4": + self.type = ScheduleEntryType.LIVE_4 # ------------------------------------------------------------------------------------------ # @staticmethod @@ -219,8 +229,6 @@ class ScheduleEntry(DB.Model, AuraDatabaseModel): entry.programme_index = cnt cnt = cnt + 1 - - return all_entries # ------------------------------------------------------------------------------------------ # @@ -245,8 +253,8 @@ class ScheduleEntry(DB.Model, AuraDatabaseModel): # ------------------------------------------------------------------------------------------ # @staticmethod - def upcoming(datefrom=datetime.datetime.now()): - upcomingtracks = DB.session.query(ScheduleEntry).filter(ScheduleEntry.start > datefrom).all() + def select_upcoming(datefrom=datetime.datetime.now()): + upcomingtracks = DB.session.query(ScheduleEntry).filter(ScheduleEntry.entry_start > datefrom).order_by(ScheduleEntry.entry_start).all() return upcomingtracks # ------------------------------------------------------------------------------------------ # @@ -263,7 +271,7 @@ class ScheduleEntry(DB.Model, AuraDatabaseModel): if self.type == self.type.FILESYSTEM: return "fs" - if self.type == self.type.LIVE: + if self.type == self.type.LIVE_0 or self.type == self.type.LIVE_1 or self.type == self.type.LIVE_2 or self.type == self.type.LIVE_3 or self.type == self.type.LIVE_4: return "aura_linein_"+self.cleansource # .cleanprotocol[8] if self.type == self.type.STREAM: diff --git a/libraries/enum/auraenumerations.py b/libraries/enum/auraenumerations.py index 575d371bc02fb5bce5f5dfa7d1b1fbbf385e48aa..9b81bed56a0d541cee1ec9c9412135f8919ab5ab 100644 --- a/libraries/enum/auraenumerations.py +++ b/libraries/enum/auraenumerations.py @@ -60,6 +60,11 @@ class RedisChannel(Enum): class ScheduleEntryType(Enum): + # enumeration with names of liquidsoap inputs FILESYSTEM = "fs" STREAM = "http" - LIVE = "live" \ No newline at end of file + LIVE_0 = "aura_linein_0" + LIVE_1 = "aura_linein_1" + LIVE_2 = "aura_linein_2" + LIVE_3 = "aura_linein_3" + LIVE_4 = "aura_linein_4" \ No newline at end of file diff --git a/modules/communication/liquidsoap/communicator.py b/modules/communication/liquidsoap/communicator.py index 593e1d67ffb15f65ce53638089c858820cebf35d..7944a09d5b088c2bc15a219142ebc4e67aeddc26 100644 --- a/modules/communication/liquidsoap/communicator.py +++ b/modules/communication/liquidsoap/communicator.py @@ -171,6 +171,8 @@ class LiquidSoapCommunicator(ExceptionLogger): else: self.recorder_start_one(num) + self.disable_transaction() + # ------------------------------------------------------------------------------------------ # def recorder_start_all(self): self.enable_transaction() @@ -188,62 +190,75 @@ class LiquidSoapCommunicator(ExceptionLogger): self.__send_lqc_command__(self.client, "recorder", str(num), "start") # ------------------------------------------------------------------------------------------ # - def activate(self, entry): - active_type = self.scheduler.get_active_entry().type + def activate(self, new_entry): + # grab the actual active entry + old_entry = self.scheduler.get_active_entry() + # determine its type + old_type = old_entry.type try: # enable transaction self.enable_transaction() - if active_type == entry.type: + if old_type == new_entry.type: # push something to active channel - self.activate_same_channel(entry) + self.activate_same_channel(new_entry) else: # switch to another channel - self.activate_different_channel(entry, active_type) + self.activate_different_channel(new_entry, old_type) # disable conn self.disable_transaction() # insert playlist entry - self.insert_track_service_entry(entry) + self.insert_track_service_entry(new_entry) except LQConnectionError as e: - # we already caught and handled this error in __send_lqc_command__, but we do not want to execute this function further + # we already caught and handled this error in __send_lqc_command__, but we do not want to execute this function further and pass the exception pass # ------------------------------------------------------------------------------------------ # - def activate_same_channel(self, entry, silent=False): - if not silent: + def activate_same_channel(self, entry, activate_different_channel=False): + if not activate_different_channel: self.logger.info(TerminalColors.PINK.value + entry.type.value + " already active!" + TerminalColors.ENDC.value) # push to fs or stream if entry.type == ScheduleEntryType.FILESYSTEM: self.playlist_push(entry.source) if entry.type == ScheduleEntryType.STREAM: - self.http_start_stop(True) self.set_http_url(entry.source) + self.http_start_stop(True) # nothing to do when we are live => just leave it as is + # set active channel to wanted volume + if not activate_different_channel: + self.channel_volume(entry.type.value, entry.volume) + # ------------------------------------------------------------------------------------------ # def activate_different_channel(self, entry, active_type): self.logger.info(TerminalColors.PINK.value + "LiquidSoapCommunicator is activating " + entry.type.value + " & deactivating " + active_type.value + "!" + TerminalColors.ENDC.value) + # reuse of this function, because activate_same_channel and activate_different_channel are doing pretty the same except setting of the volume self.activate_same_channel(entry, True) - # set others to zero volume + # set other channels to zero volume others = self.all_inputs_but(entry.getChannel()) for o in others: self.channel_volume(o, 0) + # set active channel to wanted volume self.channel_volume(entry.type.value, entry.volume) # ------------------------------------------------------------------------------------------ # def insert_track_service_entry(self, schedule_entry): + # create trackservice entry trackservice_entry = TrackService() + + # set foreign keys trackservice_entry.playlist_id = schedule_entry.playlist_id trackservice_entry.entry_num = schedule_entry.entry_num trackservice_entry.source = schedule_entry.source + # store trackservice_entry.store(add=True, commit=True) # ------------------------------------------------------------------------------------------ # @@ -323,7 +338,7 @@ class LiquidSoapCommunicator(ExceptionLogger): return message except (AttributeError, ValueError) as e: #(LQConnectionError, AttributeError): self.disable_transaction(force=True) - self.logger.critical("Ran into exception when setting volume of channel " + channel + ". Reason: " + str(e)) + self.logger.error("Ran into exception when setting volume of channel " + channel + ". Reason: " + str(e)) # ------------------------------------------------------------------------------------------ # def auraengine_state(self): @@ -351,7 +366,10 @@ class LiquidSoapCommunicator(ExceptionLogger): @param uri: Die Uri """ return self.__send_lqc_command__(self.client, "fs", "push", uri) -# self.notifyClient() + + # ------------------------------------------------------------------------------------------ # + def playlist_seek(self, seconds_to_seek): + return self.__send_lqc_command__(self.client, "fs", "seek", seconds_to_seek) # ------------------------------------------------------------------------------------------ # def version(self): diff --git a/modules/communication/liquidsoap/initthread.py b/modules/communication/liquidsoap/initthread.py index a973e00b41ddf9c22f820eb2b816c9a8816c3a5a..a8404ee572d56234f544949bf84562e4317f0a3f 100644 --- a/modules/communication/liquidsoap/initthread.py +++ b/modules/communication/liquidsoap/initthread.py @@ -24,6 +24,7 @@ import time import logging +import datetime import threading from libraries.enum.auraenumerations import ScheduleEntryType @@ -43,7 +44,7 @@ class LiquidSoapInitThread(threading.Thread): # ------------------------------------------------------------------------------------------ # def run(self): try: - # sleep needed, because the socket is created to slow by liquidsoap + # sleep needed, because the socket is created too slow by liquidsoap time.sleep(1) self.logger.info("Waited 1s for liquidsoap. Jez soit a si gspian") @@ -53,16 +54,16 @@ class LiquidSoapInitThread(threading.Thread): # reset channels and reload them channels = self.liquidsoapcommunicator.reload_channels() - # set every volume to 0 + # for all available channels for c in channels: + # set volume to zero self.liquidsoapcommunicator.channel_volume(c, "0") + # and activate this channel + self.liquidsoapcommunicator.channel_activate(c, True) - # select all channels -# for c in channels: -# self.liquidsoapcommunicator.channel_activate(c, True) - - # setting init params + # setting init params like a blank file.. self.liquidsoapcommunicator.playlist_push(self.liquidsoapcommunicator.config.get("install_dir") + "/configuration/blank.flac") + # .. or the radio fro stream (it is overwritten as soon as one http overtake is planned) self.liquidsoapcommunicator.set_http_url("http://stream.fro.at/fro-128.ogg") # wait another second. lqs really starts slow.. @@ -73,9 +74,25 @@ class LiquidSoapInitThread(threading.Thread): self.logger.info("LiquidSoapInitThread sets activechannel: "+str(self.active_entry)) channel = self.active_entry.type + + # have to seek? + if channel == ScheduleEntryType.FILESYSTEM: + # calc how many seconds were missed + now_unix = time.mktime(datetime.datetime.now().timetuple()) + seconds_to_seek = now_unix - self.active_entry.entry_start_unix + + # and seek these seconds forward + self.liquidsoapcommunicator.playlist_seek(seconds_to_seek) + + # finally make something hearable :-) if channel != "" and channel is not None: + # activate http stream if needed self.liquidsoapcommunicator.http_start_stop(channel == ScheduleEntryType.STREAM) + # finally set the volume up self.liquidsoapcommunicator.channel_volume(channel.value, self.active_entry.volume) + else: + self.logger.error("Channel is NULL or empty! Cannot set ") + else: self.logger.warning("No active entry in the scheduler! Is a programme loaded?") diff --git a/modules/communication/liquidsoap/playerclient.py b/modules/communication/liquidsoap/playerclient.py index dc6df54f2038e76a68bca8ea7ccc684bd752ec93..2be3b61a75be883d25d2d6ffa4dd5e616fc9f71a 100644 --- a/modules/communication/liquidsoap/playerclient.py +++ b/modules/communication/liquidsoap/playerclient.py @@ -69,6 +69,9 @@ class LiquidSoapPlayerClient(LiquidSoapClient): if command == "push": return self.fs_push(*args) + if command == "seek": + return self.fs_seek(*args) + return "LiquidSoapPlayerClient does not understand fs." + command + str(args) # ------------------------------------------------------------------------------------------ # @@ -88,6 +91,11 @@ class LiquidSoapPlayerClient(LiquidSoapClient): self.command('fs', 'push', uri) return self.message + # ------------------------------------------------------------------------------------------ # + def fs_seek(self, uri): + self.command('fs', 'seek', uri) + return self.message + # ------------------------------------------------------------------------------------------ # def set_http_url(self, uri): self.command('http', 'url', uri) diff --git a/modules/communication/mail/mail.py b/modules/communication/mail/mail.py index 0b03c62861e729c2d7ff1e9305edfe66e455ae7a..9e0072f61de4e22846f7dc329a4aa83d91732477 100644 --- a/modules/communication/mail/mail.py +++ b/modules/communication/mail/mail.py @@ -22,7 +22,6 @@ # along with engine. If not, see <http://www.gnu.org/licenses/>. # -import os import smtplib from email.message import EmailMessage from libraries.exceptions.auraexceptions import MailingException diff --git a/modules/communication/redis/adapter.py b/modules/communication/redis/adapter.py index de7919dfe35b751a231d4e20dfc0ba841b3cd3f8..257066dc8368c2260453a849670a8ea58a10c530 100644 --- a/modules/communication/redis/adapter.py +++ b/modules/communication/redis/adapter.py @@ -41,7 +41,6 @@ from libraries.enum.auraenumerations import RedisChannel, TerminalColors # ------------------------------------------------------------------------------------------ # class ServerRedisAdapter(threading.Thread, RedisMessenger): debug = False - # logger = None pubsub = None config = None redisdb = None @@ -53,12 +52,9 @@ class ServerRedisAdapter(threading.Thread, RedisMessenger): # ------------------------------------------------------------------------------------------ # def __init__(self): - #super(ServerRedisAdapter, self).__init__() threading.Thread.__init__(self) RedisMessenger.__init__(self) - # self.logger = logging.getLogger("AuraEngine") - # init threading.Thread.__init__ (self) self.shutdown_event = Event() @@ -95,7 +91,7 @@ class ServerRedisAdapter(threading.Thread, RedisMessenger): try: self.work(item) except RedisConnectionException as rce: - self.logger.error(rce) + self.logger.error(str(rce)) if not self.shutdown_event.is_set(): self.logger.info(TerminalColors.ORANGE.value + "waiting for REDIS message on channel " + self.channel + TerminalColors.ENDC.value) diff --git a/modules/communication/redis/messenger.py b/modules/communication/redis/messenger.py index 5e5f71c7ee5ec9ca7a700c989ec9bd064ece5e66..43084a058a0a060efbb0e430ee4cc20d064a4485 100644 --- a/modules/communication/redis/messenger.py +++ b/modules/communication/redis/messenger.py @@ -292,7 +292,7 @@ class RedisMessenger(): next = self.rstore.db.get('next_'+playlisttype+'_file') if next is None: - return "" + next = b"" return next.decode('utf-8') diff --git a/modules/liquidsoap/engine.liq b/modules/liquidsoap/engine.liq index 877d861b8f35d2531700d9c8a17842ed74ec4896..4a262579a18742e59db92bc7152e2ca192d70a2a 100644 --- a/modules/liquidsoap/engine.liq +++ b/modules/liquidsoap/engine.liq @@ -53,16 +53,17 @@ inputs = ref [] mixer = mix(id="mixer", list.append([input_fs, input_http], !inputs)) # output source with fallbacks -# stripped_stream = strip_blank(max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold, mixer) -ignore(fallback_max_blank) -ignore(fallback_min_noise) -ignore(fallback_threshold) -ignore(timeslot_fallback) -ignore(station_fallback) -ignore(show_fallback) +stripped_stream = strip_blank(max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold, mixer) +# ignore(fallback_max_blank) +# ignore(fallback_min_noise) +# ignore(fallback_threshold) +# ignore(timeslot_fallback) +# ignore(station_fallback) +# ignore(show_fallback) # enable fallback -output_source = mixer # fallback(id="fallback", track_sensitive=false, [mksafe(stripped_stream), timeslot_fallback, show_fallback, station_fallback]) +# output_source = mixer +output_source = fallback(id="fallback", track_sensitive=false, [mksafe(stripped_stream), timeslot_fallback, show_fallback, station_fallback]) ################## # create outputs # @@ -84,6 +85,6 @@ output_source = mixer # fallback(id="fallback", track_sensitive=false, [mksafe(s # start initialization # ######################## -# system('#{list.assoc("install_dir", ini)}/guru.py --init-player --quiet') +system('#{list.assoc("install_dir", ini)}/guru.py --init-player --quiet') diff --git a/modules/liquidsoap/fallback.liq b/modules/liquidsoap/fallback.liq index 2644c3f96f71c952d42d8f51cb1462c61b529847..b8c234a38f115c95730682866253dab9abff2b75 100644 --- a/modules/liquidsoap/fallback.liq +++ b/modules/liquidsoap/fallback.liq @@ -98,8 +98,7 @@ 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 + # 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.) @@ -109,7 +108,7 @@ def fallback_create(~skip=true, name, requestor) # Skip blank when asked to source = if skip then - skip_blank(source, max_blank=10., threshold=-40.) + skip_blank(max_blank=fallback_max_blank, min_noise=fallback_min_noise, threshold=fallback_threshold, source) else source end diff --git a/modules/monitoring/diskspace_watcher.py b/modules/monitoring/diskspace_watcher.py index 036e60501a3c4997c62d587f8dfeeb0630ab1d8d..d80eebc5896921f7988a0121c1745cabb9adb441 100644 --- a/modules/monitoring/diskspace_watcher.py +++ b/modules/monitoring/diskspace_watcher.py @@ -44,6 +44,7 @@ class DiskSpaceWatcher(threading.Thread): # ------------------------------------------------------------------------------------------ # def __init__(self, config, logger, liquidsoapcommunicator): + threading.Thread.__init__(self) self.liquidsoapcommunicator = liquidsoapcommunicator self.config = config self.logger = logger @@ -60,19 +61,21 @@ class DiskSpaceWatcher(threading.Thread): except: seconds_to_wait = 600 - # while True: while not self.exit_event.is_set(): - # calc next time - next_time = datetime.datetime.now() + datetime.timedelta(seconds=seconds_to_wait) + try: + # calc next time + next_time = datetime.datetime.now() + datetime.timedelta(seconds=seconds_to_wait) - # write to logger - self.logger.info("Diskspace watcher every " + str(seconds_to_wait) + "s started. Going to start next time " + str(next_time)) + # write to logger + self.logger.info("Diskspace watcher every " + str(seconds_to_wait) + "s started. Going to start next time " + str(next_time)) - # check disk space - self.check_disk_space() + # check disk space + self.check_disk_space() - # and wait - self.exit_event.wait(seconds_to_wait) + # and wait + self.exit_event.wait(seconds_to_wait) + except BrokenPipeError as e: + self.logger.critical("Cannot check if recorder is running. It seems LiquidSoap is not running. Reason: " + str(e)) # ------------------------------------------------------------------------------------------ # def stop(self): diff --git a/modules/scheduling/calendar.py b/modules/scheduling/calendar.py index fa37f162c39f3afeccbeb967dcb6ddca69d50394..4bdc3dfc982f3d3f3c8526cec443343b00817c5f 100644 --- a/modules/scheduling/calendar.py +++ b/modules/scheduling/calendar.py @@ -232,7 +232,7 @@ class AuraCalendarService(threading.Thread): schedule_entry_db.entry_start = schedule_db.schedule_start + timedelta(seconds=self.get_length(lastentry)) schedule_entry_db.calc_unix_times() - schedule_entry_db.define_clean_source() +# schedule_entry_db.define_clean_source() self.logger.debug("Storing entries... playlist_id: " + str(playlist["playlist_id"]) + " schedule_id: " + str(schedule_db.schedule_id) + " num: " + str(entrynum)) @@ -443,7 +443,7 @@ class AuraCalendarService(threading.Thread): # ------------------------------------------------------------------------------------------ # def get_length(self, entry): - if entry is None or entry.source == ScheduleEntryType.STREAM or entry.type == ScheduleEntryType.LIVE: + if entry is None or entry.source == ScheduleEntryType.STREAM or entry.type == ScheduleEntryType.LIVE_0 or entry.type == ScheduleEntryType.LIVE_1 or entry.type == ScheduleEntryType.LIVE_2 or entry.type == ScheduleEntryType.LIVE_3 or entry.type == ScheduleEntryType.LIVE_4: return 0 audio_file = FLAC(entry.cleansource) diff --git a/modules/scheduling/scheduler.py b/modules/scheduling/scheduler.py index ecb9b9789803406cc74795c5f2c806e36f20e942..0c00141d7e67d82f15e76e1547358920af695201 100644 --- a/modules/scheduling/scheduler.py +++ b/modules/scheduling/scheduler.py @@ -201,7 +201,7 @@ class AuraScheduler(ExceptionLogger, threading.Thread): # when do we have to start? diff = entry.entry_start_unix - now_unix - diff = diff/10000 # testing purpose + diff = diff/1000 # testing purpose # create the activation threads and run them after <diff> seconds if entry.source.startswith("linein"): @@ -403,12 +403,20 @@ class AuraScheduler(ExceptionLogger, threading.Thread): def set_next_file_for(self, playlistname): self.logger.critical("HAVE TO SET NEXT FILE FOR: " + playlistname) self.logger.critical(str(self.get_active_entry())) - self.logger.critical(playlistname) - self.redismessenger.set_next_file_for(playlistname, "/var/audio/blank.flac") - #print('return self.config.get("install_dir") + "/configuration/blank.flac"') - #import sys - #sys.exit(0) + if playlistname == "station": + file = "/var/audio/fallback/eins.zwo.bombe.mp3" + elif playlistname == "timeslot": + file = "/var/audio/fallback/ratm.killing.mp3" + elif playlistname == "show": + file = "/var/audio/fallback/weezer.hash.pipe.mp3" + else: + file = "" + self.logger.critical("Should set next fallback file for " + playlistname + ", but this playlist is unknown!") + + self.logger.info("Set next fallback file for " + playlistname + ": " + file) + self.redismessenger.set_next_file_for(playlistname, file) + return file # ------------------------------------------------------------------------------------------ # class CallFunctionTimer(threading.Timer): @@ -426,5 +434,5 @@ class CallFunctionTimer(threading.Timer): self.logger = logging.getLogger("AuraEngine") - msg = "MessageTimer starting @ " + str(self.entry.entry_start) + " source '" + str(self.entry.source) + "' In seconds: " + str(self.diff) + msg = "CallFunctionTimer starting @ " + str(self.entry.entry_start) + " source '" + str(self.entry.source) + "' In seconds: " + str(self.diff) self.logger.debug(msg) diff --git a/modules/web/routes.py b/modules/web/routes.py index e602056ffed1cc82a1f958b6fd0945e3665058ec..1ac8aea0dc273c6a36caf344d10d89b959398765 100644 --- a/modules/web/routes.py +++ b/modules/web/routes.py @@ -32,7 +32,7 @@ import logging from flask import request, render_template from libraries.database.database import APP -from libraries.database.broadcasts import TrackService, Schedule +from libraries.database.broadcasts import TrackService, Schedule, ScheduleEntry @@ -49,6 +49,7 @@ def alchemyencoder(obj): else: return str(obj) + class Routes: error = None scheduler = None @@ -160,4 +161,14 @@ class Routes: def api_trackservice_now(): return simplejson.dumps({'reached': True}) + @staticmethod + @APP.route("/api/v1/upcoming/", methods=["GET"]) + def api_clock(): + servertime = datetime.datetime.now() + # get upcoming tracks + upcoming = ScheduleEntry.select_upcoming() + # convert to json string + upcoming_as_json = simplejson.dumps([tracks._asdict() for tracks in upcoming], default=alchemyencoder) + # add servertime and return it + return upcoming_as_json.replace('[{', '[{"servertime":'+str(servertime)+"},{", 1)