diff --git a/airtime_mvc/application/common/Billing.php b/airtime_mvc/application/common/Billing.php
index 57c8e3cf3..7b4905905 100644
--- a/airtime_mvc/application/common/Billing.php
+++ b/airtime_mvc/application/common/Billing.php
@@ -126,7 +126,8 @@ class Billing
}
else
{
- if ($product["status"] === "Active") {
+ if ($product["status"] === "Active" ||
+ $product["status"] === "Suspended") {
$airtimeProduct = $product;
$subdomain = '';
diff --git a/airtime_mvc/application/configs/constants.php b/airtime_mvc/application/configs/constants.php
index 22425b5c7..bfad8759e 100644
--- a/airtime_mvc/application/configs/constants.php
+++ b/airtime_mvc/application/configs/constants.php
@@ -42,6 +42,7 @@ define('DEFAULT_MICROTIME_FORMAT', 'Y-m-d H:i:s.u');
define('DEFAULT_ICECAST_PORT', 8000);
define('DEFAULT_ICECAST_PASS', 'hackme');
define('DEFAULT_SHOW_COLOR', '76aca5');
+define('DEFAULT_INTERVAL_FORMAT', 'H:i:s.u');
// Metadata Keys for files
define('MDATA_KEY_FILEPATH' , 'filepath');
diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php
index deac3f998..324a38759 100644
--- a/airtime_mvc/application/controllers/ApiController.php
+++ b/airtime_mvc/application/controllers/ApiController.php
@@ -73,6 +73,7 @@ class ApiController extends Zend_Controller_Action
->addActionContext('update-cue-values-by-silan' , 'json')
->addActionContext('get-usability-hint' , 'json')
->addActionContext('poll-celery' , 'json')
+ ->addActionContext('recalculate-schedule' , 'json') //RKTN-260
->initContext();
}
@@ -1524,9 +1525,40 @@ class ApiController extends Zend_Controller_Action
$streamData = Application_Model_StreamSetting::getEnabledStreamData();
foreach ($streamData as $stream) {
- $m3uFile .= "#EXTINF,".$stationName." - " . strtoupper($stream['codec']) . "\r\n";
+ $m3uFile .= "#EXTINF," . $stationName . " - " . strtoupper($stream['codec']) . "\r\n";
$m3uFile .= $stream['url'] . "\r\n\r\n";
}
echo $m3uFile;
}
+
+ public function recalculateScheduleAction()
+ {
+ $this->view->layout()->disableLayout();
+ $this->_helper->viewRenderer->setNoRender(true);
+
+ Zend_Session::start();
+
+ $scheduler = new Application_Model_Scheduler();
+ session_write_close();
+
+ $now = new DateTime("now", new DateTimeZone("UTC"));
+
+ $showInstances = CcShowInstancesQuery::create()
+ ->filterByDbStarts($now, Criteria::GREATER_THAN)
+ //->filterByDbModifiedInstance(false)
+ ->orderByDbStarts()
+ ->find();
+ //->find($this->con);
+ $total = $showInstances->count();
+ $progress = 0;
+ foreach ($showInstances as $instance) {
+ echo(round(floatval($progress / $total)*100) . "% - " . $instance->getDbId() . "\n
");
+ flush();
+ ob_flush();
+ //while(@ob_end_clean());
+ $scheduler->removeGaps2($instance->getDbId());
+ $progress += 1;
+ }
+ echo("Recalculated $total shows.");
+ }
}
diff --git a/airtime_mvc/application/controllers/BillingController.php b/airtime_mvc/application/controllers/BillingController.php
index bf508036b..bf4aafb1d 100644
--- a/airtime_mvc/application/controllers/BillingController.php
+++ b/airtime_mvc/application/controllers/BillingController.php
@@ -21,6 +21,12 @@ class BillingController extends Zend_Controller_Action {
public function upgradeAction()
{
+ //If you're not on a trial and you're suspended, we don't let you access the plans page and redirect you to the invoices
+ //page to force you to pay your bills first.
+ $isTrial = (Application_Model_Preference::GetPlanLevel() == 'trial');
+ if (!$isTrial && (Application_Model_Preference::getProvisioningStatus() == PROVISIONING_STATUS_SUSPENDED)) {
+ $this->_redirect('billing/invoices');
+ }
Zend_Layout::getMvcInstance()->assign('parent_page', 'Billing');
diff --git a/airtime_mvc/application/layouts/scripts/layout.phtml b/airtime_mvc/application/layouts/scripts/layout.phtml
index e4033667e..14efa0592 100644
--- a/airtime_mvc/application/layouts/scripts/layout.phtml
+++ b/airtime_mvc/application/layouts/scripts/layout.phtml
@@ -22,7 +22,9 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
partial('partialviews/trialBox.phtml', array("is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining())) ?>
- suspended) : ?>
+ suspended && $this->isTrial()) : ?>
+ partial('partialviews/suspendedtrial.phtml'); ?>
+ suspended && !$this->isTrial()) : ?>
partial('partialviews/suspended.phtml'); ?>
diff --git a/airtime_mvc/application/models/RabbitMq.php b/airtime_mvc/application/models/RabbitMq.php
index 87a89336d..9b399457a 100644
--- a/airtime_mvc/application/models/RabbitMq.php
+++ b/airtime_mvc/application/models/RabbitMq.php
@@ -141,7 +141,7 @@ class Application_Model_RabbitMq
$channel->exchange_declare($exchange, $exchangeType, false, true, $autoDeleteExchange);
$msg = new AMQPMessage($jsonData, array('content_type' => 'text/plain'));
-
+
$channel->basic_publish($msg, $exchange);
$channel->close();
$conn->close();
diff --git a/airtime_mvc/application/models/Scheduler.php b/airtime_mvc/application/models/Scheduler.php
index ec5195fde..de3b73ca1 100644
--- a/airtime_mvc/application/models/Scheduler.php
+++ b/airtime_mvc/application/models/Scheduler.php
@@ -385,6 +385,24 @@ class Application_Model_Scheduler
return $dt;
}
+ private function findTimeDifference2($p_startDT, $p_endDT) {
+ $startEpoch = $p_startDT->format("U.u");
+ $endEpoch = $p_endDT->format("U.u");
+
+ //add two float numbers to 6 subsecond precision
+ //DateTime::createFromFormat("U.u") will have a problem if there is no decimal in the resulting number.
+ $newEpoch = bcsub($endEpoch, (string)$startEpoch, 6);
+
+ $dt = DateTime::createFromFormat("U.u", $newEpoch, new DateTimeZone("UTC"));
+
+ if ($dt === false) {
+ //PHP 5.3.2 problem
+ $dt = DateTime::createFromFormat("U", intval($newEpoch), new DateTimeZone("UTC"));
+ }
+
+ return $dt;
+ }
+
/*
* @param DateTime startDT in UTC
* @param string duration
@@ -499,12 +517,70 @@ class Application_Model_Scheduler
$itemStartDT = $instance->getDbStarts(null);
foreach ($schedule as $item) {
+
+ $itemEndDT = $this->findEndTime($itemStartDT, $item->getDbClipLength());
+
+ $item->setDbStarts($itemStartDT)
+ ->setDbEnds($itemEndDT);
+
+ $itemStartDT = $itemEndDT;
+ }
+
+ $schedule->save($this->con);
+ }
+
+ /** Temporary hack to copy the track cue in, out, and length from the cc_files table to fix
+ * incorrect track lengths (RKTN-260)
+ */
+ public function removeGaps2($showInstance, $exclude = null) {
+
+ $instance = CcShowInstancesQuery::create()->findPK($showInstance, $this->con);
+ if (is_null($instance)) {
+ throw new OutDatedScheduleException(_("The schedule you're viewing is out of date!"));
+ }
+
+ $itemStartDT = $instance->getDbStarts(null);
+
+ $schedule = CcScheduleQuery::create()
+ ->filterByDbInstanceId($showInstance)
+ ->filterByDbId($exclude, Criteria::NOT_IN)
+ ->orderByDbStarts()
+ ->find($this->con);
+
+ foreach ($schedule as $item) {
+
+ //START OF TIME RECALC HACK
+
+ //TODO: Copy the cue in, cue out, and track length from the cc_files table
+ $file = $item->getCcFiles($this->con);
+ if (!$file) {
+ continue;
+ }
+ $item->setDbCueIn($file->getDbCueIn());
+ $item->setDbCueOut($file->getDbCueOut());
+
+ $cueOut = new DateTime($file->getDbCueOut());
+ $cueIn = new DateTime($file->getDbCueIn());
+ $clipLength = $this->findTimeDifference2($cueIn, $cueOut);
+
+ //The clip length is supposed to be cue out - cue in:
+ //FIXME: How do we correctly do time arithmetic in PHP without losing the millseconds?
+ $item->setDbClipLength($clipLength->format(DEFAULT_INTERVAL_FORMAT));
+ $item->save($this->con);
+ //Ensure we don't get cached results
+ CcSchedulePeer::clearInstancePool();
+ //END OF TIME RECALC HACK
+
$itemEndDT = $this->findEndTime($itemStartDT, $item->getDbClipLength());
$item->setDbStarts($itemStartDT)
->setDbEnds($itemEndDT)
->save($this->con);
$itemStartDT = $this->findTimeDifference($itemEndDT, $this->crossfadeDuration);
}
+
+ $instance->updateDbTimeFilled($this->con); //FIXME: TIME RECALC HACK (Albert)
+
+ $schedule->save($this->con);
}
/**
diff --git a/airtime_mvc/application/modules/rest/controllers/MediaController.php b/airtime_mvc/application/modules/rest/controllers/MediaController.php
index 2e6e49110..539201b36 100644
--- a/airtime_mvc/application/modules/rest/controllers/MediaController.php
+++ b/airtime_mvc/application/modules/rest/controllers/MediaController.php
@@ -136,9 +136,8 @@ class Rest_MediaController extends Zend_Rest_Controller
->appendBody("ERROR: Disk Quota reached.");
}
catch (Exception $e) {
- $this->unknownErrorResponse();
- Logging::error($e->getMessage());
- throw $e;
+ $this->serviceUnavailableResponse();
+ Logging::error($e->getMessage() . "\n" . $e->getTraceAsString());
}
}
@@ -249,5 +248,12 @@ class Rest_MediaController extends Zend_Rest_Controller
$resp->setHttpResponseCode(400);
$resp->appendBody("An unknown error occurred.");
}
+
+ private function serviceUnavailableResponse()
+ {
+ $resp = $this->getResponse();
+ $resp->setHttpResponseCode(400);
+ $resp->appendBody("An error occurred while processing your upload. Please try again in a few minutes.");
+ }
}
diff --git a/airtime_mvc/application/views/scripts/billing/upgrade.phtml b/airtime_mvc/application/views/scripts/billing/upgrade.phtml
index fc1799140..83077cb9e 100644
--- a/airtime_mvc/application/views/scripts/billing/upgrade.phtml
+++ b/airtime_mvc/application/views/scripts/billing/upgrade.phtml
@@ -265,10 +265,12 @@ echo($currentProduct["name"]);
//echo Application_Model_Preference::GetPlanLevel();
?>
+
\ No newline at end of file
diff --git a/python_apps/airtime_analyzer/airtime_analyzer/cuepoint_analyzer.py b/python_apps/airtime_analyzer/airtime_analyzer/cuepoint_analyzer.py
index 8a99626c6..47a517b59 100644
--- a/python_apps/airtime_analyzer/airtime_analyzer/cuepoint_analyzer.py
+++ b/python_apps/airtime_analyzer/airtime_analyzer/cuepoint_analyzer.py
@@ -28,12 +28,38 @@ class CuePointAnalyzer(Analyzer):
try:
results_json = subprocess.check_output(command, stderr=subprocess.STDOUT, close_fds=True)
silan_results = json.loads(results_json)
- metadata['length_seconds'] = float(silan_results['file duration'])
- # Conver the length into a formatted time string
- track_length = datetime.timedelta(seconds=metadata['length_seconds'])
- metadata["length"] = str(track_length)
- metadata['cuein'] = format(silan_results['sound'][0][0], 'f')
- metadata['cueout'] = format(silan_results['sound'][0][1], 'f')
+
+ # Defensive coding against Silan wildly miscalculating the cue in and out times:
+ silan_length_seconds = float(silan_results['file duration'])
+ silan_cuein = format(silan_results['sound'][0][0], 'f')
+ silan_cueout = format(silan_results['sound'][0][1], 'f')
+
+ # Sanity check the results against any existing metadata passed to us (presumably extracted by Mutagen):
+ if 'length_seconds' in metadata:
+ # Silan has a rare bug where it can massively overestimate the length or cue out time sometimes.
+ if (silan_length_seconds - metadata['length_seconds'] > 3) or (float(silan_cueout) - metadata['length_seconds'] > 2):
+ # Don't trust anything silan says then...
+ raise Exception("Silan cue out {0} or length {1} differs too much from the Mutagen length {2}. Ignoring Silan values."
+ .format(silan_cueout, silan_length_seconds, metadata['length_seconds']))
+ # Don't allow silan to trim more than the greater of 3 seconds or 5% off the start of a track
+ if float(silan_cuein) > max(silan_length_seconds*0.05, 3):
+ raise Exception("Silan cue in time {0} too big, ignoring.".format(silan_cuein))
+ else:
+ # Only use the Silan track length in the worst case, where Mutagen didn't give us one for some reason.
+ # (This is mostly to make the unit tests still pass.)
+ # Convert the length into a formatted time string.
+ metadata['length_seconds'] = silan_length_seconds #
+ track_length = datetime.timedelta(seconds=metadata['length_seconds'])
+ metadata["length"] = str(track_length)
+
+
+ ''' XXX: I've commented out the track_length stuff below because Mutagen seems more accurate than silan
+ as of Mutagen version 1.31. We are always going to use Mutagen's length now because Silan's
+ length can be off by a few seconds reasonably often.
+ '''
+
+ metadata['cuein'] = silan_cuein
+ metadata['cueout'] = silan_cueout
except OSError as e: # silan was not found
logging.warn("Failed to run: %s - %s. %s" % (command[0], e.strerror, "Do you have silan installed?"))
diff --git a/python_apps/airtime_analyzer/airtime_analyzer/metadata_analyzer.py b/python_apps/airtime_analyzer/airtime_analyzer/metadata_analyzer.py
index a4c466940..d1c1ebc99 100644
--- a/python_apps/airtime_analyzer/airtime_analyzer/metadata_analyzer.py
+++ b/python_apps/airtime_analyzer/airtime_analyzer/metadata_analyzer.py
@@ -67,8 +67,11 @@ class MetadataAnalyzer(Analyzer):
track_length = datetime.timedelta(seconds=info.length)
metadata["length"] = str(track_length) #time.strftime("%H:%M:%S.%f", track_length)
# Other fields for Airtime
- metadata["cueout"] = metadata["length"]
-
+ metadata["cueout"] = metadata["length"]
+
+ # Set a default cue in time in seconds
+ metadata["cuein"] = 0.0;
+
if hasattr(info, "bitrate"):
metadata["bit_rate"] = info.bitrate
diff --git a/python_apps/airtime_analyzer/setup.py b/python_apps/airtime_analyzer/setup.py
index 47e231123..c9f33f45a 100644
--- a/python_apps/airtime_analyzer/setup.py
+++ b/python_apps/airtime_analyzer/setup.py
@@ -27,7 +27,7 @@ setup(name='airtime_analyzer',
packages=['airtime_analyzer'],
scripts=['bin/airtime_analyzer'],
install_requires=[
- 'mutagen',
+ 'mutagen==1.31', # The Mutagen guys change stuff all the time that break our unit tests. Watch out for this.
'pika',
'daemon',
'python-magic',
diff --git a/python_apps/airtime_analyzer/tests/metadata_analyzer_tests.py b/python_apps/airtime_analyzer/tests/metadata_analyzer_tests.py
index ee108b362..8a2d59967 100644
--- a/python_apps/airtime_analyzer/tests/metadata_analyzer_tests.py
+++ b/python_apps/airtime_analyzer/tests/metadata_analyzer_tests.py
@@ -24,7 +24,7 @@ def test_mp3_mono():
metadata = MetadataAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-mono.mp3', dict())
check_default_metadata(metadata)
assert metadata['channels'] == 1
- assert metadata['bit_rate'] == 64000
+ assert metadata['bit_rate'] == 64876
assert abs(metadata['length_seconds'] - 3.9) < 0.1
assert metadata['mime'] == 'audio/mp3' # Not unicode because MIMEs aren't.
assert metadata['track_total'] == u'10' # MP3s can have a track_total
@@ -34,7 +34,7 @@ def test_mp3_jointstereo():
metadata = MetadataAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-jointstereo.mp3', dict())
check_default_metadata(metadata)
assert metadata['channels'] == 2
- assert metadata['bit_rate'] == 128000
+ assert metadata['bit_rate'] == 129757
assert abs(metadata['length_seconds'] - 3.9) < 0.1
assert metadata['mime'] == 'audio/mp3'
assert metadata['track_total'] == u'10' # MP3s can have a track_total
@@ -43,7 +43,7 @@ def test_mp3_simplestereo():
metadata = MetadataAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-simplestereo.mp3', dict())
check_default_metadata(metadata)
assert metadata['channels'] == 2
- assert metadata['bit_rate'] == 128000
+ assert metadata['bit_rate'] == 129757
assert abs(metadata['length_seconds'] - 3.9) < 0.1
assert metadata['mime'] == 'audio/mp3'
assert metadata['track_total'] == u'10' # MP3s can have a track_total
@@ -52,7 +52,7 @@ def test_mp3_dualmono():
metadata = MetadataAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-dualmono.mp3', dict())
check_default_metadata(metadata)
assert metadata['channels'] == 2
- assert metadata['bit_rate'] == 128000
+ assert metadata['bit_rate'] == 129757
assert abs(metadata['length_seconds'] - 3.9) < 0.1
assert metadata['mime'] == 'audio/mp3'
assert metadata['track_total'] == u'10' # MP3s can have a track_total
@@ -109,7 +109,7 @@ def test_mp3_utf8():
assert metadata['genre'] == u'Я Б Г Д Ж Й'
assert metadata['track_number'] == u'1'
assert metadata['channels'] == 2
- assert metadata['bit_rate'] == 128000
+ assert metadata['bit_rate'] == 129757
assert abs(metadata['length_seconds'] - 3.9) < 0.1
assert metadata['mime'] == 'audio/mp3'
assert metadata['track_total'] == u'10' # MP3s can have a track_total
@@ -153,7 +153,7 @@ def test_mp3_bad_channels():
metadata = MetadataAnalyzer.analyze(filename, dict())
check_default_metadata(metadata)
assert metadata['channels'] == 1
- assert metadata['bit_rate'] == 64000
+ assert metadata['bit_rate'] == 64876
assert abs(metadata['length_seconds'] - 3.9) < 0.1
assert metadata['mime'] == 'audio/mp3' # Not unicode because MIMEs aren't.
assert metadata['track_total'] == u'10' # MP3s can have a track_total
diff --git a/python_apps/pypo/pypo/listenerstat.py b/python_apps/pypo/pypo/listenerstat.py
index fc7adc74a..cd044eac3 100644
--- a/python_apps/pypo/pypo/listenerstat.py
+++ b/python_apps/pypo/pypo/listenerstat.py
@@ -1,6 +1,6 @@
from threading import Thread
import urllib2
-import xml.dom.minidom
+import defusedxml.minidom
import base64
from datetime import datetime
import traceback
@@ -64,7 +64,7 @@ class ListenerStat(Thread):
else:
url = 'http://%(host)s:%(port)s/admin/stats.xml' % ip
document = self.get_stream_server_xml(ip, url)
- dom = xml.dom.minidom.parseString(document)
+ dom = defusedxml.minidom.parseString(document)
sources = dom.getElementsByTagName("source")
mount_stats = None
@@ -87,7 +87,7 @@ class ListenerStat(Thread):
def get_shoutcast_stats(self, ip):
url = 'http://%(host)s:%(port)s/admin.cgi?sid=1&mode=viewxml' % ip
document = self.get_stream_server_xml(ip, url, is_shoutcast=True)
- dom = xml.dom.minidom.parseString(document)
+ dom = defusedxml.parseString(document)
current_listeners = dom.getElementsByTagName("CURRENTLISTENERS")
timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
diff --git a/python_apps/pypo/setup.py b/python_apps/pypo/setup.py
index a5b33b74d..06a25117c 100644
--- a/python_apps/pypo/setup.py
+++ b/python_apps/pypo/setup.py
@@ -58,7 +58,8 @@ setup(name='airtime-playout',
'pyinotify',
'pytz',
'requests',
- 'wsgiref'
+ 'wsgiref',
+ 'defusedxml'
],
zip_safe=False,
data_files=data_files)