CC-430: Audio normalization (Replaygain Support)

This commit is contained in:
Martin Konecny 2012-07-12 17:58:29 -04:00
parent 62287a2313
commit f0f033b4fb
7 changed files with 401 additions and 357 deletions

View file

@ -31,9 +31,24 @@ class ApiController extends Zend_Controller_Action
->addActionContext('check-live-stream-auth', 'json') ->addActionContext('check-live-stream-auth', 'json')
->addActionContext('update-source-status', 'json') ->addActionContext('update-source-status', 'json')
->addActionContext('get-bootstrap-info', 'json') ->addActionContext('get-bootstrap-info', 'json')
->addActionContext('get-files-without-replay-gain', 'json')
->initContext(); ->initContext();
} }
public function checkAuth()
{
global $CC_CONFIG;
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read())) {
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
}
public function indexAction() public function indexAction()
{ {
// action body // action body
@ -51,20 +66,12 @@ class ApiController extends Zend_Controller_Action
*/ */
public function versionAction() public function versionAction()
{ {
global $CC_CONFIG; $this->checkAuth();
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$jsonStr = json_encode(array("version"=>Application_Model_Preference::GetAirtimeVersion())); $jsonStr = json_encode(array("version"=>Application_Model_Preference::GetAirtimeVersion()));
echo $jsonStr; echo $jsonStr;
} }
@ -100,23 +107,12 @@ class ApiController extends Zend_Controller_Action
*/ */
public function getMediaAction() public function getMediaAction()
{ {
global $CC_CONFIG; $this->checkAuth();
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
$api_key = $this->_getParam('api_key');
if(!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
Logging::log("401 Unauthorized");
return;
}
$fileID = $this->_getParam("file"); $fileID = $this->_getParam("file");
$file_id = substr($fileID, 0, strpos($fileID, ".")); $file_id = substr($fileID, 0, strpos($fileID, "."));
@ -186,6 +182,8 @@ class ApiController extends Zend_Controller_Action
*/ */
function smartReadFile($location, $mimeType = 'audio/mp3') function smartReadFile($location, $mimeType = 'audio/mp3')
{ {
$this->checkAuth();
$size= filesize($location); $size= filesize($location);
$time= date('r', filemtime($location)); $time= date('r', filemtime($location));
@ -343,43 +341,24 @@ class ApiController extends Zend_Controller_Action
public function scheduleAction() public function scheduleAction()
{ {
global $CC_CONFIG; $this->checkAuth();
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
$api_key = $this->_getParam('api_key');
if(!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource. ';
exit;
}
$data = Application_Model_Schedule::GetScheduledPlaylists(); $data = Application_Model_Schedule::GetScheduledPlaylists();
echo json_encode($data, JSON_FORCE_OBJECT); echo json_encode($data, JSON_FORCE_OBJECT);
} }
public function notifyMediaItemStartPlayAction() public function notifyMediaItemStartPlayAction()
{ {
global $CC_CONFIG; $this->checkAuth();
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
$api_key = $this->_getParam('api_key');
if(!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$schedule_group_id = $this->_getParam("schedule_id"); $schedule_group_id = $this->_getParam("schedule_id");
$media_id = $this->_getParam("media_id"); $media_id = $this->_getParam("media_id");
$result = Application_Model_Schedule::UpdateMediaPlayedStatus($media_id); $result = Application_Model_Schedule::UpdateMediaPlayedStatus($media_id);
@ -388,16 +367,7 @@ class ApiController extends Zend_Controller_Action
public function recordedShowsAction() public function recordedShowsAction()
{ {
global $CC_CONFIG; $this->checkAuth();
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$today_timestamp = date("Y-m-d H:i:s"); $today_timestamp = date("Y-m-d H:i:s");
$now = new DateTime($today_timestamp); $now = new DateTime($today_timestamp);
@ -422,16 +392,7 @@ class ApiController extends Zend_Controller_Action
public function uploadFileAction() public function uploadFileAction()
{ {
global $CC_CONFIG; $this->checkAuth();
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$upload_dir = ini_get("upload_tmp_dir"); $upload_dir = ini_get("upload_tmp_dir");
$tempFilePath = Application_Model_StoredFile::uploadFile($upload_dir); $tempFilePath = Application_Model_StoredFile::uploadFile($upload_dir);
@ -447,16 +408,7 @@ class ApiController extends Zend_Controller_Action
public function uploadRecordedAction() public function uploadRecordedAction()
{ {
global $CC_CONFIG; $this->checkAuth();
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
//this file id is the recording for this show instance. //this file id is the recording for this show instance.
$show_instance_id = $this->_getParam('showinstanceid'); $show_instance_id = $this->_getParam('showinstanceid');
@ -520,21 +472,12 @@ class ApiController extends Zend_Controller_Action
} }
public function mediaMonitorSetupAction() { public function mediaMonitorSetupAction() {
global $CC_CONFIG; $this->checkAuth();
// disable the view and the layout // disable the view and the layout
$this->view->layout()->disableLayout(); $this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true); $this->_helper->viewRenderer->setNoRender(true);
$api_key = $this->_getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$this->view->stor = Application_Model_MusicDir::getStorDir()->getDirectory(); $this->view->stor = Application_Model_MusicDir::getStorDir()->getDirectory();
$watchedDirs = Application_Model_MusicDir::getWatchedDirs(); $watchedDirs = Application_Model_MusicDir::getWatchedDirs();
@ -546,17 +489,9 @@ class ApiController extends Zend_Controller_Action
} }
public function reloadMetadataAction() { public function reloadMetadataAction() {
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$mode = $request->getParam('mode'); $mode = $request->getParam('mode');
$params = $request->getParams(); $params = $request->getParams();
@ -650,34 +585,18 @@ class ApiController extends Zend_Controller_Action
} }
public function listAllFilesAction() { public function listAllFilesAction() {
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$dir_id = $request->getParam('dir_id'); $dir_id = $request->getParam('dir_id');
$this->view->files = Application_Model_StoredFile::listAllFiles($dir_id); $this->view->files = Application_Model_StoredFile::listAllFiles($dir_id);
} }
public function listAllWatchedDirsAction() { public function listAllWatchedDirsAction() {
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$result = array(); $result = array();
@ -694,91 +613,47 @@ class ApiController extends Zend_Controller_Action
} }
public function addWatchedDirAction() { public function addWatchedDirAction() {
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
$path = base64_decode($request->getParam('path')); $path = base64_decode($request->getParam('path'));
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$this->view->msg = Application_Model_MusicDir::addWatchedDir($path); $this->view->msg = Application_Model_MusicDir::addWatchedDir($path);
} }
public function removeWatchedDirAction() { public function removeWatchedDirAction() {
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
$path = base64_decode($request->getParam('path')); $path = base64_decode($request->getParam('path'));
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$this->view->msg = Application_Model_MusicDir::removeWatchedDir($path); $this->view->msg = Application_Model_MusicDir::removeWatchedDir($path);
} }
public function setStorageDirAction() { public function setStorageDirAction() {
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
$path = base64_decode($request->getParam('path')); $path = base64_decode($request->getParam('path'));
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$this->view->msg = Application_Model_MusicDir::setStorDir($path); $this->view->msg = Application_Model_MusicDir::setStorDir($path);
} }
public function getStreamSettingAction() { public function getStreamSettingAction() {
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$info = Application_Model_StreamSetting::getStreamSetting(); $info = Application_Model_StreamSetting::getStreamSetting();
$this->view->msg = $info; $this->view->msg = $info;
} }
public function statusAction() { public function statusAction() {
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
$getDiskInfo = $request->getParam('diskinfo') == "true"; $getDiskInfo = $request->getParam('diskinfo') == "true";
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$status = array( $status = array(
"platform"=>Application_Model_Systemstatus::GetPlatformInfo(), "platform"=>Application_Model_Systemstatus::GetPlatformInfo(),
"airtime_version"=>Application_Model_Preference::GetAirtimeVersion(), "airtime_version"=>Application_Model_Preference::GetAirtimeVersion(),
@ -844,17 +719,9 @@ class ApiController extends Zend_Controller_Action
// handles addition/deletion of mount point which watched dirs reside // handles addition/deletion of mount point which watched dirs reside
public function updateFileSystemMountAction(){ public function updateFileSystemMountAction(){
global $CC_CONFIG; $this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$params = $request->getParams(); $params = $request->getParams();
$added_list = empty($params['added_dir'])?array():explode(',',$params['added_dir']); $added_list = empty($params['added_dir'])?array():explode(',',$params['added_dir']);
@ -871,20 +738,19 @@ class ApiController extends Zend_Controller_Action
// if mount path itself was watched // if mount path itself was watched
if ($dirPath == $ad) { if ($dirPath == $ad) {
Application_Model_MusicDir::addWatchedDir($dirPath, false); Application_Model_MusicDir::addWatchedDir($dirPath, false);
} } else if(substr($dirPath, 0, strlen($ad)) === $ad && $dir->getExistsFlag() == false) {
// if dir contains any dir in removed_list( if watched dir resides on new mounted path ) // if dir contains any dir in removed_list( if watched dir resides on new mounted path )
else if(substr($dirPath, 0, strlen($ad)) === $ad && $dir->getExistsFlag() == false){
Application_Model_MusicDir::addWatchedDir($dirPath, false); Application_Model_MusicDir::addWatchedDir($dirPath, false);
} } else if (substr($ad, 0, strlen($dirPath)) === $dirPath) {
// is new mount point within the watched dir? // is new mount point within the watched dir?
// pyinotify doesn't notify anyhing in this case, so we add this mount point as // pyinotify doesn't notify anyhing in this case, so we add this mount point as
// watched dir // watched dir
else if(substr($ad, 0, strlen($dirPath)) === $dirPath){
// bypass nested loop check // bypass nested loop check
Application_Model_MusicDir::addWatchedDir($ad, false, true); Application_Model_MusicDir::addWatchedDir($ad, false, true);
} }
} }
} }
foreach( $removed_list as $rd) { foreach( $removed_list as $rd) {
$rd .= '/'; $rd .= '/';
foreach ($watched_dirs as $dir) { foreach ($watched_dirs as $dir) {
@ -892,13 +758,13 @@ class ApiController extends Zend_Controller_Action
// if dir contains any dir in removed_list( if watched dir resides on new mounted path ) // if dir contains any dir in removed_list( if watched dir resides on new mounted path )
if (substr($dirPath, 0, strlen($rd)) === $rd && $dir->getExistsFlag() == true) { if (substr($dirPath, 0, strlen($rd)) === $rd && $dir->getExistsFlag() == true) {
Application_Model_MusicDir::removeWatchedDir($dirPath, false); Application_Model_MusicDir::removeWatchedDir($dirPath, false);
} } else if (substr($rd, 0, strlen($dirPath)) === $dirPath) {
// is new mount point within the watched dir? // is new mount point within the watched dir?
// pyinotify doesn't notify anyhing in this case, so we walk through all files within // pyinotify doesn't notify anyhing in this case, so we walk through all files within
// this watched dir in DB and mark them deleted. // this watched dir in DB and mark them deleted.
// In case of h) of use cases, due to pyinotify behaviour of noticing mounted dir, we need to // In case of h) of use cases, due to pyinotify behaviour of noticing mounted dir, we need to
// compare agaisnt all files in cc_files table // compare agaisnt all files in cc_files table
else if(substr($rd, 0, strlen($dirPath)) === $dirPath ){
$watchDir = Application_Model_MusicDir::getDirByPath($rd); $watchDir = Application_Model_MusicDir::getDirByPath($rd);
// get all the files that is under $dirPath // get all the files that is under $dirPath
$files = Application_Model_StoredFile::listAllFiles($dir->getId(), true); $files = Application_Model_StoredFile::listAllFiles($dir->getId(), true);
@ -908,28 +774,21 @@ class ApiController extends Zend_Controller_Action
$f->delete(); $f->delete();
} }
} }
if($watchDir) { if($watchDir) {
Application_Model_MusicDir::removeWatchedDir($rd, false); Application_Model_MusicDir::removeWatchedDir($rd, false);
} }
} }
} }
} }
} }
// handles case where watched dir is missing // handles case where watched dir is missing
public function handleWatchedDirMissingAction(){ public function handleWatchedDirMissingAction()
global $CC_CONFIG; {
$this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
$dir = base64_decode($request->getParam('dir')); $dir = base64_decode($request->getParam('dir'));
Application_Model_MusicDir::removeWatchedDir($dir, false); Application_Model_MusicDir::removeWatchedDir($dir, false);
@ -938,24 +797,18 @@ class ApiController extends Zend_Controller_Action
/* This action is for use by our dev scripts, that make /* This action is for use by our dev scripts, that make
* a change to the database and we want rabbitmq to send * a change to the database and we want rabbitmq to send
* out a message to pypo that a potential change has been made. */ * out a message to pypo that a potential change has been made. */
public function rabbitmqDoPushAction(){ public function rabbitmqDoPushAction()
global $CC_CONFIG; {
$this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
Logging::log("Notifying RabbitMQ to send message to pypo"); Logging::log("Notifying RabbitMQ to send message to pypo");
Application_Model_RabbitMq::PushSchedule(); Application_Model_RabbitMq::PushSchedule();
} }
public function getBootstrapInfoAction(){ public function getBootstrapInfoAction()
{
$live_dj = Application_Model_Preference::GetSourceSwitchStatus('live_dj'); $live_dj = Application_Model_Preference::GetSourceSwitchStatus('live_dj');
$master_dj = Application_Model_Preference::GetSourceSwitchStatus('master_dj'); $master_dj = Application_Model_Preference::GetSourceSwitchStatus('master_dj');
$scheduled_play = Application_Model_Preference::GetSourceSwitchStatus('scheduled_play'); $scheduled_play = Application_Model_Preference::GetSourceSwitchStatus('scheduled_play');
@ -968,27 +821,20 @@ class ApiController extends Zend_Controller_Action
} }
/* This is used but Liquidsoap to check authentication of live streams*/ /* This is used but Liquidsoap to check authentication of live streams*/
public function checkLiveStreamAuthAction(){ public function checkLiveStreamAuthAction()
global $CC_CONFIG; {
$this->checkAuth();
$request = $this->getRequest(); $request = $this->getRequest();
$api_key = $request->getParam('api_key');
$username = $request->getParam('username'); $username = $request->getParam('username');
$password = $request->getParam('password'); $password = $request->getParam('password');
$djtype = $request->getParam('djtype'); $djtype = $request->getParam('djtype');
if (!in_array($api_key, $CC_CONFIG["apiKey"]) &&
is_null(Zend_Auth::getInstance()->getStorage()->read()))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
if ($djtype == 'master') { if ($djtype == 'master') {
//check against master //check against master
if($username == Application_Model_Preference::GetLiveSteamMasterUsername() && $password == Application_Model_Preference::GetLiveSteamMasterPassword()){ if ($username == Application_Model_Preference::GetLiveSteamMasterUsername()
&& $password == Application_Model_Preference::GetLiveSteamMasterPassword()) {
$this->view->msg = true; $this->view->msg = true;
} else { } else {
$this->view->msg = false; $this->view->msg = false;
@ -1026,8 +872,7 @@ class ApiController extends Zend_Controller_Action
} else { } else {
$this->view->msg = false; $this->view->msg = false;
} }
} } else {
else{
$this->view->msg = false; $this->view->msg = false;
} }
} else { } else {
@ -1036,5 +881,21 @@ class ApiController extends Zend_Controller_Action
} }
} }
} }
/* This action is for use by our dev scripts, that make
* a change to the database and we want rabbitmq to send
* out a message to pypo that a potential change has been made. */
public function getFilesWithoutReplayGainAction()
{
$this->checkAuth();
// disable the view and the layout
$this->view->layout()->disableLayout();
$dir_id = $this->_getParam('dir_id');
//connect to db and get get sql
$this->view->rows = Application_Model_StoredFile::listAllFiles2($dir_id, 0);
}
} }

View file

@ -961,6 +961,24 @@ Logging::log("getting media! - 2");
return $results; return $results;
} }
//TODO: MERGE THIS FUNCTION AND "listAllFiles" -MK
public static function listAllFiles2($dir_id=null, $limit=null)
{
$con = Propel::getConnection();
$sql = "SELECT id, filepath as fp"
." FROM CC_FILES"
." WHERE directory = $dir_id"
." AND file_exists = 'TRUE'";
if (!is_null($limit) && is_int($limit)){
$sql .= " LIMIT $limit";
}
$rows = $con->query($sql, PDO::FETCH_ASSOC);
return $rows;
}
/* Gets number of tracks uploaded to /* Gets number of tracks uploaded to
* Soundcloud in the last 24 hours * Soundcloud in the last 24 hours
*/ */

View file

@ -0,0 +1,8 @@
<?php
//disable buffering so that data is sent as we retrieve it from the database
while (@ob_end_flush());
foreach($this->rows as $row) {
echo json_encode($row)."\n";
}

View file

@ -109,3 +109,5 @@ update_source_status = 'update-source-status/format/json/api_key/%%api_key%%/sou
get_bootstrap_info = 'get-bootstrap-info/format/json/api_key/%%api_key%%' get_bootstrap_info = 'get-bootstrap-info/format/json/api_key/%%api_key%%'
get-files-without-replay-gain = 'get-files-without-replay-gain/api_key/%%api_key%%/dir_id/%%dir_id%%''

View file

@ -75,15 +75,51 @@ class AirTimeApiClient():
return response return response
def get_response_into_file(self, url, block=True):
"""
This function will query the server and download its response directly
into a temporary file. This is useful in the situation where the response
from the server can be huge and we don't want to store it into memory (potentially
causing Python to use hundreds of MB's of memory). By writing into a file we can
then open this file later, and read data a little bit at a time and be very mem
efficient.
def __get_airtime_version(self, verbose = True): The return value of this function is the path of the temporary file. Unless specified using
block = False, this function will block until a successful HTTP 200 response is received.
"""
logger = self.logger
successful_response = False
while not successful_response:
try:
path = urllib.urlretrieve(url)[0]
successful_response = True
except IOError, e:
logger.error('Error Authenticating with remote server: %s', e)
if not block:
raise
except Exception, e:
logger.error('Couldn\'t connect to remote server. Is it running?')
logger.error("%s" % e)
if not block:
raise
if not successful_response:
logger.error("Error connecting to server, waiting 5 seconds and trying again.")
time.sleep(5)
return path
def __get_airtime_version(self):
logger = self.logger 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"])
version = -1 version = -1
response = None
try: try:
data = self.get_response_from_server(url) data = self.get_response_from_server(url)
logger.debug("Data: %s", data) logger.debug("Data: %s", data)
@ -98,7 +134,7 @@ class AirTimeApiClient():
def test(self): def test(self):
logger = self.logger logger = self.logger
status, items = self.get_schedule('2010-01-01-00-00-00', '2011-01-01-00-00-00') items = self.get_schedule()[1]
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)))
count = 1 count = 1
@ -114,7 +150,7 @@ class AirTimeApiClient():
def is_server_compatible(self, verbose=True): def is_server_compatible(self, verbose=True):
logger = self.logger logger = self.logger
version = self.__get_airtime_version(verbose) version = self.__get_airtime_version()
if (version == -1): if (version == -1):
if (verbose): if (verbose):
logger.info('Unable to get Airtime version number.\n') logger.info('Unable to get Airtime version number.\n')
@ -131,7 +167,7 @@ class AirTimeApiClient():
return True return True
def get_schedule(self, start=None, end=None): def get_schedule(self):
logger = self.logger logger = self.logger
# Construct the URL # Construct the URL
@ -160,7 +196,7 @@ class AirTimeApiClient():
logger.info("try to download from %s to %s", src, dst) logger.info("try to download from %s to %s", src, dst)
src = src.replace("%%api_key%%", self.config["api_key"]) src = src.replace("%%api_key%%", self.config["api_key"])
# check if file exists already before downloading again # check if file exists already before downloading again
filename, headers = urllib.urlretrieve(src, dst) headers = urllib.urlretrieve(src, dst)[1]
logger.info(headers) logger.info(headers)
except Exception, e: except Exception, e:
logger.error("%s", e) logger.error("%s", e)
@ -192,12 +228,11 @@ class AirTimeApiClient():
return response return response
def get_liquidsoap_data(self, pkey, schedule): def get_liquidsoap_data(self, pkey, schedule):
logger = self.logger
playlist = schedule[pkey] playlist = schedule[pkey]
data = dict() data = dict()
try: try:
data["schedule_id"] = playlist['id'] data["schedule_id"] = playlist['id']
except Exception, e: except Exception:
data["schedule_id"] = 0 data["schedule_id"] = 0
return data return data
@ -254,7 +289,11 @@ class AirTimeApiClient():
return response return response
def check_live_stream_auth(self, username, password, dj_type): def check_live_stream_auth(self, username, password, dj_type):
#logger = logging.getLogger() """
TODO: Why are we using print statements here? Possibly use logger that
is directed to stdout. -MK
"""
response = '' response = ''
try: try:
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["check_live_stream_auth"]) url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["check_live_stream_auth"])
@ -267,10 +306,8 @@ class AirTimeApiClient():
response = self.get_response_from_server(url) response = self.get_response_from_server(url)
response = json.loads(response) response = json.loads(response)
except Exception, e: except Exception, e:
import traceback
top = traceback.format_exc()
print "Exception: %s", e print "Exception: %s", e
print "traceback: %s", top print "traceback: %s", traceback.format_exc()
response = None response = None
return response return response
@ -320,10 +357,8 @@ class AirTimeApiClient():
logger.info("associate recorded %s", response) logger.info("associate recorded %s", response)
except Exception, e: except Exception, e:
response = None response = None
import traceback
top = traceback.format_exc()
logger.error('Exception: %s', e) logger.error('Exception: %s', e)
logger.error("traceback: %s", top) logger.error("traceback: %s", traceback.format_exc())
return response return response
@ -453,7 +488,7 @@ class AirTimeApiClient():
url = url.replace("%%stream_id%%", stream_id) url = url.replace("%%stream_id%%", stream_id)
url = url.replace("%%boot_time%%", time) url = url.replace("%%boot_time%%", time)
response = self.get_response_from_server(url) self.get_response_from_server(url)
except Exception, e: except Exception, e:
logger.error("Exception: %s", e) logger.error("Exception: %s", e)
@ -466,7 +501,7 @@ class AirTimeApiClient():
url = url.replace("%%sourcename%%", sourcename) url = url.replace("%%sourcename%%", sourcename)
url = url.replace("%%status%%", status) url = url.replace("%%status%%", status)
response = self.get_response_from_server(url) self.get_response_from_server(url)
except Exception, e: except Exception, e:
logger.error("Exception: %s", e) logger.error("Exception: %s", e)
@ -492,10 +527,8 @@ class AirTimeApiClient():
logger.info("update file system mount: %s", json.loads(response)) logger.info("update file system mount: %s", json.loads(response))
except Exception, e: except Exception, e:
import traceback
top = traceback.format_exc()
logger.error('Exception: %s', e) logger.error('Exception: %s', e)
logger.error("traceback: %s", top) logger.error("traceback: %s", traceback.format_exc())
""" """
When watched dir is missing(unplugged or something) on boot up, this function will get called When watched dir is missing(unplugged or something) on boot up, this function will get called
@ -512,10 +545,8 @@ class AirTimeApiClient():
response = self.get_response_from_server(url) response = self.get_response_from_server(url)
logger.info("update file system mount: %s", json.loads(response)) logger.info("update file system mount: %s", json.loads(response))
except Exception, e: except Exception, e:
import traceback
top = traceback.format_exc()
logger.error('Exception: %s', e) logger.error('Exception: %s', e)
logger.error("traceback: %s", top) logger.error("traceback: %s", traceback.format_exc())
""" """
Retrive infomations needed on bootstrap time Retrive infomations needed on bootstrap time
@ -532,9 +563,29 @@ class AirTimeApiClient():
logger.info("Bootstrap info retrieved %s", response) logger.info("Bootstrap info retrieved %s", response)
except Exception, e: except Exception, e:
response = None response = None
import traceback
top = traceback.format_exc()
logger.error('Exception: %s', e) logger.error('Exception: %s', e)
logger.error("traceback: %s", top) logger.error("traceback: %s", traceback.format_exc())
return response return response
def get_files_without_replay_gain_value(self, dir_id):
"""
Download a list of files that need to have their ReplayGain value calculated. This list
of files is downloaded into a file and the path to this file is the return value.
"""
#http://localhost/api/get-files-without-replay-gain/dir_id/1
logger = self.logger
try:
url = "http://%(base_url)s:%(base_port)s/%(api_base)s/%(get-files-without-replay-gain)s/" % (self.config)
url = url.replace("%%api_key%%", self.config["api_key"])
url = url.replace("%%dir_id%%", dir_id)
file_path = self.get_response_into_file(url)
except Exception, e:
file_path = None
logger.error('Exception: %s', e)
logger.error("traceback: %s", traceback.format_exc())
return file_path

View file

@ -2,6 +2,8 @@ from subprocess import Popen, PIPE
import re import re
import os import os
import sys import sys
import shutil
import tempfile
def get_process_output(command): def get_process_output(command):
""" """
@ -26,38 +28,57 @@ def get_mime_type(file_path):
return get_process_output("timeout 5 file -b --mime-type %s" % file_path) return get_process_output("timeout 5 file -b --mime-type %s" % file_path)
def duplicate_file(file_path):
"""
Makes a duplicate of the file and returns the path of this duplicate file.
"""
fsrc = open(file_path, 'r')
fdst = tempfile.NamedTemporaryFile(delete=False)
print "Copying %s to %s" % (file_path, fdst.name)
shutil.copyfileobj(fsrc, fdst)
fsrc.close()
fdst.close()
return fdst.name
def calculate_replay_gain(file_path): def calculate_replay_gain(file_path):
""" """
This function accepts files of type mp3/ogg/flac and returns a calculated ReplayGain value in dB. This function accepts files of type mp3/ogg/flac and returns a calculated ReplayGain value in dB.
If the value cannot be calculated for some reason, then we default to 0 (Unity Gain). If the value cannot be calculated for some reason, then we default to 0 (Unity Gain).
TODO:
Currently some of the subprocesses called will actually insert metadata into the file itself,
which we do *not* want as this changes the file's hash. Need to make a copy of the file before
we run this function.
http://wiki.hydrogenaudio.org/index.php?title=ReplayGain_1.0_specification http://wiki.hydrogenaudio.org/index.php?title=ReplayGain_1.0_specification
""" """
try:
"""
Making a duplicate is required because the ReplayGain extraction utilities we use
make unwanted modifications to the file.
"""
search = None search = None
if re.search(r'mp3$', file_path, re.IGNORECASE) or get_mime_type(file_path) == "audio/mpeg": temp_file_path = duplicate_file(file_path)
if re.search(r'mp3$', temp_file_path, re.IGNORECASE) or get_mime_type(temp_file_path) == "audio/mpeg":
if run_process("which mp3gain > /dev/null") == 0: if run_process("which mp3gain > /dev/null") == 0:
out = get_process_output('mp3gain -q "%s" 2> /dev/null' % file_path) out = get_process_output('mp3gain -q "%s" 2> /dev/null' % temp_file_path)
search = re.search(r'Recommended "Track" dB change: (.*)', out) search = re.search(r'Recommended "Track" dB change: (.*)', out)
else: else:
print "mp3gain not found" print "mp3gain not found"
#Log warning #Log warning
elif re.search(r'ogg$', file_path, re.IGNORECASE) or get_mime_type(file_path) == "application/ogg": elif re.search(r'ogg$', temp_file_path, re.IGNORECASE) or get_mime_type(temp_file_path) == "application/ogg":
if run_process("which vorbisgain > /dev/null && which ogginfo > /dev/null") == 0: if run_process("which vorbisgain > /dev/null && which ogginfo > /dev/null") == 0:
run_process('vorbisgain -q -f "%s" 2>/dev/null >/dev/null' % file_path) run_process('vorbisgain -q -f "%s" 2>/dev/null >/dev/null' % temp_file_path)
out = get_process_output('ogginfo "%s"' % file_path) out = get_process_output('ogginfo "%s"' % temp_file_path)
search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out) search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out)
else: else:
print "vorbisgain/ogginfo not found" print "vorbisgain/ogginfo not found"
#Log warning #Log warning
elif re.search(r'flac$', file_path, re.IGNORECASE) or get_mime_type(file_path) == "audio/x-flac": elif re.search(r'flac$', temp_file_path, re.IGNORECASE) or get_mime_type(temp_file_path) == "audio/x-flac":
if run_process("which metaflac > /dev/null") == 0: if run_process("which metaflac > /dev/null") == 0:
out = get_process_output('metaflac --show-tag=REPLAYGAIN_TRACK_GAIN "%s"' % file_path) out = get_process_output('metaflac --show-tag=REPLAYGAIN_TRACK_GAIN "%s"' % temp_file_path)
search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out) search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out)
else: else:
print "metaflac not found" print "metaflac not found"
@ -66,6 +87,11 @@ def calculate_replay_gain(file_path):
pass pass
#Log unknown file type. #Log unknown file type.
#no longer need the temp, file simply remove it.
os.remove(temp_file_path)
except Exception, e:
print e
replay_gain = 0 replay_gain = 0
if search: if search:
matches = search.groups() matches = search.groups()

View file

@ -0,0 +1,78 @@
from threading import Thread
import traceback
import os
import logging
import json
from api_clients import api_client
import replaygain
class ReplayGainUpdater(Thread):
"""
The purpose of the class is to query the server for a list of files which do not have a ReplayGain
value calculated. This class will iterate over the list calculate the values, update the server and
repeat the process until the the server reports there are no files left.
This class will see heavy activity right after a 2.1->2.2 upgrade since 2.2 introduces ReplayGain
normalization. A fresh install of Airtime 2.2 will see this class not used at all since a file
imported in 2.2 will automatically have its ReplayGain value calculated.
"""
def __init__(self, logger):
Thread.__init__(self)
self.logger = logger
self.api_client = api_client.AirTimeApiClient()
def main(self):
#TODO
directories = self.api_client.list_all_watched_dirs()['dirs']
for dir_id, dir_path in directories.iteritems():
try:
processed_data = []
#keep getting 100 rows at a time for current music_dir (stor or watched folder).
#When we get a response with 0 rows, then we will set response to True.
finished = False
while not finished:
# return a list of pairs where the first value is the file's database row id
# and the second value is the filepath
file_path = self.api_client.get_files_without_replay_gain_value(dir_id)
print "temp file saved to %s" % file_path
num_lines = 0
with open(file_path) as f:
for line in f:
num_lines += 1
data = json.loads(line.strip())
track_path = os.path.join(dir_path, data['fp'])
processed_data.append((data['id'], replaygain.calculate_replay_gain(track_path)))
if num_lines == 0:
finished = True
os.remove(file_path)
#send data here
pass
except Exception, e:
print e
def run(self):
try: self.main()
except Exception, e:
self.logger.error('ReplayGainUpdater Exception: %s', traceback.format_exc())
self.logger.error(e)
if __name__ == "__main__":
try:
rgu = ReplayGainUpdater(logging)
print rgu.main()
except Exception, e:
print e
print traceback.format_exc()