diff --git a/airtime_mvc/application/Bootstrap.php b/airtime_mvc/application/Bootstrap.php
index f9a62c9f6..cf4d08731 100644
--- a/airtime_mvc/application/Bootstrap.php
+++ b/airtime_mvc/application/Bootstrap.php
@@ -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');
//scripts for now playing bar
- $view->headScript()->appendFile($baseUrl.'/js/playlist/helperfunctions.js','text/javascript');
- $view->headScript()->appendFile($baseUrl.'/js/playlist/playlist.js','text/javascript');
+ $view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/helperfunctions.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');
}
diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php
index 50fe4cc59..a6f0825b7 100644
--- a/airtime_mvc/application/controllers/ApiController.php
+++ b/airtime_mvc/application/controllers/ApiController.php
@@ -8,9 +8,10 @@ class ApiController extends Zend_Controller_Action
/* Initialize action controller here */
$context = $this->_helper->getHelper('contextSwitch');
$context->addActionContext('version', 'json')
- ->addActionContext('recorded-shows', 'json')
- ->addActionContext('upload-recorded', 'json')
- ->initContext();
+ ->addActionContext('recorded-shows', 'json')
+ ->addActionContext('upload-recorded', 'json')
+ ->addActionContext('reload-metadata', 'json')
+ ->initContext();
}
public function indexAction()
@@ -113,8 +114,6 @@ class ApiController extends Zend_Controller_Action
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
- $result = Schedule::GetPlayOrderRange(0, 1);
-
$date = new DateHelper;
$timeNow = $date->getTimestamp();
$result = array("env"=>APPLICATION_ENV,
@@ -123,7 +122,7 @@ class ApiController extends Zend_Controller_Action
"nextShow"=>Show_DAL::GetNextShows($timeNow, 5),
"timezone"=> date("T"),
"timezoneOffset"=> date("Z"));
-
+
//echo json_encode($result);
header("Content-type: text/javascript");
echo $_GET['callback'].'('.json_encode($result).')';
@@ -316,7 +315,37 @@ 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!";
+ }
}
}
diff --git a/airtime_mvc/application/controllers/DashboardController.php b/airtime_mvc/application/controllers/DashboardController.php
new file mode 100644
index 000000000..55652fc12
--- /dev/null
+++ b/airtime_mvc/application/controllers/DashboardController.php
@@ -0,0 +1,22 @@
+getValues();
$file->replaceDbMetadata($formdata);
+ $data = $formdata;
+ $data['filepath'] = $file->getRealFilePath();
+ RabbitMq::SendFileMetaData($data);
+
$this->_helper->redirector('index');
}
}
diff --git a/airtime_mvc/application/forms/EditAudioMD.php b/airtime_mvc/application/forms/EditAudioMD.php
index 6a002cd03..022745e6f 100644
--- a/airtime_mvc/application/forms/EditAudioMD.php
+++ b/airtime_mvc/application/forms/EditAudioMD.php
@@ -2,6 +2,24 @@
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()
{
@@ -37,6 +55,13 @@ class Application_Form_EditAudioMD extends Zend_Form
'filters' => array('StringTrim')
));
+ // Add mood field
+ $this->addElement('text', 'track_number', array(
+ 'label' => 'Track:',
+ 'class' => 'input_text',
+ 'filters' => array('StringTrim')
+ ));
+
// Add genre field
$this->addElement('text', 'genre', array(
'label' => 'Genre:',
@@ -77,9 +102,30 @@ class Application_Form_EditAudioMD extends Zend_Form
'filters' => array('StringTrim')
));
- // Add language field
- $this->addElement('text', 'language', array(
- 'label' => 'Language:',
+ // Add mood field
+ $this->addElement('text', 'bpm', array(
+ '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',
'filters' => array('StringTrim')
));
diff --git a/airtime_mvc/application/models/Dashboard.php b/airtime_mvc/application/models/Dashboard.php
new file mode 100644
index 000000000..0d0c622b9
--- /dev/null
+++ b/airtime_mvc/application/models/Dashboard.php
@@ -0,0 +1,115 @@
+$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());
+ }
+ }
+ }
+ }
+
+}
diff --git a/airtime_mvc/application/models/RabbitMq.php b/airtime_mvc/application/models/RabbitMq.php
index 1672dd4e2..971933f20 100644
--- a/airtime_mvc/application/models/RabbitMq.php
+++ b/airtime_mvc/application/models/RabbitMq.php
@@ -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();
+ }
+
}
diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php
index d83056522..d6a647d8e 100644
--- a/airtime_mvc/application/models/Schedule.php
+++ b/airtime_mvc/application/models/Schedule.php
@@ -365,9 +365,12 @@ class Schedule {
$timeNow = $date->getTimestamp();
return array("env"=>APPLICATION_ENV,
"schedulerTime"=>gmdate("Y-m-d H:i:s"),
- "previous"=>Schedule::GetScheduledItemData($timeNow, -1, $prev, "24 hours"),
- "current"=>Schedule::GetScheduledItemData($timeNow, 0),
- "next"=>Schedule::GetScheduledItemData($timeNow, 1, $next, "48 hours"),
+ //"previous"=>Schedule::GetScheduledItemData($timeNow, -1, $prev, "24 hours"),
+ //"current"=>Schedule::GetScheduledItemData($timeNow, 0),
+ //"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),
"nextShow"=>Show_DAL::GetNextShows($timeNow, 1),
"timezone"=> date("T"),
@@ -375,6 +378,52 @@ class Schedule {
"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
* the database.
diff --git a/airtime_mvc/application/models/Shows.php b/airtime_mvc/application/models/Shows.php
index 088b8b49f..7b8a8d21e 100644
--- a/airtime_mvc/application/models/Shows.php
+++ b/airtime_mvc/application/models/Shows.php
@@ -1718,6 +1718,57 @@ class ShowInstance {
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 */
diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php
index 847e284f6..d5d4faf80 100644
--- a/airtime_mvc/application/models/StoredFile.php
+++ b/airtime_mvc/application/models/StoredFile.php
@@ -543,16 +543,25 @@ class StoredFile {
public function replaceDbMetadata($p_values)
{
global $CC_CONFIG, $CC_DBC;
+
+ $data = array();
foreach ($p_values as $category => $value) {
$escapedValue = pg_escape_string($value);
$columnName = $category;
if (!is_null($columnName)) {
- $sql = "UPDATE ".$CC_CONFIG["filesTable"]
- ." SET $columnName='$escapedValue'"
- ." WHERE gunid = '".$this->gunid."'";
- $CC_DBC->query($sql);
+ $data[] = "$columnName='$escapedValue'";
}
}
+
+ $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()
diff --git a/airtime_mvc/public/js/playlist/helperfunctions.js b/airtime_mvc/public/js/airtime/dashboard/helperfunctions.js
similarity index 100%
rename from airtime_mvc/public/js/playlist/helperfunctions.js
rename to airtime_mvc/public/js/airtime/dashboard/helperfunctions.js
diff --git a/airtime_mvc/public/js/playlist/playlist.js b/airtime_mvc/public/js/airtime/dashboard/playlist.js
similarity index 72%
rename from airtime_mvc/public/js/playlist/playlist.js
rename to airtime_mvc/public/js/airtime/dashboard/playlist.js
index c6c37c213..1b2177a1e 100644
--- a/airtime_mvc/public/js/playlist/playlist.js
+++ b/airtime_mvc/public/js/airtime/dashboard/playlist.js
@@ -1,9 +1,9 @@
var estimatedSchedulePosixTime = null;
var localRemoteTimeOffset = null;
-var previousSongs = new Array();
-var currentSong = new Array();
-var nextSongs = new Array();
+var previousSong = null;
+var currentSong = null;
+var nextSong = null;
var currentShow = new Array();
var nextShow = new Array();
@@ -25,21 +25,8 @@ var nextShowPrepare = true;
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(){
- if (localRemoteTimeOffset != null){
+ if (localRemoteTimeOffset !== null){
var date = new Date();
estimatedSchedulePosixTime = date.getTime() - localRemoteTimeOffset;
updateProgressBarValue();
@@ -50,9 +37,10 @@ function secondsTimer(){
function newSongStart(){
nextSongPrepare = true;
- currentSong[0] = nextSongs.shift();
+ currentSong = nextSong;
+ nextSong = null;
- if (typeof notifySongStart == "function")
+ if (typeof notifySongStart == "function")
notifySongStart();
}
@@ -80,13 +68,13 @@ function updateProgressBarValue(){
$('#progress-show').attr("style", "width:"+showPercentDone+"%");
var songPercentDone = 0;
- if (currentSong.length > 0){
- songPercentDone = (estimatedSchedulePosixTime - currentSong[0].songStartPosixTime)/currentSong[0].songLengthMs*100;
+ if (currentSong !== null){
+ songPercentDone = (estimatedSchedulePosixTime - currentSong.songStartPosixTime)/currentSong.songLengthMs*100;
if (songPercentDone < 0 || songPercentDone > 100){
songPercentDone = 0;
- currentSong = new Array();
+ currentSong = null;
} 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");
else
$('#on-air-info').attr("class", "on-air-info off");
@@ -99,8 +87,8 @@ function updateProgressBarValue(){
$('#progress-bar').attr("style", "width:"+songPercentDone+"%");
//calculate how much time left to next song if there is any
- if (nextSongs.length > 0 && nextSongPrepare){
- var diff = nextSongs[0].songStartPosixTime - estimatedSchedulePosixTime;
+ if (nextSong !== null && nextSongPrepare){
+ var diff = nextSong.songStartPosixTime - estimatedSchedulePosixTime;
if (diff < serverUpdateInterval){
//sometimes the diff is negative (-100ms for example). Still looking
@@ -133,20 +121,26 @@ function updatePlaybar(){
$('#current').html("Current: Nothing Scheduled");
$('#next').empty();
$('#next-length').empty();
- if (previousSongs.length > 0){
- $('#previous').text(getTrackInfo(previousSongs[previousSongs.length-1]));
- $('#prev-length').text(convertToHHMMSSmm(previousSongs[previousSongs.length-1].songLengthMs));
+ if (previousSong !== null){
+ $('#previous').text(previousSong.name+",");
+ $('#prev-length').text(convertToHHMMSSmm(previousSong.songLengthMs));
}
- if (currentSong.length > 0){
- $('#current').text(getTrackInfo(currentSong[0]));
- } else if (currentShow.length > 0){
+ if (currentSong !== null){
+ if (currentSong.record == "1")
+ $('#current').html("Recording: "+currentSong.name+",");
+ else
+ $('#current').text(currentSong.name+",");
+ }
+ /*
+ else if (currentShow.length > 0){
if (currentShow[0].record == "1"){
$('#current').html("Current: Recording");
}
}
- if (nextSongs.length > 0){
- $('#next').text(getTrackInfo(nextSongs[0]));
- $('#next-length').text(convertToHHMMSSmm(nextSongs[0].songLengthMs));
+ * */
+ if (nextSong !== null){
+ $('#next').text(nextSong.name+",");
+ $('#next-length').text(convertToHHMMSSmm(nextSong.songLengthMs));
}
$('#start').empty();
@@ -154,18 +148,18 @@ function updatePlaybar(){
$('#time-elapsed').empty();
$('#time-remaining').empty();
$('#song-length').empty();
- for (var i=0; i 0){
- currentSong = obj.current;
- calcAdditionalData(currentSong);
- }
nextShow = obj.nextShow;
calcAdditionalShowData(obj.currentShow);
@@ -238,17 +231,10 @@ function getScheduleFromServer(){
setTimeout(getScheduleFromServer, serverUpdateInterval);
}
-
-function init() {
- //begin producer "thread"
- getScheduleFromServer();
-
- //begin consumer "thread"
- secondsTimer();
-
+function setupQtip(){
var qtipElem = $('#about-link');
- if (qtipElem.length > 0)
+ if (qtipElem.length > 0){
qtipElem.qtip({
content: $('#about-txt').html(),
show: 'mouseover',
@@ -267,6 +253,17 @@ function init() {
name: 'light' // Use the default light style
}
});
+ }
+}
+
+function init() {
+ //begin producer "thread"
+ getScheduleFromServer();
+
+ //begin consumer "thread"
+ secondsTimer();
+
+ setupQtip();
}
$(document).ready(function() {
diff --git a/airtime_mvc/public/js/airtime/schedule/schedule.js b/airtime_mvc/public/js/airtime/schedule/schedule.js
index 20c0321f8..a821412d9 100644
--- a/airtime_mvc/public/js/airtime/schedule/schedule.js
+++ b/airtime_mvc/public/js/airtime/schedule/schedule.js
@@ -208,12 +208,42 @@ function getId() {
function buildContentDialog(json){
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({
autoOpen: false,
title: 'Show Contents',
- width: 1100,
- height: 500,
+ width: width,
+ height: height,
modal: true,
close: closeDialog,
buttons: {"Ok": function() {
diff --git a/airtime_mvc/public/js/playlist/showlistview.js b/airtime_mvc/public/js/playlist/showlistview.js
deleted file mode 100644
index 6e2bb6b57..000000000
--- a/airtime_mvc/public/js/playlist/showlistview.js
+++ /dev/null
@@ -1,69 +0,0 @@
-function createDataGrid(datagridData){
-
- var columnHeaders = [
- { "sTitle": "name" },
- { "sTitle": "date" },
- { "sTitle": "start time" },
- { "sTitle": "end time" }
- ];
-
- $('#demo').html( '' );
- $('#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 nothing"},
- {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 'Displays usage information.',
'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();
@@ -34,6 +35,10 @@ if (isset($opts->h)) {
echo $opts->getUsageMessage();
exit;
}
+$db_install = true;
+if (isset($opts->n)){
+ $db_install = false;
+}
$overwrite = false;
if (isset($opts->o)) {
@@ -72,17 +77,21 @@ require_once(AirtimeInstall::GetAirtimeSrcDir().'/application/configs/conf.php')
echo "* Airtime Version: ".AIRTIME_VERSION.PHP_EOL;
+if ($db_install) {
+
//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();
@@ -98,6 +107,9 @@ system("python ".__DIR__."/../python_apps/pypo/install/pypo-install.py");
echo PHP_EOL."*** Recorder Installation ***".PHP_EOL;
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);
echo "******************************* Install Complete *******************************".PHP_EOL;
diff --git a/install/airtime-uninstall.php b/install/airtime-uninstall.php
index 3da55df11..065ff6f68 100644
--- a/install/airtime-uninstall.php
+++ b/install/airtime-uninstall.php
@@ -29,7 +29,10 @@ AirtimeInstall::UninstallPhpCode();
// 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;
-$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);
//------------------------------------------------------------------------
@@ -66,7 +69,7 @@ if ($dbDeleteFailed) {
// Delete the user
//------------------------------------------------------------------------
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);
if ($results == 0) {
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";
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
#AirtimeIni::RemoveIniFiles();
diff --git a/install/include/AirtimeIni.php b/install/include/AirtimeIni.php
index 930b6260d..70c059af2 100644
--- a/install/include/AirtimeIni.php
+++ b/install/include/AirtimeIni.php
@@ -26,13 +26,15 @@ class AirtimeIni
const CONF_FILE_PYPO = "/etc/airtime/pypo.cfg";
const CONF_FILE_RECORDER = "/etc/airtime/recorder.cfg";
const CONF_FILE_LIQUIDSOAP = "/etc/airtime/liquidsoap.cfg";
+ const CONF_FILE_MEDIAMONITOR = "/etc/airtime/MediaMonitor.cfg";
public static function IniFilesExist()
{
$configFiles = array(AirtimeIni::CONF_FILE_AIRTIME,
AirtimeIni::CONF_FILE_PYPO,
AirtimeIni::CONF_FILE_RECORDER,
- AirtimeIni::CONF_FILE_LIQUIDSOAP);
+ AirtimeIni::CONF_FILE_LIQUIDSOAP,
+ AirtimeIni::CONF_FILE_MEDIAMONITOR);
$exist = false;
foreach ($configFiles as $conf) {
if (file_exists($conf)) {
@@ -72,6 +74,10 @@ class AirtimeIni
echo "Could not copy liquidsoap.cfg to /etc/airtime/. Exiting.";
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);
}
+ if (file_exists(AirtimeIni::CONF_FILE_MEDIAMONITOR)){
+ unlink(AirtimeIni::CONF_FILE_MEDIAMONITOR);
+ }
+
if (file_exists("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_PYPO, '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);
}
}
diff --git a/install/include/AirtimeInstall.php b/install/include/AirtimeInstall.php
index 4395bef81..d240503a7 100644
--- a/install/include/AirtimeInstall.php
+++ b/install/include/AirtimeInstall.php
@@ -126,7 +126,7 @@ class AirtimeInstall
$username = $CC_CONFIG['dsn']['username'];
$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);
if ($results == 0) {
@@ -150,7 +150,7 @@ class AirtimeInstall
$database = $CC_CONFIG['dsn']['database'];
$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);
if ($results == 0) {
diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py
index f551ebc5f..849824090 100644
--- a/python_apps/api_clients/api_client.py
+++ b/python_apps/api_clients/api_client.py
@@ -19,6 +19,7 @@ import json
import os
from urlparse import urlparse
+AIRTIME_VERSION = "1.9.0"
def api_client_factory(config):
if config["api_client"] == "airtime":
@@ -29,7 +30,25 @@ def api_client_factory(config):
print 'API Client "'+config["api_client"]+'" not supported. Please check your config file.'
print
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:
# Implementation: optional
@@ -97,6 +116,9 @@ class ApiClientInterface:
def upload_recorded_show(self):
pass
+
+ def update_media_metadata(self, md):
+ pass
# Put here whatever tests you want to run to make sure your API is working
def test(self):
@@ -121,6 +143,8 @@ class AirTimeApiClient(ApiClientInterface):
logger.debug("Trying to contact %s", url)
url = url.replace("%%api_key%%", self.config["api_key"])
+ version = -1
+ response = None
try:
response = urllib.urlopen(url)
data = response.read()
@@ -129,34 +153,26 @@ class AirTimeApiClient(ApiClientInterface):
version = response_json['version']
logger.debug("Airtime Version %s detected", version)
except Exception, e:
- try:
- if e[1] == 401:
- if (verbose):
- print '#####################################'
- print '# YOUR API KEY SEEMS TO BE INVALID:'
- print '# ' + self.config["api_key"]
- print '#####################################'
- return False
- except Exception, e:
- pass
+ if e[1] == 401:
+ if (verbose):
+ print '#####################################'
+ print '# YOUR API KEY SEEMS TO BE INVALID:'
+ print '# ' + self.config["api_key"]
+ print '#####################################'
+ return -1
- try:
- if e[1] == 404:
- if (verbose):
- print '#####################################'
- print '# Unable to contact the Airtime-API'
- print '# ' + url
- print '#####################################'
- return False
- except Exception, e:
- pass
+ if e[1] == 404:
+ if (verbose):
+ print '#####################################'
+ print '# Unable to contact the Airtime-API'
+ print '# ' + url
+ print '#####################################'
+ return -1
- version = 0
logger.error("Unable to detect Airtime Version - %s, Response: %s", e, response)
return version
-
def test(self):
logger = logging.getLogger()
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):
version = self.__get_airtime_version(verbose)
- if (version == 0 or version == False):
+ if (version == -1):
if (verbose):
print 'Unable to get Airtime version number.'
print
return False
- elif (version[0:4] != "1.9."):
+ elif (version[0:3] != AIRTIME_VERSION):
if (verbose):
print 'Airtime version: ' + str(version)
print 'pypo not compatible with this version of Airtime.'
@@ -198,7 +214,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger()
# 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"])
logger.info("Fetching schedule from %s", export_url)
@@ -220,8 +235,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger()
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%%"
logger.info("try to download from %s to %s", src, dst)
src = src.replace("%%api_key%%", self.config["api_key"])
@@ -239,7 +252,6 @@ class AirTimeApiClient(ApiClientInterface):
logger = logging.getLogger()
playlist = schedule[pkey]
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 = url.replace("%%schedule_id%%", str(schedule_id))
@@ -268,7 +280,6 @@ class AirTimeApiClient(ApiClientInterface):
response = ''
try:
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 = url.replace("%%media_id%%", str(media_id))
url = url.replace("%%schedule_id%%", str(schedule_id))
@@ -299,7 +310,6 @@ class AirTimeApiClient(ApiClientInterface):
response = None
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 = self.config["base_url"] + self.config["api_base"] + self.config["show_schedule_url"]
logger.debug(url)
url = url.replace("%%api_key%%", self.config["api_key"])
@@ -319,7 +329,6 @@ class AirTimeApiClient(ApiClientInterface):
retries = int(self.config["upload_retries"])
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"])
logger.debug(url)
@@ -346,6 +355,26 @@ class AirTimeApiClient(ApiClientInterface):
time.sleep(retries_wait)
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
diff --git a/python_apps/pypo/install/pypo-install.py b/python_apps/pypo/install/pypo-install.py
index c547081ca..716521d88 100755
--- a/python_apps/pypo/install/pypo-install.py
+++ b/python_apps/pypo/install/pypo-install.py
@@ -64,6 +64,21 @@ def get_current_script_dir():
#print 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:
# load config file
@@ -93,11 +108,20 @@ try:
create_path(config["cache_dir"])
create_path(config["file_dir"])
create_path(config["tmp_dir"])
+
+ architecture = platform.architecture()[0]
+ natty = is_natty()
- if platform.architecture()[0] == '64bit':
+ 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"
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"
shutil.copy("%s/../liquidsoap/liquidsoap-i386"%current_script_dir, "%s/../liquidsoap/liquidsoap"%current_script_dir)
else:
diff --git a/python_apps/pypo/liquidsoap/liquidsoap-amd64-natty b/python_apps/pypo/liquidsoap/liquidsoap-amd64-natty
new file mode 100755
index 000000000..7aca06e45
Binary files /dev/null and b/python_apps/pypo/liquidsoap/liquidsoap-amd64-natty differ
diff --git a/python_apps/pypo/liquidsoap/liquidsoap-i386-natty b/python_apps/pypo/liquidsoap/liquidsoap-i386-natty
new file mode 100755
index 000000000..ff22395fc
Binary files /dev/null and b/python_apps/pypo/liquidsoap/liquidsoap-i386-natty differ
diff --git a/python_apps/pypo/pypo-cli.py b/python_apps/pypo/pypo-cli.py
index 87b24b8b4..77a1573fe 100755
--- a/python_apps/pypo/pypo-cli.py
+++ b/python_apps/pypo/pypo-cli.py
@@ -3,29 +3,14 @@
"""
Python part of radio playout (pypo)
-
-The main functions are "fetch" (./pypo_cli.py -f) and "push" (./pypo_cli.py -p)
"""
-
import time
-#import calendar
-#import traceback
from optparse import *
import sys
import os
import signal
-#import datetime
import logging
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 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("-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("-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):
self.api_client = api_client.api_client_factory(config)
- if (not self.api_client.is_server_compatible()):
- sys.exit()
+ return self.api_client.is_server_compatible()
def set_export_source(self, export_source):
self.export_source = export_source
@@ -130,7 +112,8 @@ if __name__ == '__main__':
# initialize
g = Global()
- g.selfcheck()
+
+ while not g.selfcheck(): time.sleep(5000)
logger = logging.getLogger()
diff --git a/python_apps/pytag-fs/MediaMonitor.cfg b/python_apps/pytag-fs/MediaMonitor.cfg
index e4610b720..5f2a79a83 100644
--- a/python_apps/pytag-fs/MediaMonitor.cfg
+++ b/python_apps/pytag-fs/MediaMonitor.cfg
@@ -7,11 +7,8 @@ base_port = 80
# where the binary files live
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
-log_dir = '/var/log/airtime/show-recorder'
+log_dir = '/var/log/airtime/media-monitor'
# Value needed to access the API
api_key = 'AAA'
@@ -22,8 +19,18 @@ api_base = 'api'
# URL to get the version number of the server API
version_url = 'version/api_key/%%api_key%%'
-# URL to get the schedule of shows set to record
-show_schedule_url = 'recorded-shows/format/json/api_key/%%api_key%%'
+# URL to tell Airtime to update file's meta data
+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.
diff --git a/python_apps/pytag-fs/MediaMonitor.py b/python_apps/pytag-fs/MediaMonitor.py
index 2bdf148aa..38423fcc9 100644
--- a/python_apps/pytag-fs/MediaMonitor.py
+++ b/python_apps/pytag-fs/MediaMonitor.py
@@ -1,6 +1,26 @@
+#!/usr/local/bin/python
+import logging
+import logging.config
+import json
+import time
+import datetime
import os
+import sys
+import hashlib
+import json
+
+from subprocess import Popen, PIPE, STDOUT
+
+from configobj import ConfigObj
+
+import mutagen
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
try:
@@ -11,41 +31,183 @@ except Exception, e:
# loading config file
try:
- config = ConfigObj('/etc/airtime/recorder.cfg')
+ config = ConfigObj('/etc/airtime/MediaMonitor.cfg')
except Exception, e:
print 'Error loading config file: ', e
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()
-wdd = wm.add_watch('/srv/airtime/stor', mask, rec=True)
+def checkRabbitMQ(notifier):
+ try:
+ notifier.connection.drain_events(timeout=int(config["check_airtime_events"]))
+ except Exception, e:
+ logger = logging.getLogger('root')
+ logger.info("%s", e)
-class PTmp(ProcessEvent):
- def process_IN_CREATE(self, event):
- if event.dir :
- global wm
- wdd = wm.add_watch(event.pathname, mask, rec=True)
- #print wdd.keys()
+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",\
+ }
- print "%s: %s" % (event.maskname, os.path.join(event.path, event.name))
+ 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)
+
+ def process_IN_CREATE(self, event):
+ if not event.dir :
+ filename_info = event.name.split(".")
+
+ #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):
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):
- print "%s: %s" % (event.maskname, os.path.join(event.path, event.name))
+ self.logger.info("%s: %s", event.maskname, event.pathname)
if __name__ == '__main__':
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.loop()
+ notifier.loop(callback=checkRabbitMQ)
except KeyboardInterrupt:
notifier.stop()
diff --git a/python_apps/pytag-fs/airtime-media-monitor-start b/python_apps/pytag-fs/airtime-media-monitor-start
new file mode 100755
index 000000000..084b2b1ad
--- /dev/null
+++ b/python_apps/pytag-fs/airtime-media-monitor-start
@@ -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)
diff --git a/python_apps/pytag-fs/airtime-media-monitor-stop b/python_apps/pytag-fs/airtime-media-monitor-stop
new file mode 100755
index 000000000..b51b1ba6a
--- /dev/null
+++ b/python_apps/pytag-fs/airtime-media-monitor-stop
@@ -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)
diff --git a/python_apps/pytag-fs/install/media-monitor-daemontools-logger.sh b/python_apps/pytag-fs/install/media-monitor-daemontools-logger.sh
new file mode 100755
index 000000000..6819f837d
--- /dev/null
+++ b/python_apps/pytag-fs/install/media-monitor-daemontools-logger.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec setuidgid pypo multilog t /var/log/airtime/media-monitor/main
diff --git a/python_apps/pytag-fs/install/media-monitor-daemontools.sh b/python_apps/pytag-fs/install/media-monitor-daemontools.sh
new file mode 100755
index 000000000..d629ec49d
--- /dev/null
+++ b/python_apps/pytag-fs/install/media-monitor-daemontools.sh
@@ -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
diff --git a/python_apps/pytag-fs/install/media-monitor-install.py b/python_apps/pytag-fs/install/media-monitor-install.py
new file mode 100755
index 000000000..8f3fb804e
--- /dev/null
+++ b/python_apps/pytag-fs/install/media-monitor-install.py
@@ -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)
+
+
+
diff --git a/python_apps/pytag-fs/install/media-monitor-uninstall.py b/python_apps/pytag-fs/install/media-monitor-uninstall.py
new file mode 100755
index 000000000..e0a5ca262
--- /dev/null
+++ b/python_apps/pytag-fs/install/media-monitor-uninstall.py
@@ -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)
diff --git a/python_apps/show-recorder/install/recorder-daemontools.sh b/python_apps/show-recorder/install/recorder-daemontools.sh
index 4190d1112..546f99a3e 100755
--- a/python_apps/show-recorder/install/recorder-daemontools.sh
+++ b/python_apps/show-recorder/install/recorder-daemontools.sh
@@ -17,7 +17,7 @@ exec 2>&1
export PYTHONPATH=${api_client_path}
#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
diff --git a/python_apps/show-recorder/recorder.py b/python_apps/show-recorder/recorder.py
index 7e1f7ef7a..0421086c3 100644
--- a/python_apps/show-recorder/recorder.py
+++ b/python_apps/show-recorder/recorder.py
@@ -43,7 +43,7 @@ def getDateTimeObj(time):
date = timeinfo[0].split("-")
time = timeinfo[1].split(":")
- return datetime.datetime(int(date[0]), int(date[1]), int(date[2]), int(time[0]), int(time[1]), int(time[2]))
+ return datetime.datetime(int(date[0]), int(date[1]), int(date[2]), int(time[0]), int(time[1]), int(time[2]))
class ShowRecorder(Thread):
@@ -55,6 +55,7 @@ class ShowRecorder(Thread):
self.start_time = start_time
self.filetype = filetype
self.show_instance = show_instance
+ self.logger = logging.getLogger('root')
def record_show(self):
@@ -67,11 +68,9 @@ class ShowRecorder(Thread):
#-ge:0.1,0.1,0,-1
args = command.split(" ")
- print "starting record"
-
+ self.logger.info("starting record")
code = call(args)
-
- print "finishing record, return code %s" % (code)
+ self.logger.info("finishing record, return code %s", code)
return code, filepath
@@ -94,55 +93,56 @@ class ShowRecorder(Thread):
if code == 0:
self.upload_file(filepath)
else:
- print "problem recording show"
+ self.logger.info("problem recording show")
class Record():
def __init__(self):
- self.api_client = api_client.api_client_factory(config)
- self.shows_to_record = {}
+ self.api_client = api_client.api_client_factory(config)
+ self.shows_to_record = {}
+ self.logger = logging.getLogger('root')
def process_shows(self, shows):
self.shows_to_record = {}
-
+
for show in shows:
show_starts = getDateTimeObj(show[u'starts'])
show_end = getDateTimeObj(show[u'ends'])
time_delta = show_end - show_starts
-
+
self.shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id'], show[u'name']]
def check_record(self):
-
+
tnow = datetime.datetime.now()
sorted_show_keys = sorted(self.shows_to_record.keys())
-
+
start_time = sorted_show_keys[0]
next_show = getDateTimeObj(start_time)
- print next_show
- print tnow
+ self.logger.debug("Next show %s", next_show)
+ self.logger.debug("Now %s", tnow)
delta = next_show - tnow
min_delta = datetime.timedelta(seconds=60)
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)
-
+
show_length = self.shows_to_record[start_time][0]
show_instance = self.shows_to_record[start_time][1]
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()
-
+
#remove show from shows to record.
del self.shows_to_record[start_time]
-
+
def get_shows(self):
@@ -154,7 +154,7 @@ class Record():
if len(shows):
self.process_shows(shows)
- self.check_record()
+ self.check_record()
if __name__ == '__main__':
@@ -165,7 +165,3 @@ if __name__ == '__main__':
recorder.get_shows()
time.sleep(5)
-
-
-
-