Merge branch 'devel' of dev.sourcefabric.org:airtime into devel

This commit is contained in:
James 2012-03-26 15:09:56 -04:00
commit 375be99fbd
4 changed files with 282 additions and 300 deletions

View File

@ -292,5 +292,17 @@ class Application_Model_DateHelper
return $p_dateString; return $p_dateString;
return self::ConvertToUtcDateTime($p_dateString)->format($p_format); return self::ConvertToUtcDateTime($p_dateString)->format($p_format);
} }
/*
* Example input: "00:02:32.746562". Output is a DateInterval object
* representing that 2 minute, 32.746562 second interval.
*
*/
public static function getDateIntervalFromString($p_interval){
list($hour_min_sec, $subsec) = explode(".", $p_interval);
list($hour, $min, $sec) = explode(":", $hour_min_sec);
return new DateInterval("PT{$hour}H{$min}M{$sec}S");
}
} }

View File

@ -645,16 +645,19 @@ class Application_Model_Schedule {
$uri = $storedFile->getFilePath(); $uri = $storedFile->getFilePath();
$showEndDateTime = new DateTime($item["show_end"], $utcTimeZone); $showEndDateTime = new DateTime($item["show_end"], $utcTimeZone);
$trackStartDateTime = new DateTime($item["start"], $utcTimeZone);
$trackEndDateTime = new DateTime($item["end"], $utcTimeZone); $trackEndDateTime = new DateTime($item["end"], $utcTimeZone);
/* Note: cue_out and end are always the same. */ /* Note: cue_out and end are always the same. */
/* TODO: Not all tracks will have "show_end" */ /* TODO: Not all tracks will have "show_end" */
if ($trackEndDateTime->getTimestamp() > $showEndDateTime->getTimestamp()){ if ($trackEndDateTime->getTimestamp() > $showEndDateTime->getTimestamp()){
$diff = $trackEndDateTime->getTimestamp() - $showEndDateTime->getTimestamp(); $di = $trackStartDateTime->diff($showEndDateTime);
//assuming ends takes cue_out into assumption
$item["cue_out"] = $item["cue_out"] - $diff; $item["cue_out"] = $di->format("%H:%i:%s").".000";
} }
$start = Application_Model_Schedule::AirtimeTimeToPypoTime($item["start"]); $start = Application_Model_Schedule::AirtimeTimeToPypoTime($item["start"]);
$data["media"][$start] = array( $data["media"][$start] = array(
@ -673,102 +676,6 @@ class Application_Model_Schedule {
return $data; return $data;
} }
/**
* Export the schedule in json formatted for pypo (the liquidsoap scheduler)
*
* @param string $p_fromDateTime
* In the format "YYYY-MM-DD-HH-mm-SS"
* @param string $p_toDateTime
* In the format "YYYY-MM-DD-HH-mm-SS"
*/
public static function GetScheduledPlaylistsOld($p_fromDateTime = null, $p_toDateTime = null)
{
global $CC_CONFIG, $CC_DBC;
if (is_null($p_fromDateTime)) {
$t1 = new DateTime("@".time());
$range_start = $t1->format("Y-m-d H:i:s");
} else {
$range_start = Application_Model_Schedule::PypoTimeToAirtimeTime($p_fromDateTime);
}
if (is_null($p_fromDateTime)) {
$t2 = new DateTime("@".time());
$t2->add(new DateInterval("PT24H"));
$range_end = $t2->format("Y-m-d H:i:s");
} else {
$range_end = Application_Model_Schedule::PypoTimeToAirtimeTime($p_toDateTime);
}
// Scheduler wants everything in a playlist
$data = Application_Model_Schedule::GetItems($range_start, $range_end, true);
$playlists = array();
if (is_array($data)){
foreach ($data as $dx){
$start = $dx['start'];
//chop off subseconds
$start = substr($start, 0, 19);
//Start time is the array key, needs to be in the format "YYYY-MM-DD-HH-mm-ss"
$pkey = Application_Model_Schedule::AirtimeTimeToPypoTime($start);
$timestamp = strtotime($start);
$playlists[$pkey]['source'] = "PLAYLIST";
$playlists[$pkey]['x_ident'] = $dx['group_id'];
$playlists[$pkey]['timestamp'] = $timestamp;
$playlists[$pkey]['duration'] = $dx['clip_length'];
$playlists[$pkey]['played'] = '0';
$playlists[$pkey]['schedule_id'] = $dx['group_id'];
$playlists[$pkey]['show_name'] = $dx['show_name'];
$playlists[$pkey]['show_start'] = Application_Model_Schedule::AirtimeTimeToPypoTime($dx['show_start']);
$playlists[$pkey]['show_end'] = Application_Model_Schedule::AirtimeTimeToPypoTime($dx['show_end']);
$playlists[$pkey]['user_id'] = 0;
$playlists[$pkey]['id'] = $dx['group_id'];
$playlists[$pkey]['start'] = Application_Model_Schedule::AirtimeTimeToPypoTime($dx["start"]);
$playlists[$pkey]['end'] = Application_Model_Schedule::AirtimeTimeToPypoTime($dx["end"]);
}
}
ksort($playlists);
foreach ($playlists as &$playlist)
{
$scheduleGroup = new Application_Model_ScheduleGroup($playlist["schedule_id"]);
$items = $scheduleGroup->getItems();
$medias = array();
foreach ($items as $item)
{
$storedFile = Application_Model_StoredFile::Recall($item["file_id"]);
$uri = $storedFile->getFileUrlUsingConfigAddress();
$starts = Application_Model_Schedule::AirtimeTimeToPypoTime($item["starts"]);
$medias[$starts] = array(
'id' => $storedFile->getGunid(),
'uri' => $uri,
'fade_in' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_in"]),
'fade_out' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_out"]),
'fade_cross' => 0,
'cue_in' => Application_Model_DateHelper::CalculateLengthInSeconds($item["cue_in"]),
'cue_out' => Application_Model_DateHelper::CalculateLengthInSeconds($item["cue_out"]),
'start' => $starts,
'end' => Application_Model_Schedule::AirtimeTimeToPypoTime($item["ends"])
);
}
ksort($medias);
$playlist['medias'] = $medias;
}
$result = array();
$result['status'] = array('range' => array('start' => $range_start, 'end' => $range_end),
'version' => AIRTIME_REST_VERSION);
$result['playlists'] = $playlists;
$result['check'] = 1;
$result['stream_metadata'] = array();
$result['stream_metadata']['format'] = Application_Model_Preference::GetStreamLabelFormat();
$result['stream_metadata']['station_name'] = Application_Model_Preference::GetStationName();
return $result;
}
public static function deleteAll() public static function deleteAll()
{ {
global $CC_CONFIG, $CC_DBC; global $CC_CONFIG, $CC_DBC;

View File

@ -24,21 +24,21 @@ function query($conn, $query){
} }
function getFileFromCcFiles($conn){ function getFileFromCcFiles($conn){
$query = "SELECT * from cc_files LIMIT 1"; $query = "SELECT * from cc_files LIMIT 2";
$result = query($conn, $query); $result = query($conn, $query);
$file = null; $files = array();
while ($row = pg_fetch_array($result)) { while ($row = pg_fetch_array($result)) {
$file = $row; $files[] = $row;
} }
if (is_null($file)){ if (count($files) == 0){
echo "Library is empty. Could not choose random file."; echo "Library is empty. Could not choose random file.";
exit(1); exit(1);
} }
return $file; return $files;
} }
function insertIntoCcShow($conn){ function insertIntoCcShow($conn){
@ -64,7 +64,7 @@ function insertIntoCcShow($conn){
return $show_id; return $show_id;
} }
function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $file){ function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $files){
/* Step 2: /* Step 2:
* Create a show instance. * Create a show instance.
* Column values: * Column values:
@ -75,9 +75,8 @@ function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $file){
$now = $nowDateTime->format("Y-m-d H:i:s"); $now = $nowDateTime->format("Y-m-d H:i:s");
$columns = "(starts, ends, show_id, record, rebroadcast, instance_id, file_id, time_filled, last_scheduled, modified_instance)"; $columns = "(starts, ends, show_id, record, rebroadcast, instance_id, file_id, time_filled, last_scheduled, modified_instance)";
$values = "('$starts', '$ends', $show_id, 0, 0, NULL, NULL, '$file[length]', '$now', 'f')"; $values = "('$starts', '$ends', $show_id, 0, 0, NULL, NULL, TIMESTAMP '$ends' - TIMESTAMP '$starts', '$now', 'f')";
$query = "INSERT INTO cc_show_instances $columns values $values "; $query = "INSERT INTO cc_show_instances $columns values $values ";
echo $query.PHP_EOL; echo $query.PHP_EOL;
@ -101,13 +100,39 @@ function insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $file){
* id | starts | ends | file_id | clip_length| fade_in | fade_out | cue_in | cue_out | media_item_played | instance_id * id | starts | ends | file_id | clip_length| fade_in | fade_out | cue_in | cue_out | media_item_played | instance_id
* 1 | 2012-02-29 23:25:00 | 2012-02-29 23:30:05.037166 | 1 | 00:05:05.037166 | 00:00:00 | 00:00:00 | 00:00:00 | 00:05:05.037166 | f | 5 * 1 | 2012-02-29 23:25:00 | 2012-02-29 23:30:05.037166 | 1 | 00:05:05.037166 | 00:00:00 | 00:00:00 | 00:00:00 | 00:05:05.037166 | f | 5
*/ */
function insertIntoCcSchedule($conn, $file, $show_instance_id, $starts, $ends){ function insertIntoCcSchedule($conn, $files, $show_instance_id, $p_starts, $p_ends){
$columns = "(starts, ends, file_id, clip_length, fade_in, fade_out, cue_in, cue_out, media_item_played, instance_id)"; $columns = "(starts, ends, file_id, clip_length, fade_in, fade_out, cue_in, cue_out, media_item_played, instance_id)";
$values = "('$starts', '$ends', $file[id], '$file[length]', '00:00:00', '00:00:00', '00:00:00', '$file[length]', 'f', $show_instance_id)";
$query = "INSERT INTO cc_schedule $columns VALUES $values";
echo $query.PHP_EOL;
$result = query($conn, $query); $starts = $p_starts;
foreach($files as $file){
$endsDateTime = new DateTime($starts, new DateTimeZone("UTC"));
$lengthDateInterval = getDateInterval($file["length"]);
$endsDateTime->add($lengthDateInterval);
$ends = $endsDateTime->format("Y-m-d H:i:s");
$values = "('$starts', '$ends', $file[id], '$file[length]', '00:00:00', '00:00:00', '00:00:00', '$file[length]', 'f', $show_instance_id)";
$query = "INSERT INTO cc_schedule $columns VALUES $values";
echo $query.PHP_EOL;
$starts = $ends;
$result = query($conn, $query);
}
}
function getDateInterval($interval){
list($length,) = explode(".", $interval);
list($hour, $min, $sec) = explode(":", $length);
return new DateInterval("PT{$hour}H{$min}M{$sec}S");
}
function getEndTime($startDateTime, $p_files){
foreach ($p_files as $file){
$startDateTime->add(getDateInterval($file['length']));
}
return $startDateTime;
} }
function rabbitMqNotify(){ function rabbitMqNotify(){
@ -149,14 +174,18 @@ EOD;
} }
$startDateTime = new DateTime("now + 30sec", new DateTimeZone("UTC")); $startDateTime = new DateTime("now + 30sec", new DateTimeZone("UTC"));
$endDateTime = new DateTime("now + 1min 30sec", new DateTimeZone("UTC")); //$endDateTime = new DateTime("now + 1min 30sec", new DateTimeZone("UTC"));
$starts = $startDateTime->format("Y-m-d H:i:s"); $starts = $startDateTime->format("Y-m-d H:i:s");
//$ends = $endDateTime->format("Y-m-d H:i:s");
$files = getFileFromCcFiles($conn);
$show_id = insertIntoCcShow($conn);
$endDateTime = getEndTime(clone $startDateTime, $files);
$ends = $endDateTime->format("Y-m-d H:i:s"); $ends = $endDateTime->format("Y-m-d H:i:s");
$file = getFileFromCcFiles($conn); $show_instance_id = insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $files);
$show_id = insertIntoCcShow($conn); insertIntoCcSchedule($conn, $files, $show_instance_id, $starts, $ends);
$show_instance_id = insertIntoCcShowInstances($conn, $show_id, $starts, $ends, $file);
insertIntoCcSchedule($conn, $file, $show_instance_id, $starts, $ends);
rabbitMqNotify(); rabbitMqNotify();

View File

@ -45,122 +45,79 @@ class PypoPush(Thread):
self.push_ahead = 5 self.push_ahead = 5
self.last_end_time = 0 self.last_end_time = 0
self.pushed_objects = {} self.pushed_objects = {}
self.logger = logging.getLogger('push') self.logger = logging.getLogger('push')
def push(self):
"""
The Push Loop - the push loop periodically checks if there is a playlist
that should be scheduled at the current time.
If yes, the current liquidsoap playlist gets replaced with the corresponding one,
then liquidsoap is asked (via telnet) to reload and immediately play it.
"""
liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() def main(self):
loops = 0
heartbeat_period = math.floor(30/PUSH_INTERVAL)
try: next_media_item_chain = None
self.media = self.queue.get(block=True, timeout=PUSH_INTERVAL) media_schedule = None
if not self.queue.empty(): time_until_next_play = None
while not self.queue.empty():
self.media = self.queue.get() while True:
self.logger.debug("Received data from pypo-fetch") try:
self.logger.debug('media %s' % json.dumps(self.media)) if time_until_next_play is None:
self.handle_new_media(self.media, liquidsoap_queue_approx) media_schedule = self.queue.get(block=True)
except Empty, e: else:
pass media_schedule = self.queue.get(block=True, timeout=time_until_next_play)
#We get to the following lines only if a schedule was received.
liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap()
media = self.media
if len(liquidsoap_queue_approx) < MAX_LIQUIDSOAP_QUEUE_LENGTH: chains = self.get_all_chains(media_schedule)
if media: current_chain = self.get_current_chain(chains)
if len(current_chain) > 0 and len(liquidsoap_queue_approx) == 0:
#Something is scheduled but Liquidsoap is not playing anything!
#Need to schedule it immediately
self.modify_cue_point_of_first_link(current_chain)
next_media_item_chain = current_chain
time_until_next_play = 0
else:
self.handle_new_media_schedule(media_schedule, liquidsoap_queue_approx)
chains = self.get_all_chains(media_schedule)
next_media_item_chain = self.get_next_schedule_chain(chains)
self.logger.debug("Next schedule chain: %s", next_media_item_chain)
if next_media_item_chain is not None:
tnow = datetime.utcnow()
chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S")
time_until_next_play = self.date_interval_to_seconds(chain_start - tnow)
self.logger.debug("Blocking %s seconds until show start", time_until_next_play)
else:
self.logger.debug("Blocking indefinitely since no show scheduled")
time_until_next_play = None
except Empty, e:
#We only get here when a new chain of tracks are ready to be played.
self.push_to_liquidsoap(next_media_item_chain)
tnow = datetime.utcnow() #TODO
tcoming = tnow + timedelta(seconds=self.push_ahead) time.sleep(2)
for key in media.keys():
media_item = media[key]
item_start = datetime.strptime(media_item['start'][0:19], "%Y-%m-%d-%H-%M-%S")
item_end = datetime.strptime(media_item['end'][0:19], "%Y-%m-%d-%H-%M-%S")
if len(liquidsoap_queue_approx) == 0 and item_start <= tnow and tnow < item_end:
"""
Something is scheduled now, but Liquidsoap is not playing anything! Let's play the current media_item
"""
self.logger.debug("Found media_item that should be playing! Starting...")
adjusted_cue_in = tnow - item_start
adjusted_cue_in_seconds = self.date_interval_to_seconds(adjusted_cue_in)
self.logger.debug("Found media_item that should be playing! Adjust cue point by %ss" % adjusted_cue_in_seconds)
self.push_to_liquidsoap(media_item, adjusted_cue_in_seconds)
elif tnow <= item_start and item_start < tcoming:
"""
If the media item starts in the next 10 seconds, push it to the queue.
"""
self.logger.debug('Preparing to push media item scheduled at: %s', key)
if self.push_to_liquidsoap(media_item, None):
self.logger.debug("Pushed to liquidsoap, updating 'played' status.")
"""
Temporary solution to make sure we don't push the same track multiple times. Not a full solution because if we
get a new schedule, the key becomes available again.
"""
#TODO
del media[key]
def date_interval_to_seconds(self, interval):
return (interval.microseconds + (interval.seconds + interval.days * 24 * 3600) * 10**6) / 10**6
def push_to_liquidsoap(self, media_item, adjusted_cue_in=None):
"""
This function looks at the media item, and either pushes it to the Liquidsoap
queue immediately, or if the queue is empty - waits until the start time of the
media item before pushing it.
"""
if adjusted_cue_in is not None:
media_item["cue_in"] = adjusted_cue_in + float(media_item["cue_in"])
try:
if media_item["start"] == self.last_end_time:
"""
this media item is attached to the end of the last
track, so let's push it now so that Liquidsoap can start playing
it immediately after (and prepare crossfades if need be).
"""
self.logger.debug("Push track immediately.")
self.telnet_to_liquidsoap(media_item)
self.last_end_time = media_item["end"]
else:
"""
this media item does not start right after a current playing track.
We need to sleep, and then wake up when this track starts.
"""
self.logger.debug("sleep until track start.")
self.sleep_until_start(media_item)
self.telnet_to_liquidsoap(media_item) chains = self.get_all_chains(media_schedule)
self.last_end_time = media_item["end"] next_media_item_chain = self.get_next_schedule_chain(chains)
except Exception, e: if next_media_item_chain is not None:
self.logger.error('Pypo Push Exception: %s', e) tnow = datetime.utcnow()
return False chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S")
time_until_next_play = self.date_interval_to_seconds(chain_start - tnow)
return True self.logger.debug("Blocking %s seconds until show start", time_until_next_play)
else:
self.logger.debug("Blocking indefinitely since no show scheduled next")
time_until_next_play = None
if loops % heartbeat_period == 0:
self.logger.info("heartbeat")
loops = 0
loops += 1
def get_queue_items_from_liquidsoap(self): def get_queue_items_from_liquidsoap(self):
""" """
This function connects to Liquidsoap to find what media items are in its queue. This function connects to Liquidsoap to find what media items are in its queue.
""" """
try: try:
self.telnet_lock.acquire() self.telnet_lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn = telnetlib.Telnet(LS_HOST, LS_PORT)
@ -196,9 +153,8 @@ class PypoPush(Thread):
break break
return liquidsoap_queue_approx return liquidsoap_queue_approx
def handle_new_media_schedule(self, media_schedule, liquidsoap_queue_approx):
def handle_new_media(self, media, liquidsoap_queue_approx):
""" """
This function's purpose is to gracefully handle situations where This function's purpose is to gracefully handle situations where
Liquidsoap already has a track in its queue, but the schedule Liquidsoap already has a track in its queue, but the schedule
@ -206,49 +162,132 @@ class PypoPush(Thread):
call other functions that will connect to Liquidsoap and alter its call other functions that will connect to Liquidsoap and alter its
queue. queue.
""" """
if len(liquidsoap_queue_approx) == 0: #iterate through the items we got from the liquidsoap queue and
""" #see if they are the same as the newly received schedule
liquidsoap doesn't have anything in its queue, so we have nothing iteration = 0
to worry about. Life is good. problem_at_iteration = None
""" for queue_item in liquidsoap_queue_approx:
pass
elif len(liquidsoap_queue_approx) == 1: if queue_item['start'] in media_schedule.keys():
queue_item_0_start = liquidsoap_queue_approx[0]['start'] if queue_item['id'] == media_schedule[queue_item['start']]['id']:
try: #Everything OK for this iteration.
if liquidsoap_queue_approx[0]['id'] != media[queue_item_0_start]['id']: pass
""" else:
liquidsoap's queue does not match the schedule we just received from the Airtime server. #A different item has been scheduled at the same time! Need to remove
The queue is only of length 1 which means the item in the queue is playing. #all tracks from the Liquidsoap queue starting at this point, and re-add
Need to do source.skip. #them.
problem_at_iteration = iteration
Since only one item, we don't have to worry about the current item ending and us calling break
source.skip unintentionally on the next item (there is no next item). else:
""" #There are no more items scheduled for this time! The user has shortened
#the playlist, so we simply need to remove tracks from the queue.
self.logger.debug("%s from ls does not exist in queue new schedule. Removing" % liquidsoap_queue_approx[0]['id'], media) problem_at_iteration = iteration
self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) break
except KeyError, k: iteration+=1
self.logger.debug("%s from ls does not exist in queue schedule: %s Removing" % (queue_item_0_start, media))
self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0])
if problem_at_iteration is not None:
#The first item in the Liquidsoap queue (the one that is currently playing)
elif len(liquidsoap_queue_approx) == 2: #has changed or been removed from the schedule. We need to clear the entire
queue_item_0_start = liquidsoap_queue_approx[0]['start'] #queue, and push the new schedule
queue_item_1_start = liquidsoap_queue_approx[1]['start'] self.logger.debug("Problem at iteration %s", problem_at_iteration)
self.remove_from_liquidsoap_queue(problem_at_iteration, liquidsoap_queue_approx)
def get_all_chains(self, media_schedule):
chains = []
current_chain = []
sorted_keys = sorted(media_schedule.keys())
for mkey in sorted_keys:
media_item = media_schedule[mkey]
if len(current_chain) == 0:
current_chain.append(media_item)
elif media_item['start'] == current_chain[-1]['end']:
current_chain.append(media_item)
else:
#current item is not a continuation of the chain.
#Start a new one instead
chains.append(current_chain)
current_chain = [media_item]
if len(current_chain) > 0:
chains.append(current_chain)
if queue_item_1_start in media.keys(): return chains
if liquidsoap_queue_approx[1]['id'] != media[queue_item_1_start]['id']:
self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[1]) def modify_cue_point_of_first_link(self, chain):
else: tnow = datetime.utcnow()
self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[1]) link = chain[0]
link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S")
diff_td = tnow - link_start
self.logger.debug("media item was supposed to start %s ago. Preparing to start..", diff_td)
original_cue_in_td = timedelta(seconds=float(link['cue_in']))
link['cue_in'] = self.convert_timedelta_to_seconds(original_cue_in_td + diff_td)
def convert_timedelta_to_seconds(self, td):
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
def get_current_chain(self, chains):
tnow = datetime.utcnow()
current_chain = []
for chain in chains:
iteration = 0
for link in chain:
link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S")
link_end = datetime.strptime(link['end'], "%Y-%m-%d-%H-%M-%S")
if queue_item_0_start in media.keys(): self.logger.debug("tnow %s, chain_start %s", tnow, link_start)
if liquidsoap_queue_approx[0]['id'] != media[queue_item_0_start]['id']: if link_start <= tnow and tnow < link_end:
self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) current_chain = chain[iteration:]
else: break
self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) iteration += 1
return current_chain
"""
The purpose of this function is to take a look at the last received schedule from
pypo-fetch and return the next chain of media_items. A chain is defined as a sequence
of media_items where the end time of media_item 'n' is the start time of media_item
'n+1'
"""
def get_next_schedule_chain(self, chains):
#all media_items are now divided into chains. Let's find the one that
#starts closest in the future.
tnow = datetime.utcnow()
closest_start = None
closest_chain = None
for chain in chains:
chain_start = datetime.strptime(chain[0]['start'], "%Y-%m-%d-%H-%M-%S")
self.logger.debug("tnow %s, chain_start %s", tnow, chain_start)
if (closest_start == None or chain_start < closest_start) and chain_start > tnow:
closest_start = chain_start
closest_chain = chain
return closest_chain
def date_interval_to_seconds(self, interval):
return (interval.microseconds + (interval.seconds + interval.days * 24 * 3600) * 10**6) / 10**6
def push_to_liquidsoap(self, media_item_chain):
try:
for media_item in media_item_chain:
self.telnet_to_liquidsoap(media_item)
except Exception, e:
self.logger.error('Pypo Push Exception: %s', e)
def clear_liquidsoap_queue(self): def clear_liquidsoap_queue(self):
self.logger.debug("Clearing Liquidsoap queue") self.logger.debug("Clearing Liquidsoap queue")
try: try:
@ -263,38 +302,38 @@ class PypoPush(Thread):
finally: finally:
self.telnet_lock.release() self.telnet_lock.release()
def remove_from_liquidsoap_queue(self, media_item, do_only_source_skip=False): def remove_from_liquidsoap_queue(self, problem_at_iteration, liquidsoap_queue_approx):
if 'queue_id' in media_item: iteration = 0
queue_id = media_item['queue_id']
try:
self.telnet_lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
for queue_item in liquidsoap_queue_approx:
try: if iteration >= problem_at_iteration:
self.telnet_lock.acquire()
tn = telnetlib.Telnet(LS_HOST, LS_PORT) msg = "queue.remove %s\n" % queue_item['queue_id']
msg = "queue.remove %s\n" % queue_id
self.logger.debug(msg)
tn.write(msg)
response = tn.read_until("\r\n").strip("\r\n")
if "No such request in my queue" in response:
"""
Cannot remove because Liquidsoap started playing the item. Need
to use source.skip instead
"""
msg = "source.skip\n"
self.logger.debug(msg) self.logger.debug(msg)
tn.write(msg) tn.write(msg)
response = tn.read_until("\r\n").strip("\r\n")
tn.write("exit\n") if "No such request in my queue" in response:
tn.read_all() """
except Exception, e: Cannot remove because Liquidsoap started playing the item. Need
self.logger.error(str(e)) to use source.skip instead
finally: """
self.telnet_lock.release() msg = "source.skip\n"
self.logger.debug(msg)
tn.write(msg)
iteration += 1
tn.write("exit\n")
tn.read_all()
except Exception, e:
self.logger.error(str(e))
finally:
self.telnet_lock.release()
else:
self.logger.error("'queue_id' key doesn't exist in media_item dict()")
def sleep_until_start(self, media_item): def sleep_until_start(self, media_item):
""" """
The purpose of this function is to look at the difference between The purpose of this function is to look at the difference between
@ -363,14 +402,9 @@ class PypoPush(Thread):
% (media['id'], float(media['cue_in']), float(media['cue_out']), media['row_id'], media['dst']) % (media['id'], float(media['cue_in']), float(media['cue_out']), media['row_id'], media['dst'])
def run(self): def run(self):
loops = 0 try: self.main()
heartbeat_period = math.floor(30/PUSH_INTERVAL) except Exception, e:
import traceback
while True: top = traceback.format_exc()
if loops % heartbeat_period == 0: self.logger.error('Pypo Push Exception: %s', top)
self.logger.info("heartbeat")
loops = 0
try: self.push()
except Exception, e:
self.logger.error('Pypo Push Exception: %s', e)
loops += 1