From 0716e0425c03dd2cc4ad58b50c824c7a3bc8fc38 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Thu, 15 Mar 2012 23:28:47 -0400 Subject: [PATCH 01/21] -Media monitor not handling unicode incorrectly or comparing UTF-8 with unicode --- .../airtimemediamonitorbootstrap.py | 7 ++----- .../airtimefilemonitor/mediamonitorcommon.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/python_apps/media-monitor/airtimefilemonitor/airtimemediamonitorbootstrap.py b/python_apps/media-monitor/airtimefilemonitor/airtimemediamonitorbootstrap.py index 55b8856ff..d23651955 100644 --- a/python_apps/media-monitor/airtimefilemonitor/airtimemediamonitorbootstrap.py +++ b/python_apps/media-monitor/airtimefilemonitor/airtimemediamonitorbootstrap.py @@ -42,7 +42,7 @@ class AirtimeMediaMonitorBootstrap(): for id, dir in directories.iteritems(): self.logger.debug("%s, %s", id, dir) - self.sync_database_to_filesystem(id, api_client.encode_to(dir, "utf-8")) + self.sync_database_to_filesystem(id, dir) """Gets a list of files that the Airtime database knows for a specific directory. You need to provide the directory's row ID, which is obtained when calling @@ -82,7 +82,7 @@ class AirtimeMediaMonitorBootstrap(): db_known_files_set = set() files = self.list_db_files(dir_id) for file in files['files']: - db_known_files_set.add(api_client.encode_to(file, 'utf-8')) + db_known_files_set.add(file) new_files = self.mmc.scan_dir_for_new_files(dir) @@ -123,9 +123,6 @@ class AirtimeMediaMonitorBootstrap(): new_files_set = all_files_set - db_known_files_set modified_files_set = new_and_modified_files - new_files_set - #NAOMI: Please comment out the "Known files" line, if you find the bug. - #it is for debugging purposes only (Too much data will be written to log). -mk - #self.logger.info("Known files: \n%s\n\n"%db_known_files_set) self.logger.info("Deleted files: \n%s\n\n"%deleted_files_set) self.logger.info("New files: \n%s\n\n"%new_files_set) self.logger.info("Modified files: \n%s\n\n"%modified_files_set) diff --git a/python_apps/media-monitor/airtimefilemonitor/mediamonitorcommon.py b/python_apps/media-monitor/airtimefilemonitor/mediamonitorcommon.py index 8927a9c4c..f526f2616 100644 --- a/python_apps/media-monitor/airtimefilemonitor/mediamonitorcommon.py +++ b/python_apps/media-monitor/airtimefilemonitor/mediamonitorcommon.py @@ -251,12 +251,29 @@ class MediaMonitorCommon: if p.returncode != 0: self.logger.warn("command \n%s\n return with a non-zero return value", command) self.logger.error(stderr) + + try: + """ + File name charset encoding is UTF-8. + """ + stdout = stdout.decode("UTF-8") + except Exception, e: + self.logger.error("Could not decode %s using UTF-8" % stdout) + return stdout def scan_dir_for_new_files(self, dir): command = 'find "%s" -type f -iname "*.ogg" -o -iname "*.mp3" -readable' % dir.replace('"', '\\"') self.logger.debug(command) - stdout = self.exec_command(command) + stdout = self.exec_command(command).decode("UTF-8") + + try: + """ + File name charset encoding is UTF-8. + """ + stdout = stdout.decode("UTF-8") + except Exception, e: + self.logger.error("Could not decode %s using UTF-8" % stdout) return stdout.splitlines() From 18bcdc515731770d8a6ce1ae2d341b3247fcf160 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Thu, 15 Mar 2012 23:59:36 -0400 Subject: [PATCH 02/21] -useful utility to compare database to filesystem --- dev_tools/compare_cc_files_to_fs.py | 131 ++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 dev_tools/compare_cc_files_to_fs.py diff --git a/dev_tools/compare_cc_files_to_fs.py b/dev_tools/compare_cc_files_to_fs.py new file mode 100644 index 000000000..3f35fc033 --- /dev/null +++ b/dev_tools/compare_cc_files_to_fs.py @@ -0,0 +1,131 @@ +import os +import time +import shutil +import sys +import logging + +from configobj import ConfigObj +from subprocess import Popen, PIPE +from api_clients import api_client as apc + +""" +The purpose of this script is that you can run it, and it will compare what the database has to what your filesystem +has. It will then report if there are any differences. It will *NOT* make any changes, unlike media-monitor which uses +similar code when it starts up (but then makes changes if something is different) +""" + + +class AirtimeMediaMonitorBootstrap(): + + """AirtimeMediaMonitorBootstrap constructor + + Keyword Arguments: + logger -- reference to the media-monitor logging facility + pe -- reference to an instance of ProcessEvent + api_clients -- reference of api_clients to communicate with airtime-server + """ + def __init__(self): + config = ConfigObj('/etc/airtime/media-monitor.cfg') + self.api_client = apc.api_client_factory(config) + + """ + try: + logging.config.fileConfig("logging.cfg") + except Exception, e: + print 'Error configuring logging: ', e + sys.exit(1) + """ + + self.logger = logging.getLogger() + self.logger.info("Adding %s on watch list...", "xxx") + + self.scan() + + """On bootup we want to scan all directories and look for files that + weren't there or files that changed before media-monitor process + went offline. + """ + def scan(self): + directories = self.get_list_of_watched_dirs(); + + self.logger.info("watched directories found: %s", directories) + + for id, dir in directories.iteritems(): + self.logger.debug("%s, %s", id, dir) + #CHANGED!!! + #self.sync_database_to_filesystem(id, api_client.encode_to(dir, "utf-8")) + self.sync_database_to_filesystem(id, dir) + + """Gets a list of files that the Airtime database knows for a specific directory. + You need to provide the directory's row ID, which is obtained when calling + get_list_of_watched_dirs function. + dir_id -- row id of the directory in the cc_watched_dirs database table + """ + def list_db_files(self, dir_id): + return self.api_client.list_all_db_files(dir_id) + + """ + returns the path and the database row id for this path for all watched directories. Also + returns the Stor directory, which can be identified by its row id (always has value of "1") + """ + def get_list_of_watched_dirs(self): + json = self.api_client.list_all_watched_dirs() + return json["dirs"] + + def scan_dir_for_existing_files(self, dir): + command = 'find "%s" -type f -iname "*.ogg" -o -iname "*.mp3" -readable' % dir.replace('"', '\\"') + self.logger.debug(command) + #CHANGED!! + stdout = self.exec_command(command).decode("UTF-8") + + return stdout.splitlines() + + def exec_command(self, command): + p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE) + stdout, stderr = p.communicate() + if p.returncode != 0: + self.logger.warn("command \n%s\n return with a non-zero return value", command) + self.logger.error(stderr) + return stdout + + """ + This function takes in a path name provided by the database (and its corresponding row id) + and reads the list of files in the local file system. Its purpose is to discover which files + exist on the file system but not in the database and vice versa, as well as which files have + been modified since the database was last updated. In each case, this method will call an + appropiate method to ensure that the database actually represents the filesystem. + dir_id -- row id of the directory in the cc_watched_dirs database table + dir -- pathname of the directory + """ + def sync_database_to_filesystem(self, dir_id, dir): + """ + set to hold new and/or modified files. We use a set to make it ok if files are added + twice. This is because some of the tests for new files return result sets that are not + mutually exclusive from each other. + """ + db_known_files_set = set() + + files = self.list_db_files(dir_id) + for file in files['files']: + db_known_files_set.add(file) + + existing_files = self.scan_dir_for_existing_files(dir) + + existing_files_set = set() + for file_path in existing_files: + if len(file_path.strip(" \n")) > 0: + existing_files_set.add(file_path[len(dir):]) + + + deleted_files_set = db_known_files_set - existing_files_set + new_files_set = existing_files_set - db_known_files_set + + + print ("DB Known files: \n%s\n\n"%len(db_known_files_set)) + print ("FS Known files: \n%s\n\n"%len(existing_files_set)) + + print ("Deleted files: \n%s\n\n"%deleted_files_set) + print ("New files: \n%s\n\n"%new_files_set) + +if __name__ == "__main__": + AirtimeMediaMonitorBootstrap() From 79964186eda58d4df814cf6f130ee0c1d5946af2 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 16 Mar 2012 00:48:39 -0400 Subject: [PATCH 03/21] CC-3462: If destination file already exists in pypo cache, double... -done --- python_apps/pypo/pypofetch.py | 45 ++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index c7634b878..bd441c875 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -489,17 +489,44 @@ class PypoFetch(Thread): def copy_file(self, media_item, dst): """ - Copy the file from local library directory. + Copy the file from local library directory. Note that we are not using os.path.exists + since this can lie to us! It seems the best way to get whether a file exists is to actually + do an operation on the file (such as query its size). Getting the file size of a non-existent + file will throw an exception, so we can look for this exception instead of os.path.exists. """ - if not os.path.isfile(dst): - self.logger.debug("copying from %s to local cache %s" % (media_item['uri'], dst)) - try: - shutil.copy(media_item['uri'], dst) - except: - self.logger.error("Could not copy from %s to %s" % (media_item['uri'], dst)) + + src = media_item['uri'] + + try: + src_size = os.path.getsize(src) + except Exception, e: + self.logger.error("Could not get size of source file: %s", src) + return + + + dst_exists = True + try: + dst_size = os.path.getsize(dst) + except Exception, e: + dst_exists = False + + do_copy = False + if dst_exists: + if src_size != dst_size: + do_copy = True else: - #file already exists - pass + do_copy = True + + + if do_copy: + self.logger.debug("copying from %s to local cache %s" % (src, dst)) + try: + """ + copy will overwrite dst if it already exists + """ + shutil.copy(src, dst) + except: + self.logger.error("Could not copy from %s to %s" % (src, dst)) def download_file(self, media_item, dst): From 067cd1280ad2ae9833492ad078d51e231c1cafdb Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 16 Mar 2012 00:49:08 -0400 Subject: [PATCH 04/21] -remove some annoying logging info from pypopush --- python_apps/pypo/pypopush.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index abdcb4c7b..45f28b82b 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -62,7 +62,6 @@ class PypoPush(Thread): """ 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 From 8a34023336e4126347c20d74e57e3de58923cbb4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 16 Mar 2012 14:02:53 -0400 Subject: [PATCH 05/21] CC-3465: Now playing should show tracks over shows for previous and next - added logic to include previous and next show in schedule query so the now playing bar will show the tracks from the show instead of the show itself. --- airtime_mvc/application/models/Schedule.php | 45 +++++++++++++++------ airtime_mvc/application/models/Show.php | 16 ++++++-- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index b924ee4c0..2afad25f5 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -64,14 +64,16 @@ class Application_Model_Schedule { $utcTimeNow = $date->getUtcTimestamp(); $shows = Application_Model_Show::getPrevCurrentNext($utcTimeNow); + $previousShowID = count($shows['previousShow'])>0?$shows['previousShow'][0]['id']:null; $currentShowID = count($shows['currentShow'])>0?$shows['currentShow'][0]['id']:null; - $results = Application_Model_Schedule::GetPrevCurrentNext($currentShowID, $utcTimeNow); + $nextShowID = count($shows['nextShow'])>0?$shows['nextShow'][0]['id']:null; + $results = Application_Model_Schedule::GetPrevCurrentNext($previousShowID, $currentShowID, $nextShowID, $utcTimeNow); $range = array("env"=>APPLICATION_ENV, "schedulerTime"=>$timeNow, - "previous"=>isset($results['previous'])?$results['previous']:(count($shows['previousShow'])>0?$shows['previousShow'][0]:null), - "current"=>isset($results['current'])?$results['current']:null, - "next"=> isset($results['next'])?$results['next']:(count($shows['nextShow'])>0?$shows['nextShow'][0]:null), + "previous"=>$results['previous'] !=null?$results['previous']:(count($shows['previousShow'])>0?$shows['previousShow'][0]:null), + "current"=>$results['current'] !=null?$results['current']:null, + "next"=> $results['next'] !=null?$results['next']:(count($shows['nextShow'])>0?$shows['nextShow'][0]:null), "currentShow"=>$shows['currentShow'], "nextShow"=>$shows['nextShow'], "timezone"=> date("T"), @@ -88,19 +90,36 @@ class Application_Model_Schedule { * show types are not found through this mechanism a call is made to the old way of querying * the database to find the track info. **/ - public static function GetPrevCurrentNext($p_currentShowID, $p_timeNow) + public static function GetPrevCurrentNext($p_previousShowID, $p_currentShowID, $p_nextShowID, $p_timeNow) { global $CC_CONFIG, $CC_DBC; - - if (!isset($p_currentShowID)) { - return array(); - } - $sql = "Select ft.artist_name, ft.track_title, st.starts as starts, st.ends as ends, st.media_item_played as media_item_played + $sql = 'Select ft.artist_name, ft.track_title, st.starts as starts, st.ends as ends, st.media_item_played as media_item_played FROM cc_schedule st LEFT JOIN cc_files ft ON st.file_id = ft.id - WHERE st.instance_id = '$p_currentShowID' AND st.playout_status > 0 - ORDER BY st.starts"; + WHERE '; + + if (isset($p_previousShowID)){ + if (isset($p_nextShowID) || isset($p_currentShowID)) + $sql .= '('; + $sql .= 'st.instance_id = '.$p_previousShowID; + } + if ($p_currentShowID != null){ + if ($p_previousShowID != null) + $sql .= ' OR '; + else if($p_nextShowID != null) + $sql .= '('; + $sql .= 'st.instance_id = '.$p_currentShowID; + } + if ($p_nextShowID != null) { + if ($p_previousShowID != null || $p_currentShowID != null) + $sql .= ' OR '; + $sql .= 'st.instance_id = '.$p_nextShowID; + if($p_previousShowID != null || $p_currentShowID != null) + $sql .= ')'; + } else if($p_previousShowID != null && $p_currentShowID != null) + $sql .= ')'; + + $sql .= ' AND st.playout_status > 0 ORDER BY st.starts'; - //Logging::log($sql); $rows = $CC_DBC->GetAll($sql); $numberOfRows = count($rows); diff --git a/airtime_mvc/application/models/Show.php b/airtime_mvc/application/models/Show.php index afd9255b7..60549df7a 100644 --- a/airtime_mvc/application/models/Show.php +++ b/airtime_mvc/application/models/Show.php @@ -1762,7 +1762,9 @@ class Application_Model_Show { //Find the show that is within the current time. if ((strtotime($rows[$i]['starts']) <= $timeNowAsMillis) && (strtotime($rows[$i]['ends']) >= $timeNowAsMillis)){ if ( $i - 1 >= 0){ - $results['previousShow'][0] = array("name"=>$rows[$i-1]['name'], + $results['previousShow'][0] = array( + "id"=>$rows[$i-1]['id'], + "name"=>$rows[$i-1]['name'], "start_timestamp"=>$rows[$i-1]['start_timestamp'], "end_timestamp"=>$rows[$i-1]['end_timestamp'], "starts"=>$rows[$i-1]['starts'], @@ -1772,7 +1774,9 @@ class Application_Model_Show { $results['currentShow'][0] = $rows[$i]; if ( isset($rows[$i+1])){ - $results['nextShow'][0] = array("name"=>$rows[$i+1]['name'], + $results['nextShow'][0] = array( + "id"=>$rows[$i+1]['id'], + "name"=>$rows[$i+1]['name'], "start_timestamp"=>$rows[$i+1]['start_timestamp'], "end_timestamp"=>$rows[$i+1]['end_timestamp'], "starts"=>$rows[$i+1]['starts'], @@ -1787,7 +1791,9 @@ class Application_Model_Show { } //if we hit this we know we've gone to far and can stop looping. if (strtotime($rows[$i]['starts']) > $timeNowAsMillis) { - $results['nextShow'][0] = array("name"=>$rows[$i]['name'], + $results['nextShow'][0] = array( + "id"=>$rows[$i]['id'], + "name"=>$rows[$i]['name'], "start_timestamp"=>$rows[$i]['start_timestamp'], "end_timestamp"=>$rows[$i]['end_timestamp'], "starts"=>$rows[$i]['starts'], @@ -1798,7 +1804,9 @@ class Application_Model_Show { //If we didn't find a a current show because the time didn't fit we may still have //found a previous show so use it. if (count($results['previousShow']) == 0 && isset($previousShowIndex)) { - $results['previousShow'][0] = array("name"=>$rows[$previousShowIndex]['name'], + $results['previousShow'][0] = array( + "id"=>$rows[$previousShowIndex]['id'], + "name"=>$rows[$previousShowIndex]['name'], "start_timestamp"=>$rows[$previousShowIndex]['start_timestamp'], "end_timestamp"=>$rows[$previousShowIndex]['end_timestamp'], "starts"=>$rows[$previousShowIndex]['starts'], From 77e1c1c0cbc103eebcecd424776cd914d49779d1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 16 Mar 2012 17:13:32 -0400 Subject: [PATCH 06/21] CC-3466: Select statement for Now Playing has 'AND' in statement when no shows scheduled. -added check to make the code do nothing when a previous, current and next show id aren't specified. --- airtime_mvc/application/models/Schedule.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index 2afad25f5..2fe7b576f 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -92,6 +92,9 @@ class Application_Model_Schedule { **/ public static function GetPrevCurrentNext($p_previousShowID, $p_currentShowID, $p_nextShowID, $p_timeNow) { + if ($p_previousShowID == null && $p_currentShowID == null && $p_nextShowID == null) + return; + global $CC_CONFIG, $CC_DBC; $sql = 'Select ft.artist_name, ft.track_title, st.starts as starts, st.ends as ends, st.media_item_played as media_item_played FROM cc_schedule st LEFT JOIN cc_files ft ON st.file_id = ft.id From 0b02009a0ee2ac78d8ab3bb478776f214b2e312d Mon Sep 17 00:00:00 2001 From: martin Date: Fri, 16 Mar 2012 17:36:27 -0400 Subject: [PATCH 07/21] -maintenance on liquidsoap_compile script. Add a configuration file -make it work with releases from hg repository (previously only worked with official 1.0.0 liquidsoap release) --- dev_tools/fabric/fab_liquidsoap_compile.cfg | 2 ++ dev_tools/fabric/fab_liquidsoap_compile.py | 31 +++++++++++++-------- 2 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 dev_tools/fabric/fab_liquidsoap_compile.cfg diff --git a/dev_tools/fabric/fab_liquidsoap_compile.cfg b/dev_tools/fabric/fab_liquidsoap_compile.cfg new file mode 100644 index 000000000..bf6d462d8 --- /dev/null +++ b/dev_tools/fabric/fab_liquidsoap_compile.cfg @@ -0,0 +1,2 @@ +[main] +liquidsoap_tar_url = http://dl.dropbox.com/u/256410/airtime/liquidsoap.tar.gz diff --git a/dev_tools/fabric/fab_liquidsoap_compile.py b/dev_tools/fabric/fab_liquidsoap_compile.py index 68064395d..06ca05730 100644 --- a/dev_tools/fabric/fab_liquidsoap_compile.py +++ b/dev_tools/fabric/fab_liquidsoap_compile.py @@ -10,6 +10,8 @@ import sys from fabric.api import * from fabric.contrib.files import comment, sed, append +from ConfigParser import ConfigParser + from xml.dom.minidom import parse from xml.dom.minidom import Node from xml.dom.minidom import Element @@ -157,7 +159,13 @@ def debian_squeeze_64(fresh_os=True): def compile_liquidsoap(filename="liquidsoap"): - + + config = ConfigParser() + config.readfp(open('fab_liquidsoap_compile.cfg')) + url = config.get('main', 'liquidsoap_tar_url') + + print "Will get liquidsoap from " + url + do_sudo('apt-get update') do_sudo('apt-get upgrade -y --force-yes') do_sudo('apt-get install -y --force-yes ocaml-findlib libao-ocaml-dev libportaudio-ocaml-dev ' + \ @@ -171,14 +179,15 @@ def compile_liquidsoap(filename="liquidsoap"): do_run('mkdir -p %s' % root) tmpPath = do_local("mktemp", capture=True) - do_run('wget %s -O %s' % ('https://downloads.sourceforge.net/project/savonet/liquidsoap/1.0.0/liquidsoap-1.0.0-full.tar.bz2', tmpPath)) - do_run('mv %s %s/liquidsoap-1.0.0-full.tar.bz2' % (tmpPath, root)) - do_run('cd %s && bunzip2 liquidsoap-1.0.0-full.tar.bz2 && tar xf liquidsoap-1.0.0-full.tar' % root) + do_run('wget %s -O %s' % (url, tmpPath)) + do_run('mv %s %s/liquidsoap.tar.gz' % (tmpPath, root)) + do_run('cd %s && tar xzf liquidsoap.tar.gz' % root) - do_run('cd %s/liquidsoap-1.0.0-full && cp PACKAGES.minimal PACKAGES' % root) - sed('%s/liquidsoap-1.0.0-full/PACKAGES' % root, '#ocaml-portaudio', 'ocaml-portaudio') - sed('%s/liquidsoap-1.0.0-full/PACKAGES' % root, '#ocaml-alsa', 'ocaml-alsa') - sed('%s/liquidsoap-1.0.0-full/PACKAGES' % root, '#ocaml-pulseaudio', 'ocaml-pulseaudio') - do_run('cd %s/liquidsoap-1.0.0-full && ./configure' % root) - do_run('cd %s/liquidsoap-1.0.0-full && make' % root) - get('%s/liquidsoap-1.0.0-full/liquidsoap-1.0.0/src/liquidsoap' % root, filename) + do_run('cd %s/savonet && cp PACKAGES.minimal PACKAGES' % root) + sed('%s/savonet/PACKAGES' % root, '#ocaml-portaudio', 'ocaml-portaudio') + sed('%s/savonet/PACKAGES' % root, '#ocaml-alsa', 'ocaml-alsa') + sed('%s/savonet/PACKAGES' % root, '#ocaml-pulseaudio', 'ocaml-pulseaudio') + do_run('cd %s/savonet && ./bootstrap' % root) + do_run('cd %s/savonet && ./configure' % root) + do_run('cd %s/savonet && make' % root) + get('%s/savonet/liquidsoap/src/liquidsoap' % root, filename) From 7914b302330a4dc46631cbc5aace40395767ba5e Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 16 Mar 2012 18:03:35 -0400 Subject: [PATCH 08/21] CC-3429: Firefox does not natively support mp3 file playing so preview does nothing. - after reading about the trick foudn in Mark Panaghiston's posting from this link https://groups.google.com/forum/#!topic/jplayer/gTJrSCjwftw - the current imperfect solution for firefox sould be to show a video jplayer that can play all the audio types. --- .../application/controllers/ApiController.php | 7 +++++-- airtime_mvc/public/js/jplayer/preview_jplayer.js | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index d32f2ad30..5a0d53cd9 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -150,8 +150,11 @@ class ApiController extends Zend_Controller_Action //user clicks play button for track and downloads it. header('Content-Disposition: inline; filename="'.$file_base_name.'"'); } - - $this->smartReadFile($filepath, 'audio/'.$ext); + if ($ext === 'mp3'){ + $this->smartReadFile($filepath, 'audio/mpeg'); + } else { + $this->smartReadFile($filepath, 'audio/'.$ext); + } exit; }else{ header ("HTTP/1.1 404 Not Found"); diff --git a/airtime_mvc/public/js/jplayer/preview_jplayer.js b/airtime_mvc/public/js/jplayer/preview_jplayer.js index 2f6c2bd6f..e954a74b5 100644 --- a/airtime_mvc/public/js/jplayer/preview_jplayer.js +++ b/airtime_mvc/public/js/jplayer/preview_jplayer.js @@ -8,16 +8,22 @@ var _idToPostionLookUp; */ $(document).ready(function(){ + if (useFlash()) + mySupplied = "oga, mp3, m4v"; + else + mySupplied = "oga, mp3"; + _playlist_jplayer = new jPlayerPlaylist({ jPlayer: "#jquery_jplayer_1", cssSelectorAncestor: "#jp_container_1" },[], //array of songs will be filled with below's json call { swfPath: "/js/jplayer", - //supplied: "mp3,oga", + supplied:mySupplied, wmode: "window" }); + $.jPlayer.timeFormat.showHour = true; var audioFileID = $('.audioFileID').text(); @@ -35,6 +41,10 @@ $(document).ready(function(){ } }); +function useFlash() { + console.log(navigator.userAgent); + return navigator.userAgent.toLowerCase().match('firefox'); +} /** * Sets up the jPlayerPlaylist to play. * - Get the playlist info based on the playlistID give. @@ -122,7 +132,7 @@ function play(p_playlistIndex){ function playOne(p_audioFileID) { var playlist = new Array(); var fileExtensioin = p_audioFileID.split('.').pop(); - + console.log(p_audioFileID); if (fileExtensioin === 'mp3') { media = {title: $('.audioFileTitle').text() !== 'null' ?$('.audioFileTitle').text():"", artist: $('.audioFileArtist').text() !== 'null' ?$('.audioFileArtist').text():"", @@ -135,7 +145,7 @@ function playOne(p_audioFileID) { }; } _playlist_jplayer.option("autoPlay", true); - + console.log(media); playlist[0] = media; //_playlist_jplayer.setPlaylist(playlist); --if I use this the player will call _init on the setPlaylist and on the ready _playlist_jplayer._initPlaylist(playlist); From 4d96a16cbe2a9e98c0a084bca8dcf585da633609 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 16 Mar 2012 20:39:49 -0400 Subject: [PATCH 09/21] -comment out unused function in pypo-fetch --- python_apps/pypo/pypofetch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index bd441c875..72c896339 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -529,16 +529,16 @@ class PypoFetch(Thread): self.logger.error("Could not copy from %s to %s" % (src, dst)) + """ def download_file(self, media_item, dst): - """ - Download a file from a remote server and store it in the cache. - """ + #Download a file from a remote server and store it in the cache. if os.path.isfile(dst): pass #self.logger.debug("file already in cache: %s", dst) else: self.logger.debug("try to download %s", media_item['uri']) self.api_client.get_media(media_item['uri'], dst) + """ def cleanup(self, media): """ From c8c9e462283e6405738be3bbb4542a1cd12013e7 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Thu, 15 Mar 2012 23:14:19 -0400 Subject: [PATCH 10/21] cc-3447: pypo telnet class --- python_apps/pypo/pypocli.py | 8 ++- python_apps/pypo/pypofetch.py | 84 +++++++++++++++++-------- python_apps/pypo/pypopush.py | 114 ++++++++++++++++++++-------------- 3 files changed, 132 insertions(+), 74 deletions(-) diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index 9eeb15a6f..db6dd7e1a 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -12,6 +12,8 @@ import logging.config import logging.handlers from Queue import Queue +from threading import Lock + from pypopush import PypoPush from pypofetch import PypoFetch from pypofile import PypoFile @@ -125,6 +127,8 @@ if __name__ == '__main__': recorder_q = Queue() pypoPush_q = Queue() + telnet_lock = Lock() + """ This queue is shared between pypo-fetch and pypo-file, where pypo-file is the receiver. Pypo-fetch will send every schedule it gets to pypo-file @@ -141,11 +145,11 @@ if __name__ == '__main__': pfile.daemon = True pfile.start() - pf = PypoFetch(pypoFetch_q, pypoPush_q, media_q) + pf = PypoFetch(pypoFetch_q, pypoPush_q, media_q, telnet_lock) pf.daemon = True pf.start() - pp = PypoPush(pypoPush_q) + pp = PypoPush(pypoPush_q, telnet_lock) pp.daemon = True pp.start() diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index 72c896339..fa162b0fc 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -12,6 +12,8 @@ import telnetlib import math import copy from threading import Thread +from threading import Lock + from subprocess import Popen, PIPE from datetime import datetime from datetime import timedelta @@ -38,13 +40,15 @@ except Exception, e: sys.exit() class PypoFetch(Thread): - def __init__(self, pypoFetch_q, pypoPush_q, media_q): + def __init__(self, pypoFetch_q, pypoPush_q, media_q, telnet_lock): Thread.__init__(self) self.api_client = api_client.api_client_factory(config) self.fetch_queue = pypoFetch_q self.push_queue = pypoPush_q self.media_prepare_queue = media_q + self.telnet_lock = telnet_lock + self.logger = logging.getLogger(); self.cache_dir = os.path.join(config["cache_dir"], "scheduler") @@ -113,14 +117,16 @@ class PypoFetch(Thread): elif(sourcename == "live_dj"): command += "live_dj_harbor.kick\n" + self.telnet_lock.acquire() try: tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn.write(command) tn.write('exit\n') tn.read_all() except Exception, e: - self.logger.debug(e) - self.logger.debug('Could not connect to liquidsoap') + self.logger.error(str(e)) + finally: + self.telnet_lock.release() def switch_source(self, sourcename, status): self.logger.debug('Switching source: %s to "%s" status', sourcename, status) @@ -137,14 +143,16 @@ class PypoFetch(Thread): else: command += "stop\n" + self.telnet_lock.acquire() try: tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn.write(command) tn.write('exit\n') tn.read_all() except Exception, e: - self.logger.debug(e) - self.logger.debug('Could not connect to liquidsoap') + self.logger.error(str(e)) + finally: + self.telnet_lock.release() """ This check current switch status from Airtime and update the status @@ -280,17 +288,25 @@ class PypoFetch(Thread): updates the status of liquidsoap connection to the streaming server This fucntion updates the bootup time variable in liquidsoap script """ - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - # update the boot up time of liquidsoap. Since liquidsoap is not restarting, - # we are manually adjusting the bootup time variable so the status msg will get - # updated. - current_time = time.time() - boot_up_time_command = "vars.bootup_time "+str(current_time)+"\n" - tn.write(boot_up_time_command) - tn.write("streams.connection_status\n") - tn.write('exit\n') - output = tn.read_all() + self.telnet_lock.acquire() + try: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + # update the boot up time of liquidsoap. Since liquidsoap is not restarting, + # we are manually adjusting the bootup time variable so the status msg will get + # updated. + current_time = time.time() + boot_up_time_command = "vars.bootup_time "+str(current_time)+"\n" + tn.write(boot_up_time_command) + tn.write("streams.connection_status\n") + tn.write('exit\n') + + output = tn.read_all() + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() + output_list = output.split("\r\n") stream_info = output_list[2] @@ -313,12 +329,19 @@ class PypoFetch(Thread): try: self.logger.info(LS_HOST) self.logger.info(LS_PORT) - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - command = ('vars.stream_metadata_type %s\n' % stream_format).encode('utf-8') - self.logger.info(command) - tn.write(command) - tn.write('exit\n') - tn.read_all() + + self.telnet_lock.acquire() + try: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + command = ('vars.stream_metadata_type %s\n' % stream_format).encode('utf-8') + self.logger.info(command) + tn.write(command) + tn.write('exit\n') + tn.read_all() + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() except Exception, e: self.logger.error("Exception %s", e) @@ -328,12 +351,19 @@ class PypoFetch(Thread): try: self.logger.info(LS_HOST) self.logger.info(LS_PORT) - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - command = ('vars.station_name %s\n' % station_name).encode('utf-8') - self.logger.info(command) - tn.write(command) - tn.write('exit\n') - tn.read_all() + + self.telnet_lock.acquire() + try: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + command = ('vars.station_name %s\n' % station_name).encode('utf-8') + self.logger.info(command) + tn.write(command) + tn.write('exit\n') + tn.read_all() + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() except Exception, e: self.logger.error("Exception %s", e) diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 45f28b82b..b09fb705b 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -18,6 +18,8 @@ retrieved ("first-in, first-out"); however, lists are not efficient for this pur from collections import deque from threading import Thread +from threading import Lock + from api_clients import api_client from configobj import ConfigObj @@ -38,12 +40,14 @@ except Exception, e: sys.exit() class PypoPush(Thread): - def __init__(self, q): + def __init__(self, q, telnet_lock): Thread.__init__(self) self.api_client = api_client.api_client_factory(config) self.queue = q self.media = dict() + + self.telnet_lock = telnet_lock self.liquidsoap_state_play = True self.push_ahead = 10 @@ -161,13 +165,20 @@ class PypoPush(Thread): This function connects to Liquidsoap to find what media items are in its queue. """ - tn = telnetlib.Telnet(LS_HOST, LS_PORT) + self.telnet_lock.acquire() - msg = 'queue.queue\n' - tn.write(msg) - response = tn.read_until("\r\n").strip(" \r\n") - tn.write('exit\n') - tn.read_all() + try: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + + msg = 'queue.queue\n' + tn.write(msg) + response = tn.read_until("\r\n").strip(" \r\n") + tn.write('exit\n') + tn.read_all() + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() liquidsoap_queue_approx = [] @@ -243,21 +254,28 @@ class PypoPush(Thread): 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") + self.telnet_lock.acquire() + try: + 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() + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() - tn.write("exit\n") - tn.read_all() else: self.logger.error("'queue_id' key doesn't exist in media_item dict()") @@ -294,30 +312,36 @@ class PypoPush(Thread): about which show is playing. """ - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - - #tn.write(("vars.pypo_data %s\n"%liquidsoap_data["schedule_id"]).encode('utf-8')) - - annotation = media_item['annotation'] - msg = 'queue.push %s\n' % annotation.encode('utf-8') - self.logger.debug(msg) - 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.pushed_objects[queue_id] = media_item - - show_name = media_item['show_name'] - msg = 'vars.show_name %s\n' % show_name.encode('utf-8') - tn.write(msg) - self.logger.debug(msg) - - tn.write("exit\n") - self.logger.debug(tn.read_all()) + self.telnet_lock.acquire() + try: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + + #tn.write(("vars.pypo_data %s\n"%liquidsoap_data["schedule_id"]).encode('utf-8')) + + annotation = media_item['annotation'] + msg = 'queue.push %s\n' % annotation.encode('utf-8') + self.logger.debug(msg) + 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.pushed_objects[queue_id] = media_item + + show_name = media_item['show_name'] + msg = 'vars.show_name %s\n' % show_name.encode('utf-8') + tn.write(msg) + self.logger.debug(msg) + + tn.write("exit\n") + self.logger.debug(tn.read_all()) + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() def run(self): loops = 0 From 6634e9f663ace588d58ba728ed415c5766748840 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 16 Mar 2012 20:47:46 -0400 Subject: [PATCH 11/21] cc-3447: synchronize access to liquidsoap via telnet -add another critical section --- python_apps/pypo/pypofetch.py | 26 ++++++++++++-------------- python_apps/pypo/pypopush.py | 1 - 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index fa162b0fc..2899a6750 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -166,6 +166,8 @@ class PypoFetch(Thread): def stop_current_show(self): self.logger.debug('Notifying Liquidsoap to stop playback.') + + self.telnet_lock.acquire() try: tn = telnetlib.Telnet(LS_HOST, LS_PORT) tn.write('source.skip\n') @@ -174,6 +176,8 @@ class PypoFetch(Thread): except Exception, e: self.logger.debug(e) self.logger.debug('Could not connect to liquidsoap') + finally: + self.telnet_lock.release() def regenerateLiquidsoapConf(self, setting_p): existing = {} @@ -327,23 +331,17 @@ class PypoFetch(Thread): # Push stream metadata to liquidsoap # TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!! try: - self.logger.info(LS_HOST) - self.logger.info(LS_PORT) - self.telnet_lock.acquire() - try: - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - command = ('vars.stream_metadata_type %s\n' % stream_format).encode('utf-8') - self.logger.info(command) - tn.write(command) - tn.write('exit\n') - tn.read_all() - except Exception, e: - self.logger.error(str(e)) - finally: - self.telnet_lock.release() + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + command = ('vars.stream_metadata_type %s\n' % stream_format).encode('utf-8') + self.logger.info(command) + tn.write(command) + tn.write('exit\n') + tn.read_all() except Exception, e: self.logger.error("Exception %s", e) + finally: + self.telnet_lock.release() def update_liquidsoap_station_name(self, station_name): # Push stream metadata to liquidsoap diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index b09fb705b..a8a37e24d 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -166,7 +166,6 @@ class PypoPush(Thread): """ self.telnet_lock.acquire() - try: tn = telnetlib.Telnet(LS_HOST, LS_PORT) From 903698efc53d51b5a4ebf363421eaeca7787e111 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Sat, 17 Mar 2012 13:55:56 -0400 Subject: [PATCH 12/21] CC-3470: Smarter way for pypo to recover when it should be playing something. -fixed --- python_apps/pypo/pypofetch.py | 78 +++++------------------------- python_apps/pypo/pypopush.py | 90 ++++++++++++++++++++++++++++------- 2 files changed, 85 insertions(+), 83 deletions(-) diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index 2899a6750..9e06b9204 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -83,7 +83,7 @@ class PypoFetch(Thread): if command == 'update_schedule': self.schedule_data = m['schedule'] - self.process_schedule(self.schedule_data, False) + self.process_schedule(self.schedule_data) elif command == 'update_stream_setting': self.logger.info("Updating stream setting...") self.regenerateLiquidsoapConf(m['setting']) @@ -373,7 +373,7 @@ class PypoFetch(Thread): to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss) - runs the cleanup routine, to get rid of unused cached files """ - def process_schedule(self, schedule_data, bootstrapping): + def process_schedule(self, schedule_data): self.logger.debug(schedule_data) media = schedule_data["media"] @@ -397,7 +397,7 @@ class PypoFetch(Thread): media_item['dst'] = dst self.media_prepare_queue.put(copy.copy(media)) - self.prepare_media(media, bootstrapping) + self.prepare_media(media) except Exception, e: self.logger.error("%s", e) # Send the data to pypo-push @@ -406,12 +406,12 @@ class PypoFetch(Thread): # cleanup - try: self.cleanup(media) + try: self.cache_cleanup(media) except Exception, e: self.logger.error("%s", e) - def prepare_media(self, media, bootstrapping): + def prepare_media(self, media): """ Iterate through the list of media items in "media" and download them. @@ -421,70 +421,14 @@ class PypoFetch(Thread): for mkey in mediaKeys: self.logger.debug("Media item starting at %s", mkey) media_item = media[mkey] - - if bootstrapping: - self.check_for_previous_crash(media_item) - - entry = self.create_liquidsoap_annotation(media_item) - media_item['show_name'] = "TODO" - media_item["annotation"] = entry - + + media_item['show_name'] = "TODO" except Exception, e: self.logger.error("%s", e) return media - - def create_liquidsoap_annotation(self, media): - """entry = \ - 'annotate:media_id="%s",liq_start_next="%s",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \ - % (media['id'], 0, \ - float(media['fade_in']) / 1000, \ - float(media['fade_out']) / 1000, \ - float(media['cue_in']), \ - float(media['cue_out']), \ - media['row_id'], media['dst'])""" - - entry = \ - 'annotate:media_id="%s",liq_start_next="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \ - % (media['id'], 0, \ - float(media['cue_in']), \ - float(media['cue_out']), \ - media['row_id'], media['dst']) - - return entry - - def check_for_previous_crash(self, media_item): - start = media_item['start'] - end = media_item['end'] - - dtnow = datetime.utcnow() - str_tnow_s = dtnow.strftime('%Y-%m-%d-%H-%M-%S') - - if start <= str_tnow_s and str_tnow_s < end: - #song is currently playing and we just started pypo. Maybe there - #was a power outage? Let's restart playback of this song. - start_split = map(int, start.split('-')) - media_start = datetime(start_split[0], start_split[1], start_split[2], start_split[3], start_split[4], start_split[5], 0, None) - self.logger.debug("Found media item that started at %s.", media_start) - - delta = dtnow - media_start #we get a TimeDelta object from this operation - self.logger.info("Starting media item at %d second point", delta.seconds) - - """ - Set the cue_in. This is used by Liquidsoap to determine at what point in the media - item it should start playing. If the cue_in happens to be > cue_out, then make cue_in = cue_out - """ - media_item['cue_in'] = delta.seconds + 10 if delta.seconds + 10 < media_item['cue_out'] else media_item['cue_out'] - - """ - Set the start time, which is used by pypo-push to determine when a media item is scheduled. - Pushing the start time into the future will ensure pypo-push will push this to Liquidsoap. - """ - td = timedelta(seconds=10) - media_item['start'] = (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S') - self.logger.info("Crash detected, setting playlist to restart at %s", (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S')) - + def handle_media_file(self, media_item, dst): """ Download and cache the media item. @@ -568,7 +512,7 @@ class PypoFetch(Thread): self.api_client.get_media(media_item['uri'], dst) """ - def cleanup(self, media): + def cache_cleanup(self, media): """ Get list of all files in the cache dir and remove them if they aren't being used anymore. Input dict() media, lists all files that are scheduled or currently playing. Not being in this @@ -595,7 +539,7 @@ class PypoFetch(Thread): success, self.schedule_data = self.api_client.get_schedule() if success: self.logger.info("Bootstrap schedule received: %s", self.schedule_data) - self.process_schedule(self.schedule_data, True) + self.process_schedule(self.schedule_data) self.check_switch_status() loops = 1 @@ -622,7 +566,7 @@ class PypoFetch(Thread): success, self.schedule_data = self.api_client.get_schedule() if success: - self.process_schedule(self.schedule_data, False) + self.process_schedule(self.schedule_data) loops += 1 diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index a8a37e24d..e4451b940 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -1,3 +1,6 @@ +from datetime import datetime +from datetime import timedelta + import os import sys import time @@ -49,7 +52,6 @@ class PypoPush(Thread): self.telnet_lock = telnet_lock - self.liquidsoap_state_play = True self.push_ahead = 10 self.last_end_time = 0 @@ -81,41 +83,69 @@ class PypoPush(Thread): media = self.media + self.logger.debug(liquidsoap_queue_approx) + if len(liquidsoap_queue_approx) < MAX_LIQUIDSOAP_QUEUE_LENGTH: - currently_on_air = False if media: + + tnow = datetime.utcnow() + tcoming = tnow + timedelta(seconds=self.push_ahead) + + """ 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: + 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: """ - If the media item starts in the next 30 seconds, push it to the queue. + 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): + 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. + 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. """ del media[key] - currently_on_air = True - self.liquidsoap_state_play = True + 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): + 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: """ @@ -165,8 +195,9 @@ class PypoPush(Thread): This function connects to Liquidsoap to find what media items are in its queue. """ - self.telnet_lock.acquire() + try: + self.telnet_lock.acquire() tn = telnetlib.Telnet(LS_HOST, LS_PORT) msg = 'queue.queue\n' @@ -190,7 +221,14 @@ class PypoPush(Thread): if item in self.pushed_objects: liquidsoap_queue_approx.append(self.pushed_objects[item]) else: + """ + We should only reach here if Pypo crashed and restarted (because self.pushed_objects was reset). In this case + let's clear the entire Liquidsoap queue. + """ self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue: " + item) + self.clear_liquidsoap_queue() + liquidsoap_queue_approx = [] + break return liquidsoap_queue_approx @@ -249,12 +287,27 @@ class PypoPush(Thread): else: self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + def clear_liquidsoap_queue(self): + self.logger.debug("Clearing Liquidsoap queue") + try: + self.telnet_lock.acquire() + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + msg = "source.skip\n" + tn.write(msg) + tn.write("exit\n") + tn.read_all() + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() + 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'] - self.telnet_lock.acquire() + try: + self.telnet_lock.acquire() tn = telnetlib.Telnet(LS_HOST, LS_PORT) msg = "queue.remove %s\n" % queue_id tn.write(msg) @@ -311,13 +364,14 @@ class PypoPush(Thread): about which show is playing. """ - self.telnet_lock.acquire() + try: + self.telnet_lock.acquire() tn = telnetlib.Telnet(LS_HOST, LS_PORT) #tn.write(("vars.pypo_data %s\n"%liquidsoap_data["schedule_id"]).encode('utf-8')) - annotation = media_item['annotation'] + annotation = self.create_liquidsoap_annotation(media_item) msg = 'queue.push %s\n' % annotation.encode('utf-8') self.logger.debug(msg) tn.write(msg) @@ -341,6 +395,10 @@ class PypoPush(Thread): self.logger.error(str(e)) finally: self.telnet_lock.release() + + def create_liquidsoap_annotation(self, media): + return 'annotate:media_id="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \ + % (media['id'], float(media['cue_in']), float(media['cue_out']), media['row_id'], media['dst']) def run(self): loops = 0 From db4c916d9ae256cc5c58d6aa1ee498242282d0e8 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Sat, 17 Mar 2012 13:56:41 -0400 Subject: [PATCH 13/21] -add support for PyDev code analysis (need to make pypo directory a package) --- python_apps/pypo/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 python_apps/pypo/__init__.py diff --git a/python_apps/pypo/__init__.py b/python_apps/pypo/__init__.py new file mode 100644 index 000000000..e69de29bb From 7a7eb02de5bd08fe3f284fcb8d962a0869be7869 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Sat, 17 Mar 2012 14:16:11 -0400 Subject: [PATCH 14/21] -fix errors pointed out by pydev code completion --- python_apps/pypo/pypocli.py | 1 - python_apps/pypo/pypofetch.py | 21 +++-------- python_apps/pypo/pypofile.py | 4 +-- python_apps/pypo/pypomessagehandler.py | 3 +- python_apps/pypo/pypopush.py | 50 +++----------------------- python_apps/pypo/recorder.py | 16 ++------- 6 files changed, 15 insertions(+), 80 deletions(-) diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index db6dd7e1a..6a9649d5a 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -5,7 +5,6 @@ Python part of radio playout (pypo) import time from optparse import * import sys -import os import signal import logging import logging.config diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index 9e06b9204..be4bf1625 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -1,24 +1,13 @@ import os import sys import time -import calendar import logging import logging.config import shutil -import random -import string import json import telnetlib -import math import copy from threading import Thread -from threading import Lock - -from subprocess import Popen, PIPE -from datetime import datetime -from datetime import timedelta -from Queue import Empty -import filecmp from api_clients import api_client @@ -264,19 +253,19 @@ class PypoFetch(Thread): fh.write("# THIS FILE IS AUTO GENERATED. DO NOT CHANGE!! #\n") fh.write("################################################\n") for k, d in setting: - buffer = d[u'keyname'] + " = " + buffer_str = d[u'keyname'] + " = " if(d[u'type'] == 'string'): temp = d[u'value'] if(temp == ""): temp = "" - buffer += "\"" + temp + "\"" + buffer_str += "\"" + temp + "\"" else: temp = d[u'value'] if(temp == ""): temp = "0" - buffer += temp - buffer += "\n" - fh.write(api_client.encode_to(buffer)) + buffer_str += temp + buffer_str += "\n" + fh.write(api_client.encode_to(buffer_str)) fh.write("log_file = \"/var/log/airtime/pypo-liquidsoap/