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:
Martin Konecny 2013-05-22 12:07:08 -04:00
commit b0eda6e915
60 changed files with 38938 additions and 38579 deletions

30
CREDITS
View file

@ -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)

View file

@ -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!"),

View file

@ -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']);

View file

@ -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();

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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');

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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;
@ -2710,4 +2713,4 @@ fieldset .btn-toolbar {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

View file

@ -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;

View file

@ -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,15 +447,15 @@ 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;
var $el = $(selector);
if (bVisible) { if (bVisible) {
$(selector).show(); $el.show();
} else { } else {
$(selector).hide(); $el.hide();
} }
} }
var currentColOrder = new Array();
oTable = $libTable.dataTable( { oTable = $libTable.dataTable( {
// put hidden columns at the top to insure they can never be visible // put hidden columns at the top to insure they can never be visible
@ -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": {

View file

@ -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;

View file

@ -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'>",

View file

@ -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>");
} }
}); });

View file

@ -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"

View file

@ -30,8 +30,4 @@ The new _fnDomBaseButton looks like this:
return nButton; return nButton;
}, },
--------------------------------------------------------------------------------
* Line 96 has changed
- "buttonText": "Show / hide columns",
+ "buttonText": $.i18n._("Show / hide columns"),

View file

File diff suppressed because it is too large Load diff

2
debian/watch vendored
View file

@ -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

View file

@ -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');

View file

@ -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.

View file

@ -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
View 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")

View file

@ -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)