CC-3346: Recorder: Merge recorder with pypo
- Pypo fech works as msg listner for recroder now. - recorder is part of pypo and all it does is waiting for msg from pypo fetch and spwan a show recorder thread. - added new parameter logger to api client. This way apiclient will log into specific log file instead of grabbing current log file. - show recoder is removed from all check system/status page
This commit is contained in:
parent
2ef6d230f9
commit
2f689ed583
|
@ -775,7 +775,6 @@ class ApiController extends Zend_Controller_Action
|
||||||
"rabbitmq"=>Application_Model_Systemstatus::GetRabbitMqStatus(),
|
"rabbitmq"=>Application_Model_Systemstatus::GetRabbitMqStatus(),
|
||||||
"pypo"=>Application_Model_Systemstatus::GetPypoStatus(),
|
"pypo"=>Application_Model_Systemstatus::GetPypoStatus(),
|
||||||
"liquidsoap"=>Application_Model_Systemstatus::GetLiquidsoapStatus(),
|
"liquidsoap"=>Application_Model_Systemstatus::GetLiquidsoapStatus(),
|
||||||
"show_recorder"=>Application_Model_Systemstatus::GetShowRecorderStatus(),
|
|
||||||
"media_monitor"=>Application_Model_Systemstatus::GetMediaMonitorStatus()
|
"media_monitor"=>Application_Model_Systemstatus::GetMediaMonitorStatus()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,46 +22,10 @@ class SystemstatusController extends Zend_Controller_Action
|
||||||
"rabbitmq-server"=>Application_Model_Systemstatus::GetRabbitMqStatus()
|
"rabbitmq-server"=>Application_Model_Systemstatus::GetRabbitMqStatus()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isset($_SERVER["AIRTIME_SRV"])){
|
|
||||||
$services["show-recorder"]=Application_Model_Systemstatus::GetShowRecorderStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
$partitions = Application_Model_Systemstatus::GetDiskInfo();
|
$partitions = Application_Model_Systemstatus::GetDiskInfo();
|
||||||
|
|
||||||
$this->view->status = new StdClass;
|
$this->view->status = new StdClass;
|
||||||
$this->view->status->services = $services;
|
$this->view->status->services = $services;
|
||||||
$this->view->status->partitions = $partitions;
|
$this->view->status->partitions = $partitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLogFileAction()
|
|
||||||
{
|
|
||||||
$log_files = array("pypo"=>"/var/log/airtime/pypo/pypo.log",
|
|
||||||
"liquidsoap"=>"/var/log/airtime/pypo-liquidsoap/ls_script.log",
|
|
||||||
"media-monitor"=>"/var/log/airtime/media-monitor/media-monitor.log",
|
|
||||||
"show-recorder"=>"/var/log/airtime/show-recorder/show-recorder.log",
|
|
||||||
"icecast2"=>"/var/log/icecast2/error.log");
|
|
||||||
|
|
||||||
$id = $this->_getParam('id');
|
|
||||||
Logging::log($id);
|
|
||||||
|
|
||||||
if (array_key_exists($id, $log_files)){
|
|
||||||
$filepath = $log_files[$id];
|
|
||||||
$filename = basename($filepath);
|
|
||||||
header("Content-Disposition: attachment; filename=$filename");
|
|
||||||
header("Content-Length: " . filesize($filepath));
|
|
||||||
// !! binary mode !!
|
|
||||||
$fp = fopen($filepath, 'rb');
|
|
||||||
|
|
||||||
//We can have multiple levels of output buffering. Need to
|
|
||||||
//keep looping until all have been disabled!!!
|
|
||||||
//http://www.php.net/manual/en/function.ob-end-flush.php
|
|
||||||
while (@ob_end_flush());
|
|
||||||
|
|
||||||
fpassthru($fp);
|
|
||||||
fclose($fp);
|
|
||||||
|
|
||||||
//make sure to exit here so that no other output is sent.
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ class RabbitMqPlugin extends Zend_Controller_Plugin_Abstract
|
||||||
$md = array('schedule' => Application_Model_Schedule::GetScheduledPlaylists());
|
$md = array('schedule' => Application_Model_Schedule::GetScheduledPlaylists());
|
||||||
Application_Model_RabbitMq::SendMessageToPypo("update_schedule", $md);
|
Application_Model_RabbitMq::SendMessageToPypo("update_schedule", $md);
|
||||||
if (!isset($_SERVER['AIRTIME_SRV'])){
|
if (!isset($_SERVER['AIRTIME_SRV'])){
|
||||||
Application_Model_RabbitMq::SendMessageToShowRecorder("update_schedule");
|
Application_Model_RabbitMq::SendMessageToShowRecorder("update_recorder_schedule");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ class Application_Model_RabbitMq
|
||||||
$channel = $conn->channel();
|
$channel = $conn->channel();
|
||||||
$channel->access_request($CC_CONFIG["rabbitmq"]["vhost"], false, false, true, true);
|
$channel->access_request($CC_CONFIG["rabbitmq"]["vhost"], false, false, true, true);
|
||||||
|
|
||||||
$EXCHANGE = 'airtime-show-recorder';
|
$EXCHANGE = 'airtime-pypo';
|
||||||
$channel->exchange_declare($EXCHANGE, 'direct', false, true);
|
$channel->exchange_declare($EXCHANGE, 'direct', false, true);
|
||||||
|
|
||||||
$now = new DateTime("@".time()); //in UTC timezone
|
$now = new DateTime("@".time()); //in UTC timezone
|
||||||
|
@ -82,7 +82,7 @@ class Application_Model_RabbitMq
|
||||||
|
|
||||||
$temp['event_type'] = $event_type;
|
$temp['event_type'] = $event_type;
|
||||||
$temp['server_timezone'] = Application_Model_Preference::GetTimezone();
|
$temp['server_timezone'] = Application_Model_Preference::GetTimezone();
|
||||||
if($event_type = "update_schedule"){
|
if($event_type = "update_recorder_schedule"){
|
||||||
$temp['shows'] = Application_Model_Show::getShows($now, $end_timestamp, $excludeInstance=NULL, $onlyRecord=TRUE);
|
$temp['shows'] = Application_Model_Show::getShows($now, $end_timestamp, $excludeInstance=NULL, $onlyRecord=TRUE);
|
||||||
}
|
}
|
||||||
$data = json_encode($temp);
|
$data = json_encode($temp);
|
||||||
|
|
|
@ -147,21 +147,6 @@ class Application_Model_Systemstatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function GetShowRecorderStatus(){
|
|
||||||
|
|
||||||
$component = CcServiceRegisterQuery::create()->findOneByDbName("show-recorder");
|
|
||||||
if (is_null($component)){
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
$ip = $component->getDbIp();
|
|
||||||
|
|
||||||
$docRoot = self::GetMonitStatus($ip);
|
|
||||||
$data = self::ExtractServiceInformation($docRoot, "airtime-show-recorder");
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function GetMediaMonitorStatus(){
|
public static function GetMediaMonitorStatus(){
|
||||||
|
|
||||||
$component = CcServiceRegisterQuery::create()->findOneByDbName("media-monitor");
|
$component = CcServiceRegisterQuery::create()->findOneByDbName("media-monitor");
|
||||||
|
|
|
@ -91,14 +91,6 @@ class AirtimeIni
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!copy(__DIR__."/../../python_apps/show-recorder/recorder.cfg", AirtimeIni::CONF_FILE_RECORDER)){
|
|
||||||
echo "Could not copy recorder.cfg to /etc/airtime/. Exiting.";
|
|
||||||
exit(1);
|
|
||||||
} else if (!self::ChangeFileOwnerGroupMod(AirtimeIni::CONF_FILE_RECORDER, self::CONF_PYPO_GRP)){
|
|
||||||
echo "Could not set ownership of recorder.cfg to 'pypo'. Exiting.";
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!copy(__DIR__."/../../python_apps/pypo/liquidsoap_scripts/liquidsoap.cfg", AirtimeIni::CONF_FILE_LIQUIDSOAP)){
|
if (!copy(__DIR__."/../../python_apps/pypo/liquidsoap_scripts/liquidsoap.cfg", AirtimeIni::CONF_FILE_LIQUIDSOAP)){
|
||||||
echo "Could not copy liquidsoap.cfg to /etc/airtime/. Exiting.";
|
echo "Could not copy liquidsoap.cfg to /etc/airtime/. Exiting.";
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|
|
@ -429,12 +429,9 @@ class AirtimeInstall
|
||||||
AirtimeIni::CONF_FILE_PYPO,
|
AirtimeIni::CONF_FILE_PYPO,
|
||||||
AirtimeIni::CONF_FILE_RECORDER,
|
AirtimeIni::CONF_FILE_RECORDER,
|
||||||
"/usr/lib/airtime/pypo",
|
"/usr/lib/airtime/pypo",
|
||||||
"/usr/lib/airtime/show-recorder",
|
|
||||||
"/var/log/airtime",
|
"/var/log/airtime",
|
||||||
"/var/log/airtime/pypo",
|
"/var/log/airtime/pypo",
|
||||||
"/var/log/airtime/show-recorder",
|
"/var/tmp/airtime/pypo");
|
||||||
"/var/tmp/airtime/pypo",
|
|
||||||
"/var/tmp/airtime/show-recorder");
|
|
||||||
foreach ($dirs as $f) {
|
foreach ($dirs as $f) {
|
||||||
if (file_exists($f)) {
|
if (file_exists($f)) {
|
||||||
echo "+ $f".PHP_EOL;
|
echo "+ $f".PHP_EOL;
|
||||||
|
|
|
@ -63,9 +63,9 @@ if [ "$python_service" -eq "0" ]; then
|
||||||
if [ "$pypo" = "t" ]; then
|
if [ "$pypo" = "t" ]; then
|
||||||
python $AIRTIMEROOT/python_apps/pypo/install/pypo-copy-files.py
|
python $AIRTIMEROOT/python_apps/pypo/install/pypo-copy-files.py
|
||||||
fi
|
fi
|
||||||
if [ "$showrecorder" = "t" ]; then
|
#if [ "$showrecorder" = "t" ]; then
|
||||||
python $AIRTIMEROOT/python_apps/show-recorder/install/recorder-copy-files.py
|
# python $AIRTIMEROOT/python_apps/show-recorder/install/recorder-copy-files.py
|
||||||
fi
|
#fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p /usr/lib/airtime
|
mkdir -p /usr/lib/airtime
|
||||||
|
|
|
@ -36,9 +36,9 @@ fi
|
||||||
if [ "$pypo" = "t" ]; then
|
if [ "$pypo" = "t" ]; then
|
||||||
python $AIRTIMEROOT/python_apps/pypo/install/pypo-initialize.py
|
python $AIRTIMEROOT/python_apps/pypo/install/pypo-initialize.py
|
||||||
fi
|
fi
|
||||||
if [ "$showrecorder" = "t" ]; then
|
#if [ "$showrecorder" = "t" ]; then
|
||||||
python $AIRTIMEROOT/python_apps/show-recorder/install/recorder-initialize.py
|
# python $AIRTIMEROOT/python_apps/show-recorder/install/recorder-initialize.py
|
||||||
fi
|
#fi
|
||||||
|
|
||||||
|
|
||||||
# Start monit if it is not running, or restart if it is.
|
# Start monit if it is not running, or restart if it is.
|
||||||
|
@ -61,9 +61,9 @@ if [ "$disable_auto_start_services" = "f" ]; then
|
||||||
monit monitor airtime-playout
|
monit monitor airtime-playout
|
||||||
monit monitor airtime-liquidsoap
|
monit monitor airtime-liquidsoap
|
||||||
fi
|
fi
|
||||||
if [ "$showrecorder" = "t" ]; then
|
# if [ "$showrecorder" = "t" ]; then
|
||||||
monit monitor airtime-show-recorder
|
# monit monitor airtime-show-recorder
|
||||||
fi
|
# fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
monit monitor rabbitmq-server
|
monit monitor rabbitmq-server
|
||||||
|
|
|
@ -38,8 +38,8 @@ echo "* Pypo"
|
||||||
python $AIRTIMEROOT/python_apps/pypo/install/pypo-remove-files.py
|
python $AIRTIMEROOT/python_apps/pypo/install/pypo-remove-files.py
|
||||||
echo "* Media-Monitor"
|
echo "* Media-Monitor"
|
||||||
python $AIRTIMEROOT/python_apps/media-monitor/install/media-monitor-remove-files.py
|
python $AIRTIMEROOT/python_apps/media-monitor/install/media-monitor-remove-files.py
|
||||||
echo "* Show-Recorder"
|
#echo "* Show-Recorder"
|
||||||
python $AIRTIMEROOT/python_apps/show-recorder/install/recorder-remove-files.py
|
#python $AIRTIMEROOT/python_apps/show-recorder/install/recorder-remove-files.py
|
||||||
|
|
||||||
#remove symlinks
|
#remove symlinks
|
||||||
rm -f /usr/bin/airtime-import
|
rm -f /usr/bin/airtime-import
|
||||||
|
|
|
@ -19,8 +19,8 @@ set +e
|
||||||
monit unmonitor airtime-media-monitor >/dev/null 2>&1
|
monit unmonitor airtime-media-monitor >/dev/null 2>&1
|
||||||
monit unmonitor airtime-liquidsoap >/dev/null 2>&1
|
monit unmonitor airtime-liquidsoap >/dev/null 2>&1
|
||||||
monit unmonitor airtime-playout >/dev/null 2>&1
|
monit unmonitor airtime-playout >/dev/null 2>&1
|
||||||
monit unmonitor airtime-show-recorder >/dev/null 2>&1
|
#monit unmonitor airtime-show-recorder >/dev/null 2>&1
|
||||||
#monit unmonitor rabbitmq-server
|
monit unmonitor rabbitmq-server
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
#virtualenv_bin="/usr/lib/airtime/airtime_virtualenv/bin/"
|
#virtualenv_bin="/usr/lib/airtime/airtime_virtualenv/bin/"
|
||||||
|
@ -29,7 +29,7 @@ set -e
|
||||||
#uninitialize Airtime services
|
#uninitialize Airtime services
|
||||||
python $AIRTIMEROOT/python_apps/pypo/install/pypo-uninitialize.py
|
python $AIRTIMEROOT/python_apps/pypo/install/pypo-uninitialize.py
|
||||||
python $AIRTIMEROOT/python_apps/media-monitor/install/media-monitor-uninitialize.py
|
python $AIRTIMEROOT/python_apps/media-monitor/install/media-monitor-uninitialize.py
|
||||||
python $AIRTIMEROOT/python_apps/show-recorder/install/recorder-uninitialize.py
|
#python $AIRTIMEROOT/python_apps/show-recorder/install/recorder-uninitialize.py
|
||||||
|
|
||||||
#call Airtime uninstall script
|
#call Airtime uninstall script
|
||||||
php --php-ini ${SCRIPTPATH}/../airtime-php.ini ${SCRIPTPATH}/airtime-uninstall.php
|
php --php-ini ${SCRIPTPATH}/../airtime-php.ini ${SCRIPTPATH}/airtime-uninstall.php
|
||||||
|
|
|
@ -24,14 +24,17 @@ import string
|
||||||
|
|
||||||
AIRTIME_VERSION = "2.1.0"
|
AIRTIME_VERSION = "2.1.0"
|
||||||
|
|
||||||
def api_client_factory(config):
|
def api_client_factory(config, logger=None):
|
||||||
logger = logging.getLogger()
|
if logger != None:
|
||||||
|
temp_logger = logger
|
||||||
|
else:
|
||||||
|
temp_logger = logging.getLogger()
|
||||||
if config["api_client"] == "airtime":
|
if config["api_client"] == "airtime":
|
||||||
return AirTimeApiClient()
|
return AirTimeApiClient(temp_logger)
|
||||||
elif config["api_client"] == "obp":
|
elif config["api_client"] == "obp":
|
||||||
return ObpApiClient()
|
return ObpApiClient()
|
||||||
else:
|
else:
|
||||||
logger.info('API Client "'+config["api_client"]+'" not supported. Please check your config file.\n')
|
temp_logger.info('API Client "'+config["api_client"]+'" not supported. Please check your config file.\n')
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def to_unicode(obj, encoding='utf-8'):
|
def to_unicode(obj, encoding='utf-8'):
|
||||||
|
@ -161,17 +164,20 @@ class ApiClientInterface:
|
||||||
|
|
||||||
class AirTimeApiClient(ApiClientInterface):
|
class AirTimeApiClient(ApiClientInterface):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, logger=None):
|
||||||
|
if logger != None:
|
||||||
|
self.logger = logger
|
||||||
|
else:
|
||||||
|
self.logger = logging.getLogger()
|
||||||
# loading config file
|
# loading config file
|
||||||
try:
|
try:
|
||||||
self.config = ConfigObj('/etc/airtime/api_client.cfg')
|
self.config = ConfigObj('/etc/airtime/api_client.cfg')
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger = logging.getLogger()
|
self.logger.error('Error loading config file: %s', e)
|
||||||
logger.error('Error loading config file: %s', e)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def get_response_from_server(self, url):
|
def get_response_from_server(self, url):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
successful_response = False
|
successful_response = False
|
||||||
|
|
||||||
while not successful_response:
|
while not successful_response:
|
||||||
|
@ -191,7 +197,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
|
|
||||||
|
|
||||||
def __get_airtime_version(self, verbose = True):
|
def __get_airtime_version(self, verbose = True):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["version_url"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["version_url"])
|
||||||
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"])
|
||||||
|
@ -211,7 +217,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return version
|
return version
|
||||||
|
|
||||||
def test(self):
|
def test(self):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
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')
|
||||||
schedule = items["playlists"]
|
schedule = items["playlists"]
|
||||||
logger.debug("Number of playlists found: %s", str(len(schedule)))
|
logger.debug("Number of playlists found: %s", str(len(schedule)))
|
||||||
|
@ -227,7 +233,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
|
|
||||||
|
|
||||||
def is_server_compatible(self, verbose = True):
|
def is_server_compatible(self, verbose = True):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
version = self.__get_airtime_version(verbose)
|
version = self.__get_airtime_version(verbose)
|
||||||
if (version == -1):
|
if (version == -1):
|
||||||
if (verbose):
|
if (verbose):
|
||||||
|
@ -246,7 +252,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
|
|
||||||
|
|
||||||
def get_schedule(self, start=None, end=None):
|
def get_schedule(self, start=None, end=None):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
|
|
||||||
# Construct the URL
|
# Construct the 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"])
|
||||||
|
@ -267,7 +273,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
|
|
||||||
|
|
||||||
def get_media(self, uri, dst):
|
def get_media(self, uri, dst):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
src = uri + "/api_key/%%api_key%%"
|
src = uri + "/api_key/%%api_key%%"
|
||||||
|
@ -284,7 +290,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
Tell server that the scheduled *playlist* has started.
|
Tell server that the scheduled *playlist* has started.
|
||||||
"""
|
"""
|
||||||
def notify_scheduled_item_start_playing(self, pkey, schedule):
|
def notify_scheduled_item_start_playing(self, pkey, schedule):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
playlist = schedule[pkey]
|
playlist = schedule[pkey]
|
||||||
schedule_id = playlist["schedule_id"]
|
schedule_id = playlist["schedule_id"]
|
||||||
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"])
|
||||||
|
@ -311,7 +317,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
liquidsoap in get_liquidsoap_data().
|
liquidsoap in get_liquidsoap_data().
|
||||||
"""
|
"""
|
||||||
def notify_media_item_start_playing(self, data, media_id):
|
def notify_media_item_start_playing(self, data, media_id):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
response = ''
|
response = ''
|
||||||
try:
|
try:
|
||||||
schedule_id = data
|
schedule_id = data
|
||||||
|
@ -331,7 +337,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_liquidsoap_data(self, pkey, schedule):
|
def get_liquidsoap_data(self, pkey, schedule):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
playlist = schedule[pkey]
|
playlist = schedule[pkey]
|
||||||
data = dict()
|
data = dict()
|
||||||
try:
|
try:
|
||||||
|
@ -341,13 +347,12 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_shows_to_record(self):
|
def get_shows_to_record(self):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
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"])
|
||||||
logger.debug(url)
|
logger.debug(url)
|
||||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||||
|
|
||||||
response = self.get_response_from_server(url)
|
response = self.get_response_from_server(url)
|
||||||
|
|
||||||
response = json.loads(response)
|
response = json.loads(response)
|
||||||
|
@ -360,7 +365,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def upload_recorded_show(self, data, headers):
|
def upload_recorded_show(self, data, headers):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
response = ''
|
response = ''
|
||||||
|
|
||||||
retries = int(self.config["upload_retries"])
|
retries = int(self.config["upload_retries"])
|
||||||
|
@ -394,7 +399,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def setup_media_monitor(self):
|
def setup_media_monitor(self):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
|
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
|
@ -411,7 +416,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def update_media_metadata(self, md, mode, is_record=False):
|
def update_media_metadata(self, md, mode, is_record=False):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
@ -456,7 +461,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
#Note that these are relative paths to the given directory. The full
|
#Note that these are relative paths to the given directory. The full
|
||||||
#path is not returned.
|
#path is not returned.
|
||||||
def list_all_db_files(self, dir_id):
|
def list_all_db_files(self, dir_id):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
try:
|
try:
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["list_all_db_files"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["list_all_db_files"])
|
||||||
|
|
||||||
|
@ -473,7 +478,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def list_all_watched_dirs(self):
|
def list_all_watched_dirs(self):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
try:
|
try:
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["list_all_watched_dirs"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["list_all_watched_dirs"])
|
||||||
|
|
||||||
|
@ -489,7 +494,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def add_watched_dir(self, path):
|
def add_watched_dir(self, path):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
try:
|
try:
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["add_watched_dir"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["add_watched_dir"])
|
||||||
|
|
||||||
|
@ -506,7 +511,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def remove_watched_dir(self, path):
|
def remove_watched_dir(self, path):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
try:
|
try:
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["remove_watched_dir"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["remove_watched_dir"])
|
||||||
|
|
||||||
|
@ -523,7 +528,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def set_storage_dir(self, path):
|
def set_storage_dir(self, path):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
try:
|
try:
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["set_storage_dir"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["set_storage_dir"])
|
||||||
|
|
||||||
|
@ -540,7 +545,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_stream_setting(self):
|
def get_stream_setting(self):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
try:
|
try:
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["get_stream_setting"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["get_stream_setting"])
|
||||||
|
|
||||||
|
@ -561,7 +566,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
via a http server.
|
via a http server.
|
||||||
"""
|
"""
|
||||||
def register_component(self, component):
|
def register_component(self, component):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
try:
|
try:
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["register_component"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["register_component"])
|
||||||
|
|
||||||
|
@ -573,7 +578,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
logger.error("Exception: %s", e)
|
logger.error("Exception: %s", e)
|
||||||
|
|
||||||
def notify_liquidsoap_status(self, msg, stream_id, time):
|
def notify_liquidsoap_status(self, msg, stream_id, time):
|
||||||
logger = logging.getLogger()
|
logger = self.logger
|
||||||
try:
|
try:
|
||||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_liquidsoap_status"])
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_liquidsoap_status"])
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,8 @@ try:
|
||||||
create_dir(config['file_dir'])
|
create_dir(config['file_dir'])
|
||||||
create_dir(config['tmp_dir'])
|
create_dir(config['tmp_dir'])
|
||||||
|
|
||||||
|
create_dir(config["base_recorded_files"])
|
||||||
|
|
||||||
#copy files to bin dir
|
#copy files to bin dir
|
||||||
copy_dir("%s/.."%current_script_dir, config["bin_dir"]+"/bin/")
|
copy_dir("%s/.."%current_script_dir, config["bin_dir"]+"/bin/")
|
||||||
|
|
||||||
|
@ -72,6 +74,7 @@ try:
|
||||||
os.system("chmod 755 "+os.path.join(config["bin_dir"], "bin/liquidsoap_scripts/notify.sh"))
|
os.system("chmod 755 "+os.path.join(config["bin_dir"], "bin/liquidsoap_scripts/notify.sh"))
|
||||||
os.system("chown -R pypo:pypo "+config["bin_dir"])
|
os.system("chown -R pypo:pypo "+config["bin_dir"])
|
||||||
os.system("chown -R pypo:pypo "+config["cache_base_dir"])
|
os.system("chown -R pypo:pypo "+config["cache_base_dir"])
|
||||||
|
os.system("chown -R pypo:pypo "+config["base_recorded_files"])
|
||||||
|
|
||||||
#copy init.d script
|
#copy init.d script
|
||||||
shutil.copy(config["bin_dir"]+"/bin/airtime-playout-init-d", "/etc/init.d/airtime-playout")
|
shutil.copy(config["bin_dir"]+"/bin/airtime-playout-init-d", "/etc/init.d/airtime-playout")
|
||||||
|
|
|
@ -1,34 +1,46 @@
|
||||||
[loggers]
|
[loggers]
|
||||||
keys=root,fetch,push
|
keys=root,fetch,push,recorder
|
||||||
|
|
||||||
[handlers]
|
[handlers]
|
||||||
keys=fileOutHandler
|
keys=pypo,recorder
|
||||||
|
|
||||||
[formatters]
|
[formatters]
|
||||||
keys=simpleFormatter
|
keys=simpleFormatter
|
||||||
|
|
||||||
[logger_root]
|
[logger_root]
|
||||||
level=DEBUG
|
level=DEBUG
|
||||||
handlers=fileOutHandler
|
handlers=pypo
|
||||||
|
|
||||||
[logger_fetch]
|
[logger_fetch]
|
||||||
level=DEBUG
|
level=DEBUG
|
||||||
handlers=fileOutHandler
|
handlers=pypo
|
||||||
qualname=fetch
|
qualname=fetch
|
||||||
propagate=0
|
propagate=0
|
||||||
|
|
||||||
[logger_push]
|
[logger_push]
|
||||||
level=DEBUG
|
level=DEBUG
|
||||||
handlers=fileOutHandler
|
handlers=pypo
|
||||||
qualname=push
|
qualname=push
|
||||||
propagate=0
|
propagate=0
|
||||||
|
|
||||||
[handler_fileOutHandler]
|
[logger_recorder]
|
||||||
|
level=DEBUG
|
||||||
|
handlers=recorder
|
||||||
|
qualname=recorder
|
||||||
|
propagate=0
|
||||||
|
|
||||||
|
[handler_pypo]
|
||||||
class=logging.handlers.RotatingFileHandler
|
class=logging.handlers.RotatingFileHandler
|
||||||
level=DEBUG
|
level=DEBUG
|
||||||
formatter=simpleFormatter
|
formatter=simpleFormatter
|
||||||
args=("/var/log/airtime/pypo/pypo.log", 'a', 1000000, 5,)
|
args=("/var/log/airtime/pypo/pypo.log", 'a', 1000000, 5,)
|
||||||
|
|
||||||
|
[handler_recorder]
|
||||||
|
class=logging.handlers.RotatingFileHandler
|
||||||
|
level=DEBUG
|
||||||
|
formatter=simpleFormatter
|
||||||
|
args=("/var/log/airtime/pypo/show-recorder.log", 'a', 1000000, 5,)
|
||||||
|
|
||||||
[formatter_simpleFormatter]
|
[formatter_simpleFormatter]
|
||||||
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
|
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
|
||||||
datefmt=
|
datefmt=
|
||||||
|
|
|
@ -15,6 +15,7 @@ from Queue import Queue
|
||||||
|
|
||||||
from pypopush import PypoPush
|
from pypopush import PypoPush
|
||||||
from pypofetch import PypoFetch
|
from pypofetch import PypoFetch
|
||||||
|
from recorder import Recorder
|
||||||
|
|
||||||
from configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
|
|
||||||
|
@ -128,11 +129,17 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
q = Queue()
|
q = Queue()
|
||||||
|
|
||||||
|
recorder_q = Queue()
|
||||||
|
|
||||||
pp = PypoPush(q)
|
pp = PypoPush(q)
|
||||||
pp.daemon = True
|
pp.daemon = True
|
||||||
pp.start()
|
pp.start()
|
||||||
|
|
||||||
pf = PypoFetch(q)
|
recorder = Recorder(recorder_q)
|
||||||
|
recorder.daemon = True
|
||||||
|
recorder.start()
|
||||||
|
|
||||||
|
pf = PypoFetch(q, recorder_q)
|
||||||
pf.daemon = True
|
pf.daemon = True
|
||||||
pf.start()
|
pf.start()
|
||||||
|
|
||||||
|
|
|
@ -71,3 +71,17 @@ push_interval = 1 # in seconds
|
||||||
# while 'otf' (on the fly) cues while loading into ls
|
# while 'otf' (on the fly) cues while loading into ls
|
||||||
# (needs the post_processor patch)
|
# (needs the post_processor patch)
|
||||||
cue_style = 'pre'
|
cue_style = 'pre'
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Recorded Audio settings #
|
||||||
|
############################################
|
||||||
|
record_bitrate = 256
|
||||||
|
record_samplerate = 44100
|
||||||
|
record_channels = 2
|
||||||
|
record_sample_size = 16
|
||||||
|
|
||||||
|
#can be either ogg|mp3, mp3 recording requires installation of the package "lame"
|
||||||
|
record_file_type = 'ogg'
|
||||||
|
|
||||||
|
# base path to store recordered shows at
|
||||||
|
base_recorded_files = '/var/tmp/airtime/show-recorder/'
|
||||||
|
|
|
@ -42,11 +42,12 @@ except Exception, e:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
class PypoFetch(Thread):
|
class PypoFetch(Thread):
|
||||||
def __init__(self, q):
|
def __init__(self, q, recorder_q):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.api_client = api_client.api_client_factory(config)
|
self.api_client = api_client.api_client_factory(config)
|
||||||
self.set_export_source('scheduler')
|
self.set_export_source('scheduler')
|
||||||
self.queue = q
|
self.queue = q
|
||||||
|
self.recorder_queue = recorder_q
|
||||||
self.schedule_data = []
|
self.schedule_data = []
|
||||||
logger = logging.getLogger('fetch')
|
logger = logging.getLogger('fetch')
|
||||||
logger.info("PypoFetch: init complete")
|
logger.info("PypoFetch: init complete")
|
||||||
|
@ -94,6 +95,12 @@ class PypoFetch(Thread):
|
||||||
elif command == 'cancel_current_show':
|
elif command == 'cancel_current_show':
|
||||||
logger.info("Cancel current show command received...")
|
logger.info("Cancel current show command received...")
|
||||||
self.stop_current_show()
|
self.stop_current_show()
|
||||||
|
elif command == 'update_recorder_schedule':
|
||||||
|
temp = m
|
||||||
|
if temp is not None:
|
||||||
|
self.parse_shows(temp)
|
||||||
|
elif command == 'cancel_recording':
|
||||||
|
self.recorder_queue.put('cancel_recording')
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error("Exception in handling RabbitMQ message: %s", e)
|
logger.error("Exception in handling RabbitMQ message: %s", e)
|
||||||
|
|
||||||
|
@ -313,6 +320,30 @@ class PypoFetch(Thread):
|
||||||
try: self.cleanup(self.export_source)
|
try: self.cleanup(self.export_source)
|
||||||
except Exception, e: logger.error("%s", e)
|
except Exception, e: logger.error("%s", e)
|
||||||
|
|
||||||
|
def getDateTimeObj(self,time):
|
||||||
|
timeinfo = time.split(" ")
|
||||||
|
date = timeinfo[0].split("-")
|
||||||
|
time = timeinfo[1].split(":")
|
||||||
|
|
||||||
|
date = map(int, date)
|
||||||
|
time = map(int, time)
|
||||||
|
|
||||||
|
return datetime(date[0], date[1], date[2], time[0], time[1], time[2], 0, None)
|
||||||
|
|
||||||
|
def parse_shows(self, m):
|
||||||
|
logger = logging.getLogger('fetch')
|
||||||
|
logger.info("Parsing recording show schedules...")
|
||||||
|
shows_to_record = {}
|
||||||
|
shows = m['shows']
|
||||||
|
for show in shows:
|
||||||
|
show_starts = self.getDateTimeObj(show[u'starts'])
|
||||||
|
show_end = self.getDateTimeObj(show[u'ends'])
|
||||||
|
time_delta = show_end - show_starts
|
||||||
|
|
||||||
|
shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id'], show[u'name'], m['server_timezone']]
|
||||||
|
self.recorder_queue.put(shows_to_record)
|
||||||
|
logger.info(shows_to_record)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
In this function every audio file is cut as necessary (cue_in/cue_out != 0)
|
In this function every audio file is cut as necessary (cue_in/cue_out != 0)
|
||||||
|
@ -487,6 +518,17 @@ class PypoFetch(Thread):
|
||||||
if status == 1:
|
if status == 1:
|
||||||
logger.info("Bootstrap schedule received: %s", self.schedule_data)
|
logger.info("Bootstrap schedule received: %s", self.schedule_data)
|
||||||
self.process_schedule(self.schedule_data, "scheduler", True)
|
self.process_schedule(self.schedule_data, "scheduler", True)
|
||||||
|
|
||||||
|
# Bootstrap: since we are just starting up, we need to grab the
|
||||||
|
# most recent schedule. After that we can just wait for updates.
|
||||||
|
try:
|
||||||
|
temp = self.api_client.get_shows_to_record()
|
||||||
|
if temp is not None:
|
||||||
|
self.parse_shows(temp)
|
||||||
|
logger.info("Bootstrap recorder schedule received: %s", temp)
|
||||||
|
except Exception, e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
logger.info("Bootstrap complete: got initial copy of the schedule")
|
logger.info("Bootstrap complete: got initial copy of the schedule")
|
||||||
|
|
||||||
|
|
||||||
|
@ -515,6 +557,16 @@ class PypoFetch(Thread):
|
||||||
status, self.schedule_data = self.api_client.get_schedule()
|
status, self.schedule_data = self.api_client.get_schedule()
|
||||||
if status == 1:
|
if status == 1:
|
||||||
self.process_schedule(self.schedule_data, "scheduler", False)
|
self.process_schedule(self.schedule_data, "scheduler", False)
|
||||||
|
"""
|
||||||
|
Fetch recorder schedule
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
temp = self.api_client.get_shows_to_record()
|
||||||
|
if temp is not None:
|
||||||
|
self.parse_shows(temp)
|
||||||
|
logger.info("updated recorder schedule received: %s", temp)
|
||||||
|
except Exception, e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
loops += 1
|
loops += 1
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,270 @@
|
||||||
|
import urllib
|
||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
|
import pytz
|
||||||
|
import signal
|
||||||
|
import math
|
||||||
|
|
||||||
|
from configobj import ConfigObj
|
||||||
|
|
||||||
|
from poster.encode import multipart_encode
|
||||||
|
from poster.streaminghttp import register_openers
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
from subprocess import Popen
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
import mutagen
|
||||||
|
|
||||||
|
from api_clients import api_client
|
||||||
|
|
||||||
|
# For RabbitMQ
|
||||||
|
from kombu.connection import BrokerConnection
|
||||||
|
from kombu.messaging import Exchange, Queue, Consumer, Producer
|
||||||
|
|
||||||
|
# loading config file
|
||||||
|
try:
|
||||||
|
config = ConfigObj('/etc/airtime/pypo.cfg')
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.error('Error loading config file: %s', e)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
def getDateTimeObj(time):
|
||||||
|
timeinfo = time.split(" ")
|
||||||
|
date = timeinfo[0].split("-")
|
||||||
|
time = timeinfo[1].split(":")
|
||||||
|
|
||||||
|
date = map(int, date)
|
||||||
|
time = map(int, time)
|
||||||
|
|
||||||
|
return datetime.datetime(date[0], date[1], date[2], time[0], time[1], time[2], 0, None)
|
||||||
|
|
||||||
|
PUSH_INTERVAL = 2
|
||||||
|
|
||||||
|
class ShowRecorder(Thread):
|
||||||
|
|
||||||
|
def __init__ (self, show_instance, show_name, filelength, start_time):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.logger = logging.getLogger('recorder')
|
||||||
|
self.api_client = api_client.api_client_factory(config, self.logger)
|
||||||
|
self.filelength = filelength
|
||||||
|
self.start_time = start_time
|
||||||
|
self.show_instance = show_instance
|
||||||
|
self.show_name = show_name
|
||||||
|
self.p = None
|
||||||
|
|
||||||
|
def record_show(self):
|
||||||
|
length = str(self.filelength)+".0"
|
||||||
|
filename = self.start_time
|
||||||
|
filename = filename.replace(" ", "-")
|
||||||
|
|
||||||
|
if config["record_file_type"] in ["mp3", "ogg"]:
|
||||||
|
filetype = config["record_file_type"]
|
||||||
|
else:
|
||||||
|
filetype = "ogg";
|
||||||
|
|
||||||
|
filepath = "%s%s.%s" % (config["base_recorded_files"], filename, filetype)
|
||||||
|
|
||||||
|
br = config["record_bitrate"]
|
||||||
|
sr = config["record_samplerate"]
|
||||||
|
c = config["record_channels"]
|
||||||
|
ss = config["record_sample_size"]
|
||||||
|
|
||||||
|
#-f:16,2,44100
|
||||||
|
#-b:256
|
||||||
|
command = "ecasound -f:%s,%s,%s -i alsa -o %s,%s000 -t:%s" % (ss, c, sr, filepath, br, length)
|
||||||
|
args = command.split(" ")
|
||||||
|
|
||||||
|
self.logger.info("starting record")
|
||||||
|
self.logger.info("command " + command)
|
||||||
|
|
||||||
|
self.p = Popen(args)
|
||||||
|
|
||||||
|
#blocks at the following line until the child process
|
||||||
|
#quits
|
||||||
|
code = self.p.wait()
|
||||||
|
|
||||||
|
self.logger.info("finishing record, return code %s", self.p.returncode)
|
||||||
|
code = self.p.returncode
|
||||||
|
|
||||||
|
self.p = None
|
||||||
|
|
||||||
|
return code, filepath
|
||||||
|
|
||||||
|
def cancel_recording(self):
|
||||||
|
#add 3 second delay before actually cancelling the show. The reason
|
||||||
|
#for this is because it appears that ecasound starts 1 second later than
|
||||||
|
#it should, and therefore this method is sometimes incorrectly called 1
|
||||||
|
#second before the show ends.
|
||||||
|
#time.sleep(3)
|
||||||
|
|
||||||
|
#send signal interrupt (2)
|
||||||
|
self.logger.info("Show manually cancelled!")
|
||||||
|
if (self.p is not None):
|
||||||
|
self.p.send_signal(signal.SIGINT)
|
||||||
|
|
||||||
|
#if self.p is defined, then the child process ecasound is recording
|
||||||
|
def is_recording(self):
|
||||||
|
return (self.p is not None)
|
||||||
|
|
||||||
|
def upload_file(self, filepath):
|
||||||
|
|
||||||
|
filename = os.path.split(filepath)[1]
|
||||||
|
|
||||||
|
# Register the streaming http handlers with urllib2
|
||||||
|
register_openers()
|
||||||
|
|
||||||
|
# headers contains the necessary Content-Type and Content-Length
|
||||||
|
# datagen is a generator object that yields the encoded parameters
|
||||||
|
datagen, headers = multipart_encode({"file": open(filepath, "rb"), 'name': filename, 'show_instance': self.show_instance})
|
||||||
|
|
||||||
|
self.api_client.upload_recorded_show(datagen, headers)
|
||||||
|
|
||||||
|
def set_metadata_and_save(self, filepath):
|
||||||
|
try:
|
||||||
|
date = self.start_time
|
||||||
|
md = date.split(" ")
|
||||||
|
time = md[1].replace(":", "-")
|
||||||
|
self.logger.info("time: %s" % time)
|
||||||
|
|
||||||
|
name = time+"-"+self.show_name
|
||||||
|
artist = api_client.encode_to("Airtime Show Recorder",'utf-8')
|
||||||
|
|
||||||
|
#set some metadata for our file daemon
|
||||||
|
recorded_file = mutagen.File(filepath, easy=True)
|
||||||
|
recorded_file['title'] = name
|
||||||
|
recorded_file['artist'] = artist
|
||||||
|
recorded_file['date'] = md[0]
|
||||||
|
recorded_file['tracknumber'] = self.show_instance
|
||||||
|
recorded_file.save()
|
||||||
|
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.error("Exception: %s", e)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
code, filepath = self.record_show()
|
||||||
|
|
||||||
|
if code == 0:
|
||||||
|
try:
|
||||||
|
self.logger.info("Preparing to upload %s" % filepath)
|
||||||
|
|
||||||
|
self.set_metadata_and_save(filepath)
|
||||||
|
|
||||||
|
self.upload_file(filepath)
|
||||||
|
os.remove(filepath)
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.error(e)
|
||||||
|
else:
|
||||||
|
self.logger.info("problem recording show")
|
||||||
|
os.remove(filepath)
|
||||||
|
|
||||||
|
class Recorder(Thread):
|
||||||
|
def __init__(self, q):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.logger = logging.getLogger('recorder')
|
||||||
|
self.api_client = api_client.api_client_factory(config)
|
||||||
|
self.api_client.register_component("show-recorder")
|
||||||
|
self.sr = None
|
||||||
|
self.shows_to_record = {}
|
||||||
|
self.server_timezone = ''
|
||||||
|
self.queue = q
|
||||||
|
self.logger.info("RecorderFetch: init complete")
|
||||||
|
|
||||||
|
def handle_message(self):
|
||||||
|
if not self.queue.empty():
|
||||||
|
msg = self.queue.get()
|
||||||
|
self.logger.info("Receivied msg from Pypo Fetch: %s", msg)
|
||||||
|
if msg == 'cancel_recording':
|
||||||
|
if self.sr is not None and self.sr.is_recording():
|
||||||
|
self.sr.cancel_recording()
|
||||||
|
else:
|
||||||
|
self.shows_to_record = msg
|
||||||
|
|
||||||
|
if self.shows_to_record:
|
||||||
|
self.start_record()
|
||||||
|
|
||||||
|
def get_time_till_next_show(self):
|
||||||
|
if len(self.shows_to_record) != 0:
|
||||||
|
tnow = datetime.datetime.utcnow()
|
||||||
|
sorted_show_keys = sorted(self.shows_to_record.keys())
|
||||||
|
|
||||||
|
start_time = sorted_show_keys[0]
|
||||||
|
next_show = getDateTimeObj(start_time)
|
||||||
|
|
||||||
|
delta = next_show - tnow
|
||||||
|
out = delta.seconds
|
||||||
|
|
||||||
|
if out < 5:
|
||||||
|
self.logger.debug("Shows %s", self.shows_to_record)
|
||||||
|
self.logger.debug("Next show %s", next_show)
|
||||||
|
self.logger.debug("Now %s", tnow)
|
||||||
|
return out
|
||||||
|
|
||||||
|
def start_record(self):
|
||||||
|
if len(self.shows_to_record) != 0:
|
||||||
|
try:
|
||||||
|
delta = self.get_time_till_next_show()
|
||||||
|
if delta < 5:
|
||||||
|
self.logger.debug("sleeping %s seconds until show", delta)
|
||||||
|
time.sleep(delta)
|
||||||
|
|
||||||
|
sorted_show_keys = sorted(self.shows_to_record.keys())
|
||||||
|
start_time = sorted_show_keys[0]
|
||||||
|
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]
|
||||||
|
server_timezone = self.shows_to_record[start_time][3]
|
||||||
|
|
||||||
|
T = pytz.timezone(server_timezone)
|
||||||
|
start_time_on_UTC = getDateTimeObj(start_time)
|
||||||
|
start_time_on_server = start_time_on_UTC.replace(tzinfo=pytz.utc).astimezone(T)
|
||||||
|
start_time_formatted = '%(year)d-%(month)02d-%(day)02d %(hour)02d:%(min)02d:%(sec)02d' % \
|
||||||
|
{'year': start_time_on_server.year, 'month': start_time_on_server.month, 'day': start_time_on_server.day,\
|
||||||
|
'hour': start_time_on_server.hour, 'min': start_time_on_server.minute, 'sec': start_time_on_server.second}
|
||||||
|
self.sr = ShowRecorder(show_instance, show_name, show_length.seconds, start_time_formatted)
|
||||||
|
self.sr.start()
|
||||||
|
#remove show from shows to record.
|
||||||
|
del self.shows_to_record[start_time]
|
||||||
|
#self.time_till_next_show = self.get_time_till_next_show()
|
||||||
|
except Exception,e :
|
||||||
|
import traceback
|
||||||
|
top = traceback.format_exc()
|
||||||
|
self.logger.error('Exception: %s', e)
|
||||||
|
self.logger.error("traceback: %s", top)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Main loop of the thread:
|
||||||
|
Wait for schedule updates from RabbitMQ, but in case there arent any,
|
||||||
|
poll the server to get the upcoming schedule.
|
||||||
|
"""
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self.logger.info("Started...")
|
||||||
|
|
||||||
|
recording = False
|
||||||
|
|
||||||
|
loops = 0
|
||||||
|
heartbeat_period = math.floor(30/PUSH_INTERVAL)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if loops % heartbeat_period == 0:
|
||||||
|
self.logger.info("heartbeat")
|
||||||
|
loops = 0
|
||||||
|
try: self.handle_message()
|
||||||
|
except Exception, e:
|
||||||
|
self.logger.error('Pypo Recorder Exception: %s', e)
|
||||||
|
time.sleep(PUSH_INTERVAL)
|
||||||
|
loops += 1
|
||||||
|
except Exception,e :
|
||||||
|
import traceback
|
||||||
|
top = traceback.format_exc()
|
||||||
|
self.logger.error('Exception: %s', e)
|
||||||
|
self.logger.error("traceback: %s", top)
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
virtualenv_bin="/usr/lib/airtime/airtime_virtualenv/bin/"
|
|
||||||
. ${virtualenv_bin}activate
|
|
||||||
|
|
||||||
recorder_user="pypo"
|
|
||||||
|
|
||||||
|
|
||||||
# Location of pypo_cli.py Python script
|
|
||||||
recorder_path="/usr/lib/airtime/show-recorder/"
|
|
||||||
recorder_script="recorder.py"
|
|
||||||
|
|
||||||
api_client_path="/usr/lib/airtime/"
|
|
||||||
cd ${recorder_path}
|
|
||||||
|
|
||||||
exec 2>&1
|
|
||||||
|
|
||||||
export HOME="/var/tmp/airtime/show-recorder/"
|
|
||||||
export TERM=xterm
|
|
||||||
export PYTHONPATH=${api_client_path}
|
|
||||||
|
|
||||||
#this line works: su ${recorder_user} -c "python -u ${recorder_path}${recorder_script}"
|
|
||||||
# Note the -u when calling python! we need it to get unbuffered binary stdout and stderr
|
|
||||||
exec python -u ${recorder_path}${recorder_script} > /var/log/airtime/show-recorder/py-interpreter.log 2>&1
|
|
||||||
|
|
||||||
# EOF
|
|
|
@ -1,71 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
### BEGIN INIT INFO
|
|
||||||
# Provides: airtime-show-recorder
|
|
||||||
# Required-Start: $local_fs $remote_fs $network $syslog
|
|
||||||
# Required-Stop: $local_fs $remote_fs $network $syslog
|
|
||||||
# Default-Start: 2 3 4 5
|
|
||||||
# Default-Stop: 0 1 6
|
|
||||||
# Short-Description: Manage airtime-show-recorder daemon
|
|
||||||
### END INIT INFO
|
|
||||||
|
|
||||||
USERID=pypo
|
|
||||||
GROUPID=pypo
|
|
||||||
NAME=Airtime\ Show\ Recorder
|
|
||||||
|
|
||||||
DAEMON=/usr/lib/airtime/show-recorder/airtime-show-recorder
|
|
||||||
PIDFILE=/var/run/airtime-show-recorder.pid
|
|
||||||
|
|
||||||
start () {
|
|
||||||
start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE --startas $DAEMON
|
|
||||||
monit monitor airtime-show-recorder >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
stop () {
|
|
||||||
# Send TERM after 5 seconds, wait at most 30 seconds.
|
|
||||||
|
|
||||||
monit unmonitor airtime-show-recorder >/dev/null 2>&1
|
|
||||||
start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE
|
|
||||||
rm -f $PIDFILE
|
|
||||||
}
|
|
||||||
|
|
||||||
start_no_monit() {
|
|
||||||
start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE --startas $DAEMON
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case "${1:-''}" in
|
|
||||||
'start')
|
|
||||||
# start commands here
|
|
||||||
echo -n "Starting $NAME: "
|
|
||||||
start
|
|
||||||
echo "Done."
|
|
||||||
;;
|
|
||||||
'stop')
|
|
||||||
# stop commands here
|
|
||||||
echo -n "Stopping $NAME: "
|
|
||||||
stop
|
|
||||||
echo "Done."
|
|
||||||
;;
|
|
||||||
'restart')
|
|
||||||
# restart commands here
|
|
||||||
echo -n "Restarting $NAME: "
|
|
||||||
stop
|
|
||||||
start
|
|
||||||
echo "Done."
|
|
||||||
;;
|
|
||||||
'start-no-monit')
|
|
||||||
# restart commands here
|
|
||||||
echo -n "Starting $NAME: "
|
|
||||||
start_no_monit
|
|
||||||
echo "Done."
|
|
||||||
;;
|
|
||||||
'status')
|
|
||||||
# status commands here
|
|
||||||
/usr/bin/airtime-check-system
|
|
||||||
;;
|
|
||||||
*) # no parameter specified
|
|
||||||
echo "Usage: $SELF start|stop|restart|status"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
|
@ -1,74 +0,0 @@
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
from configobj import ConfigObj
|
|
||||||
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
print "Please run this as root."
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def get_current_script_dir():
|
|
||||||
current_script_dir = os.path.realpath(__file__)
|
|
||||||
index = current_script_dir.rindex('/')
|
|
||||||
return current_script_dir[0:index]
|
|
||||||
|
|
||||||
def copy_dir(src_dir, dest_dir):
|
|
||||||
if (os.path.exists(dest_dir)) and (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 create_dir(path):
|
|
||||||
try:
|
|
||||||
os.makedirs(path)
|
|
||||||
except Exception, e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
PATH_INI_FILE = '/etc/airtime/recorder.cfg'
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Absolute path this script is in
|
|
||||||
current_script_dir = get_current_script_dir()
|
|
||||||
|
|
||||||
if not os.path.exists(PATH_INI_FILE):
|
|
||||||
shutil.copy('%s/../recorder.cfg'%current_script_dir, PATH_INI_FILE)
|
|
||||||
|
|
||||||
# load config file
|
|
||||||
try:
|
|
||||||
config = ConfigObj(PATH_INI_FILE)
|
|
||||||
except Exception, e:
|
|
||||||
print 'Error loading config file: ', e
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
#copy monit files
|
|
||||||
shutil.copy('%s/../../monit/monit-airtime-generic.cfg'%current_script_dir, '/etc/monit/conf.d/')
|
|
||||||
if os.environ["disable_auto_start_services"] == "f":
|
|
||||||
shutil.copy('%s/../monit-airtime-show-recorder.cfg'%current_script_dir, '/etc/monit/conf.d/')
|
|
||||||
|
|
||||||
#create temporary media-storage directory
|
|
||||||
#print "Creating temporary media storage directory"
|
|
||||||
create_dir(config["base_recorded_files"])
|
|
||||||
#os.system("chmod -R 755 "+config["base_recorded_files"])
|
|
||||||
os.system("chown -R pypo:pypo "+config["base_recorded_files"])
|
|
||||||
|
|
||||||
#create log directories
|
|
||||||
#print "Creating log directories"
|
|
||||||
create_dir(config["log_dir"])
|
|
||||||
os.system("chmod -R 755 " + config["log_dir"])
|
|
||||||
os.system("chown -R pypo:pypo "+config["log_dir"])
|
|
||||||
|
|
||||||
#copy python files
|
|
||||||
copy_dir("%s/.."%current_script_dir, config["bin_dir"])
|
|
||||||
|
|
||||||
#set python file permissions
|
|
||||||
#print "Setting permissions"
|
|
||||||
os.system("chmod -R 755 "+config["bin_dir"])
|
|
||||||
os.system("chown -R pypo:pypo "+config["bin_dir"])
|
|
||||||
|
|
||||||
#copy init.d script
|
|
||||||
shutil.copy(config["bin_dir"]+"/airtime-show-recorder-init-d", "/etc/init.d/airtime-show-recorder")
|
|
||||||
|
|
||||||
except Exception, e:
|
|
||||||
print e
|
|
|
@ -1,24 +0,0 @@
|
||||||
from subprocess import Popen
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
print "Please run this as root."
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if os.environ["disable_auto_start_services"] == "f":
|
|
||||||
#register init.d script
|
|
||||||
p = Popen("update-rc.d airtime-show-recorder defaults >/dev/null 2>&1", shell=True)
|
|
||||||
sts = os.waitpid(p.pid, 0)[1]
|
|
||||||
|
|
||||||
#start daemon
|
|
||||||
print "* Waiting for show-recorder processes to start..."
|
|
||||||
"""
|
|
||||||
p = Popen("/etc/init.d/airtime-show-recorder stop", shell=True)
|
|
||||||
sts = os.waitpid(p.pid, 0)[1]
|
|
||||||
"""
|
|
||||||
p = Popen("/etc/init.d/airtime-show-recorder start-no-monit", shell=True)
|
|
||||||
sts = os.waitpid(p.pid, 0)[1]
|
|
||||||
except Exception, e:
|
|
||||||
print e
|
|
|
@ -1,48 +0,0 @@
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
from configobj import ConfigObj
|
|
||||||
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
print "Please run this as root."
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def remove_file(path):
|
|
||||||
try:
|
|
||||||
os.remove(path)
|
|
||||||
except Exception, e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
PATH_INI_FILE = '/etc/airtime/recorder.cfg'
|
|
||||||
|
|
||||||
# load config file
|
|
||||||
try:
|
|
||||||
config = ConfigObj(PATH_INI_FILE)
|
|
||||||
except Exception, e:
|
|
||||||
print 'Error loading config file: ', e
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
#remove init.d script
|
|
||||||
print " * Removing Show-Recorder init.d Script"
|
|
||||||
remove_file('/etc/init.d/airtime-show-recorder')
|
|
||||||
|
|
||||||
#remove bin dir
|
|
||||||
print " * Removing Show-Recorder Program Directory"
|
|
||||||
shutil.rmtree(config["bin_dir"], ignore_errors=True)
|
|
||||||
|
|
||||||
#remove log dir
|
|
||||||
print " * Removing Show-Recorder Log Directory"
|
|
||||||
shutil.rmtree(config["log_dir"], ignore_errors=True)
|
|
||||||
|
|
||||||
#remove temporary media-storage dir
|
|
||||||
print " * Removing Show-Recorder Temporary Directory"
|
|
||||||
shutil.rmtree(config["base_recorded_files"], ignore_errors=True)
|
|
||||||
|
|
||||||
#remove monit files
|
|
||||||
print " * Removing Show-Recorder Monit Files"
|
|
||||||
remove_file("/etc/monit/conf.d/monit-airtime-show-recorder.cfg")
|
|
||||||
remove_file("/etc/monit/conf.d/monit-airtime-generic.cfg")
|
|
||||||
|
|
||||||
except Exception, e:
|
|
||||||
print "Error %s" % e
|
|
|
@ -1,19 +0,0 @@
|
||||||
from subprocess import Popen
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
print "Please run this as root."
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
print "Waiting for show-recorder processes to stop...",
|
|
||||||
if (os.path.exists('/etc/init.d/airtime-show-recorder')):
|
|
||||||
p = Popen("/etc/init.d/airtime-show-recorder stop", shell=True)
|
|
||||||
sts = os.waitpid(p.pid, 0)[1]
|
|
||||||
print "OK"
|
|
||||||
else:
|
|
||||||
print "Wasn't running"
|
|
||||||
except Exception, e:
|
|
||||||
print e
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from configobj import ConfigObj
|
|
||||||
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
print "Please run this as root."
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
PATH_INI_FILE = '/etc/airtime/recorder.cfg'
|
|
||||||
|
|
||||||
def remove_path(path):
|
|
||||||
os.system('rm -rf "%s"' % path)
|
|
||||||
|
|
||||||
def get_current_script_dir():
|
|
||||||
current_script_dir = os.path.realpath(__file__)
|
|
||||||
index = current_script_dir.rindex('/')
|
|
||||||
return current_script_dir[0:index]
|
|
||||||
|
|
||||||
def remove_monit_file():
|
|
||||||
os.system("rm -f /etc/monit/conf.d/monit-airtime-show-recorder.cfg")
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
# load config file
|
|
||||||
try:
|
|
||||||
config = ConfigObj(PATH_INI_FILE)
|
|
||||||
except Exception, e:
|
|
||||||
print 'Error loading config file: ', e
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
os.system("/etc/init.d/airtime-show-recorder stop")
|
|
||||||
os.system("rm -f /etc/init.d/airtime-show-recorder")
|
|
||||||
os.system("update-rc.d -f airtime-show-recorder remove >/dev/null 2>&1")
|
|
||||||
|
|
||||||
print "Removing monit file"
|
|
||||||
remove_monit_file()
|
|
||||||
|
|
||||||
print "Removing log directories"
|
|
||||||
remove_path(config["log_dir"])
|
|
||||||
|
|
||||||
print "Removing symlinks"
|
|
||||||
os.system("rm -f /usr/bin/airtime-show-recorder")
|
|
||||||
|
|
||||||
print "Removing application files"
|
|
||||||
remove_path(config["bin_dir"])
|
|
||||||
|
|
||||||
print "Removing media files"
|
|
||||||
remove_path(config["base_recorded_files"])
|
|
||||||
|
|
||||||
print "Uninstall complete."
|
|
||||||
except Exception, e:
|
|
||||||
print "exception:" + str(e)
|
|
|
@ -1,22 +0,0 @@
|
||||||
[loggers]
|
|
||||||
keys=root
|
|
||||||
|
|
||||||
[handlers]
|
|
||||||
keys=fileOutHandler
|
|
||||||
|
|
||||||
[formatters]
|
|
||||||
keys=simpleFormatter
|
|
||||||
|
|
||||||
[logger_root]
|
|
||||||
level=DEBUG
|
|
||||||
handlers=fileOutHandler
|
|
||||||
|
|
||||||
[handler_fileOutHandler]
|
|
||||||
class=logging.handlers.RotatingFileHandler
|
|
||||||
level=DEBUG
|
|
||||||
formatter=simpleFormatter
|
|
||||||
args=("/var/log/airtime/show-recorder/show-recorder.log", 'a', 1000000, 5,)
|
|
||||||
|
|
||||||
[formatter_simpleFormatter]
|
|
||||||
format=%(asctime)s %(levelname)s - [%(filename)s : %(funcName)s() : line %(lineno)d] - %(message)s
|
|
||||||
datefmt=
|
|
|
@ -1,9 +0,0 @@
|
||||||
set daemon 10 # Poll at 5 second intervals
|
|
||||||
set logfile /var/log/monit.log
|
|
||||||
|
|
||||||
set httpd port 2812
|
|
||||||
|
|
||||||
check process airtime-show-recorder
|
|
||||||
with pidfile "/var/run/airtime-show-recorder.pid"
|
|
||||||
start program = "/etc/init.d/airtime-show-recorder start" with timeout 10 seconds
|
|
||||||
stop program = "/etc/init.d/airtime-show-recorder stop"
|
|
|
@ -1,31 +0,0 @@
|
||||||
api_client = "airtime"
|
|
||||||
|
|
||||||
# where the binary files live
|
|
||||||
bin_dir = '/usr/lib/airtime/show-recorder'
|
|
||||||
|
|
||||||
# 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'
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# RabbitMQ settings #
|
|
||||||
############################################
|
|
||||||
rabbitmq_host = 'localhost'
|
|
||||||
rabbitmq_user = 'guest'
|
|
||||||
rabbitmq_password = 'guest'
|
|
||||||
rabbitmq_vhost = '/'
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Recorded Audio settings #
|
|
||||||
############################################
|
|
||||||
record_bitrate = 256
|
|
||||||
record_samplerate = 44100
|
|
||||||
record_channels = 2
|
|
||||||
record_sample_size = 16
|
|
||||||
record_timeout = 3600
|
|
||||||
|
|
||||||
#can be either ogg|mp3, mp3 recording requires installation of the package "lame"
|
|
||||||
record_file_type = 'ogg'
|
|
||||||
|
|
|
@ -1,348 +0,0 @@
|
||||||
import urllib
|
|
||||||
import logging
|
|
||||||
import logging.config
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
import socket
|
|
||||||
import pytz
|
|
||||||
import signal
|
|
||||||
|
|
||||||
from configobj import ConfigObj
|
|
||||||
|
|
||||||
from poster.encode import multipart_encode
|
|
||||||
from poster.streaminghttp import register_openers
|
|
||||||
import urllib2
|
|
||||||
|
|
||||||
from subprocess import Popen
|
|
||||||
from threading import Thread
|
|
||||||
|
|
||||||
import mutagen
|
|
||||||
|
|
||||||
from api_clients import api_client
|
|
||||||
|
|
||||||
# For RabbitMQ
|
|
||||||
from kombu.connection import BrokerConnection
|
|
||||||
from kombu.messaging import Exchange, Queue, Consumer, Producer
|
|
||||||
|
|
||||||
# configure logging
|
|
||||||
try:
|
|
||||||
logging.config.fileConfig("logging.cfg")
|
|
||||||
except Exception, e:
|
|
||||||
print 'Error configuring logging: ', e
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
# loading config file
|
|
||||||
try:
|
|
||||||
config = ConfigObj('/etc/airtime/recorder.cfg')
|
|
||||||
except Exception, e:
|
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.error('Error loading config file: %s', e)
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
def getDateTimeObj(time):
|
|
||||||
timeinfo = time.split(" ")
|
|
||||||
date = timeinfo[0].split("-")
|
|
||||||
time = timeinfo[1].split(":")
|
|
||||||
|
|
||||||
date = map(int, date)
|
|
||||||
time = map(int, time)
|
|
||||||
|
|
||||||
return datetime.datetime(date[0], date[1], date[2], time[0], time[1], time[2], 0, None)
|
|
||||||
|
|
||||||
class ShowRecorder(Thread):
|
|
||||||
|
|
||||||
def __init__ (self, show_instance, show_name, filelength, start_time):
|
|
||||||
Thread.__init__(self)
|
|
||||||
self.api_client = api_client.api_client_factory(config)
|
|
||||||
self.filelength = filelength
|
|
||||||
self.start_time = start_time
|
|
||||||
self.show_instance = show_instance
|
|
||||||
self.show_name = show_name
|
|
||||||
self.logger = logging.getLogger('root')
|
|
||||||
self.p = None
|
|
||||||
|
|
||||||
def record_show(self):
|
|
||||||
length = str(self.filelength)+".0"
|
|
||||||
filename = self.start_time
|
|
||||||
filename = filename.replace(" ", "-")
|
|
||||||
|
|
||||||
if config["record_file_type"] in ["mp3", "ogg"]:
|
|
||||||
filetype = config["record_file_type"]
|
|
||||||
else:
|
|
||||||
filetype = "ogg";
|
|
||||||
|
|
||||||
filepath = "%s%s.%s" % (config["base_recorded_files"], filename, filetype)
|
|
||||||
|
|
||||||
br = config["record_bitrate"]
|
|
||||||
sr = config["record_samplerate"]
|
|
||||||
c = config["record_channels"]
|
|
||||||
ss = config["record_sample_size"]
|
|
||||||
|
|
||||||
#-f:16,2,44100
|
|
||||||
#-b:256
|
|
||||||
command = "ecasound -f:%s,%s,%s -i alsa -o %s,%s000 -t:%s" % (ss, c, sr, filepath, br, length)
|
|
||||||
args = command.split(" ")
|
|
||||||
|
|
||||||
self.logger.info("starting record")
|
|
||||||
self.logger.info("command " + command)
|
|
||||||
|
|
||||||
self.p = Popen(args)
|
|
||||||
|
|
||||||
#blocks at the following line until the child process
|
|
||||||
#quits
|
|
||||||
code = self.p.wait()
|
|
||||||
|
|
||||||
self.logger.info("finishing record, return code %s", self.p.returncode)
|
|
||||||
code = self.p.returncode
|
|
||||||
|
|
||||||
self.p = None
|
|
||||||
|
|
||||||
return code, filepath
|
|
||||||
|
|
||||||
def cancel_recording(self):
|
|
||||||
#add 3 second delay before actually cancelling the show. The reason
|
|
||||||
#for this is because it appears that ecasound starts 1 second later than
|
|
||||||
#it should, and therefore this method is sometimes incorrectly called 1
|
|
||||||
#second before the show ends.
|
|
||||||
#time.sleep(3)
|
|
||||||
|
|
||||||
#send signal interrupt (2)
|
|
||||||
self.logger.info("Show manually cancelled!")
|
|
||||||
if (self.p is not None):
|
|
||||||
self.p.send_signal(signal.SIGINT)
|
|
||||||
|
|
||||||
#if self.p is defined, then the child process ecasound is recording
|
|
||||||
def is_recording(self):
|
|
||||||
return (self.p is not None)
|
|
||||||
|
|
||||||
def upload_file(self, filepath):
|
|
||||||
|
|
||||||
filename = os.path.split(filepath)[1]
|
|
||||||
|
|
||||||
# Register the streaming http handlers with urllib2
|
|
||||||
register_openers()
|
|
||||||
|
|
||||||
# headers contains the necessary Content-Type and Content-Length
|
|
||||||
# datagen is a generator object that yields the encoded parameters
|
|
||||||
datagen, headers = multipart_encode({"file": open(filepath, "rb"), 'name': filename, 'show_instance': self.show_instance})
|
|
||||||
|
|
||||||
self.api_client.upload_recorded_show(datagen, headers)
|
|
||||||
|
|
||||||
def set_metadata_and_save(self, filepath):
|
|
||||||
try:
|
|
||||||
date = self.start_time
|
|
||||||
md = date.split(" ")
|
|
||||||
time = md[1].replace(":", "-")
|
|
||||||
self.logger.info("time: %s" % time)
|
|
||||||
|
|
||||||
name = time+"-"+self.show_name
|
|
||||||
artist = api_client.encode_to("Airtime Show Recorder",'utf-8')
|
|
||||||
|
|
||||||
#set some metadata for our file daemon
|
|
||||||
recorded_file = mutagen.File(filepath, easy=True)
|
|
||||||
recorded_file['title'] = name
|
|
||||||
recorded_file['artist'] = artist
|
|
||||||
recorded_file['date'] = md[0]
|
|
||||||
recorded_file['tracknumber'] = self.show_instance
|
|
||||||
recorded_file.save()
|
|
||||||
|
|
||||||
except Exception, e:
|
|
||||||
self.logger.error("Exception: %s", e)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
code, filepath = self.record_show()
|
|
||||||
|
|
||||||
if code == 0:
|
|
||||||
try:
|
|
||||||
self.logger.info("Preparing to upload %s" % filepath)
|
|
||||||
|
|
||||||
self.set_metadata_and_save(filepath)
|
|
||||||
|
|
||||||
self.upload_file(filepath)
|
|
||||||
os.remove(filepath)
|
|
||||||
except Exception, e:
|
|
||||||
self.logger.error(e)
|
|
||||||
else:
|
|
||||||
self.logger.info("problem recording show")
|
|
||||||
os.remove(filepath)
|
|
||||||
|
|
||||||
class CommandListener():
|
|
||||||
def __init__(self):
|
|
||||||
#Thread.__init__(self)
|
|
||||||
self.api_client = api_client.api_client_factory(config)
|
|
||||||
self.api_client.register_component("show-recorder")
|
|
||||||
self.logger = logging.getLogger('root')
|
|
||||||
self.sr = None
|
|
||||||
self.current_schedule = {}
|
|
||||||
self.shows_to_record = {}
|
|
||||||
self.time_till_next_show = config["record_timeout"]
|
|
||||||
self.logger.info("RecorderFetch: init complete")
|
|
||||||
self.server_timezone = '';
|
|
||||||
|
|
||||||
def init_rabbit_mq(self):
|
|
||||||
self.logger.info("Initializing RabbitMQ stuff")
|
|
||||||
try:
|
|
||||||
schedule_exchange = Exchange("airtime-show-recorder", "direct", durable=True, auto_delete=True)
|
|
||||||
schedule_queue = Queue("recorder-fetch", exchange=schedule_exchange, key="foo")
|
|
||||||
self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], config["rabbitmq_vhost"])
|
|
||||||
channel = self.connection.channel()
|
|
||||||
consumer = Consumer(channel, schedule_queue)
|
|
||||||
consumer.register_callback(self.handle_message)
|
|
||||||
consumer.consume()
|
|
||||||
except Exception, e:
|
|
||||||
self.logger.error(e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def handle_message(self, body, message):
|
|
||||||
# ACK the message to take it off the queue
|
|
||||||
message.ack()
|
|
||||||
self.logger.info("Received command from RabbitMQ: " + message.body)
|
|
||||||
m = json.loads(message.body)
|
|
||||||
command = m['event_type']
|
|
||||||
self.logger.info("Handling command: " + command)
|
|
||||||
|
|
||||||
if(command == 'update_schedule'):
|
|
||||||
temp = m['shows']
|
|
||||||
if temp is not None:
|
|
||||||
self.parse_shows(temp)
|
|
||||||
self.server_timezone = m['server_timezone']
|
|
||||||
elif(command == 'cancel_recording'):
|
|
||||||
if self.sr is not None and self.sr.is_recording():
|
|
||||||
self.sr.cancel_recording()
|
|
||||||
|
|
||||||
def parse_shows(self, shows):
|
|
||||||
self.logger.info("Parsing show schedules...")
|
|
||||||
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']]
|
|
||||||
delta = self.get_time_till_next_show()
|
|
||||||
# awake at least 5 seconds prior to the show start
|
|
||||||
self.time_till_next_show = delta - 5
|
|
||||||
|
|
||||||
self.logger.info(self.shows_to_record)
|
|
||||||
|
|
||||||
def get_time_till_next_show(self):
|
|
||||||
if len(self.shows_to_record) != 0:
|
|
||||||
tnow = datetime.datetime.utcnow()
|
|
||||||
sorted_show_keys = sorted(self.shows_to_record.keys())
|
|
||||||
|
|
||||||
start_time = sorted_show_keys[0]
|
|
||||||
next_show = getDateTimeObj(start_time)
|
|
||||||
|
|
||||||
delta = next_show - tnow
|
|
||||||
out = delta.seconds
|
|
||||||
|
|
||||||
self.logger.debug("Next show %s", next_show)
|
|
||||||
self.logger.debug("Now %s", tnow)
|
|
||||||
else:
|
|
||||||
out = config["record_timeout"]
|
|
||||||
return out
|
|
||||||
|
|
||||||
def start_record(self):
|
|
||||||
if len(self.shows_to_record) != 0:
|
|
||||||
try:
|
|
||||||
delta = self.get_time_till_next_show()
|
|
||||||
|
|
||||||
self.logger.debug("sleeping %s seconds until show", delta)
|
|
||||||
time.sleep(delta)
|
|
||||||
|
|
||||||
sorted_show_keys = sorted(self.shows_to_record.keys())
|
|
||||||
start_time = sorted_show_keys[0]
|
|
||||||
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]
|
|
||||||
|
|
||||||
T = pytz.timezone(self.server_timezone)
|
|
||||||
start_time_on_UTC = getDateTimeObj(start_time)
|
|
||||||
start_time_on_server = start_time_on_UTC.replace(tzinfo=pytz.utc).astimezone(T)
|
|
||||||
start_time_formatted = '%(year)d-%(month)02d-%(day)02d %(hour)02d:%(min)02d:%(sec)02d' % \
|
|
||||||
{'year': start_time_on_server.year, 'month': start_time_on_server.month, 'day': start_time_on_server.day,\
|
|
||||||
'hour': start_time_on_server.hour, 'min': start_time_on_server.minute, 'sec': start_time_on_server.second}
|
|
||||||
self.sr = ShowRecorder(show_instance, show_name, show_length.seconds, start_time_formatted)
|
|
||||||
self.sr.start()
|
|
||||||
#remove show from shows to record.
|
|
||||||
del self.shows_to_record[start_time]
|
|
||||||
self.time_till_next_show = self.get_time_till_next_show()
|
|
||||||
except Exception,e :
|
|
||||||
import traceback
|
|
||||||
top = traceback.format_exc()
|
|
||||||
self.logger.error('Exception: %s', e)
|
|
||||||
self.logger.error("traceback: %s", top)
|
|
||||||
else:
|
|
||||||
self.logger.debug("No recording scheduled...")
|
|
||||||
|
|
||||||
"""
|
|
||||||
Main loop of the thread:
|
|
||||||
Wait for schedule updates from RabbitMQ, but in case there arent any,
|
|
||||||
poll the server to get the upcoming schedule.
|
|
||||||
"""
|
|
||||||
def run(self):
|
|
||||||
self.logger.info("Started...")
|
|
||||||
while not self.init_rabbit_mq():
|
|
||||||
self.logger.error("Error connecting to RabbitMQ Server. Trying again in few seconds")
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
# Bootstrap: since we are just starting up, we need to grab the
|
|
||||||
# most recent schedule. After that we can just wait for updates.
|
|
||||||
try:
|
|
||||||
temp = self.api_client.get_shows_to_record()
|
|
||||||
if temp is not None:
|
|
||||||
shows = temp['shows']
|
|
||||||
self.server_timezone = temp['server_timezone']
|
|
||||||
self.parse_shows(shows)
|
|
||||||
self.logger.info("Bootstrap complete: got initial copy of the schedule")
|
|
||||||
except Exception, e:
|
|
||||||
self.logger.error(e)
|
|
||||||
|
|
||||||
loops = 1
|
|
||||||
recording = False
|
|
||||||
|
|
||||||
while True:
|
|
||||||
self.logger.info("Loop #%s", loops)
|
|
||||||
try:
|
|
||||||
# block until 5 seconds before the next show start
|
|
||||||
self.connection.drain_events(timeout=int(self.time_till_next_show))
|
|
||||||
except socket.timeout, s:
|
|
||||||
self.logger.info(s)
|
|
||||||
|
|
||||||
# start_record set time_till_next_show to config["record_timeout"] so we should check before
|
|
||||||
# if timeout amount was 1 hr..
|
|
||||||
update_schedule = False
|
|
||||||
if int(self.time_till_next_show) == int(config["record_timeout"]) :
|
|
||||||
update_schedule = True
|
|
||||||
|
|
||||||
# start recording
|
|
||||||
self.start_record()
|
|
||||||
|
|
||||||
# if real timeout happended get show schedule from airtime
|
|
||||||
if update_schedule :
|
|
||||||
temp = self.api_client.get_shows_to_record()
|
|
||||||
if temp is not None:
|
|
||||||
shows = temp['shows']
|
|
||||||
self.server_timezone = temp['server_timezone']
|
|
||||||
self.parse_shows(shows)
|
|
||||||
self.logger.info("Real Timeout: the schedule has updated")
|
|
||||||
|
|
||||||
except Exception, e:
|
|
||||||
import traceback
|
|
||||||
top = traceback.format_exc()
|
|
||||||
self.logger.error('Exception: %s', e)
|
|
||||||
self.logger.error("traceback: %s", top)
|
|
||||||
time.sleep(3)
|
|
||||||
loops += 1
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
cl = CommandListener()
|
|
||||||
cl.run()
|
|
||||||
|
|
||||||
|
|
|
@ -189,17 +189,6 @@ class AirtimeCheck {
|
||||||
self::output_status("MEDIA_MONITOR_MEM_PERC", "0%");
|
self::output_status("MEDIA_MONITOR_MEM_PERC", "0%");
|
||||||
self::output_status("MEDIA_MONITOR_CPU_PERC", "0%");
|
self::output_status("MEDIA_MONITOR_CPU_PERC", "0%");
|
||||||
}
|
}
|
||||||
if (isset($services->show_recorder)) {
|
|
||||||
self::output_status("SHOW_RECORDER_PROCESS_ID", $data->services->show_recorder->process_id);
|
|
||||||
self::output_status("SHOW_RECORDER_RUNNING_SECONDS", $data->services->show_recorder->uptime_seconds);
|
|
||||||
self::output_status("SHOW_RECORDER_MEM_PERC", $data->services->show_recorder->memory_perc);
|
|
||||||
self::output_status("SHOW_RECORDER_CPU_PERC", $data->services->show_recorder->cpu_perc);
|
|
||||||
} else {
|
|
||||||
self::output_status("SHOW_RECORDER_PROCESS_ID", "FAILED");
|
|
||||||
self::output_status("SHOW_RECORDER_RUNNING_SECONDS", "0");
|
|
||||||
self::output_status("SHOW_RECORDER_MEM_PERC", "0%");
|
|
||||||
self::output_status("SHOW_RECORDER_CPU_PERC", "0%");
|
|
||||||
}
|
|
||||||
if (isset($services->rabbitmq)) {
|
if (isset($services->rabbitmq)) {
|
||||||
self::output_status("RABBITMQ_PROCESS_ID", $data->services->rabbitmq->process_id);
|
self::output_status("RABBITMQ_PROCESS_ID", $data->services->rabbitmq->process_id);
|
||||||
self::output_status("RABBITMQ_RUNNING_SECONDS", $data->services->rabbitmq->uptime_seconds);
|
self::output_status("RABBITMQ_RUNNING_SECONDS", $data->services->rabbitmq->uptime_seconds);
|
||||||
|
|
Loading…
Reference in New Issue