Commit af88263d authored by David Trattnig's avatar David Trattnig
Browse files

Extended track-service and scheduling logic.

parent 289954e5
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment