From 8e7e0226e6f0988bdd44e55dbec3079eb45e62ba Mon Sep 17 00:00:00 2001 From: "paul.baranowski" Date: Tue, 23 Nov 2010 19:21:05 -0500 Subject: [PATCH] Fixed some API stuff to return the right values for pypo. Created a test script to schedule a test file one minute after running it. Moved the database connection initialization to conf.php instead of in ui_conf.php. Changed some includes to rely on the pear path instead of specifying it directly in the include. This will make it easier to use the system defaults (for Ubuntu/Debian for example). --- 3rd_party/pypo/api_clients/api_client.py | 1 + 3rd_party/pypo/pypo_cli.py | 53 ++++++----- backend/Playlist.php | 32 +++++-- backend/Schedule.php | 112 +++++++++++++++++++++-- backend/StoredFile.php | 19 ++++ backend/tests/pdoTest.php | 22 +++++ backend/tests/pypoTester.php | 70 ++++++++++++++ backend/tests/transTest.php | 2 +- conf.php | 18 +++- htmlUI/ui_browser_init.php | 2 +- htmlUI/ui_conf.php | 26 +----- 11 files changed, 289 insertions(+), 68 deletions(-) create mode 100644 backend/tests/pdoTest.php create mode 100644 backend/tests/pypoTester.php diff --git a/3rd_party/pypo/api_clients/api_client.py b/3rd_party/pypo/api_clients/api_client.py index db36c3871..e69ead13e 100644 --- a/3rd_party/pypo/api_clients/api_client.py +++ b/3rd_party/pypo/api_clients/api_client.py @@ -164,6 +164,7 @@ class CampcasterApiClient(ApiClientInterface): # Construct the URL export_url = self.config["base_url"] + self.config["api_base"] + self.config["export_url"] + logger.debug("Exporting schedule using URL: "+export_url) # Insert the start and end times into the URL export_url = export_url.replace('%%api_key%%', self.config["api_key"]) diff --git a/3rd_party/pypo/pypo_cli.py b/3rd_party/pypo/pypo_cli.py index c341156e2..b572608ec 100755 --- a/3rd_party/pypo/pypo_cli.py +++ b/3rd_party/pypo/pypo_cli.py @@ -373,21 +373,21 @@ class Playout: """ logger = logging.getLogger() for media in playlist['medias']: - logger.debug("found track at %s", media['uri']) + logger.debug("Processing track %s", media['uri']) try: - src = media['uri'] - if str(media['cue_in']) == '0' and str(media['cue_out']) == '0': + logger.debug('No cue in/out detected for this file') dst = "%s%s/%s.mp3" % (self.cache_dir, str(pkey), str(media['id'])) do_cue = False else: + logger.debug('Cue in/out detected') dst = "%s%s/%s_cue_%s-%s.mp3" % \ (self.cache_dir, str(pkey), str(media['id']), str(float(media['cue_in']) / 1000), str(float(media['cue_out']) / 1000)) do_cue = True # check if it is a remote file, if yes download - if src[0:4] == 'http': + if media['uri'][0:4] == 'http': self.handle_remote_file(media, dst, do_cue) else: # Assume local file @@ -405,7 +405,7 @@ class Playout: pl_entry = 'annotate:export_source="%s",media_id="%s",liq_start_next="%s",liq_fade_in="%s",liq_fade_out="%s":%s' % \ (str(media['export_source']), media['id'], 0, str(float(media['fade_in']) / 1000), str(float(media['fade_out']) / 1000), dst) - print pl_entry + logger.debug(pl_entry) """ Tracks are only added to the playlist if they are accessible @@ -417,7 +417,7 @@ class Playout: logger.debug("everything ok, adding %s to playlist", pl_entry) else: - print 'zero-file: ' + dst + ' from ' + src + print 'zero-file: ' + dst + ' from ' + media['uri'] logger.warning("zero-size file - skiping %s. will not add it to playlist", dst) else: @@ -433,8 +433,8 @@ class Playout: if os.path.isfile(dst): logger.debug("file already in cache: %s", dst) else: - logger.debug("try to download %s", src) - api_client.get_media(src, dst) + logger.debug("try to download %s", media['uri']) + self.api_client.get_media(media['uri'], dst) else: if os.path.isfile(dst): @@ -442,13 +442,13 @@ class Playout: print 'cached' else: - logger.debug("try to download and cue %s", src) + logger.debug("try to download and cue %s", media['uri']) - print '***' + #print '***' dst_tmp = self.tmp_dir + "".join([random.choice(string.letters) for i in xrange(10)]) + '.mp3' - print dst_tmp - print '***' - api_client.get_media(src, dst_tmp) + #print dst_tmp + #print '***' + self.api_client.get_media(media['uri'], dst_tmp) # cue print "STARTING CUE" @@ -488,10 +488,10 @@ class Playout: logger.debug("file already in cache: %s", dst) else: - logger.debug("try to copy file to cache %s", src) + logger.debug("try to copy file to cache %s", media['uri']) try: - shutil.copy(src, dst) - logger.info("copied %s to %s", src, dst) + shutil.copy(media['uri'], dst) + logger.info("copied %s to %s", media['uri'], dst) except Exception, e: logger.error("%s", e) else: @@ -499,7 +499,7 @@ class Playout: logger.debug("file already in cache: %s", dst) else: - logger.debug("try to copy and cue %s", src) + logger.debug("try to copy and cue %s", media['uri']) print '***' dst_tmp = self.tmp_dir + "".join([random.choice(string.letters) for i in xrange(10)]) @@ -507,8 +507,8 @@ class Playout: print '***' try: - shutil.copy(src, dst_tmp) - logger.info("copied %s to %s", src, dst_tmp) + shutil.copy(media['uri'], dst_tmp) + logger.info("copied %s to %s", media['uri'], dst_tmp) except Exception, e: logger.error("%s", e) @@ -537,17 +537,16 @@ class Playout: def cleanup(self, export_source): + """ + Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR" + and deletes them. + """ logger = logging.getLogger() self.export_source = export_source self.cache_dir = CACHE_DIR + self.export_source + '/' self.schedule_file = self.cache_dir + 'schedule' - """ - Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR" - and deletes them. - """ - offset = 3600 * int(CACHE_FOR) now = time.time() @@ -619,7 +618,7 @@ class Playout: else: for pkey in self.schedule: - logger.debug('found playlist schedulet at: %s', pkey) + logger.debug('found playlist scheduled at: %s', pkey) #if pkey[0:16] == str_tnow: if pkey[0:16] == str_tcomming: @@ -674,8 +673,8 @@ class Playout: self.cache_dir = CACHE_DIR + self.export_source + '/' self.schedule_file = self.cache_dir + 'schedule' - # load the shedule from cache - logger.debug('load shedule from cache') + # load the schedule from cache + logger.debug('loading schedule from cache...') try: schedule_file = open(self.schedule_file, "r") schedule = pickle.load(schedule_file) diff --git a/backend/Playlist.php b/backend/Playlist.php index bc13938b4..3419c1eef 100644 --- a/backend/Playlist.php +++ b/backend/Playlist.php @@ -57,16 +57,23 @@ class Playlist { private $categories = array("dc:title" => "DbName", "dc:creator" => "DbCreator", "dc:description" => "DbDescription", "dcterms:extent" => "length"); + /** + * @param string $p_gunid + */ public function __construct($p_gunid=NULL) { } - public static function Insert($p_values) + /** + * @param array $p_name + * The name of the playlist + */ + private static function Insert($p_name = null) { // Create the StoredPlaylist object $storedPlaylist = new Playlist(); - $storedPlaylist->name = isset($p_values['filename']) ? $p_values['filename'] : date("H:i:s"); + $storedPlaylist->name = !empty($p_name) ? $p_name : date("H:i:s"); $storedPlaylist->mtime = new DateTime("now"); $pl = new CcPlaylist(); @@ -325,8 +332,9 @@ class Playlist { ->findPK($this->id) ->computeLastPosition(); - if(is_null($res)) - return 0; + if(is_null($res)) { + return 0; + } return $res + 1; } @@ -355,8 +363,9 @@ class Playlist { ->findPK($this->id) ->computeLength(); - if(is_null($res)) - return '00:00:00.000000'; + if(is_null($res)) { + return '00:00:00.000000'; + } return $res; } @@ -370,12 +379,19 @@ class Playlist { */ public function create($fname=NULL) { - $values = array("filename" => $fname); - $pl_id = Playlist::Insert($values); + $pl_id = Playlist::Insert($fname); $this->id = $pl_id; return $this->id; } + + public static function findPlaylistByName($p_name) + { + $res = CcPlaylistQuery::create()->findByDbName($p_name); + return $res; + } + + /** * Lock playlist for edit * diff --git a/backend/Schedule.php b/backend/Schedule.php index 988014890..2d64e7281 100644 --- a/backend/Schedule.php +++ b/backend/Schedule.php @@ -213,11 +213,13 @@ class Schedule { } /** - * Return true if there is nothing in the schedule for the given times. + * Return true if there is nothing in the schedule for the given start time + * up to the length of time after that. * * @param string $p_datetime + * In the format YYYY-MM-DD HH:MM:SS.mmmmmm * @param string $p_length - * + * In the format HH:MM:SS.mmmmmm * @return boolean|PEAR_Error */ public static function isScheduleEmptyInRange($p_datetime, $p_length) { @@ -268,7 +270,8 @@ class Schedule { * "start"/"starts" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn * "end"/"ends" (aliases to the same thing) as YYYY-MM-DD HH:MM:SS.nnnnnn * "group_id"/"id" (aliases to the same thing) - * "clip_length" (for playlists only, this is the length of the entire playlist) + * "clip_length" (for audio clips this is the length of the audio clip, + * for playlists this is the length of the entire playlist) * "name" (playlist only) * "creator" (playlist only) * "file_id" (audioclip only) @@ -339,18 +342,85 @@ class Schedule { } - private static function CcTimeToPypoTime($p_time) { + /** + * Convert a time string in the format "YYYY-MM-DD HH:mm:SS" + * to "YYYY-MM-DD-HH-mm-SS". + * + * @param string $p_time + * @return string + */ + private static function CcTimeToPypoTime($p_time) + { $p_time = substr($p_time, 0, 19); $p_time = str_replace(" ", "-", $p_time); $p_time = str_replace(":", "-", $p_time); return $p_time; } - private static function PypoTimeToCcTime($p_time) { + /** + * Convert a time string in the format "YYYY-MM-DD-HH-mm-SS" to + * "YYYY-MM-DD HH:mm:SS". + * + * @param string $p_time + * @return string + */ + private static function PypoTimeToCcTime($p_time) + { $t = explode("-", $p_time); return $t[0]."-".$t[1]."-".$t[2]." ".$t[3].":".$t[4].":00"; } + /** + * Converts a time value as a string (with format HH:MM:SS.mmmmmm) to + * millisecs. + * + * @param string $p_time + * @return int + */ + private static function WallTimeToMillisecs($p_time) + { + $t = explode(":", $p_time); + $millisecs = 0; + if (strpos($t[2], ".")) { + $secParts = explode(".", $t[2]); + $millisecs = $secParts[1]; + $millisecs = substr($millisecs, 0, 3); + $millisecs = intval($millisecs); + $seconds = intval($secParts[0]); + } else { + $seconds = intval($t[2]); + } + $ret = $millisecs + ($seconds * 1000) + ($t[1] * 60 * 1000) + ($t[0] * 60 * 60 * 1000); + return $ret; + } + + + /** + * Compute the difference between two times in the format "HH:MM:SS.mmmmmm". + * Note: currently only supports calculating millisec differences. + * + * @param string $p_time1 + * @param string $p_time2 + * @return double + */ + private static function TimeDiff($p_time1, $p_time2) + { + $parts1 = explode(".", $p_time1); + $parts2 = explode(".", $p_time2); + $diff = 0; + if ( (count($parts1) > 1) && (count($parts2) > 1) ) { + $millisec1 = substr($parts1[1], 0, 3); + $millisec1 = str_pad($millisec1, 3, "0"); + $millisec1 = intval($millisec1); + $millisec2 = substr($parts2[1], 0, 3); + $millisec2 = str_pad($millisec2, 3, "0"); + $millisec2 = intval($millisec2); + $diff = abs(millisec1 - millisec2)/1000; + } + return $diff; + } + + /** * Export the schedule in json formatted for pypo (the liquidsoap scheduler) * @@ -404,14 +474,20 @@ class Schedule { { $storedFile = StoredFile::Recall($item["file_id"]); $uri = $storedFile->getFileUrl(); + + // For pypo, a cueout of zero means no cueout + $cueOut = "0"; + if (Schedule::TimeDiff($item["cue_out"], $item["clip_length"]) > 0.001) { + $cueOut = Schedule::WallTimeToMillisecs($item["cue_out"]); + } $medias[] = array( 'id' => $storedFile->getGunid(), //$item["file_id"], 'uri' => $uri, - 'fade_in' => $item["fade_in"], - 'fade_out' => $item["fade_out"], + 'fade_in' => Schedule::WallTimeToMillisecs($item["fade_in"]), + 'fade_out' => Schedule::WallTimeToMillisecs($item["fade_out"]), 'fade_cross' => 0, - 'cue_in' => $item["cue_in"], - 'cue_out' => $item["cue_out"], + 'cue_in' => Schedule::WallTimeToMillisecs($item["cue_in"]), + 'cue_out' => $cueOut ); } $playlist['medias'] = $medias; @@ -425,6 +501,24 @@ class Schedule { print json_encode($result); } + + /** + * Remove all items from the schedule in the given range. + * + * @param string $p_start + * In the format YYYY-MM-DD HH:MM:SS.nnnnnn + * @param string $p_end + * In the format YYYY-MM-DD HH:MM:SS.nnnnnn + */ + public static function RemoveItemsInRange($p_start, $p_end) + { + $items = Schedule::GetItems($p_start, $p_end); + foreach ($items as $item) { + $scheduleGroup = new ScheduleGroup($item["group_id"]); + $scheduleGroup->remove(); + } + } + } ?> \ No newline at end of file diff --git a/backend/StoredFile.php b/backend/StoredFile.php index a4f48a8a7..d1313c66d 100644 --- a/backend/StoredFile.php +++ b/backend/StoredFile.php @@ -835,6 +835,25 @@ class StoredFile { } + /** + * Find and return the first exact match for the original file name + * that was used on import. + * @param string $p_name + */ + public static function findByOriginalName($p_name) + { + global $CC_CONFIG, $CC_DBC; + $sql = "SELECT id FROM ".$CC_CONFIG["filesTable"] + ." WHERE name='".pg_escape_string($p_name)."'"; + $id = $CC_DBC->getOne($sql); + if (is_numeric($id)) { + return StoredFile::Recall($id); + } else { + return NULL; + } + } + + /** * Delete and insert media file * diff --git a/backend/tests/pdoTest.php b/backend/tests/pdoTest.php new file mode 100644 index 000000000..3aae4465b --- /dev/null +++ b/backend/tests/pdoTest.php @@ -0,0 +1,22 @@ += '2010-01-01 00:00:00.000') " + ." AND (ends <= (TIMESTAMP '2011-01-01 00:00:00.000' + INTERVAL '01:00:00.123456'))"; +$rows1 = $con->query($sql); +var_dump($rows1->fetchAll()); + +$sql2 = "SELECT COUNT(*) FROM cc_playlistcontents"; +$rows2 = $con->query($sql2); +var_dump($rows2->fetchAll()); + +$sql3 = "SELECT TIMESTAMP '2011-01-01 00:00:00.000' + INTERVAL '01:00:00.123456'"; +$result3 = $con->query($sql3); +var_dump($result3->fetchAll()); + +?> \ No newline at end of file diff --git a/backend/tests/pypoTester.php b/backend/tests/pypoTester.php new file mode 100644 index 000000000..36925e923 --- /dev/null +++ b/backend/tests/pypoTester.php @@ -0,0 +1,70 @@ +delete(); +} +echo "done.\n"; + +// Create a new playlist +echo "Creating new playlist '$playlistName'..."; +$pl = new Playlist(); +$pl->create($playlistName); + +// Add a media clip +$mediaFile = StoredFile::findByOriginalName("test10001.mp3"); +if (is_null($mediaFile)) { + echo "Adding test audio clip to the database.\n"; + $v = array("filepath" => __DIR__."/test10001.mp3"); + $mediaFile = StoredFile::Insert($v); +} +$pl->addAudioClip($mediaFile->getId()); +echo "done.\n"; + +//$pl2 = Playlist::findPlaylistByName("pypo_playlist_test"); +//var_dump($pl2); + +// Get current time +// In the format YYYY-MM-DD HH:MM:SS.nnnnnn +$startTime = date("Y-m-d H:i:s"); +$endTime = date("Y-m-d H:i:s", time()+(60*60)); + +echo "Removing everything from the scheduler between $startTime and $endTime..."; +// Scheduler: remove any playlists for the next hour +Schedule::RemoveItemsInRange($startTime, $endTime); +// Check for succcess +$scheduleClear = Schedule::isScheduleEmptyInRange($startTime, "01:00:00"); +if (!$scheduleClear) { + echo "\nERROR: Schedule could not be cleared.\n\n"; + var_dump(Schedule::GetItems($startTime, $endTime)); + exit; +} +echo "done.\n"; + +// Schedule the playlist for two minutes from now +echo "Scheduling new playlist...\n"; +$playTime = date("Y-m-d H:i:s", time()+(60*$minutesFromNow)); +$scheduleGroup = new ScheduleGroup(); +$scheduleGroup->add($playTime, null, $pl->getId()); + +echo " SUCCESS: Playlist scheduled at $playTime\n\n"; +?> \ No newline at end of file diff --git a/backend/tests/transTest.php b/backend/tests/transTest.php index 3a48b1faa..edf04354c 100644 --- a/backend/tests/transTest.php +++ b/backend/tests/transTest.php @@ -41,7 +41,7 @@ $values = array( "gunid" => $gunid, "filetype" => "audioclip" ); -$storedFile = $gb->bsPutFile($values); +$storedFile = StoredFile::Insert($values); if (PEAR::isError($storedFile)) { if ($storedFile->getCode()!=GBERR_GUNID) { echo "ERROR: ".$storedFile->getMessage()."\n"; diff --git a/conf.php b/conf.php index f43288408..3e98b0dc1 100644 --- a/conf.php +++ b/conf.php @@ -41,7 +41,7 @@ $CC_CONFIG = array( "rootDir" => dirname(__FILE__), "smartyTemplate" => dirname(__FILE__)."/htmlUI/templates", "smartyTemplateCompiled" => dirname(__FILE__)."/htmlUI/templates_c", - 'pearPath' => dirname(__FILE__).'/3rd_party/php/pear', + 'pearPath' => dirname(__FILE__).'/3rd_party/php/pear/', 'zendPath' => dirname(__FILE__).'/3rd_party/php/Zend', 'phingPath' => dirname(__FILE__).'/3rd_party/php/phing', 'LogPath' => dirname(__FILE__).'/3rd_party/php/Log', @@ -166,6 +166,22 @@ set_include_path('.'.PATH_SEPARATOR.$CC_CONFIG['pearPath'] .PATH_SEPARATOR.$CC_CONFIG['zendPath'] .PATH_SEPARATOR.$old_include_path); +require_once('DB.php'); + +// Connect to the database +$CC_DBC = DB::connect($CC_CONFIG['dsn']); +if (PEAR::isError($CC_DBC)) { + echo "*** conf.php ***
"; + echo "Could not connect to database. Your current configuration is:
"; + echo ""; + echo ""; + echo ""; + echo ""; + echo "
Host name:".$CC_CONFIG['dsn']['hostspec']."
Database name:".$CC_CONFIG['dsn']['database']."
User name:".$CC_CONFIG['dsn']['username']."
"; + exit; +} +$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); + // Check that all the required directories exist. //foreach (array('storageDir', 'bufferDir', 'transDir', 'accessDir', 'cronDir') as $d) { // $test = file_exists($CC_CONFIG[$d]); diff --git a/htmlUI/ui_browser_init.php b/htmlUI/ui_browser_init.php index a78c072cb..f9bc2f897 100644 --- a/htmlUI/ui_browser_init.php +++ b/htmlUI/ui_browser_init.php @@ -8,7 +8,7 @@ require_once(dirname(__FILE__).'/ui_handler.class.php'); // often used classes ############################################### require_once(dirname(__FILE__).'/../3rd_party/php/propel/runtime/lib/Propel.php'); require_once(dirname(__FILE__).'/../3rd_party/php/Smarty/libs/Smarty.class.php'); -require_once(dirname(__FILE__).'/../3rd_party/php/pear/HTML/QuickForm/Renderer/ArraySmarty.php'); +require_once('HTML/QuickForm/Renderer/ArraySmarty.php'); require_once(dirname(__FILE__).'/ui_scratchpad.class.php'); require_once(dirname(__FILE__).'/ui_search.class.php'); require_once(dirname(__FILE__).'/ui_browse.class.php'); diff --git a/htmlUI/ui_conf.php b/htmlUI/ui_conf.php index 8ffd88ceb..1d2ebd32a 100644 --- a/htmlUI/ui_conf.php +++ b/htmlUI/ui_conf.php @@ -11,6 +11,9 @@ define('UI_ERROR', TRUE); // parts of the application do not read in this file. $WHITE_SCREEN_OF_DEATH = false; +if ($WHITE_SCREEN_OF_DEATH) { + echo "ui_conf.php: start
"; +} if (UI_DEBUG) { error_reporting(E_ALL); } @@ -173,29 +176,10 @@ if ($WHITE_SCREEN_OF_DEATH) { echo __FILE__.':line '.__LINE__.": Loaded GreenBox
"; } require_once(dirname(__FILE__).'/formmask/generic.inc.php'); -require_once(dirname(__FILE__).'/../3rd_party/php/pear/DB.php'); -require_once(dirname(__FILE__).'/../3rd_party/php/pear/HTML/QuickForm.php'); - - -// Connect to the database -$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE); -if (PEAR::isError($CC_DBC)) { - echo "Could not connect to database. Your current configuration is:
"; - echo ""; - echo ""; - echo ""; - echo ""; - echo "
Host name:".$CC_CONFIG['dsn']['hostspec']."
Database name:".$CC_CONFIG['dsn']['database']."
User name:".$CC_CONFIG['dsn']['username']."
"; - exit; -} - -if ($WHITE_SCREEN_OF_DEATH) { - echo __FILE__.':line '.__LINE__.": Connected to database
"; -} -$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC); +require_once('HTML/QuickForm.php'); //PEAR::setErrorHandling(PEAR_ERROR_TRIGGER, E_USER_WARNING); //PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'errCallBack'); PEAR::setErrorHandling(PEAR_ERROR_RETURN); //PEAR::setErrorHandling(PEAR_ERROR_PRINT); -?> +?> \ No newline at end of file