Merge branch 'devel' of dev.sourcefabric.org:airtime

This commit is contained in:
martin 2011-05-16 11:45:23 -04:00
commit b87e661f96
34 changed files with 1076 additions and 270 deletions

View File

@ -71,8 +71,8 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
$view->headScript()->appendFile($baseUrl.'/js/qtip/jquery.qtip-1.0.0.min.js','text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/qtip/jquery.qtip-1.0.0.min.js','text/javascript');
//scripts for now playing bar //scripts for now playing bar
$view->headScript()->appendFile($baseUrl.'/js/playlist/helperfunctions.js','text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/helperfunctions.js','text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/playlist/playlist.js','text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/playlist.js','text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/airtime/common/common.js','text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/airtime/common/common.js','text/javascript');
} }

View File

@ -8,9 +8,10 @@ class ApiController extends Zend_Controller_Action
/* Initialize action controller here */ /* Initialize action controller here */
$context = $this->_helper->getHelper('contextSwitch'); $context = $this->_helper->getHelper('contextSwitch');
$context->addActionContext('version', 'json') $context->addActionContext('version', 'json')
->addActionContext('recorded-shows', 'json') ->addActionContext('recorded-shows', 'json')
->addActionContext('upload-recorded', 'json') ->addActionContext('upload-recorded', 'json')
->initContext(); ->addActionContext('reload-metadata', 'json')
->initContext();
} }
public function indexAction() public function indexAction()
@ -113,8 +114,6 @@ class ApiController extends Zend_Controller_Action
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
$result = Schedule::GetPlayOrderRange(0, 1);
$date = new DateHelper; $date = new DateHelper;
$timeNow = $date->getTimestamp(); $timeNow = $date->getTimestamp();
$result = array("env"=>APPLICATION_ENV, $result = array("env"=>APPLICATION_ENV,
@ -318,5 +317,35 @@ class ApiController extends Zend_Controller_Action
$this->view->id = $file->getId(); $this->view->id = $file->getId();
} }
public function reloadMetadataAction() {
global $CC_CONFIG;
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$md = $this->_getParam('md');
$file = StoredFile::Recall(null, $md['gunid']);
if (PEAR::isError($file) || is_null($file)) {
$this->view->response = "File not in Airtime's Database";
return;
}
$res = $file->replaceDbMetadata($md);
if (PEAR::isError($res)) {
$this->view->response = "Metadata Change Failed";
}
else {
$this->view->response = "Success!";
}
}
} }

View File

@ -0,0 +1,22 @@
<?php
class DashboardController extends Zend_Controller_Action
{
public function init()
{
}
public function indexAction()
{
// action body
}
public function helpAction()
{
// action body
}
}

View File

@ -168,6 +168,10 @@ class LibraryController extends Zend_Controller_Action
$formdata = $form->getValues(); $formdata = $form->getValues();
$file->replaceDbMetadata($formdata); $file->replaceDbMetadata($formdata);
$data = $formdata;
$data['filepath'] = $file->getRealFilePath();
RabbitMq::SendFileMetaData($data);
$this->_helper->redirector('index'); $this->_helper->redirector('index');
} }
} }

View File

@ -2,6 +2,24 @@
class Application_Form_EditAudioMD extends Zend_Form class Application_Form_EditAudioMD extends Zend_Form
{ {
/*
"title": "track_title",\
"artist": "artist_name",\
"album": "album_title",\
"genre": "genre",\
"mood": "mood",\
"tracknumber": "track_number",\
"bpm": "bpm",\
"organization": "label",\
"composer": "composer",\
"encodedby": "encoded_by",\
"conductor": "conductor",\
"date": "year",\
"website": "info_url",\
"isrc": "isrc_number",\
"copyright": "copyright",\
*/
public function init() public function init()
{ {
@ -37,6 +55,13 @@ class Application_Form_EditAudioMD extends Zend_Form
'filters' => array('StringTrim') 'filters' => array('StringTrim')
)); ));
// Add mood field
$this->addElement('text', 'track_number', array(
'label' => 'Track:',
'class' => 'input_text',
'filters' => array('StringTrim')
));
// Add genre field // Add genre field
$this->addElement('text', 'genre', array( $this->addElement('text', 'genre', array(
'label' => 'Genre:', 'label' => 'Genre:',
@ -77,9 +102,30 @@ class Application_Form_EditAudioMD extends Zend_Form
'filters' => array('StringTrim') 'filters' => array('StringTrim')
)); ));
// Add language field // Add mood field
$this->addElement('text', 'language', array( $this->addElement('text', 'bpm', array(
'label' => 'Language:', 'label' => 'BPM:',
'class' => 'input_text',
'filters' => array('StringTrim')
));
// Add mood field
$this->addElement('text', 'copyright', array(
'label' => 'Copyright:',
'class' => 'input_text',
'filters' => array('StringTrim')
));
// Add mood field
$this->addElement('text', 'isrc_number', array(
'label' => 'ISRC Number:',
'class' => 'input_text',
'filters' => array('StringTrim')
));
// Add mood field
$this->addElement('text', 'info_url', array(
'label' => 'Website:',
'class' => 'input_text', 'class' => 'input_text',
'filters' => array('StringTrim') 'filters' => array('StringTrim')
)); ));

View File

@ -0,0 +1,115 @@
<?php
class Application_Model_Dashboard
{
public static function GetPreviousItem($p_timeNow){
//get previous show and previous item in the schedule table.
//Compare the two and if the last show was recorded and started
//after the last item in the schedule table, then return the show's
//name. Else return the last item from the schedule.
$showInstance = ShowInstance::GetLastShowInstance($p_timeNow);
$row = Schedule::GetLastScheduleItem($p_timeNow);
if (is_null($showInstance)){
if (count($row) == 0){
return null;
} else {
//should never reach here. Doesnt make sense to have
//a schedule item not within a show_instance.
}
} else {
if (count($row) == 0){
//last item is a show instance
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd());
} else {
//return the one that started later.
if ($row[0]["starts"] >= $showInstance->getShowStart()){
return array("name"=>$row[0]["artist_name"]." - ".$row[0]["track_title"],
"starts"=>$row[0]["starts"],
"ends"=>$row[0]["ends"]);
} else {
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd());
}
}
}
}
public static function GetCurrentItem($p_timeNow){
//get previous show and previous item in the schedule table.
//Compare the two and if the last show was recorded and started
//after the last item in the schedule table, then return the show's
//name. Else return the last item from the schedule.
$showInstance = ShowInstance::GetCurrentShowInstance($p_timeNow);
$row = Schedule::GetCurrentScheduleItem($p_timeNow);
if (is_null($showInstance)){
if (count($row) == 0){
return null;
} else {
//should never reach here. Doesnt make sense to have
//a schedule item not within a show_instance.
}
} else {
if (count($row) == 0){
//last item is a show instance
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd(),
"media_item_played"=>false, //TODO
"record"=>$showInstance->isRecorded()); //TODO
} else {
return array("name"=>$row[0]["artist_name"]." - ".$row[0]["track_title"],
"starts"=>$row[0]["starts"],
"ends"=>$row[0]["ends"],
"media_item_played"=>$row[0]["media_item_played"],
"record"=>0);
}
}
}
public static function GetNextItem($p_timeNow){
//get previous show and previous item in the schedule table.
//Compare the two and if the last show was recorded and started
//after the last item in the schedule table, then return the show's
//name. Else return the last item from the schedule.
$showInstance = ShowInstance::GetNextShowInstance($p_timeNow);
$row = Schedule::GetNextScheduleItem($p_timeNow);
if (is_null($showInstance)){
if (count($row) == 0){
return null;
} else {
//should never reach here. Doesnt make sense to have
//a schedule item not within a show_instance.
}
} else {
if (count($row) == 0){
//last item is a show instance
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd());
} else {
//return the one that starts sooner.
if ($row[0]["starts"] <= $showInstance->getShowStart()){
return array("name"=>$row[0]["artist_name"]." - ".$row[0]["track_title"],
"starts"=>$row[0]["starts"],
"ends"=>$row[0]["ends"]);
} else {
return array("name"=>$showInstance->getName(),
"starts"=>$showInstance->getShowStart(),
"ends"=>$showInstance->getShowEnd());
}
}
}
}
}

View File

@ -40,5 +40,27 @@ class RabbitMq
} }
} }
public static function SendFileMetaData($md)
{
global $CC_CONFIG;
$conn = new AMQPConnection($CC_CONFIG["rabbitmq"]["host"],
$CC_CONFIG["rabbitmq"]["port"],
$CC_CONFIG["rabbitmq"]["user"],
$CC_CONFIG["rabbitmq"]["password"]);
$channel = $conn->channel();
$channel->access_request($CC_CONFIG["rabbitmq"]["vhost"], false, false, true, true);
$EXCHANGE = 'airtime-media-monitor';
$channel->exchange_declare($EXCHANGE, 'direct', false, true);
$data = json_encode($md);
$msg = new AMQPMessage($data, array('content_type' => 'text/plain'));
$channel->basic_publish($msg, $EXCHANGE);
$channel->close();
$conn->close();
}
} }

View File

@ -365,9 +365,12 @@ class Schedule {
$timeNow = $date->getTimestamp(); $timeNow = $date->getTimestamp();
return array("env"=>APPLICATION_ENV, return array("env"=>APPLICATION_ENV,
"schedulerTime"=>gmdate("Y-m-d H:i:s"), "schedulerTime"=>gmdate("Y-m-d H:i:s"),
"previous"=>Schedule::GetScheduledItemData($timeNow, -1, $prev, "24 hours"), //"previous"=>Schedule::GetScheduledItemData($timeNow, -1, $prev, "24 hours"),
"current"=>Schedule::GetScheduledItemData($timeNow, 0), //"current"=>Schedule::GetScheduledItemData($timeNow, 0),
"next"=>Schedule::GetScheduledItemData($timeNow, 1, $next, "48 hours"), //"next"=>Schedule::GetScheduledItemData($timeNow, 1, $next, "48 hours"),
"previous"=>Application_Model_Dashboard::GetPreviousItem($timeNow),
"current"=>Application_Model_Dashboard::GetCurrentItem($timeNow),
"next"=>Application_Model_Dashboard::GetNextItem($timeNow),
"currentShow"=>Show_DAL::GetCurrentShow($timeNow), "currentShow"=>Show_DAL::GetCurrentShow($timeNow),
"nextShow"=>Show_DAL::GetNextShows($timeNow, 1), "nextShow"=>Show_DAL::GetNextShows($timeNow, 1),
"timezone"=> date("T"), "timezone"=> date("T"),
@ -375,6 +378,52 @@ class Schedule {
"apiKey"=>$CC_CONFIG['apiKey'][0]); "apiKey"=>$CC_CONFIG['apiKey'][0]);
} }
public static function GetLastScheduleItem($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT *"
." FROM $CC_CONFIG[scheduleTable] st"
." LEFT JOIN $CC_CONFIG[filesTable] ft"
." ON st.file_id = ft.id"
." WHERE st.ends < TIMESTAMP '$p_timeNow'"
." ORDER BY st.ends DESC"
." LIMIT 1";
$row = $CC_DBC->GetAll($sql);
return $row;
}
public static function GetCurrentScheduleItem($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT *"
." FROM $CC_CONFIG[scheduleTable] st"
." LEFT JOIN $CC_CONFIG[filesTable] ft"
." ON st.file_id = ft.id"
." WHERE st.starts <= TIMESTAMP '$p_timeNow'"
." AND st.ends > TIMESTAMP '$p_timeNow'"
." LIMIT 1";
$row = $CC_DBC->GetAll($sql);
return $row;
}
public static function GetNextScheduleItem($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT *"
." FROM $CC_CONFIG[scheduleTable] st"
." LEFT JOIN $CC_CONFIG[filesTable] ft"
." ON st.file_id = ft.id"
." WHERE st.starts > TIMESTAMP '$p_timeNow'"
." ORDER BY st.starts"
." LIMIT 1";
$row = $CC_DBC->GetAll($sql);
return $row;
}
/** /**
* Builds an SQL Query for accessing scheduled item information from * Builds an SQL Query for accessing scheduled item information from
* the database. * the database.

View File

@ -1718,6 +1718,57 @@ class ShowInstance {
return ($diff < 0) ? 0 : $diff; return ($diff < 0) ? 0 : $diff;
} }
public static function GetLastShowInstance($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT si.id"
." FROM $CC_CONFIG[showInstances] si"
." WHERE si.ends < TIMESTAMP '$p_timeNow'"
." ORDER BY si.ends DESC"
." LIMIT 1";
$id = $CC_DBC->GetOne($sql);
if (is_null($id)){
return null;
} else {
return new ShowInstance($id);
}
}
public static function GetCurrentShowInstance($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT si.id"
." FROM $CC_CONFIG[showInstances] si"
." WHERE si.starts <= TIMESTAMP '$p_timeNow'"
." AND si.ends > TIMESTAMP '$p_timeNow'"
." LIMIT 1";
$id = $CC_DBC->GetOne($sql);
if (is_null($id)){
return null;
} else {
return new ShowInstance($id);
}
}
public static function GetNextShowInstance($p_timeNow){
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT si.id"
." FROM $CC_CONFIG[showInstances] si"
." WHERE si.starts > TIMESTAMP '$p_timeNow'"
." ORDER BY si.starts"
." LIMIT 1";
$id = $CC_DBC->GetOne($sql);
if (is_null($id)){
return null;
} else {
return new ShowInstance($id);
}
}
} }
/* Show Data Access Layer */ /* Show Data Access Layer */

View File

@ -543,16 +543,25 @@ class StoredFile {
public function replaceDbMetadata($p_values) public function replaceDbMetadata($p_values)
{ {
global $CC_CONFIG, $CC_DBC; global $CC_CONFIG, $CC_DBC;
$data = array();
foreach ($p_values as $category => $value) { foreach ($p_values as $category => $value) {
$escapedValue = pg_escape_string($value); $escapedValue = pg_escape_string($value);
$columnName = $category; $columnName = $category;
if (!is_null($columnName)) { if (!is_null($columnName)) {
$sql = "UPDATE ".$CC_CONFIG["filesTable"] $data[] = "$columnName='$escapedValue'";
." SET $columnName='$escapedValue'"
." WHERE gunid = '".$this->gunid."'";
$CC_DBC->query($sql);
} }
} }
$data = join(",", $data);
$sql = "UPDATE ".$CC_CONFIG["filesTable"]
." SET $data"
." WHERE gunid = '".$this->gunid."'";
$res = $CC_DBC->query($sql);
if (PEAR::isError($res)) {
$CC_DBC->query("ROLLBACK");
return $res;
}
} }
public function clearMetadata() public function clearMetadata()

View File

@ -1,9 +1,9 @@
var estimatedSchedulePosixTime = null; var estimatedSchedulePosixTime = null;
var localRemoteTimeOffset = null; var localRemoteTimeOffset = null;
var previousSongs = new Array(); var previousSong = null;
var currentSong = new Array(); var currentSong = null;
var nextSongs = new Array(); var nextSong = null;
var currentShow = new Array(); var currentShow = new Array();
var nextShow = new Array(); var nextShow = new Array();
@ -25,21 +25,8 @@ var nextShowPrepare = true;
var apiKey = ""; var apiKey = "";
function getTrackInfo(song){
var str = "";
if (song.track_title != null)
str += song.track_title;
if (song.artist_name != null)
str += " - " + song.artist_name;
str += ","
return str;
}
function secondsTimer(){ function secondsTimer(){
if (localRemoteTimeOffset != null){ if (localRemoteTimeOffset !== null){
var date = new Date(); var date = new Date();
estimatedSchedulePosixTime = date.getTime() - localRemoteTimeOffset; estimatedSchedulePosixTime = date.getTime() - localRemoteTimeOffset;
updateProgressBarValue(); updateProgressBarValue();
@ -50,7 +37,8 @@ function secondsTimer(){
function newSongStart(){ function newSongStart(){
nextSongPrepare = true; nextSongPrepare = true;
currentSong[0] = nextSongs.shift(); currentSong = nextSong;
nextSong = null;
if (typeof notifySongStart == "function") if (typeof notifySongStart == "function")
notifySongStart(); notifySongStart();
@ -80,13 +68,13 @@ function updateProgressBarValue(){
$('#progress-show').attr("style", "width:"+showPercentDone+"%"); $('#progress-show').attr("style", "width:"+showPercentDone+"%");
var songPercentDone = 0; var songPercentDone = 0;
if (currentSong.length > 0){ if (currentSong !== null){
songPercentDone = (estimatedSchedulePosixTime - currentSong[0].songStartPosixTime)/currentSong[0].songLengthMs*100; songPercentDone = (estimatedSchedulePosixTime - currentSong.songStartPosixTime)/currentSong.songLengthMs*100;
if (songPercentDone < 0 || songPercentDone > 100){ if (songPercentDone < 0 || songPercentDone > 100){
songPercentDone = 0; songPercentDone = 0;
currentSong = new Array(); currentSong = null;
} else { } else {
if (currentSong[0].media_item_played == "t" && currentShow.length > 0) if (currentSong.media_item_played == "t" && currentShow.length > 0)
$('#on-air-info').attr("class", "on-air-info on"); $('#on-air-info').attr("class", "on-air-info on");
else else
$('#on-air-info').attr("class", "on-air-info off"); $('#on-air-info').attr("class", "on-air-info off");
@ -99,8 +87,8 @@ function updateProgressBarValue(){
$('#progress-bar').attr("style", "width:"+songPercentDone+"%"); $('#progress-bar').attr("style", "width:"+songPercentDone+"%");
//calculate how much time left to next song if there is any //calculate how much time left to next song if there is any
if (nextSongs.length > 0 && nextSongPrepare){ if (nextSong !== null && nextSongPrepare){
var diff = nextSongs[0].songStartPosixTime - estimatedSchedulePosixTime; var diff = nextSong.songStartPosixTime - estimatedSchedulePosixTime;
if (diff < serverUpdateInterval){ if (diff < serverUpdateInterval){
//sometimes the diff is negative (-100ms for example). Still looking //sometimes the diff is negative (-100ms for example). Still looking
@ -133,20 +121,26 @@ function updatePlaybar(){
$('#current').html("Current: <span style='color:red; font-weight:bold'>Nothing Scheduled</span>"); $('#current').html("Current: <span style='color:red; font-weight:bold'>Nothing Scheduled</span>");
$('#next').empty(); $('#next').empty();
$('#next-length').empty(); $('#next-length').empty();
if (previousSongs.length > 0){ if (previousSong !== null){
$('#previous').text(getTrackInfo(previousSongs[previousSongs.length-1])); $('#previous').text(previousSong.name+",");
$('#prev-length').text(convertToHHMMSSmm(previousSongs[previousSongs.length-1].songLengthMs)); $('#prev-length').text(convertToHHMMSSmm(previousSong.songLengthMs));
} }
if (currentSong.length > 0){ if (currentSong !== null){
$('#current').text(getTrackInfo(currentSong[0])); if (currentSong.record == "1")
} else if (currentShow.length > 0){ $('#current').html("<span style='color:red; font-weight:bold'>Recording: </span>"+currentSong.name+",");
else
$('#current').text(currentSong.name+",");
}
/*
else if (currentShow.length > 0){
if (currentShow[0].record == "1"){ if (currentShow[0].record == "1"){
$('#current').html("Current: <span style='color:red; font-weight:bold'>Recording</span>"); $('#current').html("Current: <span style='color:red; font-weight:bold'>Recording</span>");
} }
} }
if (nextSongs.length > 0){ * */
$('#next').text(getTrackInfo(nextSongs[0])); if (nextSong !== null){
$('#next-length').text(convertToHHMMSSmm(nextSongs[0].songLengthMs)); $('#next').text(nextSong.name+",");
$('#next-length').text(convertToHHMMSSmm(nextSong.songLengthMs));
} }
$('#start').empty(); $('#start').empty();
@ -154,18 +148,18 @@ function updatePlaybar(){
$('#time-elapsed').empty(); $('#time-elapsed').empty();
$('#time-remaining').empty(); $('#time-remaining').empty();
$('#song-length').empty(); $('#song-length').empty();
for (var i=0; i<currentSong.length; i++){ if (currentSong !== null){
$('#start').text(currentSong[i].starts.substring(currentSong[i].starts.indexOf(" ")+1)); $('#start').text(currentSong.starts.substring(currentSong.starts.indexOf(" ")+1));
$('#end').text(currentSong[i].ends.substring(currentSong[i].starts.indexOf(" ")+1)); $('#end').text(currentSong.ends.substring(currentSong.starts.indexOf(" ")+1));
/* Get rid of the millisecond accuracy so that the second counters for both /* Get rid of the millisecond accuracy so that the second counters for both
* show and song change at the same time. */ * show and song change at the same time. */
var songStartRoughly = parseInt(Math.round(currentSong[i].songStartPosixTime/1000))*1000; var songStartRoughly = parseInt(Math.round(currentSong.songStartPosixTime/1000))*1000;
var songEndRoughly = parseInt(Math.round(currentSong[i].songEndPosixTime/1000))*1000; var songEndRoughly = parseInt(Math.round(currentSong.songEndPosixTime/1000))*1000;
$('#time-elapsed').text(convertToHHMMSS(estimatedSchedulePosixTime - songStartRoughly)); $('#time-elapsed').text(convertToHHMMSS(estimatedSchedulePosixTime - songStartRoughly));
$('#time-remaining').text(convertToHHMMSS(songEndRoughly - estimatedSchedulePosixTime)); $('#time-remaining').text(convertToHHMMSS(songEndRoughly - estimatedSchedulePosixTime));
$('#song-length').text(convertToHHMMSSmm(currentSong[i].songLengthMs)); $('#song-length').text(convertToHHMMSSmm(currentSong.songLengthMs));
} }
/* Column 1 update */ /* Column 1 update */
$('#playlist').text("Current Show:"); $('#playlist').text("Current Show:");
@ -187,11 +181,9 @@ function updatePlaybar(){
} }
function calcAdditionalData(currentItem){ function calcAdditionalData(currentItem){
for (var i=0; i<currentItem.length; i++){ currentItem.songStartPosixTime = convertDateToPosixTime(currentItem.starts);
currentItem[i].songStartPosixTime = convertDateToPosixTime(currentItem[i].starts); currentItem.songEndPosixTime = convertDateToPosixTime(currentItem.ends);
currentItem[i].songEndPosixTime = convertDateToPosixTime(currentItem[i].ends); currentItem.songLengthMs = currentItem.songEndPosixTime - currentItem.songStartPosixTime;
currentItem[i].songLengthMs = currentItem[i].songEndPosixTime - currentItem[i].songStartPosixTime;
}
} }
function calcAdditionalShowData(show){ function calcAdditionalShowData(show){
@ -208,17 +200,18 @@ function parseItems(obj){
$('#time-zone').text(obj.timezone); $('#time-zone').text(obj.timezone);
previousSongs = obj.previous; previousSong = obj.previous;
nextSongs = obj.next; currentSong = obj.current;
nextSong = obj.next;
calcAdditionalData(previousSongs); if (previousSong !== null)
calcAdditionalData(nextSongs); calcAdditionalData(previousSong);
if (currentSong !== null)
calcAdditionalData(currentSong);
if (nextSong !== null)
calcAdditionalData(nextSong);
currentShow = obj.currentShow; currentShow = obj.currentShow;
if(currentShow.length > 0){
currentSong = obj.current;
calcAdditionalData(currentSong);
}
nextShow = obj.nextShow; nextShow = obj.nextShow;
calcAdditionalShowData(obj.currentShow); calcAdditionalShowData(obj.currentShow);
@ -238,17 +231,10 @@ function getScheduleFromServer(){
setTimeout(getScheduleFromServer, serverUpdateInterval); setTimeout(getScheduleFromServer, serverUpdateInterval);
} }
function setupQtip(){
function init() {
//begin producer "thread"
getScheduleFromServer();
//begin consumer "thread"
secondsTimer();
var qtipElem = $('#about-link'); var qtipElem = $('#about-link');
if (qtipElem.length > 0) if (qtipElem.length > 0){
qtipElem.qtip({ qtipElem.qtip({
content: $('#about-txt').html(), content: $('#about-txt').html(),
show: 'mouseover', show: 'mouseover',
@ -267,6 +253,17 @@ function init() {
name: 'light' // Use the default light style name: 'light' // Use the default light style
} }
}); });
}
}
function init() {
//begin producer "thread"
getScheduleFromServer();
//begin consumer "thread"
secondsTimer();
setupQtip();
} }
$(document).ready(function() { $(document).ready(function() {

View File

@ -209,11 +209,41 @@ function getId() {
function buildContentDialog(json){ function buildContentDialog(json){
var dialog = $(json.dialog); var dialog = $(json.dialog);
var viewportwidth;
var viewportheight;
// the more standards compliant browsers (mozilla/netscape/opera/IE7) use
// window.innerWidth and window.innerHeight
if (typeof window.innerWidth != 'undefined') {
viewportwidth = window.innerWidth, viewportheight = window.innerHeight;
}
// IE6 in standards compliant mode (i.e. with a valid doctype as the first
// line in the document)
else if (typeof document.documentElement != 'undefined'
&& typeof document.documentElement.clientWidth != 'undefined'
&& document.documentElement.clientWidth != 0) {
viewportwidth = document.documentElement.clientWidth;
viewportheight = document.documentElement.clientHeight;
}
// older versions of IE
else {
viewportwidth = document.getElementsByTagName('body')[0].clientWidth;
viewportheight = document.getElementsByTagName('body')[0].clientHeight;
}
var height = viewportheight * 2/3;
var width = viewportwidth * 4/5;
dialog.dialog({ dialog.dialog({
autoOpen: false, autoOpen: false,
title: 'Show Contents', title: 'Show Contents',
width: 1100, width: width,
height: 500, height: height,
modal: true, modal: true,
close: closeDialog, close: closeDialog,
buttons: {"Ok": function() { buttons: {"Ok": function() {

View File

@ -1,69 +0,0 @@
function createDataGrid(datagridData){
var columnHeaders = [
{ "sTitle": "name" },
{ "sTitle": "date" },
{ "sTitle": "start time" },
{ "sTitle": "end time" }
];
$('#demo').html( '<table cellpadding="0" cellspacing="0" border="0" width="100%" id="nowplayingtable"></table>' );
$('#nowplayingtable').dataTable( {
"bSort" : false,
"bJQueryUI": true,
"bFilter": false,
"bInfo": false,
"bLengthChange": false,
"aaData": datagridData.rows,
"aoColumns": columnHeaders
} );
var options1 = [
{title:"Menu Item 1 - Go TO www.google.com", action:{type:"gourl",url:"http://www.google.com/"}},
{title:"Menu Item 2 - do <b style='color:red;'>nothing</b>"},
{title:"Menu Item 3 - submenu", type:"sub", src:[{title:"Submenu 1"},{title:"Submenu 2"},{title:"Submenu 3"}, {title:"Submenu 4 - submenu", type:"sub", src:[{title:"SubSubmenu 1"},{title:"SubSubmenu 2"}]}]},
{title:"Menu Item 4 - Js function", action:{type:"fn",callback:"(function(){ alert('THIS IS THE TEST'); })"}}
];
var userData = {};
var effects = {
show:"default", //type of show effect
orientation: "auto", //type of menu orientation - to top, to bottom, auto (to bottom, if doesn't fit on screen - to top)
xposition:"mouse", // position of menu (left side or right side of trigger element)
yposition:"mouse"
}
$('#demo').jjmenu('both', options1, userData, effects );
}
function initShowListView(){
$.ajax({ url: "/Schedule/get-show-data/format/json", dataType:"text", success:function(data){
$('#json-string').text(data);
}});
$.ajax({ url: "/Schedule/get-show-data/format/json", dataType:"json", success:function(data){
var temp = data.data;
var rows = new Array();
for (var i=0; i<temp.length; i++){
rows[i] = [temp[i].name.toString(), temp[i].first_show.toString(), temp[i].start_time.toString(), temp[i].end_time.toString()];
var datagridData = {rows:rows};
createDataGrid(datagridData);
}
}});
//setTimeout(initShowListView, 5000);
}
$(document).ready(function() {
initShowListView();
});

View File

@ -0,0 +1,23 @@
<?php
set_include_path(__DIR__.'/../airtime_mvc/library' . PATH_SEPARATOR . get_include_path());
require_once(dirname(__FILE__).'/include/AirtimeIni.php');
require_once(dirname(__FILE__).'/include/AirtimeInstall.php');
require_once(AirtimeInstall::GetAirtimeSrcDir().'/application/configs/conf.php');
//echo PHP_EOL."*** Database Installation ***".PHP_EOL;
AirtimeInstall::CreateDatabaseUser();
AirtimeInstall::CreateDatabase();
AirtimeInstall::DbConnect(true);
AirtimeInstall::InstallPostgresScriptingLanguage();
AirtimeInstall::CreateDatabaseTables();
AirtimeInstall::SetAirtimeVersion(AIRTIME_VERSION);

View File

@ -22,7 +22,8 @@ try {
array( array(
'help|h' => 'Displays usage information.', 'help|h' => 'Displays usage information.',
'overwrite|o' => 'Overwrite any existing config files.', 'overwrite|o' => 'Overwrite any existing config files.',
'preserve|p' => 'Keep any existing config files.' 'preserve|p' => 'Keep any existing config files.',
'no-db|n' => 'Turn off database install.'
) )
); );
$opts->parse(); $opts->parse();
@ -34,6 +35,10 @@ if (isset($opts->h)) {
echo $opts->getUsageMessage(); echo $opts->getUsageMessage();
exit; exit;
} }
$db_install = true;
if (isset($opts->n)){
$db_install = false;
}
$overwrite = false; $overwrite = false;
if (isset($opts->o)) { if (isset($opts->o)) {
@ -72,17 +77,21 @@ require_once(AirtimeInstall::GetAirtimeSrcDir().'/application/configs/conf.php')
echo "* Airtime Version: ".AIRTIME_VERSION.PHP_EOL; echo "* Airtime Version: ".AIRTIME_VERSION.PHP_EOL;
if ($db_install) {
//echo PHP_EOL."*** Database Installation ***".PHP_EOL; //echo PHP_EOL."*** Database Installation ***".PHP_EOL;
AirtimeInstall::CreateDatabaseUser(); /* AirtimeInstall::CreateDatabaseUser();
AirtimeInstall::CreateDatabase(); AirtimeInstall::CreateDatabase();
AirtimeInstall::DbConnect(true); AirtimeInstall::DbConnect(true);
AirtimeInstall::InstallPostgresScriptingLanguage(); AirtimeInstall::InstallPostgresScriptingLanguage();
AirtimeInstall::CreateDatabaseTables(); AirtimeInstall::CreateDatabaseTables();*/
require( 'airtime-db-install.php' );
}
AirtimeInstall::InstallStorageDirectory(); AirtimeInstall::InstallStorageDirectory();
@ -98,6 +107,9 @@ system("python ".__DIR__."/../python_apps/pypo/install/pypo-install.py");
echo PHP_EOL."*** Recorder Installation ***".PHP_EOL; echo PHP_EOL."*** Recorder Installation ***".PHP_EOL;
system("python ".__DIR__."/../python_apps/show-recorder/install/recorder-install.py"); system("python ".__DIR__."/../python_apps/show-recorder/install/recorder-install.py");
echo PHP_EOL."*** Media Monitor Installation ***".PHP_EOL;
system("python ".__DIR__."/../python_apps/pytag-fs/install/media-monitor-install.py");
AirtimeInstall::SetAirtimeVersion(AIRTIME_VERSION); AirtimeInstall::SetAirtimeVersion(AIRTIME_VERSION);
echo "******************************* Install Complete *******************************".PHP_EOL; echo "******************************* Install Complete *******************************".PHP_EOL;

View File

@ -29,7 +29,10 @@ AirtimeInstall::UninstallPhpCode();
// still be a connection to the database and you wont be able to delete it. // still be a connection to the database and you wont be able to delete it.
//------------------------------------------------------------------------ //------------------------------------------------------------------------
echo " * Dropping the database '".$CC_CONFIG['dsn']['database']."'...".PHP_EOL; echo " * Dropping the database '".$CC_CONFIG['dsn']['database']."'...".PHP_EOL;
$command = "su postgres -c \"dropdb {$CC_CONFIG['dsn']['database']}\"";
// check if DB exists
$command = "echo \"DROP DATABASE IF EXISTS ".$CC_CONFIG['dsn']['database']."\" | sudo -u postgres psql";
@exec($command, $output, $dbDeleteFailed); @exec($command, $output, $dbDeleteFailed);
//------------------------------------------------------------------------ //------------------------------------------------------------------------
@ -66,7 +69,7 @@ if ($dbDeleteFailed) {
// Delete the user // Delete the user
//------------------------------------------------------------------------ //------------------------------------------------------------------------
echo " * Deleting database user '{$CC_CONFIG['dsn']['username']}'...".PHP_EOL; echo " * Deleting database user '{$CC_CONFIG['dsn']['username']}'...".PHP_EOL;
$command = "echo \"DROP USER {$CC_CONFIG['dsn']['username']}\" | su postgres -c psql"; $command = "echo \"DROP USER IF EXISTS {$CC_CONFIG['dsn']['username']}\" | su postgres -c psql";
@exec($command, $output, $results); @exec($command, $output, $results);
if ($results == 0) { if ($results == 0) {
echo " * User '{$CC_CONFIG['dsn']['username']}' deleted.".PHP_EOL; echo " * User '{$CC_CONFIG['dsn']['username']}' deleted.".PHP_EOL;
@ -85,6 +88,10 @@ echo PHP_EOL."*** Uninstalling Show Recorder ***".PHP_EOL;
$command = "python ".__DIR__."/../python_apps/show-recorder/install/recorder-uninstall.py"; $command = "python ".__DIR__."/../python_apps/show-recorder/install/recorder-uninstall.py";
system($command); system($command);
echo PHP_EOL."*** Uninstalling Media Monitor ***".PHP_EOL;
$command = "python ".__DIR__."/../python_apps/pytag-fs/install/media-monitor-uninstall.py";
system($command);
#Disabled as this should be a manual process #Disabled as this should be a manual process
#AirtimeIni::RemoveIniFiles(); #AirtimeIni::RemoveIniFiles();

View File

@ -26,13 +26,15 @@ class AirtimeIni
const CONF_FILE_PYPO = "/etc/airtime/pypo.cfg"; const CONF_FILE_PYPO = "/etc/airtime/pypo.cfg";
const CONF_FILE_RECORDER = "/etc/airtime/recorder.cfg"; const CONF_FILE_RECORDER = "/etc/airtime/recorder.cfg";
const CONF_FILE_LIQUIDSOAP = "/etc/airtime/liquidsoap.cfg"; const CONF_FILE_LIQUIDSOAP = "/etc/airtime/liquidsoap.cfg";
const CONF_FILE_MEDIAMONITOR = "/etc/airtime/MediaMonitor.cfg";
public static function IniFilesExist() public static function IniFilesExist()
{ {
$configFiles = array(AirtimeIni::CONF_FILE_AIRTIME, $configFiles = array(AirtimeIni::CONF_FILE_AIRTIME,
AirtimeIni::CONF_FILE_PYPO, AirtimeIni::CONF_FILE_PYPO,
AirtimeIni::CONF_FILE_RECORDER, AirtimeIni::CONF_FILE_RECORDER,
AirtimeIni::CONF_FILE_LIQUIDSOAP); AirtimeIni::CONF_FILE_LIQUIDSOAP,
AirtimeIni::CONF_FILE_MEDIAMONITOR);
$exist = false; $exist = false;
foreach ($configFiles as $conf) { foreach ($configFiles as $conf) {
if (file_exists($conf)) { if (file_exists($conf)) {
@ -72,6 +74,10 @@ class AirtimeIni
echo "Could not copy liquidsoap.cfg to /etc/airtime/. Exiting."; echo "Could not copy liquidsoap.cfg to /etc/airtime/. Exiting.";
exit(1); exit(1);
} }
if (!copy(__DIR__."/../../python_apps/pytag-fs/MediaMonitor.cfg", AirtimeIni::CONF_FILE_MEDIAMONITOR)){
echo "Could not copy MediaMonitor.cfg to /etc/airtime/. Exiting.";
exit(1);
}
} }
/** /**
@ -96,6 +102,10 @@ class AirtimeIni
unlink(AirtimeIni::CONF_FILE_LIQUIDSOAP); unlink(AirtimeIni::CONF_FILE_LIQUIDSOAP);
} }
if (file_exists(AirtimeIni::CONF_FILE_MEDIAMONITOR)){
unlink(AirtimeIni::CONF_FILE_MEDIAMONITOR);
}
if (file_exists("etc/airtime")){ if (file_exists("etc/airtime")){
rmdir("/etc/airtime/"); rmdir("/etc/airtime/");
} }
@ -171,6 +181,7 @@ class AirtimeIni
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_AIRTIME, 'airtime_dir', AirtimeInstall::CONF_DIR_WWW); AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_AIRTIME, 'airtime_dir', AirtimeInstall::CONF_DIR_WWW);
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_PYPO, 'api_key', "'$api_key'"); AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_PYPO, 'api_key', "'$api_key'");
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_RECORDER, 'api_key', "'$api_key'"); AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_RECORDER, 'api_key', "'$api_key'");
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_MEDIAMONITOR, 'api_key', "'$api_key'");
AirtimeIni::UpdateIniValue(AirtimeInstall::CONF_DIR_WWW.'/build/build.properties', 'project.home', AirtimeInstall::CONF_DIR_WWW); AirtimeIni::UpdateIniValue(AirtimeInstall::CONF_DIR_WWW.'/build/build.properties', 'project.home', AirtimeInstall::CONF_DIR_WWW);
} }
} }

View File

@ -126,7 +126,7 @@ class AirtimeInstall
$username = $CC_CONFIG['dsn']['username']; $username = $CC_CONFIG['dsn']['username'];
$password = $CC_CONFIG['dsn']['password']; $password = $CC_CONFIG['dsn']['password'];
$command = "echo \"CREATE USER $username ENCRYPTED PASSWORD '$password' LOGIN CREATEDB NOCREATEUSER;\" | su postgres -c psql"; $command = "echo \"CREATE USER $username ENCRYPTED PASSWORD '$password' LOGIN CREATEDB NOCREATEUSER;\" | su postgres -c psql 2>/dev/null";
@exec($command, $output, $results); @exec($command, $output, $results);
if ($results == 0) { if ($results == 0) {
@ -150,7 +150,7 @@ class AirtimeInstall
$database = $CC_CONFIG['dsn']['database']; $database = $CC_CONFIG['dsn']['database'];
$username = $CC_CONFIG['dsn']['username']; $username = $CC_CONFIG['dsn']['username'];
$command = "echo \"CREATE DATABASE $database OWNER $username\" | su postgres -c psql"; $command = "echo \"CREATE DATABASE $database OWNER $username\" | su postgres -c psql 2>/dev/null";
@exec($command, $output, $results); @exec($command, $output, $results);
if ($results == 0) { if ($results == 0) {

View File

@ -19,6 +19,7 @@ import json
import os import os
from urlparse import urlparse from urlparse import urlparse
AIRTIME_VERSION = "1.9.0"
def api_client_factory(config): def api_client_factory(config):
if config["api_client"] == "airtime": if config["api_client"] == "airtime":
@ -30,6 +31,24 @@ def api_client_factory(config):
print print
sys.exit() sys.exit()
def recursive_urlencode(d):
def recursion(d, base=None):
pairs = []
for key, value in d.items():
if hasattr(value, 'values'):
pairs += recursion(value, key)
else:
new_pair = None
if base:
new_pair = "%s[%s]=%s" % (base, urllib.quote(unicode(key)), urllib.quote(unicode(value)))
else:
new_pair = "%s=%s" % (urllib.quote(unicode(key)), urllib.quote(unicode(value)))
pairs.append(new_pair)
return pairs
return '&'.join(recursion(d))
class ApiClientInterface: class ApiClientInterface:
# Implementation: optional # Implementation: optional
@ -98,6 +117,9 @@ class ApiClientInterface:
def upload_recorded_show(self): def upload_recorded_show(self):
pass pass
def update_media_metadata(self, md):
pass
# Put here whatever tests you want to run to make sure your API is working # Put here whatever tests you want to run to make sure your API is working
def test(self): def test(self):
pass pass
@ -121,6 +143,8 @@ class AirTimeApiClient(ApiClientInterface):
logger.debug("Trying to contact %s", url) logger.debug("Trying to contact %s", url)
url = url.replace("%%api_key%%", self.config["api_key"]) url = url.replace("%%api_key%%", self.config["api_key"])
version = -1
response = None
try: try:
response = urllib.urlopen(url) response = urllib.urlopen(url)
data = response.read() data = response.read()
@ -129,34 +153,26 @@ class AirTimeApiClient(ApiClientInterface):
version = response_json['version'] version = response_json['version']
logger.debug("Airtime Version %s detected", version) logger.debug("Airtime Version %s detected", version)
except Exception, e: except Exception, e:
try: if e[1] == 401:
if e[1] == 401: if (verbose):
if (verbose): print '#####################################'
print '#####################################' print '# YOUR API KEY SEEMS TO BE INVALID:'
print '# YOUR API KEY SEEMS TO BE INVALID:' print '# ' + self.config["api_key"]
print '# ' + self.config["api_key"] print '#####################################'
print '#####################################' return -1
return False
except Exception, e:
pass
try: if e[1] == 404:
if e[1] == 404: if (verbose):
if (verbose): print '#####################################'
print '#####################################' print '# Unable to contact the Airtime-API'
print '# Unable to contact the Airtime-API' print '# ' + url
print '# ' + url print '#####################################'
print '#####################################' return -1
return False
except Exception, e:
pass
version = 0
logger.error("Unable to detect Airtime Version - %s, Response: %s", e, response) logger.error("Unable to detect Airtime Version - %s, Response: %s", e, response)
return version return version
def test(self): def test(self):
logger = logging.getLogger() logger = logging.getLogger()
status, items = self.get_schedule('2010-01-01-00-00-00', '2011-01-01-00-00-00') status, items = self.get_schedule('2010-01-01-00-00-00', '2011-01-01-00-00-00')
@ -175,12 +191,12 @@ class AirTimeApiClient(ApiClientInterface):
def is_server_compatible(self, verbose = True): def is_server_compatible(self, verbose = True):
version = self.__get_airtime_version(verbose) version = self.__get_airtime_version(verbose)
if (version == 0 or version == False): if (version == -1):
if (verbose): if (verbose):
print 'Unable to get Airtime version number.' print 'Unable to get Airtime version number.'
print print
return False return False
elif (version[0:4] != "1.9."): elif (version[0:3] != AIRTIME_VERSION):
if (verbose): if (verbose):
print 'Airtime version: ' + str(version) print 'Airtime version: ' + str(version)
print 'pypo not compatible with this version of Airtime.' print 'pypo not compatible with this version of Airtime.'
@ -198,7 +214,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger() logger = logging.getLogger()
# Construct the URL # Construct the URL
#export_url = self.config["base_url"] + self.config["api_base"] + self.config["export_url"]
export_url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["export_url"]) export_url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["export_url"])
logger.info("Fetching schedule from %s", export_url) logger.info("Fetching schedule from %s", export_url)
@ -220,8 +235,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger() logger = logging.getLogger()
try: try:
#src = "http://%s:%s/%s/%s" % \
#(self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["get_media_url"])
src = uri + "/api_key/%%api_key%%" src = uri + "/api_key/%%api_key%%"
logger.info("try to download from %s to %s", src, dst) logger.info("try to download from %s to %s", src, dst)
src = src.replace("%%api_key%%", self.config["api_key"]) src = src.replace("%%api_key%%", self.config["api_key"])
@ -239,7 +252,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger() logger = logging.getLogger()
playlist = schedule[pkey] playlist = schedule[pkey]
schedule_id = playlist["schedule_id"] schedule_id = playlist["schedule_id"]
#url = self.config["base_url"] + self.config["api_base"] + self.config["update_item_url"]
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_item_url"]) url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_item_url"])
url = url.replace("%%schedule_id%%", str(schedule_id)) url = url.replace("%%schedule_id%%", str(schedule_id))
@ -268,7 +280,6 @@ class AirTimeApiClient(ApiClientInterface):
response = '' response = ''
try: try:
schedule_id = data schedule_id = data
#url = self.config["base_url"] + self.config["api_base"] + self.config["update_start_playing_url"]
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_start_playing_url"]) url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_start_playing_url"])
url = url.replace("%%media_id%%", str(media_id)) url = url.replace("%%media_id%%", str(media_id))
url = url.replace("%%schedule_id%%", str(schedule_id)) url = url.replace("%%schedule_id%%", str(schedule_id))
@ -299,7 +310,6 @@ class AirTimeApiClient(ApiClientInterface):
response = None response = None
try: try:
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["show_schedule_url"]) url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["show_schedule_url"])
#url = self.config["base_url"] + self.config["api_base"] + self.config["show_schedule_url"]
logger.debug(url) logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"]) url = url.replace("%%api_key%%", self.config["api_key"])
@ -319,7 +329,6 @@ class AirTimeApiClient(ApiClientInterface):
retries = int(self.config["upload_retries"]) retries = int(self.config["upload_retries"])
retries_wait = int(self.config["upload_wait"]) retries_wait = int(self.config["upload_wait"])
#url = self.config["base_url"] + self.config["api_base"] + self.config["upload_file_url"]
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["upload_file_url"]) url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["upload_file_url"])
logger.debug(url) logger.debug(url)
@ -347,6 +356,26 @@ class AirTimeApiClient(ApiClientInterface):
return response return response
def update_media_metadata(self, md):
logger = logging.getLogger()
response = None
try:
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_media_url"])
logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"])
data = recursive_urlencode(md)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req).read()
logger.info("update media %s", response)
response = json.loads(response)
except Exception, e:
logger.error("Exception: %s", e)
return response
################################################################################ ################################################################################

View File

@ -64,6 +64,21 @@ def get_current_script_dir():
#print current_script_dir[0:index] #print current_script_dir[0:index]
return current_script_dir[0:index] return current_script_dir[0:index]
def is_natty():
try:
f = open('/etc/lsb-release')
except IOError as e:
#File doesn't exist, so we're not even dealing with Ubuntu
return False
for line in f:
split = line.split("=")
split[0] = split[0].strip(" \r\n")
split[1] = split[1].strip(" \r\n")
if split[0] == "DISTRIB_CODENAME" and split[1] == "natty":
return True
return False
try: try:
# load config file # load config file
@ -94,10 +109,19 @@ try:
create_path(config["file_dir"]) create_path(config["file_dir"])
create_path(config["tmp_dir"]) create_path(config["tmp_dir"])
if platform.architecture()[0] == '64bit': architecture = platform.architecture()[0]
natty = is_natty()
if architecture == '64bit' and natty:
print "Installing 64-bit liquidsoap binary (Natty)"
shutil.copy("%s/../liquidsoap/liquidsoap-amd64-natty"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
elif architecture == '32bit' and natty:
print "Installing 32-bit liquidsoap binary (Natty)"
shutil.copy("%s/../liquidsoap/liquidsoap-i386-natty"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
elif architecture == '64bit' and not natty:
print "Installing 64-bit liquidsoap binary" print "Installing 64-bit liquidsoap binary"
shutil.copy("%s/../liquidsoap/liquidsoap-amd64"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir) shutil.copy("%s/../liquidsoap/liquidsoap-amd64"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
elif platform.architecture()[0] == '32bit': elif architecture == '32bit' and not natty:
print "Installing 32-bit liquidsoap binary" print "Installing 32-bit liquidsoap binary"
shutil.copy("%s/../liquidsoap/liquidsoap-i386"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir) shutil.copy("%s/../liquidsoap/liquidsoap-i386"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
else: else:

Binary file not shown.

Binary file not shown.

View File

@ -3,29 +3,14 @@
""" """
Python part of radio playout (pypo) Python part of radio playout (pypo)
The main functions are "fetch" (./pypo_cli.py -f) and "push" (./pypo_cli.py -p)
""" """
import time import time
#import calendar
#import traceback
from optparse import * from optparse import *
import sys import sys
import os import os
import signal import signal
#import datetime
import logging import logging
import logging.config import logging.config
#import shutil
#import urllib
#import urllib2
#import pickle
#import telnetlib
#import random
#import string
#import operator
#import inspect
from Queue import Queue from Queue import Queue
from pypopush import PypoPush from pypopush import PypoPush
@ -50,8 +35,6 @@ parser = OptionParser(usage=usage)
parser.add_option("-v", "--compat", help="Check compatibility with server API version", default=False, action="store_true", dest="check_compat") 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("-t", "--test", help="Do a test to make sure everything is working properly.", default=False, action="store_true", dest="test")
parser.add_option("-f", "--fetch-scheduler", help="Fetch the schedule from server. This is a polling process that runs forever.", default=False, action="store_true", dest="fetch_scheduler")
parser.add_option("-p", "--push-scheduler", help="Push the schedule to Liquidsoap. This is a polling process that runs forever.", default=False, action="store_true", dest="push_scheduler")
parser.add_option("-b", "--cleanup", help="Cleanup", default=False, action="store_true", dest="cleanup") 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") parser.add_option("-c", "--check", help="Check the cached schedule and exit", default=False, action="store_true", dest="check")
@ -76,8 +59,7 @@ class Global:
def selfcheck(self): def selfcheck(self):
self.api_client = api_client.api_client_factory(config) self.api_client = api_client.api_client_factory(config)
if (not self.api_client.is_server_compatible()): return self.api_client.is_server_compatible()
sys.exit()
def set_export_source(self, export_source): def set_export_source(self, export_source):
self.export_source = export_source self.export_source = export_source
@ -130,7 +112,8 @@ if __name__ == '__main__':
# initialize # initialize
g = Global() g = Global()
g.selfcheck()
while not g.selfcheck(): time.sleep(5000)
logger = logging.getLogger() logger = logging.getLogger()

View File

@ -7,11 +7,8 @@ base_port = 80
# where the binary files live # where the binary files live
bin_dir = '/usr/lib/airtime/media-monitor' bin_dir = '/usr/lib/airtime/media-monitor'
# base path to store recordered shows at
base_recorded_files = '/var/tmp/airtime/show-recorder/'
# where the logging files live # where the logging files live
log_dir = '/var/log/airtime/show-recorder' log_dir = '/var/log/airtime/media-monitor'
# Value needed to access the API # Value needed to access the API
api_key = 'AAA' api_key = 'AAA'
@ -22,8 +19,18 @@ api_base = 'api'
# URL to get the version number of the server API # URL to get the version number of the server API
version_url = 'version/api_key/%%api_key%%' version_url = 'version/api_key/%%api_key%%'
# URL to get the schedule of shows set to record # URL to tell Airtime to update file's meta data
show_schedule_url = 'recorded-shows/format/json/api_key/%%api_key%%' update_media_url = 'reload-metadata/format/json/api_key/%%api_key%%'
# URL to upload the recorded show's file to Airtime ############################################
upload_file_url = 'upload-recorded/format/json/api_key/%%api_key%%' # RabbitMQ settings #
############################################
rabbitmq_host = 'localhost'
rabbitmq_user = 'guest'
rabbitmq_password = 'guest'
############################################
# Media-Monitor preferences #
############################################
check_filesystem_events = 30 #how long to queue up events performed on the files themselves.
check_airtime_events = 30 #how long to queue metadata input from airtime.

View File

@ -1,6 +1,26 @@
#!/usr/local/bin/python
import logging
import logging.config
import json
import time
import datetime
import os import os
import sys
import hashlib
import json
from subprocess import Popen, PIPE, STDOUT
from configobj import ConfigObj
import mutagen
import pyinotify import pyinotify
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent from pyinotify import WatchManager, Notifier, ProcessEvent
# For RabbitMQ
from kombu.connection import BrokerConnection
from kombu.messaging import Exchange, Queue, Consumer, Producer
from api_clients import api_client
# configure logging # configure logging
try: try:
@ -11,41 +31,183 @@ except Exception, e:
# loading config file # loading config file
try: try:
config = ConfigObj('/etc/airtime/recorder.cfg') config = ConfigObj('/etc/airtime/MediaMonitor.cfg')
except Exception, e: except Exception, e:
print 'Error loading config file: ', e print 'Error loading config file: ', e
sys.exit() sys.exit()
# watched events """
mask = pyinotify.ALL_EVENTS list of supported easy tags in mutagen version 1.20
['albumartistsort', 'musicbrainz_albumstatus', 'lyricist', 'releasecountry', 'date', 'performer', 'musicbrainz_albumartistid', 'composer', 'encodedby', 'tracknumber', 'musicbrainz_albumid', 'album', 'asin', 'musicbrainz_artistid', 'mood', 'copyright', 'author', 'media', 'length', 'version', 'artistsort', 'titlesort', 'discsubtitle', 'website', 'musicip_fingerprint', 'conductor', 'compilation', 'barcode', 'performer:*', 'composersort', 'musicbrainz_discid', 'musicbrainz_albumtype', 'genre', 'isrc', 'discnumber', 'musicbrainz_trmid', 'replaygain_*_gain', 'musicip_puid', 'artist', 'title', 'bpm', 'musicbrainz_trackid', 'arranger', 'albumsort', 'replaygain_*_peak', 'organization']
"""
wm = WatchManager() def checkRabbitMQ(notifier):
wdd = wm.add_watch('/srv/airtime/stor', mask, rec=True) try:
notifier.connection.drain_events(timeout=int(config["check_airtime_events"]))
except Exception, e:
logger = logging.getLogger('root')
logger.info("%s", e)
class AirtimeNotifier(Notifier):
def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, threshold=0, timeout=None):
Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, threshold, timeout)
self.airtime2mutagen = {\
"track_title": "title",\
"artist_name": "artist",\
"album_title": "album",\
"genre": "genre",\
"mood": "mood",\
"track_number": "tracknumber",\
"bpm": "bpm",\
"label": "organization",\
"composer": "composer",\
"encoded_by": "encodedby",\
"conductor": "conductor",\
"year": "date",\
"info_url": "website",\
"isrc_number": "isrc",\
"copyright": "copyright",\
}
schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True)
schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem")
self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/")
channel = self.connection.channel()
consumer = Consumer(channel, schedule_queue)
consumer.register_callback(self.handle_message)
consumer.consume()
def handle_message(self, body, message):
# ACK the message to take it off the queue
message.ack()
logger = logging.getLogger('root')
logger.info("Received md from RabbitMQ: " + body)
m = json.loads(message.body)
airtime_file = mutagen.File(m['filepath'], easy=True)
del m['filepath']
for key in m.keys() :
if m[key] != "" :
airtime_file[self.airtime2mutagen[key]] = m[key]
airtime_file.save()
class MediaMonitor(ProcessEvent):
def my_init(self):
"""
Method automatically called from ProcessEvent.__init__(). Additional
keyworded arguments passed to ProcessEvent.__init__() are then
delegated to my_init().
"""
self.api_client = api_client.api_client_factory(config)
self.mutagen2airtime = {\
"title": "track_title",\
"artist": "artist_name",\
"album": "album_title",\
"genre": "genre",\
"mood": "mood",\
"tracknumber": "track_number",\
"bpm": "bpm",\
"organization": "label",\
"composer": "composer",\
"encodedby": "encoded_by",\
"conductor": "conductor",\
"date": "year",\
"website": "info_url",\
"isrc": "isrc_number",\
"copyright": "copyright",\
}
self.logger = logging.getLogger('root')
self.temp_files = {}
def update_airtime(self, event):
self.logger.info("Updating Change to Airtime")
try:
f = open(event.pathname, 'rb')
m = hashlib.md5()
m.update(f.read())
md5 = m.hexdigest()
gunid = event.name.split('.')[0]
md = {'gunid':gunid, 'md5':md5}
file_info = mutagen.File(event.pathname, easy=True)
attrs = self.mutagen2airtime
for key in file_info.keys() :
if key in attrs :
md[attrs[key]] = file_info[key][0]
data = {'md': md}
response = self.api_client.update_media_metadata(data)
except Exception, e:
self.logger.info("%s", e)
class PTmp(ProcessEvent):
def process_IN_CREATE(self, event): def process_IN_CREATE(self, event):
if event.dir : if not event.dir :
global wm filename_info = event.name.split(".")
wdd = wm.add_watch(event.pathname, mask, rec=True)
#print wdd.keys()
print "%s: %s" % (event.maskname, os.path.join(event.path, event.name)) #file created is a tmp file which will be modified and then moved back to the original filename.
if len(filename_info) > 2 :
self.temp_files[event.pathname] = None
#This is a newly imported file.
else :
pass
self.logger.info("%s: %s", event.maskname, event.pathname)
#event.path : /srv/airtime/stor/bd2
#event.name : bd2aa73b58d9c8abcced989621846e99.mp3
#event.pathname : /srv/airtime/stor/bd2/bd2aa73b58d9c8abcced989621846e99.mp3
def process_IN_MODIFY(self, event): def process_IN_MODIFY(self, event):
if not event.dir : if not event.dir :
print event.path filename_info = event.name.split(".")
print "%s: %s" % (event.maskname, os.path.join(event.path, event.name)) #file modified is not a tmp file.
if len(filename_info) == 2 :
self.update_airtime(event)
self.logger.info("%s: path: %s name: %s", event.maskname, event.path, event.name)
def process_IN_MOVED_FROM(self, event):
if event.pathname in self.temp_files :
del self.temp_files[event.pathname]
self.temp_files[event.cookie] = event.pathname
self.logger.info("%s: %s", event.maskname, event.pathname)
def process_IN_MOVED_TO(self, event):
if event.cookie in self.temp_files :
del self.temp_files[event.cookie]
self.update_airtime(event)
self.logger.info("%s: %s", event.maskname, event.pathname)
def process_default(self, event): def process_default(self, event):
print "%s: %s" % (event.maskname, os.path.join(event.path, event.name)) self.logger.info("%s: %s", event.maskname, event.pathname)
if __name__ == '__main__': if __name__ == '__main__':
try: try:
notifier = Notifier(wm, PTmp(), read_freq=2, timeout=1) # watched events
mask = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO
#mask = pyinotify.ALL_EVENTS
wm = WatchManager()
wdd = wm.add_watch('/srv/airtime/stor', mask, rec=True, auto_add=True)
notifier = AirtimeNotifier(wm, MediaMonitor(), read_freq=int(config["check_filesystem_events"]), timeout=1)
notifier.coalesce_events() notifier.coalesce_events()
notifier.loop() notifier.loop(callback=checkRabbitMQ)
except KeyboardInterrupt: except KeyboardInterrupt:
notifier.stop() notifier.stop()

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
if os.geteuid() != 0:
print "Please run this as root."
sys.exit(1)
try:
print "Starting daemontool script recorder"
os.system("svc -u /etc/service/recorder")
except Exception, e:
print "exception:" + str(e)

View File

@ -0,0 +1,27 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import subprocess
if os.geteuid() != 0:
print "Please run this as root."
sys.exit(1)
try:
print "Stopping daemontool script recorder"
p1 = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["awk", "/recorder.py/ && !/awk/ {print $2}"], stdin=p1.stdout, stdout=subprocess.PIPE)
recorder_pid = p2.communicate()[0].strip(" \n\r\t")
if (len(recorder_pid) > 0):
os.system("svc -d /etc/service/recorder 1>/dev/null 2>&1")
os.system("svc -d /etc/service/recorder/log 1>/dev/null 2>&1")
os.system("kill -2 %s" % recorder_pid)
print "Success."
else:
print "Not Running."
except Exception, e:
print "exception:" + str(e)

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec setuidgid pypo multilog t /var/log/airtime/media-monitor/main

View File

@ -0,0 +1,17 @@
#!/bin/sh
media_monitor_user="pypo"
# Location of pypo_cli.py Python script
media_monitor_path="/usr/lib/airtime/media-monitor/"
media_monitor_script="MediaMonitor.py"
api_client_path="/usr/lib/airtime/pypo/"
cd ${media_monitor_path}
echo "*** Daemontools: starting daemon"
exec 2>&1
# Note the -u when calling python! we need it to get unbuffered binary stdout and stderr
export PYTHONPATH=${api_client_path}
setuidgid ${media_monitor_user} python -u ${media_monitor_path}${media_monitor_script}
# EOF

View File

@ -0,0 +1,127 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import os
import traceback
from optparse import *
import sys
import time
import datetime
import logging
import logging.config
import shutil
import string
import platform
from configobj import ConfigObj
from subprocess import Popen, PIPE, STDOUT
if os.geteuid() != 0:
print "Please run this as root."
sys.exit(1)
PATH_INI_FILE = '/etc/airtime/MediaMonitor.cfg'
def create_path(path):
if not (os.path.exists(path)):
print "Creating directory " + path
os.makedirs(path)
def create_user(username):
print "Checking for user "+username
p = Popen('id '+username, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
output = p.stdout.read()
if (output[0:3] != "uid"):
# Make the pypo user
print "Creating user "+username
os.system("adduser --system --quiet --group --shell /bin/bash "+username)
#set pypo password
p = os.popen('/usr/bin/passwd pypo 1>/dev/null 2>&1', 'w')
p.write('pypo\n')
p.write('pypo\n')
p.close()
else:
print "User already exists."
#add pypo to audio group
os.system("adduser " + username + " audio 1>/dev/null 2>&1")
#add pypo to www-data group
os.system("adduser " + username + " www-data 1>/dev/null 2>&1")
def copy_dir(src_dir, dest_dir):
if (os.path.exists(dest_dir)) and (dest_dir != "/"):
print "Removing old directory "+dest_dir
shutil.rmtree(dest_dir)
if not (os.path.exists(dest_dir)):
print "Copying directory "+os.path.realpath(src_dir)+" to "+os.path.realpath(dest_dir)
shutil.copytree(src_dir, dest_dir)
def get_current_script_dir():
current_script_dir = os.path.realpath(__file__)
index = current_script_dir.rindex('/')
#print current_script_dir[0:index]
return current_script_dir[0:index]
try:
# load config file
try:
config = ConfigObj(PATH_INI_FILE)
except Exception, e:
print 'Error loading config file: ', e
sys.exit()
current_script_dir = get_current_script_dir()
print "Checking and removing any existing media monitor processes"
os.system("python %s/media-monitor-uninstall.py 1>/dev/null 2>&1"% current_script_dir)
time.sleep(5)
# Create users
create_user("pypo")
print "Creating log directories"
create_path(config["log_dir"])
os.system("chmod -R 755 " + config["log_dir"])
os.system("chown -R pypo:pypo "+config["log_dir"])
copy_dir("%s/.."%current_script_dir, config["bin_dir"])
print "Setting permissions"
os.system("chmod -R 755 "+config["bin_dir"])
os.system("chown -R pypo:pypo "+config["bin_dir"])
print "Creating symbolic links"
os.system("rm -f /usr/bin/airtime-media-monitor-start")
os.system("ln -s "+config["bin_dir"]+"/airtime-media-monitor-start /usr/bin/")
os.system("rm -f /usr/bin/airtime-media-monitor-stop")
os.system("ln -s "+config["bin_dir"]+"/airtime-media-monitor-stop /usr/bin/")
print "Installing recorder daemon"
create_path("/etc/service/media-monitor")
create_path("/etc/service/media-monitor/log")
shutil.copy("%s/media-monitor-daemontools.sh"%current_script_dir, "/etc/service/media-monitor/run")
shutil.copy("%s/media-monitor-daemontools-logger.sh"%current_script_dir, "/etc/service/media-monitor/log/run")
os.system("chmod -R 755 /etc/service/media-monitor")
os.system("chown -R pypo:pypo /etc/service/media-monitor")
print "Waiting for processes to start..."
time.sleep(5)
os.system("python /usr/bin/airtime-media-monitor-start")
time.sleep(2)
found = True
p = Popen('svstat /etc/service/media-monitor', shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
output = p.stdout.read()
if (output.find("unable to open supervise/ok: file does not exist") >= 0):
found = False
print output
if not found:
print "Media monitor install has completed, but daemontools is not running, please make sure you have it installed and then reboot."
except Exception, e:
print "exception:" + str(e)
sys.exit(1)

View File

@ -0,0 +1,58 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import time
from configobj import ConfigObj
if os.geteuid() != 0:
print "Please run this as root."
sys.exit(1)
PATH_INI_FILE = '/etc/airtime/MediaMonitor.cfg'
def remove_path(path):
os.system("rm -rf " + path)
def remove_user(username):
os.system("killall -u %s 1>/dev/null 2>&1" % username)
#allow all process to be completely closed before we attempt to delete user
print "Waiting for processes to close..."
time.sleep(5)
os.system("deluser --remove-home " + username + " 1>/dev/null 2>&1")
def get_current_script_dir():
current_script_dir = os.path.realpath(__file__)
index = current_script_dir.rindex('/')
return current_script_dir[0:index]
try:
# load config file
try:
config = ConfigObj(PATH_INI_FILE)
except Exception, e:
print 'Error loading config file: ', e
sys.exit()
os.system("python /usr/bin/airtime-media-monitor-stop")
print "Removing log directories"
remove_path(config["log_dir"])
print "Removing symlinks"
os.system("rm -f /usr/bin/airtime-media-monitor-start")
os.system("rm -f /usr/bin/airtime-media-monitor-stop")
print "Removing application files"
remove_path(config["bin_dir"])
print "Removing daemontool script media-monitor"
remove_path("rm -rf /etc/service/media-monitor")
remove_user("pypo")
print "Uninstall complete."
except Exception, e:
print "exception:" + str(e)

View File

@ -17,7 +17,7 @@ exec 2>&1
export PYTHONPATH=${api_client_path} export PYTHONPATH=${api_client_path}
#su ${recorder_user} -c "python -u ${recorder_path}${recorder_script}" #su ${recorder_user} -c "python -u ${recorder_path}${recorder_script}"
setuidgid ${recorder_user} ${recorder_path}${recorder_script} setuidgid ${recorder_user} python -u ${recorder_path}${recorder_script}
# EOF # EOF

View File

@ -55,6 +55,7 @@ class ShowRecorder(Thread):
self.start_time = start_time self.start_time = start_time
self.filetype = filetype self.filetype = filetype
self.show_instance = show_instance self.show_instance = show_instance
self.logger = logging.getLogger('root')
def record_show(self): def record_show(self):
@ -67,11 +68,9 @@ class ShowRecorder(Thread):
#-ge:0.1,0.1,0,-1 #-ge:0.1,0.1,0,-1
args = command.split(" ") args = command.split(" ")
print "starting record" self.logger.info("starting record")
code = call(args) code = call(args)
self.logger.info("finishing record, return code %s", code)
print "finishing record, return code %s" % (code)
return code, filepath return code, filepath
@ -94,7 +93,7 @@ class ShowRecorder(Thread):
if code == 0: if code == 0:
self.upload_file(filepath) self.upload_file(filepath)
else: else:
print "problem recording show" self.logger.info("problem recording show")
class Record(): class Record():
@ -102,6 +101,7 @@ class Record():
def __init__(self): def __init__(self):
self.api_client = api_client.api_client_factory(config) self.api_client = api_client.api_client_factory(config)
self.shows_to_record = {} self.shows_to_record = {}
self.logger = logging.getLogger('root')
def process_shows(self, shows): def process_shows(self, shows):
@ -123,21 +123,21 @@ class Record():
start_time = sorted_show_keys[0] start_time = sorted_show_keys[0]
next_show = getDateTimeObj(start_time) next_show = getDateTimeObj(start_time)
print next_show self.logger.debug("Next show %s", next_show)
print tnow self.logger.debug("Now %s", tnow)
delta = next_show - tnow delta = next_show - tnow
min_delta = datetime.timedelta(seconds=60) min_delta = datetime.timedelta(seconds=60)
if delta <= min_delta: if delta <= min_delta:
print "sleeping %s seconds until show" % (delta.seconds) self.logger.debug("sleeping %s seconds until show", delta.seconds)
time.sleep(delta.seconds) time.sleep(delta.seconds)
show_length = self.shows_to_record[start_time][0] show_length = self.shows_to_record[start_time][0]
show_instance = self.shows_to_record[start_time][1] show_instance = self.shows_to_record[start_time][1]
show_name = self.shows_to_record[start_time][2] show_name = self.shows_to_record[start_time][2]
show = ShowRecorder(show_instance, show_length.seconds, show_name, start_time, filetype="mp3", ) show = ShowRecorder(show_instance, show_length.seconds, show_name, start_time, filetype="mp3")
show.start() show.start()
#remove show from shows to record. #remove show from shows to record.
@ -165,7 +165,3 @@ if __name__ == '__main__':
recorder.get_shows() recorder.get_shows()
time.sleep(5) time.sleep(5)