diff --git a/libraries/database/broadcasts.py b/libraries/database/broadcasts.py index 42ecae392db80da31634bfddcc766e9613316d09..cca40fe6f5e39126a0568fbf987cfb0b2419a927 100644 --- a/libraries/database/broadcasts.py +++ b/libraries/database/broadcasts.py @@ -300,6 +300,20 @@ class Playlist(DB.Model, AuraDatabaseModel): return total + @hybrid_property + def current_entry(self): + """ + Retrieves the entry to be played at the very, current point in time. + """ + now_unix = SimpleUtil.timestamp() + + for entry in self.entries: + if entry.start_unix < now_unix < entry.end_unix: + return entry + + return None + + def __str__(self): """ String representation of the object. @@ -366,9 +380,24 @@ class PlaylistEntry(DB.Model, AuraDatabaseModel): elif self.cleansource == "4": return ScheduleEntryType.LIVE_4 + + def get_prev_entries(self): + """ + Retrieves all previous entries as part of the current entry's playlist. + + Returns: + (List): List of PlaylistEntry + """ + prev_entries = [] + for entry in self.playlist.entries: + if entry.entry_start < self.entry_start: + prev_entries.append(entry) + return prev_entries + + def get_next_entries(self): """ - Retrieves all following entries as part of the current entries playlist. + Retrieves all following entries as part of the current entry's playlist. Returns: (List): List of PlaylistEntry @@ -430,9 +459,9 @@ class TrackService(DB.Model, AuraDatabaseModel): # Foreign keys schedule_start = Column(DateTime, ForeignKey("schedule.schedule_start")) artificial_playlist_entry_id = Column(Integer, ForeignKey("playlist_entry.artificial_id")) + #xxx = Column(DateTime) # Data - entry_start = Column(DateTime, nullable=False, default=func.now()) schedule = relationship("Schedule", foreign_keys=[schedule_start], lazy="joined") playlist_entry = relationship("PlaylistEntry", primaryjoin="and_(TrackService.artificial_playlist_entry_id==PlaylistEntry.artificial_id)", lazy="joined") fallback = Column(String(255), nullable=True) @@ -444,6 +473,7 @@ class TrackService(DB.Model, AuraDatabaseModel): """ self.artificial_playlist_entry_id = playlist_entry.artificial_id self.schedule_start = playlist_entry.playlist.schedule_start + # self.actual_start = datetime.datetime.now() @staticmethod @@ -464,21 +494,22 @@ class TrackService(DB.Model, AuraDatabaseModel): return tracks - @staticmethod - def select_by_range(from_day, to_day): - """ - Selects the track-service items for a day range. - """ - tracks = DB.session.query(TrackService).filter(TrackService.start >= str(from_day), - TrackService.start < str(to_day)).all() - return tracks + # @staticmethod + # def select_by_range(from_day, to_day): + # """ + # Selects the track-service items for a day range. + # """ + # tracks = DB.session.query(TrackService).filter(TrackService.start >= str(from_day), + # TrackService.start < str(to_day)).all() + # return tracks def __str__(self): """ Convert to String. """ - return "TrackServiceID: #" + str(self.trackservice_id) + " playlist_id: " + str(self.playlist_id) + " started @ " + str(self.start) + " and played " + self.source + return "TrackServiceID: #" + str(self.trackservice_id) + " playlist_id: " + str(self.playlist_entry.playlist.playlist_id) + #return "TrackServiceID: #" + str(self.trackservice_id) + " playlist_id: " + str(self.playlist_entry.playlist_id) + " started @ " + str(self.actual_start)# + " and played " + self.source diff --git a/modules/base/simpleutil.py b/modules/base/simpleutil.py index 53ce58abaf8cd6d15e98f852f772532d1f710226..90a4c7f411453915c7c69bc11994f68810d5b6a0 100644 --- a/modules/base/simpleutil.py +++ b/modules/base/simpleutil.py @@ -68,3 +68,20 @@ class SimpleUtil: (Integer): timestamp in seconds. """ return time.mktime(date_and_time.timetuple()) + + + @staticmethod + def strike(text): + """ + Creates a strikethrough version of the given text. + + Args: + (String) text: the text to strike. + + Returns: + (String): the striked text. + """ + result = "" + for c in text: + result += c + '\u0336' + return result \ No newline at end of file diff --git a/modules/communication/liquidsoap/communicator.py b/modules/communication/liquidsoap/communicator.py index 83696219f21bf2def75d7c9a0054d4e94d4dd2cd..672f44f535291f9caea5d98955fb181e40670712 100644 --- a/modules/communication/liquidsoap/communicator.py +++ b/modules/communication/liquidsoap/communicator.py @@ -33,12 +33,12 @@ from modules.communication.mail import AuraMailer from libraries.enum.auraenumerations import TerminalColors, ScheduleEntryType from libraries.exceptions.auraexceptions import LQConnectionError -#from libraries.database.broadcasts import TrackService from libraries.exceptions.exception_logger import ExceptionLogger """ - LiquidSoapCommunicator Class - Uses LiquidSoapClient, but introduces more complex commands, transactions and error handling + LiquidSoapCommunicator Class + + Uses LiquidSoapClient, but introduces more complex commands, transactions and error handling. """ class LiquidSoapCommunicator(ExceptionLogger): @@ -294,6 +294,7 @@ class LiquidSoapCommunicator(ExceptionLogger): Args: new_entry (PlaylistEntry): The track to be played + cue_in (Float): Start/cue-time of track (For some reason Liquidsoap doesn't acknowledge this yet) Raises: (LQConnectionError): In case connecting to LiquidSoap isn't possible @@ -309,14 +310,16 @@ class LiquidSoapCommunicator(ExceptionLogger): try: self.enable_transaction() if current_channel == new_entry.type: + + # TODO Add logic, if some track on the same channel isn't finished yet, + # it should be transitioned using a second filesystem channel. + # self.activate_same_channel(new_entry, cue_in) else: self.activate_different_channel(new_entry, cue_in, current_channel) self.disable_transaction() - # FIXME Implement TrackService bi-directionally, log fallbacks too. - self.logger.critical("FIXME: Implement TrackService") -# self.insert_track_service_entry(new_entry) + self.scheduler.update_track_service(new_entry) except LQConnectionError: # 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 @@ -330,6 +333,7 @@ class LiquidSoapCommunicator(ExceptionLogger): Args: new_entry (Playlist): The playlist to be played + cue_in (Float): Start/cue-time of track (For some reason Liquidsoap doesn't acknowledge this yet) Raises: (LQConnectionError): In case connecting to LiquidSoap isn't possible @@ -369,9 +373,8 @@ class LiquidSoapCommunicator(ExceptionLogger): self.disable_transaction() - # FIXME Implement TrackService bi-directionally, log fallbacks too. self.logger.critical("FIXME: Implement TrackService") -# self.insert_track_service_entry(new_entry) + #self.scheduler.update_track_service(new_entry) except LQConnectionError: # 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 @@ -446,20 +449,6 @@ class LiquidSoapCommunicator(ExceptionLogger): return self.__send_lqc_command__(self.client, "fs", "clear", ) - - # ------------------------------------------------------------------------------------------ # - 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) - # ------------------------------------------------------------------------------------------ # def all_inputs_but(self, input_type): try: @@ -489,6 +478,7 @@ class LiquidSoapCommunicator(ExceptionLogger): return self.__send_lqc_command__(self.client, "mixer", "status", mixernumber) # ------------------------------------------------------------------------------------------ # + def init_player(self): """ Initializes the LiquidSoap Player after startup of the engine. @@ -689,7 +679,7 @@ class LiquidSoapCommunicator(ExceptionLogger): self.transaction = self.transaction + 1 - self.logger.debug(TerminalColors.WARNING.value + "ENabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value) + self.logger.debug(TerminalColors.WARNING.value + "Enabling transaction! cnt: " + str(self.transaction) + TerminalColors.ENDC.value) if self.transaction > 1: return diff --git a/modules/scheduling/scheduler.py b/modules/scheduling/scheduler.py index a3bd55d42811392f92e6219241fbbca68fc26ecb..b1c6d8cc4c583642d63a3574c02cd91b546834e1 100644 --- a/modules/scheduling/scheduler.py +++ b/modules/scheduling/scheduler.py @@ -44,7 +44,7 @@ from modules.base.simpleutil import SimpleUtil from modules.communication.redis.messenger import RedisMessenger from modules.scheduling.calendar import AuraCalendarService from modules.scheduling.fallback_manager import FallbackManager -from libraries.database.broadcasts import Schedule, Playlist, AuraDatabaseModel +from libraries.database.broadcasts import AuraDatabaseModel, Schedule, Playlist, TrackService from libraries.exceptions.exception_logger import ExceptionLogger from libraries.enum.auraenumerations import ScheduleEntryType, TimerType, TerminalColors @@ -299,6 +299,21 @@ class AuraScheduler(ExceptionLogger, threading.Thread): return file + + def update_track_service(self, entry): + """ + Inserts the given, currently playing `PlaylistEntry` to the track-service. + Called by LiquidSoapCommunicator when a new playlist item is going to be activated. + + Args: + entry (PlaylistEntry): The item which is currently playing + """ + trackservice_entry = TrackService(entry) + trackservice_entry.store(add=True, commit=True) + self.logger.info("Stored track-service entry %s" % trackservice_entry) + + + def adapt_trackservice_title(self, artist, title): """ Updates the track-service entry with the info from a fallback track/playlist. @@ -418,7 +433,7 @@ class AuraScheduler(ExceptionLogger, threading.Thread): def queue_programme(self): """ Queues the current programme (playlists as per schedule) by creating - timed commands to Liquidsoap to enable the individuals tracks of playlists. + timed commands to Liquidsoap to enable the individual tracks of playlists. """ active_schedule, active_playlist = self.get_active_playlist() playlists = self.get_next_playlists() @@ -429,6 +444,23 @@ class AuraScheduler(ExceptionLogger, threading.Thread): s += "\n│ Playing schedule %s " % active_schedule if active_playlist: s += "\n│ └── Playlist %s " % active_playlist + + active_entry = active_playlist.current_entry + # Finished entries + for entry in active_playlist.entries: + if active_entry == entry: + break + else: + s += "\n│ └── Entry %s " % SimpleUtil.strike(str(entry)) + + # Entry currently being played + if active_entry: + s += "\n│ └── Entry %s " % (TerminalColors.GREEN.value+"PLAYING > "+str(active_entry)+TerminalColors.ENDC.value) + + # Open entries for current playlist + rest_of_playlist = active_entry.get_next_entries() + s += self.queue_playlist_entries(rest_of_playlist) + else: s += "\n│ └── %s No Playlist active. Did it finish before the end of the schedule? %s" % (TerminalColors.ORANGE.value, TerminalColors.ENDC.value) else: @@ -446,44 +478,51 @@ class AuraScheduler(ExceptionLogger, threading.Thread): s += "\n│ └── Playlist %s " % next_playlist if next_playlist.end_unix > next_playlist.schedule.end_unix: s += "\n│ %s ↑↑↑ Playlist #%s ends after Schedule #%s!%s " % (TerminalColors.RED.value, next_playlist.playlist_id, next_playlist.schedule.schedule_id, TerminalColors.ENDC.value) - self.schedule_playlist(next_playlist) + s += self.queue_playlist_entries(next_playlist.entries) s += "\n└──────────────────────────────────────────────────────────────────────────────────────────────────────\n\n" self.logger.info(s) - def schedule_playlist(self, playlist): + def queue_playlist_entries(self, entries): """ - Creates a schedule at the planned time for the LiquidSoap player. + Creates Liquidsoap player commands for all playlist items to be executed at the scheduled time. Args: - playlist(Playlist): The playlist to be scheduled for playout - """ - now_unix = time.mktime(datetime.datetime.now().timetuple()) - diff = playlist.start_unix - now_unix - def func(playlist): - self.logger.info("=== Executing scheduled LQS command: activate_playlist(...) ===") - self.liquidsoapcommunicator.activate_playlist(playlist) + entries([PlaylistEntry]): The playlist entries to be scheduled for playout - planned_timer = self.is_something_planned_at_time(playlist.start_unix) - timer = None - - if planned_timer: - # Check if the playlist_id's are different - if planned_timer.entry.playlist_id != playlist.playlist_id: - # If not, stop and remove the old timer, create a new one - self.stop_timer(planned_timer) - timer = self.create_timer(diff, func, [playlist], switcher=True) + Returns: + (String): Formatted string to display playlist entries in log + """ + msg = "" + + for entry in entries: + + # Function to be called by timer + def func(entry): + self.logger.info("=== Executing timed LQS command: activate('%s') ===" % entry) + self.liquidsoapcommunicator.activate(entry) + + planned_timer = self.is_something_planned_at_time(entry.start_unix) + now_unix = SimpleUtil.timestamp() + diff = entry.start_unix - now_unix + msg += "\n│ └── Entry %s " % entry + + if planned_timer: + # Check if the playlist_id's are different + if planned_timer.entry.entry_id != entry.entry_id: + # If not, stop and remove the old timer, create a new one + self.stop_timer(planned_timer) + entry.switchtimer = self.create_timer(diff, func, [entry], switcher=True) + else: + # If the playlists do not differ => reuse the old timer and do nothing + self.logger.info("Playlist Entry %s is already scheduled - No new timer created!" % entry) else: - # If the playlists do not differ => reuse the old timer and do nothing - self.logger.info("Playlist %s is already scheduled - No new timer created!" % playlist) - else: - # If nothing is planned at given time, create a new timer - timer = self.create_timer(diff, func, [playlist], switcher=True) + # If nothing is planned at given time, create a new timer + entry.switchtimer = self.create_timer(diff, func, [entry], switcher=True) - if timer: - playlist.switchtimer = timer + return msg