Merge branch '2.4.x'
Conflicts: airtime_mvc/public/js/airtime/preferences/streamsetting.js python_apps/pypo/pypocli.py python_apps/pypo/schedule/telnetliquidsoap.py
This commit is contained in:
commit
b0eda6e915
60 changed files with 38938 additions and 38579 deletions
30
CREDITS
30
CREDITS
|
@ -2,6 +2,36 @@
|
||||||
CREDITS
|
CREDITS
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
Version 2.4.0
|
||||||
|
-------------
|
||||||
|
Martin Konecny (martin.konecny@sourcefabric.org)
|
||||||
|
Role: Developer Team Lead
|
||||||
|
|
||||||
|
Denise Rigato (denise.rigato@sourcefabric.org)
|
||||||
|
Role: Software Developer
|
||||||
|
|
||||||
|
Naomi Aro (naomi.aro@sourcefabric.org)
|
||||||
|
Role: Software Developer
|
||||||
|
|
||||||
|
Cliff Wang (cliff.wang@sourcefabric.org)
|
||||||
|
Role: QA
|
||||||
|
|
||||||
|
Daniel James (daniel.james@sourcefabric.org)
|
||||||
|
Role: Documentor & QA
|
||||||
|
|
||||||
|
Localizations:
|
||||||
|
|
||||||
|
Albert (French)
|
||||||
|
Helmut Müller, Christoph Rombach, Micz Flor, Silvio Mende (German)
|
||||||
|
Claudia Cruz (Spanish)
|
||||||
|
Katerina Michailidis (Greek)
|
||||||
|
Erich Pöttinger (Austrian)
|
||||||
|
Luba Sirina (Russian)
|
||||||
|
Luciano De Fazio (Brazilian Portuguese)
|
||||||
|
Sebastian Matuszewski (Polish)
|
||||||
|
Staff Pingu (Italian)
|
||||||
|
Magyar Zsolt (Hungarian)
|
||||||
|
|
||||||
Version 2.3.0/2.3.1
|
Version 2.3.0/2.3.1
|
||||||
-------------
|
-------------
|
||||||
Martin Konecny (martin.konecny@sourcefabric.org)
|
Martin Konecny (martin.konecny@sourcefabric.org)
|
||||||
|
|
|
@ -62,7 +62,7 @@ class LocaleController extends Zend_Controller_Action
|
||||||
"Deselect all" => _("Deselect all"),
|
"Deselect all" => _("Deselect all"),
|
||||||
"Are you sure you want to delete the selected item(s)?" => _("Are you sure you want to delete the selected item(s)?"),
|
"Are you sure you want to delete the selected item(s)?" => _("Are you sure you want to delete the selected item(s)?"),
|
||||||
"Scheduled" => _("Scheduled"),
|
"Scheduled" => _("Scheduled"),
|
||||||
"Playlist" => _("Playlist"),
|
"Playlist" => _("Playlist / Block"),
|
||||||
"Title" => _("Title"),
|
"Title" => _("Title"),
|
||||||
"Creator" => _("Creator"),
|
"Creator" => _("Creator"),
|
||||||
"Album" => _("Album"),
|
"Album" => _("Album"),
|
||||||
|
@ -110,6 +110,7 @@ class LocaleController extends Zend_Controller_Action
|
||||||
"You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?"
|
"You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?"
|
||||||
=> _("You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?"),
|
=> _("You are currently uploading files. %sGoing to another screen will cancel the upload process. %sAre you sure you want to leave the page?"),
|
||||||
//library/spl.js
|
//library/spl.js
|
||||||
|
"Open Media Builder" => _("Open Media Builder"),
|
||||||
"please put in a time '00:00:00 (.0)'" => _("please put in a time '00:00:00 (.0)'"),
|
"please put in a time '00:00:00 (.0)'" => _("please put in a time '00:00:00 (.0)'"),
|
||||||
"please put in a time in seconds '00 (.0)'" => _("please put in a time in seconds '00 (.0)'"),
|
"please put in a time in seconds '00 (.0)'" => _("please put in a time in seconds '00 (.0)'"),
|
||||||
"Your browser does not support playing this file type: " => _("Your browser does not support playing this file type: "),
|
"Your browser does not support playing this file type: " => _("Your browser does not support playing this file type: "),
|
||||||
|
@ -191,6 +192,7 @@ class LocaleController extends Zend_Controller_Action
|
||||||
"Specify custom authentication which will work only for this show." => _("Specify custom authentication which will work only for this show."),
|
"Specify custom authentication which will work only for this show." => _("Specify custom authentication which will work only for this show."),
|
||||||
"If your live streaming client does not ask for a username, this field should be 'source'." => _("If your live streaming client does not ask for a username, this field should be 'source'."),
|
"If your live streaming client does not ask for a username, this field should be 'source'." => _("If your live streaming client does not ask for a username, this field should be 'source'."),
|
||||||
"The show instance doesn't exist anymore!" => _("The show instance doesn't exist anymore!"),
|
"The show instance doesn't exist anymore!" => _("The show instance doesn't exist anymore!"),
|
||||||
|
"Warning: Shows cannot be re-linked" => _("Warning: Shows cannot be re-linked"),
|
||||||
//schedule/full-calendar-functions
|
//schedule/full-calendar-functions
|
||||||
//already in schedule/add-show.js
|
//already in schedule/add-show.js
|
||||||
//"The show instance doesn't exist anymore!" => _("The show instance doesn't exist anymore!"),
|
//"The show instance doesn't exist anymore!" => _("The show instance doesn't exist anymore!"),
|
||||||
|
|
|
@ -510,7 +510,7 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
|
||||||
}
|
}
|
||||||
} elseif ($column->getType() == PropelColumnTypes::TIMESTAMP) {
|
} elseif ($column->getType() == PropelColumnTypes::TIMESTAMP) {
|
||||||
if (!preg_match("/(\d{4})-(\d{2})-(\d{2})/", $d['sp_criteria_value'])) {
|
if (!preg_match("/(\d{4})-(\d{2})-(\d{2})/", $d['sp_criteria_value'])) {
|
||||||
$element->addError(_("The value should be in timestamp format(eg. 0000-00-00 or 00-00-00 00:00:00)"));
|
$element->addError(_("The value should be in timestamp format (e.g. 0000-00-00 or 0000-00-00 00:00:00)"));
|
||||||
$isValid = false;
|
$isValid = false;
|
||||||
} else {
|
} else {
|
||||||
$result = Application_Common_DateHelper::checkDateTimeRangeForSQL($d['sp_criteria_value']);
|
$result = Application_Common_DateHelper::checkDateTimeRangeForSQL($d['sp_criteria_value']);
|
||||||
|
@ -523,7 +523,7 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
|
||||||
|
|
||||||
if (isset($d['sp_criteria_extra'])) {
|
if (isset($d['sp_criteria_extra'])) {
|
||||||
if (!preg_match("/(\d{4})-(\d{2})-(\d{2})/", $d['sp_criteria_extra'])) {
|
if (!preg_match("/(\d{4})-(\d{2})-(\d{2})/", $d['sp_criteria_extra'])) {
|
||||||
$element->addError(_("The value should be in timestamp format(eg. 0000-00-00 or 00-00-00 00:00:00)"));
|
$element->addError(_("The value should be in timestamp format (e.g. 0000-00-00 or 0000-00-00 00:00:00)"));
|
||||||
$isValid = false;
|
$isValid = false;
|
||||||
} else {
|
} else {
|
||||||
$result = Application_Common_DateHelper::checkDateTimeRangeForSQL($d['sp_criteria_extra']);
|
$result = Application_Common_DateHelper::checkDateTimeRangeForSQL($d['sp_criteria_extra']);
|
||||||
|
|
|
@ -29,24 +29,6 @@ WHERE ends > now() AT TIME ZONE 'UTC'
|
||||||
AND file_id is not null
|
AND file_id is not null
|
||||||
SQL;
|
SQL;
|
||||||
|
|
||||||
/* If an instance id gets passed into this function we need to check
|
|
||||||
* if it is a repeating show. If it is a repeating show, we need to
|
|
||||||
* check for any files scheduled in the future in the linked instances
|
|
||||||
* as well
|
|
||||||
*/
|
|
||||||
if (!is_null($instanceId)) {
|
|
||||||
$excludeIds = array();
|
|
||||||
$ccShow = CcShowInstancesQuery::create()
|
|
||||||
->findPk($instanceId)
|
|
||||||
->getCcShow();
|
|
||||||
if ($ccShow->isLinked()) {
|
|
||||||
foreach ($ccShow->getOtherInstances($instanceId) as $instance) {
|
|
||||||
$excludeIds[] = $instance->getDbId();
|
|
||||||
}
|
|
||||||
$sql .= " AND instance_id IN (".implode(",", $excludeIds).")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$files = Application_Common_Database::prepareAndExecute( $sql, array());
|
$files = Application_Common_Database::prepareAndExecute( $sql, array());
|
||||||
|
|
||||||
$real_files = array();
|
$real_files = array();
|
||||||
|
|
|
@ -1323,7 +1323,7 @@ SQL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setIsScheduled($p_scheduleItem, $p_status,
|
public static function setIsScheduled($p_scheduleItem, $p_status,
|
||||||
$p_fileId=null, $instanceId=null) {
|
$p_fileId=null) {
|
||||||
|
|
||||||
if (is_null($p_fileId)) {
|
if (is_null($p_fileId)) {
|
||||||
$fileId = Application_Model_Schedule::GetFileId($p_scheduleItem);
|
$fileId = Application_Model_Schedule::GetFileId($p_scheduleItem);
|
||||||
|
@ -1334,7 +1334,7 @@ SQL;
|
||||||
$updateIsScheduled = false;
|
$updateIsScheduled = false;
|
||||||
|
|
||||||
if (!is_null($fileId) && !in_array($fileId,
|
if (!is_null($fileId) && !in_array($fileId,
|
||||||
Application_Model_Schedule::getAllFutureScheduledFiles($instanceId))) {
|
Application_Model_Schedule::getAllFutureScheduledFiles())) {
|
||||||
$file->_file->setDbIsScheduled($p_status)->save();
|
$file->_file->setDbIsScheduled($p_status)->save();
|
||||||
$updateIsScheduled = true;
|
$updateIsScheduled = true;
|
||||||
}
|
}
|
||||||
|
@ -1344,26 +1344,25 @@ SQL;
|
||||||
|
|
||||||
public static function updatePastFilesIsScheduled()
|
public static function updatePastFilesIsScheduled()
|
||||||
{
|
{
|
||||||
/* Retrieve files that are scheduled in the past OR that belong
|
/* Set the is_scheduled flag to false where it was true in the
|
||||||
* to a show that has ended. We need to check if the show has
|
* past, and where tracks are not scheduled in the future and do
|
||||||
* ended incase a track is overbooked, since that alone will
|
* not belong to a show that has not ended yet. We need to check
|
||||||
* indicate the show is still scheduled in the future
|
* for show end times in case a track is overbooked, which would
|
||||||
|
* indicate it is still scheduled in the future
|
||||||
*/
|
*/
|
||||||
$sql = <<<SQL
|
$sql = <<<SQL
|
||||||
SELECT s.file_id, s.instance_id FROM cc_schedule AS s
|
UPDATE cc_files SET is_scheduled = false
|
||||||
LEFT JOIN cc_show_instances AS i
|
WHERE is_scheduled = true
|
||||||
ON s.instance_id = i.id
|
AND id NOT IN (
|
||||||
WHERE s.ends < now() at time zone 'UTC'
|
SELECT s.file_id FROM cc_schedule AS s
|
||||||
OR i.ends < now() at time zone 'UTC'
|
LEFT JOIN cc_show_instances AS i
|
||||||
|
ON s.instance_id = i.id
|
||||||
|
WHERE s.ends > now() at time zone 'UTC'
|
||||||
|
AND i.ends > now() at time zone 'UTC'
|
||||||
|
)
|
||||||
SQL;
|
SQL;
|
||||||
$files = Application_Common_Database::prepareAndExecute($sql);
|
Application_Common_Database::prepareAndExecute($sql, array(),
|
||||||
|
Application_Common_Database::EXECUTE);
|
||||||
foreach ($files as $file) {
|
|
||||||
if (!is_null($file['file_id'])) {
|
|
||||||
self::setIsScheduled(null, false, $file['file_id'], $file['instance_id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRealClipLength($p_cuein, $p_cueout) {
|
public function getRealClipLength($p_cuein, $p_cueout) {
|
||||||
|
|
|
@ -194,7 +194,7 @@ class CcShow extends BaseCcShow {
|
||||||
{
|
{
|
||||||
return CcShowInstancesQuery::create()
|
return CcShowInstancesQuery::create()
|
||||||
->filterByCcShow($this)
|
->filterByCcShow($this)
|
||||||
->filterByDbId($instanceId, Criteria::NOT_IN)
|
->filterByDbId($instanceId, Criteria::NOT_EQUAL)
|
||||||
->find();
|
->find();
|
||||||
}
|
}
|
||||||
} // CcShow
|
} // CcShow
|
||||||
|
|
|
@ -569,6 +569,8 @@ SQL;
|
||||||
$this->deleteShowInstances($ccShowInstances, $ccShowInstance->getDbShowId());
|
$this->deleteShowInstances($ccShowInstances, $ccShowInstance->getDbShowId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Application_Model_StoredFile::updatePastFilesIsScheduled();
|
||||||
|
|
||||||
Application_Model_RabbitMq::PushSchedule();
|
Application_Model_RabbitMq::PushSchedule();
|
||||||
|
|
||||||
$con->commit();
|
$con->commit();
|
||||||
|
@ -876,10 +878,13 @@ SQL;
|
||||||
/*
|
/*
|
||||||
* Make sure start date is less than populate until date AND
|
* Make sure start date is less than populate until date AND
|
||||||
* last show date is null OR start date is less than last show date
|
* last show date is null OR start date is less than last show date
|
||||||
|
*
|
||||||
|
* (NOTE: We cannot call getTimestamp() to compare the dates because of
|
||||||
|
* a PHP 5.3.3 bug with DatePeriod objects - See CC-5159 for more details)
|
||||||
*/
|
*/
|
||||||
if ($utcStartDateTime->getTimestamp() <= $populateUntil->getTimestamp() &&
|
if ($utcStartDateTime->format("Y-m-d H:i:s") <= $populateUntil->format("Y-m-d H:i:s") &&
|
||||||
( is_null($utcLastShowDateTime) ||
|
( is_null($utcLastShowDateTime) ||
|
||||||
$utcStartDateTime->getTimestamp() < $utcLastShowDateTime->getTimestamp()) ) {
|
$utcStartDateTime->format("Y-m-d H:i:s") < $utcLastShowDateTime->format("Y-m-d H:i:s")) ) {
|
||||||
|
|
||||||
/* There may not always be an instance when editing a show
|
/* There may not always be an instance when editing a show
|
||||||
* This will be the case when we are adding a new show day to
|
* This will be the case when we are adding a new show day to
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
<?php $baseUrl = Application_Common_OsPath::getBaseDir(); ?>
|
<?php $baseUrl = Application_Common_OsPath::getBaseDir(); ?>
|
||||||
<div id="import_status" class="library_import" style="display:none"><? echo _("File import in progress..."); ?><img src=<?php echo $baseUrl . "css/images/file_import_loader.gif"?>></img></div>
|
<div id="import_status" class="library_import" style="display:none">
|
||||||
|
<?php echo _("File import in progress..."); ?>
|
||||||
|
<img src=<?php echo $baseUrl . "css/images/file_import_loader.gif"?>></img>
|
||||||
|
</div>
|
||||||
|
|
||||||
<fieldset class="toggle closed" id="filter_options">
|
<fieldset class="toggle closed" id="filter_options">
|
||||||
<legend style="cursor: pointer;"><span class="ui-icon ui-icon-triangle-2-n-s"></span><? echo _("Advanced Search Options") ?></legend>
|
<legend style="cursor: pointer;">
|
||||||
|
<span class="ui-icon ui-icon-triangle-2-n-s"></span>
|
||||||
|
<?php echo _("Advanced Search Options") ?>
|
||||||
|
</legend>
|
||||||
<div id="advanced_search" class="advanced_search form-horizontal"></div>
|
<div id="advanced_search" class="advanced_search form-horizontal"></div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<table id="library_display" cellpadding="0" cellspacing="0" class="datatable">
|
<table id="library_display" cellpadding="0" cellspacing="0" class="datatable">
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -330,8 +330,8 @@ INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('en_GB', 'English (Brit
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('en_US', 'English (USA)');
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('en_US', 'English (USA)');
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('cs_CZ', 'Český');
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('cs_CZ', 'Český');
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('de_DE', 'Deutsch');
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('de_DE', 'Deutsch');
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('hu_HU', 'hʌŋɡəri');
|
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('de_AT', 'Österreichisches Deutsch');
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('de_AT', 'Österreichisches Deutsch');
|
||||||
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('hu_HU', 'Magyar');
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('es_ES', 'Español');
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('es_ES', 'Español');
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('fr_FR', 'Français');
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('fr_FR', 'Français');
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('it_IT', 'Italiano');
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('it_IT', 'Italiano');
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Binary file not shown.
File diff suppressed because it is too large
Load diff
3
airtime_mvc/public/css/bootstrap.css
vendored
3
airtime_mvc/public/css/bootstrap.css
vendored
|
@ -232,6 +232,9 @@ a.badge:hover {
|
||||||
.btn-small [class^="icon-"] {
|
.btn-small [class^="icon-"] {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
#pl_edit.btn.btn-small {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
.btn-mini {
|
.btn-mini {
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|
BIN
airtime_mvc/public/css/images/is_playlist.png
Normal file
BIN
airtime_mvc/public/css/images/is_playlist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 801 B |
BIN
airtime_mvc/public/css/images/is_scheduled.png
Normal file
BIN
airtime_mvc/public/css/images/is_scheduled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 543 B |
|
@ -2006,6 +2006,14 @@ span.errors.sp-errors{
|
||||||
.small-icon.show-partial-filled, .small-icon.media-item-in-use {
|
.small-icon.show-partial-filled, .small-icon.media-item-in-use {
|
||||||
background:url(images/icon_alert_cal_alt2.png) no-repeat 0 0;
|
background:url(images/icon_alert_cal_alt2.png) no-repeat 0 0;
|
||||||
}
|
}
|
||||||
|
.small-icon.is_scheduled {
|
||||||
|
background:url(images/is_scheduled.png) no-repeat 0 0;
|
||||||
|
height: 16px !important;
|
||||||
|
}
|
||||||
|
.small-icon.is_playlist {
|
||||||
|
background:url(images/is_playlist.png) no-repeat 0 0;
|
||||||
|
height: 16px !important;
|
||||||
|
}
|
||||||
.medium-icon {
|
.medium-icon {
|
||||||
display:block;
|
display:block;
|
||||||
width:25px;
|
width:25px;
|
||||||
|
|
|
@ -413,7 +413,7 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
|
|
||||||
if (ele.bVisible) {
|
if (ele.bVisible) {
|
||||||
advanceSearchDiv.append(
|
advanceSearchDiv.append(
|
||||||
"<div id='advanced_search_col_"+currentColId+" class='control-group'>" +
|
"<div id='advanced_search_col_"+currentColId+"' class='control-group'>" +
|
||||||
"<label class='control-label'"+labelStyle+">"+ele.sTitle+" : </label>" +
|
"<label class='control-label'"+labelStyle+">"+ele.sTitle+" : </label>" +
|
||||||
"<div id='"+ele.mDataProp+"' class='controls "+inputClass+"'></div>" +
|
"<div id='"+ele.mDataProp+"' class='controls "+inputClass+"'></div>" +
|
||||||
"</div>");
|
"</div>");
|
||||||
|
@ -447,14 +447,14 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
function setFilterElement(iColumn, bVisible){
|
function setFilterElement(iColumn, bVisible){
|
||||||
var actualId = colReorderMap[iColumn];
|
var actualId = colReorderMap[iColumn];
|
||||||
var selector = "div#advanced_search_col_"+actualId;
|
var selector = "div#advanced_search_col_"+actualId;
|
||||||
if (bVisible) {
|
var $el = $(selector);
|
||||||
$(selector).show();
|
|
||||||
} else {
|
|
||||||
$(selector).hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentColOrder = new Array();
|
if (bVisible) {
|
||||||
|
$el.show();
|
||||||
|
} else {
|
||||||
|
$el.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oTable = $libTable.dataTable( {
|
oTable = $libTable.dataTable( {
|
||||||
|
|
||||||
|
@ -468,7 +468,7 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
/* Checkbox */ { "sTitle" : "" , "mDataProp" : "checkbox" , "bSortable" : false , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_checkbox" } ,
|
/* Checkbox */ { "sTitle" : "" , "mDataProp" : "checkbox" , "bSortable" : false , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_checkbox" } ,
|
||||||
/* Type */ { "sTitle" : "" , "mDataProp" : "image" , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_type" , "iDataSort" : 0 } ,
|
/* Type */ { "sTitle" : "" , "mDataProp" : "image" , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_type" , "iDataSort" : 0 } ,
|
||||||
/* Is Scheduled */ { "sTitle" : $.i18n._("Scheduled") , "mDataProp" : "is_scheduled" , "bSearchable" : false , "sWidth" : "90px" , "sClass" : "library_is_scheduled"} ,
|
/* Is Scheduled */ { "sTitle" : $.i18n._("Scheduled") , "mDataProp" : "is_scheduled" , "bSearchable" : false , "sWidth" : "90px" , "sClass" : "library_is_scheduled"} ,
|
||||||
/* Is Playlist */ { "sTitle" : $.i18n._("Playlist") , "mDataProp" : "is_playlist" , "bSearchable" : false , "sWidth" : "70px" , "sClass" : "library_is_playlist"} ,
|
/* Is Playlist */ { "sTitle" : $.i18n._("Playlist / Block") , "mDataProp" : "is_playlist" , "bSearchable" : false , "sWidth" : "110px" , "sClass" : "library_is_playlist"} ,
|
||||||
/* Title */ { "sTitle" : $.i18n._("Title") , "mDataProp" : "track_title" , "sClass" : "library_title" , "sWidth" : "170px" } ,
|
/* Title */ { "sTitle" : $.i18n._("Title") , "mDataProp" : "track_title" , "sClass" : "library_title" , "sWidth" : "170px" } ,
|
||||||
/* Creator */ { "sTitle" : $.i18n._("Creator") , "mDataProp" : "artist_name" , "sClass" : "library_creator" , "sWidth" : "160px" } ,
|
/* Creator */ { "sTitle" : $.i18n._("Creator") , "mDataProp" : "artist_name" , "sClass" : "library_creator" , "sWidth" : "160px" } ,
|
||||||
/* Album */ { "sTitle" : $.i18n._("Album") , "mDataProp" : "album_title" , "sClass" : "library_album" , "sWidth" : "150px" } ,
|
/* Album */ { "sTitle" : $.i18n._("Album") , "mDataProp" : "album_title" , "sClass" : "library_album" , "sWidth" : "150px" } ,
|
||||||
|
@ -618,12 +618,12 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aData.is_scheduled) {
|
if (aData.is_scheduled) {
|
||||||
$(nRow).find("td.library_is_scheduled").html('<span class="small-icon media-item-in-use"></span>');
|
$(nRow).find("td.library_is_scheduled").html('<span class="small-icon is_scheduled"></span>');
|
||||||
} else if (!aData.is_scheduled) {
|
} else if (!aData.is_scheduled) {
|
||||||
$(nRow).find("td.library_is_scheduled").html('');
|
$(nRow).find("td.library_is_scheduled").html('');
|
||||||
}
|
}
|
||||||
if (aData.is_playlist) {
|
if (aData.is_playlist) {
|
||||||
$(nRow).find("td.library_is_playlist").html('<span class="small-icon media-item-in-use"></span>');
|
$(nRow).find("td.library_is_playlist").html('<span class="small-icon is_playlist"></span>');
|
||||||
} else if (!aData.is_playlist) {
|
} else if (!aData.is_playlist) {
|
||||||
$(nRow).find("td.library_is_playlist").html('');
|
$(nRow).find("td.library_is_playlist").html('');
|
||||||
}
|
}
|
||||||
|
@ -764,7 +764,8 @@ var AIRTIME = (function(AIRTIME) {
|
||||||
"sAlign": "right",
|
"sAlign": "right",
|
||||||
"aiExclude": [0, 1, 2],
|
"aiExclude": [0, 1, 2],
|
||||||
"sSize": "css",
|
"sSize": "css",
|
||||||
"fnStateChange": setFilterElement
|
"fnStateChange": setFilterElement,
|
||||||
|
"buttonText": $.i18n._("Show / hide columns")
|
||||||
},
|
},
|
||||||
|
|
||||||
"oColReorder": {
|
"oColReorder": {
|
||||||
|
|
|
@ -12,7 +12,7 @@ var AIRTIME = (function(AIRTIME){
|
||||||
viewport,
|
viewport,
|
||||||
$lib,
|
$lib,
|
||||||
$pl,
|
$pl,
|
||||||
$togglePl = $("<button id='pl_edit' class='btn btn-small' href='#' title='"+$.i18n._("Open Playlist Editor")+"'>"+$.i18n._("Open Playlist Editor")+"</button>"),
|
$togglePl = $("<button id='pl_edit' class='btn btn-small' href='#' title='"+$.i18n._("Open Media Builder")+"'>"+$.i18n._("Open Media Builder")+"</button>"),
|
||||||
widgetHeight,
|
widgetHeight,
|
||||||
resizeTimeout,
|
resizeTimeout,
|
||||||
width;
|
width;
|
||||||
|
|
|
@ -403,7 +403,7 @@ function setupEventListeners() {
|
||||||
$(".stream_type_help_icon").qtip({
|
$(".stream_type_help_icon").qtip({
|
||||||
content: {
|
content: {
|
||||||
text: sprintf(
|
text: sprintf(
|
||||||
$.i18n._("Some steam types require extra configuration. Details about enabling %sAAC+ Support%s or %sOpus Support%s are provided."),
|
$.i18n._("Some stream types require extra configuration. Details about enabling %sAAC+ Support%s or %sOpus Support%s are provided."),
|
||||||
"<a target='_blank' href='https://wiki.sourcefabric.org/x/NgPQ'>",
|
"<a target='_blank' href='https://wiki.sourcefabric.org/x/NgPQ'>",
|
||||||
"</a>",
|
"</a>",
|
||||||
"<a target='_blank' href='https://wiki.sourcefabric.org/x/KgPQ'>",
|
"<a target='_blank' href='https://wiki.sourcefabric.org/x/KgPQ'>",
|
||||||
|
|
|
@ -188,8 +188,8 @@ function setAddShowEvents() {
|
||||||
});
|
});
|
||||||
|
|
||||||
form.find("#add_show_linked").click(function(){
|
form.find("#add_show_linked").click(function(){
|
||||||
if (!$(this).attr("checked")) {
|
if (!$(this).attr("checked") && $("#show-link-warning").length === 0) {
|
||||||
$(this).parent().after("<ul class='errors'><li>Warning: Shows cannot be re-linked</li></ul>");
|
$(this).parent().after("<ul id='show-link-warning' class='errors'><li>"+$.i18n._("Warning: Shows cannot be re-linked")+"</li></ul>");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -817,17 +817,18 @@ var AIRTIME = (function(AIRTIME){
|
||||||
mod.checkToolBarIcons();
|
mod.checkToolBarIcons();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// R = ColReorder, C = ColVis
|
||||||
|
"sDom": 'R<"dt-process-rel"r><"sb-padded"<"H"C>><"dataTables_scrolling sb-padded"t>',
|
||||||
|
|
||||||
"oColVis": {
|
"oColVis": {
|
||||||
"aiExclude": [ 0, 1 ]
|
"aiExclude": [ 0, 1 ],
|
||||||
|
"buttonText": $.i18n._("Show / hide columns"),
|
||||||
},
|
},
|
||||||
|
|
||||||
"oColReorder": {
|
"oColReorder": {
|
||||||
"iFixedColumns": 2
|
"iFixedColumns": 2
|
||||||
},
|
},
|
||||||
|
|
||||||
// R = ColReorderResize, C = ColVis
|
|
||||||
"sDom": 'R<"dt-process-rel"r><"sb-padded"<"H"C>><"dataTables_scrolling sb-padded"t>',
|
|
||||||
|
|
||||||
"sAjaxDataProp": "schedule",
|
"sAjaxDataProp": "schedule",
|
||||||
"oLanguage": datatables_dict,
|
"oLanguage": datatables_dict,
|
||||||
"sAjaxSource": baseUrl+"showbuilder/builder-feed"
|
"sAjaxSource": baseUrl+"showbuilder/builder-feed"
|
||||||
|
|
|
@ -31,7 +31,3 @@ The new _fnDomBaseButton looks like this:
|
||||||
return nButton;
|
return nButton;
|
||||||
},
|
},
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
* Line 96 has changed
|
|
||||||
- "buttonText": "Show / hide columns",
|
|
||||||
+ "buttonText": $.i18n._("Show / hide columns"),
|
|
0
airtime_mvc/public/js/datatables/plugin/dataTables.ColReorder.js
Normal file → Executable file
0
airtime_mvc/public/js/datatables/plugin/dataTables.ColReorder.js
Normal file → Executable file
43
airtime_mvc/public/js/datatables/plugin/dataTables.ColVis.js
Normal file → Executable file
43
airtime_mvc/public/js/datatables/plugin/dataTables.ColVis.js
Normal file → Executable file
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* File: ColVis.js
|
* File: ColVis.js
|
||||||
* Version: 1.0.7
|
* Version: 1.0.8
|
||||||
* CVS: $Id$
|
* CVS: $Id$
|
||||||
* Description: Controls for column visiblity in DataTables
|
* Description: Controls for column visiblity in DataTables
|
||||||
* Author: Allan Jardine (www.sprymedia.co.uk)
|
* Author: Allan Jardine (www.sprymedia.co.uk)
|
||||||
|
@ -95,7 +95,7 @@ ColVis = function( oDTSettings, oInit )
|
||||||
* @type String
|
* @type String
|
||||||
* @default Show / hide columns
|
* @default Show / hide columns
|
||||||
*/
|
*/
|
||||||
"buttonText": $.i18n._("Show / hide columns"),
|
"buttonText": "Show / hide columns",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to say if the collection is hidden
|
* Flag to say if the collection is hidden
|
||||||
|
@ -313,6 +313,7 @@ ColVis.prototype = {
|
||||||
this._fnApplyCustomisation();
|
this._fnApplyCustomisation();
|
||||||
|
|
||||||
var that = this;
|
var that = this;
|
||||||
|
var i, iLen;
|
||||||
this.dom.wrapper = document.createElement('div');
|
this.dom.wrapper = document.createElement('div');
|
||||||
this.dom.wrapper.className = "ColVis TableTools";
|
this.dom.wrapper.className = "ColVis TableTools";
|
||||||
|
|
||||||
|
@ -327,7 +328,7 @@ ColVis.prototype = {
|
||||||
this._fnAddButtons();
|
this._fnAddButtons();
|
||||||
|
|
||||||
/* Store the original visbility information */
|
/* Store the original visbility information */
|
||||||
for ( var i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
|
for ( i=0, iLen=this.s.dt.aoColumns.length ; i<iLen ; i++ )
|
||||||
{
|
{
|
||||||
this.s.abOriginal.push( this.s.dt.aoColumns[i].bVisible );
|
this.s.abOriginal.push( this.s.dt.aoColumns[i].bVisible );
|
||||||
}
|
}
|
||||||
|
@ -339,6 +340,20 @@ ColVis.prototype = {
|
||||||
},
|
},
|
||||||
"sName": "ColVis"
|
"sName": "ColVis"
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
/* If columns are reordered, then we need to update our exclude list and
|
||||||
|
* rebuild the displayed list
|
||||||
|
*/
|
||||||
|
$(this.s.dt.oInstance).bind( 'column-reorder', function ( e, oSettings, oReorder ) {
|
||||||
|
for ( i=0, iLen=that.s.aiExclude.length ; i<iLen ; i++ ) {
|
||||||
|
that.s.aiExclude[i] = oReorder.aiInvertMapping[ that.s.aiExclude[i] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
var mStore = that.s.abOriginal.splice( oReorder.iFrom, 1 )[0];
|
||||||
|
that.s.abOriginal.splice( oReorder.iTo, 0, mStore );
|
||||||
|
|
||||||
|
that.fnRebuild();
|
||||||
|
} );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -420,8 +435,8 @@ ColVis.prototype = {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On each table draw, check the visiblity checkboxes as needed. This allows any process to
|
* On each table draw, check the visibility checkboxes as needed. This allows any process to
|
||||||
* update the table's column visiblity and ColVis will still be accurate.
|
* update the table's column visibility and ColVis will still be accurate.
|
||||||
* @method _fnDrawCallback
|
* @method _fnDrawCallback
|
||||||
* @returns void
|
* @returns void
|
||||||
* @private
|
* @private
|
||||||
|
@ -515,6 +530,7 @@ ColVis.prototype = {
|
||||||
that.s.dt.oInstance.fnSetColumnVis( i, that.s.abOriginal[i], false );
|
that.s.dt.oInstance.fnSetColumnVis( i, that.s.abOriginal[i], false );
|
||||||
}
|
}
|
||||||
that._fnAdjustOpenRows();
|
that._fnAdjustOpenRows();
|
||||||
|
that.s.dt.oInstance.fnAdjustColumnSizing( false );
|
||||||
that.s.dt.oInstance.fnDraw( false );
|
that.s.dt.oInstance.fnDraw( false );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -549,6 +565,7 @@ ColVis.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
that._fnAdjustOpenRows();
|
that._fnAdjustOpenRows();
|
||||||
|
that.s.dt.oInstance.fnAdjustColumnSizing( false );
|
||||||
that.s.dt.oInstance.fnDraw( false );
|
that.s.dt.oInstance.fnDraw( false );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -574,6 +591,7 @@ ColVis.prototype = {
|
||||||
|
|
||||||
nButton.className = !dt.bJUI ? "ColVis_Button TableTools_Button" :
|
nButton.className = !dt.bJUI ? "ColVis_Button TableTools_Button" :
|
||||||
"ColVis_Button TableTools_Button ui-button ui-state-default";
|
"ColVis_Button TableTools_Button ui-button ui-state-default";
|
||||||
|
|
||||||
nButton.appendChild( nSpan );
|
nButton.appendChild( nSpan );
|
||||||
var sTitle = this.s.fnLabel===null ? oColumn.sTitle : this.s.fnLabel( i, oColumn.sTitle, oColumn.nTh );
|
var sTitle = this.s.fnLabel===null ? oColumn.sTitle : this.s.fnLabel( i, oColumn.sTitle, oColumn.nTh );
|
||||||
$(nSpan).html(
|
$(nSpan).html(
|
||||||
|
@ -597,6 +615,7 @@ ColVis.prototype = {
|
||||||
if ( dt.oFeatures.bServerSide && (dt.oScroll.sX !== "" || dt.oScroll.sY !== "" ) )
|
if ( dt.oFeatures.bServerSide && (dt.oScroll.sX !== "" || dt.oScroll.sY !== "" ) )
|
||||||
{
|
{
|
||||||
that.s.dt.oInstance.fnSetColumnVis( i, showHide, false );
|
that.s.dt.oInstance.fnSetColumnVis( i, showHide, false );
|
||||||
|
that.s.dt.oInstance.fnAdjustColumnSizing( false );
|
||||||
that.s.dt.oInstance.oApi._fnScrollDraw( that.s.dt );
|
that.s.dt.oInstance.oApi._fnScrollDraw( that.s.dt );
|
||||||
that._fnDrawCallback();
|
that._fnDrawCallback();
|
||||||
}
|
}
|
||||||
|
@ -648,18 +667,18 @@ ColVis.prototype = {
|
||||||
var
|
var
|
||||||
that = this,
|
that = this,
|
||||||
nButton = document.createElement('button'),
|
nButton = document.createElement('button'),
|
||||||
|
nCaret = document.createElement('span'),
|
||||||
nSpan = document.createElement('span'),
|
nSpan = document.createElement('span'),
|
||||||
sEvent = this.s.activate=="mouseover" ? "mouseover" : "click";
|
sEvent = this.s.activate=="mouseover" ? "mouseover" : "click";
|
||||||
|
|
||||||
//nButton.className = !this.s.dt.bJUI ? "ColVis_Button TableTools_Button" :
|
nButton.className = "btn-toolbar btn btn-small dropdown-toggle";
|
||||||
//"ColVis_Button TableTools_Button ui-button ui-state-default";
|
nCaret.className = "caret";
|
||||||
nButton.className = "btn-toolbar btn btn-small dropdown-toggle ColVis_MasterButton";
|
|
||||||
|
|
||||||
nButton.appendChild( nSpan );
|
nButton.appendChild( nSpan );
|
||||||
$(nButton).append("<span class='caret'></span>");
|
nButton.appendChild( nCaret );
|
||||||
nSpan.innerHTML = text;
|
nSpan.innerHTML = text;
|
||||||
|
|
||||||
$(nButton).bind( 'click', function (e) {
|
$(nButton).bind( sEvent, function (e) {
|
||||||
that._fnCollectionShow();
|
that._fnCollectionShow();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
} );
|
} );
|
||||||
|
@ -926,7 +945,7 @@ ColVis.fnRebuild = function ( oTable )
|
||||||
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
* Static object propterties
|
* Static object properties
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -961,7 +980,7 @@ ColVis.prototype.CLASS = "ColVis";
|
||||||
* @type String
|
* @type String
|
||||||
* @default See code
|
* @default See code
|
||||||
*/
|
*/
|
||||||
ColVis.VERSION = "1.0.7";
|
ColVis.VERSION = "1.0.8";
|
||||||
ColVis.prototype.VERSION = ColVis.VERSION;
|
ColVis.prototype.VERSION = ColVis.VERSION;
|
||||||
|
|
||||||
|
|
||||||
|
|
2
debian/watch
vendored
2
debian/watch
vendored
|
@ -1,2 +1,2 @@
|
||||||
version=3
|
version=3
|
||||||
http://sf.net/airtime/airtime-([\d\.]+)\.tar\.gz
|
http://sf.net/airtime/airtime-([\d\.]+)-ga\.tar\.gz
|
||||||
|
|
|
@ -16,4 +16,4 @@ UPDATE cc_files
|
||||||
SET is_playlist = true
|
SET is_playlist = true
|
||||||
WHERE id IN (SELECT DISTINCT(file_id) FROM cc_blockcontents);
|
WHERE id IN (SELECT DISTINCT(file_id) FROM cc_blockcontents);
|
||||||
|
|
||||||
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('hu_HU', 'hʌŋɡəri');
|
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('hu_HU', 'Magyar');
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
# by adding blank when the source is not available.
|
# by adding blank when the source is not available.
|
||||||
# @param s the source to turn infaillible
|
# @param s the source to turn infaillible
|
||||||
# @category Source / Track Processing
|
# @category Source / Track Processing
|
||||||
def mksafe(s)
|
def mksafe(~id="mksafe",s)
|
||||||
fallback(id="mksafe",track_sensitive=false,[s,blank(id="safe_blank")])
|
fallback(id=id,track_sensitive=false,[s,blank(id="safe_blank")])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Alias for the <code>l[k]</code> notation.
|
# Alias for the <code>l[k]</code> notation.
|
||||||
|
|
|
@ -231,6 +231,7 @@ s = switch(id="schedule_noise_switch",
|
||||||
|
|
||||||
s = if dj_live_stream_port != 0 and dj_live_stream_mp != "" then
|
s = if dj_live_stream_port != 0 and dj_live_stream_mp != "" then
|
||||||
dj_live = mksafe(
|
dj_live = mksafe(
|
||||||
|
id="dj_live_mksafe",
|
||||||
audio_to_stereo(
|
audio_to_stereo(
|
||||||
input.harbor(id="live_dj_harbor",
|
input.harbor(id="live_dj_harbor",
|
||||||
dj_live_stream_mp,
|
dj_live_stream_mp,
|
||||||
|
@ -253,6 +254,7 @@ end
|
||||||
|
|
||||||
s = if master_live_stream_port != 0 and master_live_stream_mp != "" then
|
s = if master_live_stream_port != 0 and master_live_stream_mp != "" then
|
||||||
master_dj = mksafe(
|
master_dj = mksafe(
|
||||||
|
id="master_dj_mksafe",
|
||||||
audio_to_stereo(
|
audio_to_stereo(
|
||||||
input.harbor(id="master_harbor",
|
input.harbor(id="master_harbor",
|
||||||
master_live_stream_mp,
|
master_live_stream_mp,
|
||||||
|
|
296
python_apps/pypo/pypocli.py
Normal file
296
python_apps/pypo/pypocli.py
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
"""
|
||||||
|
Python part of radio playout (pypo)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from optparse import OptionParser
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
import logging
|
||||||
|
import locale
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from Queue import Queue
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
from pypopush import PypoPush
|
||||||
|
from pypofetch import PypoFetch
|
||||||
|
from pypofile import PypoFile
|
||||||
|
from recorder import Recorder
|
||||||
|
from listenerstat import ListenerStat
|
||||||
|
from pypomessagehandler import PypoMessageHandler
|
||||||
|
from pypoliquidsoap import PypoLiquidsoap
|
||||||
|
|
||||||
|
from media.update.replaygainupdater import ReplayGainUpdater
|
||||||
|
from media.update.silananalyzer import SilanAnalyzer
|
||||||
|
|
||||||
|
from configobj import ConfigObj
|
||||||
|
|
||||||
|
# custom imports
|
||||||
|
from api_clients import api_client
|
||||||
|
from std_err_override import LogWriter
|
||||||
|
import pure
|
||||||
|
|
||||||
|
# Set up command-line options
|
||||||
|
parser = OptionParser()
|
||||||
|
|
||||||
|
# help screen / info
|
||||||
|
usage = "%prog [options]" + " - python playout system"
|
||||||
|
parser = OptionParser(usage=usage)
|
||||||
|
|
||||||
|
# Options
|
||||||
|
parser.add_option("-v", "--compat",
|
||||||
|
help="Check compatibility with server API version",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
dest="check_compat")
|
||||||
|
|
||||||
|
parser.add_option("-t", "--test",
|
||||||
|
help="Do a test to make sure everything is working properly.",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
dest="test")
|
||||||
|
|
||||||
|
parser.add_option("-b",
|
||||||
|
"--cleanup",
|
||||||
|
help="Cleanup",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
dest="cleanup")
|
||||||
|
|
||||||
|
parser.add_option("-c",
|
||||||
|
"--check",
|
||||||
|
help="Check the cached schedule and exit",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
dest="check")
|
||||||
|
|
||||||
|
# parse options
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
|
LIQUIDSOAP_MIN_VERSION = "1.1.1"
|
||||||
|
|
||||||
|
|
||||||
|
#need to wait for Python 2.7 for this..
|
||||||
|
#logging.captureWarnings(True)
|
||||||
|
|
||||||
|
# configure logging
|
||||||
|
try:
|
||||||
|
logging.config.fileConfig("logging.cfg")
|
||||||
|
logger = logging.getLogger()
|
||||||
|
LogWriter.override_std_err(logger)
|
||||||
|
except Exception, e:
|
||||||
|
print "Couldn't configure logging"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def configure_locale():
|
||||||
|
"""
|
||||||
|
Silly hacks to force Python 2.x to run in UTF-8 mode. Not portable at all,
|
||||||
|
however serves our purpose at the moment.
|
||||||
|
|
||||||
|
More information available here:
|
||||||
|
http://stackoverflow.com/questions/3828723/why-we-need-sys-setdefaultencodingutf-8-in-a-py-script
|
||||||
|
"""
|
||||||
|
logger.debug("Before %s", locale.nl_langinfo(locale.CODESET))
|
||||||
|
current_locale = locale.getlocale()
|
||||||
|
|
||||||
|
if current_locale[1] is None:
|
||||||
|
logger.debug("No locale currently set. Attempting to get default locale.")
|
||||||
|
default_locale = locale.getdefaultlocale()
|
||||||
|
|
||||||
|
if default_locale[1] is None:
|
||||||
|
logger.debug("No default locale exists. Let's try loading from \
|
||||||
|
/etc/default/locale")
|
||||||
|
if os.path.exists("/etc/default/locale"):
|
||||||
|
locale_config = ConfigObj('/etc/default/locale')
|
||||||
|
lang = locale_config.get('LANG')
|
||||||
|
new_locale = lang
|
||||||
|
else:
|
||||||
|
logger.error("/etc/default/locale could not be found! Please \
|
||||||
|
run 'sudo update-locale' from command-line.")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
new_locale = default_locale
|
||||||
|
|
||||||
|
logger.info("New locale set to: %s", \
|
||||||
|
locale.setlocale(locale.LC_ALL, new_locale))
|
||||||
|
|
||||||
|
reload(sys)
|
||||||
|
sys.setdefaultencoding("UTF-8")
|
||||||
|
current_locale_encoding = locale.getlocale()[1].lower()
|
||||||
|
logger.debug("sys default encoding %s", sys.getdefaultencoding())
|
||||||
|
logger.debug("After %s", locale.nl_langinfo(locale.CODESET))
|
||||||
|
|
||||||
|
if current_locale_encoding not in ['utf-8', 'utf8']:
|
||||||
|
logger.error("Need a UTF-8 locale. Currently '%s'. Exiting..." % \
|
||||||
|
current_locale_encoding)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
configure_locale()
|
||||||
|
|
||||||
|
# loading config file
|
||||||
|
try:
|
||||||
|
config = ConfigObj('/etc/airtime/pypo.cfg')
|
||||||
|
except Exception, e:
|
||||||
|
logger.error('Error loading config file: %s', e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
class Global:
|
||||||
|
def __init__(self, api_client):
|
||||||
|
self.api_client = api_client
|
||||||
|
|
||||||
|
def selfcheck(self):
|
||||||
|
return self.api_client.is_server_compatible()
|
||||||
|
|
||||||
|
def test_api(self):
|
||||||
|
self.api_client.test()
|
||||||
|
|
||||||
|
def keyboardInterruptHandler(signum, frame):
|
||||||
|
logger = logging.getLogger()
|
||||||
|
logger.info('\nKeyboard Interrupt\n')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def liquidsoap_get_info(telnet_lock, host, port, logger):
|
||||||
|
logger.debug("Checking to see if Liquidsoap is running")
|
||||||
|
try:
|
||||||
|
telnet_lock.acquire()
|
||||||
|
tn = telnetlib.Telnet(host, port)
|
||||||
|
msg = "version\n"
|
||||||
|
tn.write(msg)
|
||||||
|
tn.write("exit\n")
|
||||||
|
response = tn.read_all()
|
||||||
|
except Exception, e:
|
||||||
|
logger.error(str(e))
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
telnet_lock.release()
|
||||||
|
|
||||||
|
return get_liquidsoap_version(response)
|
||||||
|
|
||||||
|
def get_liquidsoap_version(version_string):
|
||||||
|
m = re.match(r"Liquidsoap (\d+.\d+.\d+)", version_string)
|
||||||
|
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
if m:
|
||||||
|
current_version = m.group(1)
|
||||||
|
return pure.version_cmp(current_version, LIQUIDSOAP_MIN_VERSION) >= 0
|
||||||
|
return False
|
||||||
|
|
||||||
|
def liquidsoap_startup_test():
|
||||||
|
|
||||||
|
liquidsoap_version_string = \
|
||||||
|
liquidsoap_get_info(telnet_lock, ls_host, ls_port, logger)
|
||||||
|
while not liquidsoap_version_string:
|
||||||
|
logger.warning("Liquidsoap doesn't appear to be running!, " + \
|
||||||
|
"Sleeping and trying again")
|
||||||
|
time.sleep(1)
|
||||||
|
liquidsoap_version_string = \
|
||||||
|
liquidsoap_get_info(telnet_lock, ls_host, ls_port, logger)
|
||||||
|
|
||||||
|
while pure.version_cmp(liquidsoap_version_string, LIQUIDSOAP_MIN_VERSION) < 0:
|
||||||
|
logger.warning("Liquidsoap is running but in incorrect version! " + \
|
||||||
|
"Make sure you have at least Liquidsoap %s installed" % LIQUIDSOAP_MIN_VERSION)
|
||||||
|
time.sleep(1)
|
||||||
|
liquidsoap_version_string = \
|
||||||
|
liquidsoap_get_info(telnet_lock, ls_host, ls_port, logger)
|
||||||
|
|
||||||
|
logger.info("Liquidsoap version string found %s" % liquidsoap_version_string)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logger.info('###########################################')
|
||||||
|
logger.info('# *** pypo *** #')
|
||||||
|
logger.info('# Liquidsoap Scheduled Playout System #')
|
||||||
|
logger.info('###########################################')
|
||||||
|
|
||||||
|
#Although all of our calculations are in UTC, it is useful to know what timezone
|
||||||
|
#the local machine is, so that we have a reference for what time the actual
|
||||||
|
#log entries were made
|
||||||
|
logger.info("Timezone: %s" % str(time.tzname))
|
||||||
|
logger.info("UTC time: %s" % str(datetime.utcnow()))
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, keyboardInterruptHandler)
|
||||||
|
|
||||||
|
api_client = api_client.AirtimeApiClient()
|
||||||
|
g = Global(api_client)
|
||||||
|
|
||||||
|
while not g.selfcheck():
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
success = False
|
||||||
|
while not success:
|
||||||
|
try:
|
||||||
|
api_client.register_component('pypo')
|
||||||
|
success = True
|
||||||
|
except Exception, e:
|
||||||
|
logger.error(str(e))
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
telnet_lock = Lock()
|
||||||
|
|
||||||
|
ls_host = config['ls_host']
|
||||||
|
ls_port = config['ls_port']
|
||||||
|
|
||||||
|
liquidsoap_startup_test()
|
||||||
|
|
||||||
|
if options.test:
|
||||||
|
g.test_api()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
ReplayGainUpdater.start_reply_gain(api_client)
|
||||||
|
SilanAnalyzer.start_silan(api_client, logger)
|
||||||
|
|
||||||
|
pypoFetch_q = Queue()
|
||||||
|
recorder_q = Queue()
|
||||||
|
pypoPush_q = Queue()
|
||||||
|
|
||||||
|
pypo_liquidsoap = PypoLiquidsoap(logger, telnet_lock,\
|
||||||
|
ls_host, ls_port)
|
||||||
|
|
||||||
|
"""
|
||||||
|
This queue is shared between pypo-fetch and pypo-file, where pypo-file
|
||||||
|
is the consumer. Pypo-fetch will send every schedule it gets to pypo-file
|
||||||
|
and pypo will parse this schedule to determine which file has the highest
|
||||||
|
priority, and retrieve it.
|
||||||
|
"""
|
||||||
|
media_q = Queue()
|
||||||
|
|
||||||
|
pmh = PypoMessageHandler(pypoFetch_q, recorder_q, config)
|
||||||
|
pmh.daemon = True
|
||||||
|
pmh.start()
|
||||||
|
|
||||||
|
pfile = PypoFile(media_q, config)
|
||||||
|
pfile.daemon = True
|
||||||
|
pfile.start()
|
||||||
|
|
||||||
|
pf = PypoFetch(pypoFetch_q, pypoPush_q, media_q, telnet_lock, pypo_liquidsoap, config)
|
||||||
|
pf.daemon = True
|
||||||
|
pf.start()
|
||||||
|
|
||||||
|
pp = PypoPush(pypoPush_q, telnet_lock, pypo_liquidsoap, config)
|
||||||
|
pp.daemon = True
|
||||||
|
pp.start()
|
||||||
|
|
||||||
|
recorder = Recorder(recorder_q)
|
||||||
|
recorder.daemon = True
|
||||||
|
recorder.start()
|
||||||
|
|
||||||
|
stat = ListenerStat()
|
||||||
|
stat.daemon = True
|
||||||
|
stat.start()
|
||||||
|
|
||||||
|
pf.join()
|
||||||
|
|
||||||
|
logger.info("System exit")
|
|
@ -73,12 +73,10 @@ class TelnetLiquidsoap:
|
||||||
|
|
||||||
def queue_push(self, queue_id, media_item):
|
def queue_push(self, queue_id, media_item):
|
||||||
with self.telnet_lock:
|
with self.telnet_lock:
|
||||||
|
|
||||||
if not self.__is_empty(queue_id):
|
if not self.__is_empty(queue_id):
|
||||||
raise QueueNotEmptyException()
|
raise QueueNotEmptyException()
|
||||||
|
|
||||||
tn = self.__connect()
|
tn = self.__connect()
|
||||||
|
|
||||||
annotation = create_liquidsoap_annotation(media_item)
|
annotation = create_liquidsoap_annotation(media_item)
|
||||||
msg = '%s.push %s\n' % (queue_id, annotation.encode('utf-8'))
|
msg = '%s.push %s\n' % (queue_id, annotation.encode('utf-8'))
|
||||||
self.logger.debug(msg)
|
self.logger.debug(msg)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue