From 2e1f88a7d68bedda33e83107820f44fa71a0c713 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Mon, 5 Mar 2012 20:47:14 -0500 Subject: [PATCH 1/6] CC-2675: Edit ongoing show -initial commit --- .../pypo/liquidsoap_scripts/ls_script.liq | 2 +- python_apps/pypo/pypopush.py | 135 ++++++++++++++---- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index 42bc1e479..64ce07e48 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -8,7 +8,7 @@ set("server.telnet.port", 1234) time = ref string_of(gettimeofday()) -queue = audio_to_stereo(request.queue(id="queue", length=0.5)) +queue = audio_to_stereo(request.equeue(id="queue", length=0.5)) queue = cue_cut(queue) pypo_data = ref '0' diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 6cb2558e7..6d6543dc2 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -9,10 +9,16 @@ import telnetlib import calendar import json import math + +""" +It is possible to use a list as a queue, where the first element added is the first element +retrieved ("first-in, first-out"); however, lists are not efficient for this purpose. Let's use +"deque" +""" +from collections import deque + from threading import Thread - from api_clients import api_client - from configobj import ConfigObj @@ -25,6 +31,7 @@ try: LS_HOST = config['ls_host'] LS_PORT = config['ls_port'] PUSH_INTERVAL = 2 + MAX_LIQUIDSOAP_QUEUE_LENGTH = 2 except Exception, e: logger = logging.getLogger() logger.error('Error loading config file %s', e) @@ -42,6 +49,8 @@ class PypoPush(Thread): self.push_ahead = 10 self.last_end_time = 0 + self.liquidsoap_queue = deque() + self.logger = logging.getLogger('push') def push(self): @@ -51,6 +60,8 @@ class PypoPush(Thread): If yes, the current liquidsoap playlist gets replaced with the corresponding one, then liquidsoap is asked (via telnet) to reload and immediately play it. """ + + self.update_liquidsoap_queue() timenow = time.time() # get a new schedule from pypo-fetch @@ -58,38 +69,46 @@ class PypoPush(Thread): # make sure we get the latest schedule while not self.queue.empty(): self.media = self.queue.get() + self.logger.debug("Received data from pypo-fetch") self.logger.debug('media %s' % json.dumps(self.media)) + self.handle_new_media(self.media) + media = self.media - currently_on_air = False - if media: - tnow = time.gmtime(timenow) - tcoming = time.gmtime(timenow + self.push_ahead) - str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) - str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5]) - - for key in media.keys(): - media_item = media[key] - item_start = media_item['start'][0:19] - - if str_tnow_s <= item_start and item_start < str_tcoming_s: - """ - If the media item starts in the next 30 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): - self.logger.debug("Pushed to liquidsoap, updating 'played' status.") - + if len(self.liquidsoap_queue) < MAX_LIQUIDSOAP_QUEUE_LENGTH: + """ + We only care about what's scheduled if the number of items in the queue is less + than our max queue limit. + """ + currently_on_air = False + if media: + tnow = time.gmtime(timenow) + tcoming = time.gmtime(timenow + self.push_ahead) + str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) + str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5]) + + for key in media.keys(): + media_item = media[key] + item_start = media_item['start'][0:19] + + if str_tnow_s <= item_start and item_start < str_tcoming_s: """ - Temporary solution to make sure we don't push the same track multiple times. + If the media item starts in the next 30 seconds, push it to the queue. """ - del media[key] - - currently_on_air = True - self.liquidsoap_state_play = True + self.logger.debug('Preparing to push media item scheduled at: %s', key) + + if self.push_to_liquidsoap(media_item): + self.logger.debug("Pushed to liquidsoap, updating 'played' status.") + + """ + Temporary solution to make sure we don't push the same track multiple times. + """ + del media[key] + + currently_on_air = True + self.liquidsoap_state_play = True def push_to_liquidsoap(self, media_item): """ @@ -119,6 +138,60 @@ class PypoPush(Thread): return False return True + + def update_liquidsoap_queue(self): + """ + the queue variable liquidsoap_queue is our attempt to mirror + what liquidsoap actually has in its own queue. Liquidsoap automatically + updates its own queue when an item finishes playing, we have to do this + manually. + + This function will iterate through the liquidsoap_queue and remove items + whose end time are in the past. + """ + + tnow = time.gmtime(timenow) + str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) + + while len(self.liquidsoap_queue) > 0: + if self.liquidsoap_queue[0]["end"] < str_tnow_s: + self.liquidsoap_queue.popleft() + + def handle_new_media(self, media): + """ + This function's purpose is to gracefully handle situations where + Liquidsoap already has a track in its queue, but the schedule + has changed. + + If the media_item is already being played, then we can't + do anything about it. If it is in the Liquidsoap queue, but not + yet playing then we can remove it. Note that currently + there can only ever be one media_item in the queue that isn't + being played (for a max of two items in the queue). + """ + + """ + TODO: What happens if we remove the second item in the queue when + it just became the primary item? + """ + if len(self.liquidsoap_queue) > 1: + media_item = self.liquidsoap_queue[1] + + if media["id"] != liquidsoap_queue["id"]: + """ + The md5s are the not same, so a different + item has been scheduled! + """ + #remove from actual liquidsoap queue + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + msg = 'queue.remove %s\n' % media_item["queue_id"] + tn.write(msg) + tn.write("exit\n") + self.logger.debug(tn.read_all()) + + #remove from our liquidsoap queue + self.liquidsoap_queue.remove(media_item) + def sleep_until_start(self, media_item): """ @@ -159,8 +232,14 @@ class PypoPush(Thread): annotation = media_item['annotation'] msg = 'queue.push %s\n' % annotation.encode('utf-8') - tn.write(msg) self.logger.debug(msg) + tn.write(msg) + queue_id = tn.read_until("\r\n").strip("\r\n") + + media_item['queue_id'] = queue_id + + #add media_item to the end of our queue + self.liquidsoap_queue.append(media_item) show_name = media_item['show_name'] msg = 'vars.show_name %s\n' % show_name.encode('utf-8') From adc8e8e69fb498fa17853c45d98ed81dd394c579 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Sat, 10 Mar 2012 14:13:38 -0500 Subject: [PATCH 2/6] cc-2675: Edit ongoing show -progress being made --- python_apps/pypo/pypopush.py | 107 +++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 24 deletions(-) diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 6d6543dc2..fa3f27177 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -161,37 +161,94 @@ class PypoPush(Thread): """ This function's purpose is to gracefully handle situations where Liquidsoap already has a track in its queue, but the schedule - has changed. - - If the media_item is already being played, then we can't - do anything about it. If it is in the Liquidsoap queue, but not - yet playing then we can remove it. Note that currently - there can only ever be one media_item in the queue that isn't - being played (for a max of two items in the queue). + has changed. """ """ - TODO: What happens if we remove the second item in the queue when - it just became the primary item? + First let's connect to Liquidsoap and find what media items are in its queue. + + We will compare these items to the schedule we've received and decide if any + action needs to take place. """ - if len(self.liquidsoap_queue) > 1: - media_item = self.liquidsoap_queue[1] - - if media["id"] != liquidsoap_queue["id"]: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + + msg = 'queue.queue %s\n' % media_item["queue_id"] + tn.write(msg) + response = tn.read_until("\r\n").strip("\r\n") + tn.write('exit\n') + tn.read_all() + + list = response.split(" ") + + liquidsoap_queue_mirror = [] + + for l in list: + if l in self.pushed_objects: + liquidsoap_queue_mirror.append(self.pushed_objects[l]) + else: + self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue") + + #TODO: Keys should already be sorted. Verify this. + sorted_keys = sort(media.keys()) + + if len(liquidsoap_queue_mirror) == 0: + """ + liquidsoap doesn't have anything in its queue, so we have nothing + to worry about. + """ + pass + + if len(liquidsoap_queue_mirror) == 1: + if liquidsoap_queue_mirror[0]['id'] != media_item[sorted_keys[0]]['id']: """ - The md5s are the not same, so a different - item has been scheduled! + liquidsoap queue does not match the newest schedule. The queue is only of + length 1, and so that means the item in the queue is playing. Need to do source.skip """ - #remove from actual liquidsoap queue - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - msg = 'queue.remove %s\n' % media_item["queue_id"] - tn.write(msg) - tn.write("exit\n") - self.logger.debug(tn.read_all()) + self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[0]) - #remove from our liquidsoap queue - self.liquidsoap_queue.remove(media_item) + if len(liquidsoap_queue_mirror) == 2: + if liquidsoap_queue_mirror[0]['id'] == media_item[sorted_keys[0]]['id'] \ + and liquidsoap_queue_mirror[1]['id'] == media_item[sorted_keys[1]]['id']: + """ + What's in the queue matches what's in the schedule. Nothing to do. + """ + pass + elif liquidsoap_queue_mirror[0]['id'] == media_item[sorted_keys[0]]['id'] \ + and liquidsoap_queue_mirror[1]['id'] != media_item[sorted_keys[1]]['id']: + """ + instruct liquidsoap to remove the second item from the queue + """ + self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[1]) + elif liquidsoap_queue_mirror[0]['id'] != media_item[sorted_keys[0]]['id']: + """ + remove both items from the queue. Remove in reverse order so that source.skip + doesn't skip to the second song which we also plan on removing. + """ + self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[1]) + self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[0]) + + def remove_from_liquidsoap_queue(self, media_item): + if 'queue_id' in media_item: + queue_id = media_item['queue_id'] + + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + msg = "queue.remove %s\n" % queue_id + 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" + tn.write("source.skip") + + tn.write("exit\n") + tn.read_all() + else: + self.logger.error("'queue_id' key doesn't exist in media_item dict()") def sleep_until_start(self, media_item): """ @@ -236,10 +293,12 @@ class PypoPush(Thread): tn.write(msg) queue_id = tn.read_until("\r\n").strip("\r\n") + #remember the media_item's queue id which we may use + #later if we need to remove it from the queue. media_item['queue_id'] = queue_id #add media_item to the end of our queue - self.liquidsoap_queue.append(media_item) + self.pushed_objects[queue_id] = media_item show_name = media_item['show_name'] msg = 'vars.show_name %s\n' % show_name.encode('utf-8') From e93b1cb8020c6c0363ac1a5f4778aa4508a5f95d Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Tue, 13 Mar 2012 15:24:24 -0400 Subject: [PATCH 3/6] cc-2675: edit ongoing show -almost there? --- python_apps/pypo/pypopush.py | 153 ++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 72 deletions(-) diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index fa3f27177..c6a50df6f 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -49,8 +49,8 @@ class PypoPush(Thread): self.push_ahead = 10 self.last_end_time = 0 - self.liquidsoap_queue = deque() - + self.pushed_objects = {} + self.logger = logging.getLogger('push') def push(self): @@ -61,7 +61,8 @@ class PypoPush(Thread): then liquidsoap is asked (via telnet) to reload and immediately play it. """ - self.update_liquidsoap_queue() + liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() + self.logger.debug('liquidsoap_queue_approx %s', liquidsoap_queue_approx) timenow = time.time() # get a new schedule from pypo-fetch @@ -72,16 +73,12 @@ class PypoPush(Thread): self.logger.debug("Received data from pypo-fetch") self.logger.debug('media %s' % json.dumps(self.media)) - self.handle_new_media(self.media) + self.handle_new_media(self.media, liquidsoap_queue_approx) media = self.media - if len(self.liquidsoap_queue) < MAX_LIQUIDSOAP_QUEUE_LENGTH: - """ - We only care about what's scheduled if the number of items in the queue is less - than our max queue limit. - """ + if len(liquidsoap_queue_approx) < MAX_LIQUIDSOAP_QUEUE_LENGTH: currently_on_air = False if media: tnow = time.gmtime(timenow) @@ -139,16 +136,15 @@ class PypoPush(Thread): return True + """ def update_liquidsoap_queue(self): - """ - the queue variable liquidsoap_queue is our attempt to mirror - what liquidsoap actually has in its own queue. Liquidsoap automatically - updates its own queue when an item finishes playing, we have to do this - manually. - - This function will iterate through the liquidsoap_queue and remove items - whose end time are in the past. - """ +# the queue variable liquidsoap_queue is our attempt to mirror +# what liquidsoap actually has in its own queue. Liquidsoap automatically +# updates its own queue when an item finishes playing, we have to do this +# manually. +# +# This function will iterate through the liquidsoap_queue and remove items +# whose end time are in the past. tnow = time.gmtime(timenow) str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) @@ -156,79 +152,92 @@ class PypoPush(Thread): while len(self.liquidsoap_queue) > 0: if self.liquidsoap_queue[0]["end"] < str_tnow_s: self.liquidsoap_queue.popleft() + """ - def handle_new_media(self, media): + def get_queue_items_from_liquidsoap(self): """ - This function's purpose is to gracefully handle situations where - Liquidsoap already has a track in its queue, but the schedule - has changed. + This function connects to Liquidsoap to find what media items are in its queue. """ - """ - First let's connect to Liquidsoap and find what media items are in its queue. - - We will compare these items to the schedule we've received and decide if any - action needs to take place. - """ tn = telnetlib.Telnet(LS_HOST, LS_PORT) - msg = 'queue.queue %s\n' % media_item["queue_id"] + msg = 'queue.queue\n' tn.write(msg) - response = tn.read_until("\r\n").strip("\r\n") + response = tn.read_until("\r\n").strip(" \r\n") tn.write('exit\n') tn.read_all() - list = response.split(" ") - - liquidsoap_queue_mirror = [] - - for l in list: - if l in self.pushed_objects: - liquidsoap_queue_mirror.append(self.pushed_objects[l]) - else: - self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue") + liquidsoap_queue_approx = [] + if len(response) > 0: + items_in_queue = response.split(" ") + + self.logger.debug("items_in_queue: %s", items_in_queue) + + for item in items_in_queue: + if item in self.pushed_objects: + liquidsoap_queue_approx.append(self.pushed_objects[item]) + else: + self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue: " + item) + + return liquidsoap_queue_approx + + + def handle_new_media(self, media, liquidsoap_queue_approx): + """ + This function's purpose is to gracefully handle situations where + Liquidsoap already has a track in its queue, but the schedule + has changed. If the schedule has changed, this function's job is to + call other functions that will connect to Liquidsoap and alter its + queue. + """ + #TODO: Keys should already be sorted. Verify this. - sorted_keys = sort(media.keys()) + sorted_keys = sorted(media.keys()) - if len(liquidsoap_queue_mirror) == 0: + if len(liquidsoap_queue_approx) == 0: """ liquidsoap doesn't have anything in its queue, so we have nothing - to worry about. + to worry about. Life is good. """ pass - - if len(liquidsoap_queue_mirror) == 1: - if liquidsoap_queue_mirror[0]['id'] != media_item[sorted_keys[0]]['id']: - """ - liquidsoap queue does not match the newest schedule. The queue is only of - length 1, and so that means the item in the queue is playing. Need to do source.skip - """ - self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[0]) + elif len(liquidsoap_queue_approx) == 1: + queue_item_0_start = liquidsoap_queue_approx[0]['start'] + try: + if liquidsoap_queue_approx[0]['id'] != media[queue_item_0_start]['id']: + """ + liquidsoap's queue does not match the schedule we just received from the Airtime server. + The queue is only of length 1 which means the item in the queue is playing. + Need to do source.skip. + + Since only one item, we don't have to worry about the current item ending and us calling + source.skip unintentionally on the next item (there is no next item). + """ + + self.logger.debug("%s from ls does not exist in queue new schedule. Removing" % liquidsoap_queue_approx[0]['id'], media) + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + except KeyError, k: + 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]) + + + elif len(liquidsoap_queue_approx) == 2: + queue_item_0_start = liquidsoap_queue_approx[0]['start'] + queue_item_1_start = liquidsoap_queue_approx[1]['start'] + + if queue_item_1_start in media.keys(): + if liquidsoap_queue_approx[1]['id'] != media[queue_item_1_start]['id']: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[1]) + else: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[1]) - - if len(liquidsoap_queue_mirror) == 2: - if liquidsoap_queue_mirror[0]['id'] == media_item[sorted_keys[0]]['id'] \ - and liquidsoap_queue_mirror[1]['id'] == media_item[sorted_keys[1]]['id']: - """ - What's in the queue matches what's in the schedule. Nothing to do. - """ - pass - elif liquidsoap_queue_mirror[0]['id'] == media_item[sorted_keys[0]]['id'] \ - and liquidsoap_queue_mirror[1]['id'] != media_item[sorted_keys[1]]['id']: - """ - instruct liquidsoap to remove the second item from the queue - """ - self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[1]) - elif liquidsoap_queue_mirror[0]['id'] != media_item[sorted_keys[0]]['id']: - """ - remove both items from the queue. Remove in reverse order so that source.skip - doesn't skip to the second song which we also plan on removing. - """ - self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[1]) - self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[0]) + if queue_item_0_start in media.keys(): + if liquidsoap_queue_approx[0]['id'] != media[queue_item_0_start]['id']: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + else: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) - def remove_from_liquidsoap_queue(self, media_item): + def remove_from_liquidsoap_queue(self, media_item, do_only_source_skip=False): if 'queue_id' in media_item: queue_id = media_item['queue_id'] From 3259eb06a0bb43592f981d9deab8bfe956fab9f5 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Mon, 5 Mar 2012 20:47:14 -0500 Subject: [PATCH 4/6] CC-2675: Edit ongoing show -initial commit --- .../pypo/liquidsoap_scripts/ls_script.liq | 2 +- python_apps/pypo/pypopush.py | 135 ++++++++++++++---- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index 42bc1e479..64ce07e48 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -8,7 +8,7 @@ set("server.telnet.port", 1234) time = ref string_of(gettimeofday()) -queue = audio_to_stereo(request.queue(id="queue", length=0.5)) +queue = audio_to_stereo(request.equeue(id="queue", length=0.5)) queue = cue_cut(queue) pypo_data = ref '0' diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 61635fc55..5aadfd94c 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -9,10 +9,16 @@ import telnetlib import calendar import json import math + +""" +It is possible to use a list as a queue, where the first element added is the first element +retrieved ("first-in, first-out"); however, lists are not efficient for this purpose. Let's use +"deque" +""" +from collections import deque + from threading import Thread - from api_clients import api_client - from configobj import ConfigObj @@ -25,6 +31,7 @@ try: LS_HOST = config['ls_host'] LS_PORT = config['ls_port'] PUSH_INTERVAL = 2 + MAX_LIQUIDSOAP_QUEUE_LENGTH = 2 except Exception, e: logger = logging.getLogger() logger.error('Error loading config file %s', e) @@ -42,6 +49,8 @@ class PypoPush(Thread): self.push_ahead = 10 self.last_end_time = 0 + self.liquidsoap_queue = deque() + self.logger = logging.getLogger('push') def push(self): @@ -51,6 +60,8 @@ class PypoPush(Thread): If yes, the current liquidsoap playlist gets replaced with the corresponding one, then liquidsoap is asked (via telnet) to reload and immediately play it. """ + + self.update_liquidsoap_queue() timenow = time.time() # get a new schedule from pypo-fetch @@ -58,38 +69,46 @@ class PypoPush(Thread): # make sure we get the latest schedule while not self.queue.empty(): self.media = self.queue.get() + self.logger.debug("Received data from pypo-fetch") self.logger.debug('media %s' % json.dumps(self.media)) + self.handle_new_media(self.media) + media = self.media - currently_on_air = False - if media: - tnow = time.gmtime(timenow) - tcoming = time.gmtime(timenow + self.push_ahead) - str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) - str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5]) - - for key in media.keys(): - media_item = media[key] - item_start = media_item['start'][0:19] - - if str_tnow_s <= item_start and item_start < str_tcoming_s: - """ - If the media item starts in the next 30 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): - self.logger.debug("Pushed to liquidsoap, updating 'played' status.") - + if len(self.liquidsoap_queue) < MAX_LIQUIDSOAP_QUEUE_LENGTH: + """ + We only care about what's scheduled if the number of items in the queue is less + than our max queue limit. + """ + currently_on_air = False + if media: + tnow = time.gmtime(timenow) + tcoming = time.gmtime(timenow + self.push_ahead) + str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) + str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5]) + + for key in media.keys(): + media_item = media[key] + item_start = media_item['start'][0:19] + + if str_tnow_s <= item_start and item_start < str_tcoming_s: """ - Temporary solution to make sure we don't push the same track multiple times. + If the media item starts in the next 30 seconds, push it to the queue. """ - del media[key] - - currently_on_air = True - self.liquidsoap_state_play = True + self.logger.debug('Preparing to push media item scheduled at: %s', key) + + if self.push_to_liquidsoap(media_item): + self.logger.debug("Pushed to liquidsoap, updating 'played' status.") + + """ + Temporary solution to make sure we don't push the same track multiple times. + """ + del media[key] + + currently_on_air = True + self.liquidsoap_state_play = True def push_to_liquidsoap(self, media_item): """ @@ -122,6 +141,60 @@ class PypoPush(Thread): return False return True + + def update_liquidsoap_queue(self): + """ + the queue variable liquidsoap_queue is our attempt to mirror + what liquidsoap actually has in its own queue. Liquidsoap automatically + updates its own queue when an item finishes playing, we have to do this + manually. + + This function will iterate through the liquidsoap_queue and remove items + whose end time are in the past. + """ + + tnow = time.gmtime(timenow) + str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) + + while len(self.liquidsoap_queue) > 0: + if self.liquidsoap_queue[0]["end"] < str_tnow_s: + self.liquidsoap_queue.popleft() + + def handle_new_media(self, media): + """ + This function's purpose is to gracefully handle situations where + Liquidsoap already has a track in its queue, but the schedule + has changed. + + If the media_item is already being played, then we can't + do anything about it. If it is in the Liquidsoap queue, but not + yet playing then we can remove it. Note that currently + there can only ever be one media_item in the queue that isn't + being played (for a max of two items in the queue). + """ + + """ + TODO: What happens if we remove the second item in the queue when + it just became the primary item? + """ + if len(self.liquidsoap_queue) > 1: + media_item = self.liquidsoap_queue[1] + + if media["id"] != liquidsoap_queue["id"]: + """ + The md5s are the not same, so a different + item has been scheduled! + """ + #remove from actual liquidsoap queue + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + msg = 'queue.remove %s\n' % media_item["queue_id"] + tn.write(msg) + tn.write("exit\n") + self.logger.debug(tn.read_all()) + + #remove from our liquidsoap queue + self.liquidsoap_queue.remove(media_item) + def sleep_until_start(self, media_item): """ @@ -162,8 +235,14 @@ class PypoPush(Thread): annotation = media_item['annotation'] msg = 'queue.push %s\n' % annotation.encode('utf-8') - tn.write(msg) self.logger.debug(msg) + tn.write(msg) + queue_id = tn.read_until("\r\n").strip("\r\n") + + media_item['queue_id'] = queue_id + + #add media_item to the end of our queue + self.liquidsoap_queue.append(media_item) show_name = media_item['show_name'] msg = 'vars.show_name %s\n' % show_name.encode('utf-8') From 2aa84f5bd17d47f6345a5e25c2ddf8742c7c6d83 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Sat, 10 Mar 2012 14:13:38 -0500 Subject: [PATCH 5/6] cc-2675: Edit ongoing show -progress being made --- python_apps/pypo/pypopush.py | 107 +++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 24 deletions(-) diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 5aadfd94c..24f1bf95a 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -164,37 +164,94 @@ class PypoPush(Thread): """ This function's purpose is to gracefully handle situations where Liquidsoap already has a track in its queue, but the schedule - has changed. - - If the media_item is already being played, then we can't - do anything about it. If it is in the Liquidsoap queue, but not - yet playing then we can remove it. Note that currently - there can only ever be one media_item in the queue that isn't - being played (for a max of two items in the queue). + has changed. """ """ - TODO: What happens if we remove the second item in the queue when - it just became the primary item? + First let's connect to Liquidsoap and find what media items are in its queue. + + We will compare these items to the schedule we've received and decide if any + action needs to take place. """ - if len(self.liquidsoap_queue) > 1: - media_item = self.liquidsoap_queue[1] - - if media["id"] != liquidsoap_queue["id"]: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + + msg = 'queue.queue %s\n' % media_item["queue_id"] + tn.write(msg) + response = tn.read_until("\r\n").strip("\r\n") + tn.write('exit\n') + tn.read_all() + + list = response.split(" ") + + liquidsoap_queue_mirror = [] + + for l in list: + if l in self.pushed_objects: + liquidsoap_queue_mirror.append(self.pushed_objects[l]) + else: + self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue") + + #TODO: Keys should already be sorted. Verify this. + sorted_keys = sort(media.keys()) + + if len(liquidsoap_queue_mirror) == 0: + """ + liquidsoap doesn't have anything in its queue, so we have nothing + to worry about. + """ + pass + + if len(liquidsoap_queue_mirror) == 1: + if liquidsoap_queue_mirror[0]['id'] != media_item[sorted_keys[0]]['id']: """ - The md5s are the not same, so a different - item has been scheduled! + liquidsoap queue does not match the newest schedule. The queue is only of + length 1, and so that means the item in the queue is playing. Need to do source.skip """ - #remove from actual liquidsoap queue - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - msg = 'queue.remove %s\n' % media_item["queue_id"] - tn.write(msg) - tn.write("exit\n") - self.logger.debug(tn.read_all()) + self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[0]) - #remove from our liquidsoap queue - self.liquidsoap_queue.remove(media_item) + if len(liquidsoap_queue_mirror) == 2: + if liquidsoap_queue_mirror[0]['id'] == media_item[sorted_keys[0]]['id'] \ + and liquidsoap_queue_mirror[1]['id'] == media_item[sorted_keys[1]]['id']: + """ + What's in the queue matches what's in the schedule. Nothing to do. + """ + pass + elif liquidsoap_queue_mirror[0]['id'] == media_item[sorted_keys[0]]['id'] \ + and liquidsoap_queue_mirror[1]['id'] != media_item[sorted_keys[1]]['id']: + """ + instruct liquidsoap to remove the second item from the queue + """ + self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[1]) + elif liquidsoap_queue_mirror[0]['id'] != media_item[sorted_keys[0]]['id']: + """ + remove both items from the queue. Remove in reverse order so that source.skip + doesn't skip to the second song which we also plan on removing. + """ + self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[1]) + self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[0]) + + def remove_from_liquidsoap_queue(self, media_item): + if 'queue_id' in media_item: + queue_id = media_item['queue_id'] + + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + msg = "queue.remove %s\n" % queue_id + 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" + tn.write("source.skip") + + tn.write("exit\n") + tn.read_all() + else: + self.logger.error("'queue_id' key doesn't exist in media_item dict()") def sleep_until_start(self, media_item): """ @@ -239,10 +296,12 @@ class PypoPush(Thread): tn.write(msg) queue_id = tn.read_until("\r\n").strip("\r\n") + #remember the media_item's queue id which we may use + #later if we need to remove it from the queue. media_item['queue_id'] = queue_id #add media_item to the end of our queue - self.liquidsoap_queue.append(media_item) + self.pushed_objects[queue_id] = media_item show_name = media_item['show_name'] msg = 'vars.show_name %s\n' % show_name.encode('utf-8') From 33df626c3d072c95769bc608141470532f0eea2e Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Tue, 13 Mar 2012 15:24:24 -0400 Subject: [PATCH 6/6] cc-2675: edit ongoing show -almost there? --- python_apps/pypo/pypopush.py | 153 ++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 72 deletions(-) diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 24f1bf95a..abdcb4c7b 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -49,8 +49,8 @@ class PypoPush(Thread): self.push_ahead = 10 self.last_end_time = 0 - self.liquidsoap_queue = deque() - + self.pushed_objects = {} + self.logger = logging.getLogger('push') def push(self): @@ -61,7 +61,8 @@ class PypoPush(Thread): then liquidsoap is asked (via telnet) to reload and immediately play it. """ - self.update_liquidsoap_queue() + liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() + self.logger.debug('liquidsoap_queue_approx %s', liquidsoap_queue_approx) timenow = time.time() # get a new schedule from pypo-fetch @@ -72,16 +73,12 @@ class PypoPush(Thread): self.logger.debug("Received data from pypo-fetch") self.logger.debug('media %s' % json.dumps(self.media)) - self.handle_new_media(self.media) + self.handle_new_media(self.media, liquidsoap_queue_approx) media = self.media - if len(self.liquidsoap_queue) < MAX_LIQUIDSOAP_QUEUE_LENGTH: - """ - We only care about what's scheduled if the number of items in the queue is less - than our max queue limit. - """ + if len(liquidsoap_queue_approx) < MAX_LIQUIDSOAP_QUEUE_LENGTH: currently_on_air = False if media: tnow = time.gmtime(timenow) @@ -142,16 +139,15 @@ class PypoPush(Thread): return True + """ def update_liquidsoap_queue(self): - """ - the queue variable liquidsoap_queue is our attempt to mirror - what liquidsoap actually has in its own queue. Liquidsoap automatically - updates its own queue when an item finishes playing, we have to do this - manually. - - This function will iterate through the liquidsoap_queue and remove items - whose end time are in the past. - """ +# the queue variable liquidsoap_queue is our attempt to mirror +# what liquidsoap actually has in its own queue. Liquidsoap automatically +# updates its own queue when an item finishes playing, we have to do this +# manually. +# +# This function will iterate through the liquidsoap_queue and remove items +# whose end time are in the past. tnow = time.gmtime(timenow) str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) @@ -159,79 +155,92 @@ class PypoPush(Thread): while len(self.liquidsoap_queue) > 0: if self.liquidsoap_queue[0]["end"] < str_tnow_s: self.liquidsoap_queue.popleft() + """ - def handle_new_media(self, media): + def get_queue_items_from_liquidsoap(self): """ - This function's purpose is to gracefully handle situations where - Liquidsoap already has a track in its queue, but the schedule - has changed. + This function connects to Liquidsoap to find what media items are in its queue. """ - """ - First let's connect to Liquidsoap and find what media items are in its queue. - - We will compare these items to the schedule we've received and decide if any - action needs to take place. - """ tn = telnetlib.Telnet(LS_HOST, LS_PORT) - msg = 'queue.queue %s\n' % media_item["queue_id"] + msg = 'queue.queue\n' tn.write(msg) - response = tn.read_until("\r\n").strip("\r\n") + response = tn.read_until("\r\n").strip(" \r\n") tn.write('exit\n') tn.read_all() - list = response.split(" ") - - liquidsoap_queue_mirror = [] - - for l in list: - if l in self.pushed_objects: - liquidsoap_queue_mirror.append(self.pushed_objects[l]) - else: - self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue") + liquidsoap_queue_approx = [] + if len(response) > 0: + items_in_queue = response.split(" ") + + self.logger.debug("items_in_queue: %s", items_in_queue) + + for item in items_in_queue: + if item in self.pushed_objects: + liquidsoap_queue_approx.append(self.pushed_objects[item]) + else: + self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue: " + item) + + return liquidsoap_queue_approx + + + def handle_new_media(self, media, liquidsoap_queue_approx): + """ + This function's purpose is to gracefully handle situations where + Liquidsoap already has a track in its queue, but the schedule + has changed. If the schedule has changed, this function's job is to + call other functions that will connect to Liquidsoap and alter its + queue. + """ + #TODO: Keys should already be sorted. Verify this. - sorted_keys = sort(media.keys()) + sorted_keys = sorted(media.keys()) - if len(liquidsoap_queue_mirror) == 0: + if len(liquidsoap_queue_approx) == 0: """ liquidsoap doesn't have anything in its queue, so we have nothing - to worry about. + to worry about. Life is good. """ pass - - if len(liquidsoap_queue_mirror) == 1: - if liquidsoap_queue_mirror[0]['id'] != media_item[sorted_keys[0]]['id']: - """ - liquidsoap queue does not match the newest schedule. The queue is only of - length 1, and so that means the item in the queue is playing. Need to do source.skip - """ - self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[0]) + elif len(liquidsoap_queue_approx) == 1: + queue_item_0_start = liquidsoap_queue_approx[0]['start'] + try: + if liquidsoap_queue_approx[0]['id'] != media[queue_item_0_start]['id']: + """ + liquidsoap's queue does not match the schedule we just received from the Airtime server. + The queue is only of length 1 which means the item in the queue is playing. + Need to do source.skip. + + Since only one item, we don't have to worry about the current item ending and us calling + source.skip unintentionally on the next item (there is no next item). + """ + + self.logger.debug("%s from ls does not exist in queue new schedule. Removing" % liquidsoap_queue_approx[0]['id'], media) + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + except KeyError, k: + 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]) + + + elif len(liquidsoap_queue_approx) == 2: + queue_item_0_start = liquidsoap_queue_approx[0]['start'] + queue_item_1_start = liquidsoap_queue_approx[1]['start'] + + if queue_item_1_start in media.keys(): + if liquidsoap_queue_approx[1]['id'] != media[queue_item_1_start]['id']: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[1]) + else: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[1]) - - if len(liquidsoap_queue_mirror) == 2: - if liquidsoap_queue_mirror[0]['id'] == media_item[sorted_keys[0]]['id'] \ - and liquidsoap_queue_mirror[1]['id'] == media_item[sorted_keys[1]]['id']: - """ - What's in the queue matches what's in the schedule. Nothing to do. - """ - pass - elif liquidsoap_queue_mirror[0]['id'] == media_item[sorted_keys[0]]['id'] \ - and liquidsoap_queue_mirror[1]['id'] != media_item[sorted_keys[1]]['id']: - """ - instruct liquidsoap to remove the second item from the queue - """ - self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[1]) - elif liquidsoap_queue_mirror[0]['id'] != media_item[sorted_keys[0]]['id']: - """ - remove both items from the queue. Remove in reverse order so that source.skip - doesn't skip to the second song which we also plan on removing. - """ - self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[1]) - self.remove_from_liquidsoap_queue(liquidsoap_queue_mirror[0]) + if queue_item_0_start in media.keys(): + if liquidsoap_queue_approx[0]['id'] != media[queue_item_0_start]['id']: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + else: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) - def remove_from_liquidsoap_queue(self, media_item): + def remove_from_liquidsoap_queue(self, media_item, do_only_source_skip=False): if 'queue_id' in media_item: queue_id = media_item['queue_id']