Merge branch 'cc-1799-put-airtime-storage-into-a-human-readable-file-naming-convention' into devel
This commit is contained in:
commit
66e73b930d
|
@ -9,6 +9,7 @@ class ApiController extends Zend_Controller_Action
|
||||||
$context = $this->_helper->getHelper('contextSwitch');
|
$context = $this->_helper->getHelper('contextSwitch');
|
||||||
$context->addActionContext('version', 'json')
|
$context->addActionContext('version', 'json')
|
||||||
->addActionContext('recorded-shows', 'json')
|
->addActionContext('recorded-shows', 'json')
|
||||||
|
->addActionContext('upload-file', 'json')
|
||||||
->addActionContext('upload-recorded', 'json')
|
->addActionContext('upload-recorded', 'json')
|
||||||
->addActionContext('media-monitor-setup', 'json')
|
->addActionContext('media-monitor-setup', 'json')
|
||||||
->addActionContext('media-item-status', 'json')
|
->addActionContext('media-item-status', 'json')
|
||||||
|
@ -288,6 +289,22 @@ class ApiController extends Zend_Controller_Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function uploadFileAction()
|
||||||
|
{
|
||||||
|
global $CC_CONFIG;
|
||||||
|
|
||||||
|
$api_key = $this->_getParam('api_key');
|
||||||
|
if (!in_array($api_key, $CC_CONFIG["apiKey"]))
|
||||||
|
{
|
||||||
|
header('HTTP/1.0 401 Unauthorized');
|
||||||
|
print 'You are not allowed to access this resource.';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$upload_dir = ini_get("upload_tmp_dir");
|
||||||
|
StoredFile::uploadFile($upload_dir);
|
||||||
|
}
|
||||||
|
|
||||||
public function uploadRecordedAction()
|
public function uploadRecordedAction()
|
||||||
{
|
{
|
||||||
global $CC_CONFIG;
|
global $CC_CONFIG;
|
||||||
|
@ -300,12 +317,17 @@ class ApiController extends Zend_Controller_Action
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//this file id is the recording for this show instance.
|
||||||
|
$show_instance_id = $this->_getParam('showinstanceid');
|
||||||
|
$file_id = $this->_getParam('fileid');
|
||||||
|
|
||||||
|
$this->view->fileid = $file_id;
|
||||||
|
$this->view->showinstanceid = $show_instance_id;
|
||||||
|
|
||||||
|
/*
|
||||||
$showCanceled = false;
|
$showCanceled = false;
|
||||||
$show_instance = $this->_getParam('show_instance');
|
$show_instance = $this->_getParam('show_instance');
|
||||||
|
|
||||||
$upload_dir = ini_get("upload_tmp_dir");
|
|
||||||
$file = StoredFile::uploadFile($upload_dir);
|
|
||||||
|
|
||||||
$show_name = "";
|
$show_name = "";
|
||||||
try {
|
try {
|
||||||
$show_inst = new ShowInstance($show_instance);
|
$show_inst = new ShowInstance($show_instance);
|
||||||
|
@ -357,6 +379,7 @@ class ApiController extends Zend_Controller_Action
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->view->id = $file->getId();
|
$this->view->id = $file->getId();
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mediaMonitorSetupAction() {
|
public function mediaMonitorSetupAction() {
|
||||||
|
|
|
@ -318,6 +318,7 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
|
|
||||||
logger.debug(url)
|
logger.debug(url)
|
||||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||||
|
logger.debug(url)
|
||||||
|
|
||||||
for i in range(0, retries):
|
for i in range(0, retries):
|
||||||
logger.debug("Upload attempt: %s", i+1)
|
logger.debug("Upload attempt: %s", i+1)
|
||||||
|
@ -360,14 +361,15 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def update_media_metadata(self, md, mode):
|
def update_media_metadata(self, md, mode, is_record=False):
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
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["update_media_url"])
|
|
||||||
|
|
||||||
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_media_url"])
|
||||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||||
url = url.replace("%%mode%%", mode)
|
url = url.replace("%%mode%%", mode)
|
||||||
|
logger.debug(url)
|
||||||
|
|
||||||
data = urllib.urlencode(md)
|
data = urllib.urlencode(md)
|
||||||
req = urllib2.Request(url, data)
|
req = urllib2.Request(url, data)
|
||||||
|
@ -376,6 +378,22 @@ class AirTimeApiClient(ApiClientInterface):
|
||||||
response = json.loads(response)
|
response = json.loads(response)
|
||||||
logger.info("update media %s", response)
|
logger.info("update media %s", response)
|
||||||
|
|
||||||
|
if(is_record):
|
||||||
|
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["upload_recorded"])
|
||||||
|
logger.debug(url)
|
||||||
|
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||||
|
logger.debug(url)
|
||||||
|
url = url.replace("%%fileid%%", str(response[u'id']))
|
||||||
|
logger.debug(url)
|
||||||
|
url = url.replace("%%showinstanceid%%", str(md['MDATA_KEY_TRACKNUMBER']))
|
||||||
|
logger.debug(url)
|
||||||
|
|
||||||
|
req = urllib2.Request(url)
|
||||||
|
response = urllib2.urlopen(req).read()
|
||||||
|
response = json.loads(response)
|
||||||
|
logger.info("associate recorded %s", response)
|
||||||
|
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
response = None
|
response = None
|
||||||
logger.error("Exception: %s", e)
|
logger.error("Exception: %s", e)
|
||||||
|
|
|
@ -100,19 +100,24 @@ class AirtimeNotifier(Notifier):
|
||||||
if file_md is None:
|
if file_md is None:
|
||||||
mutagen = self.md_manager.get_md_from_file(filepath)
|
mutagen = self.md_manager.get_md_from_file(filepath)
|
||||||
md.update(mutagen)
|
md.update(mutagen)
|
||||||
data = md
|
|
||||||
|
if d['is_recorded_show']:
|
||||||
|
self.api_client.update_media_metadata(md, mode, True)
|
||||||
|
else:
|
||||||
|
self.api_client.update_media_metadata(md, mode)
|
||||||
|
|
||||||
elif (os.path.exists(filepath) and (mode == self.config.MODE_MODIFY)):
|
elif (os.path.exists(filepath) and (mode == self.config.MODE_MODIFY)):
|
||||||
mutagen = self.md_manager.get_md_from_file(filepath)
|
mutagen = self.md_manager.get_md_from_file(filepath)
|
||||||
md.update(mutagen)
|
md.update(mutagen)
|
||||||
data = md
|
self.api_client.update_media_metadata(md, mode)
|
||||||
|
|
||||||
elif (mode == self.config.MODE_MOVED):
|
elif (mode == self.config.MODE_MOVED):
|
||||||
md['MDATA_KEY_MD5'] = self.md_manager.get_md5(filepath)
|
md['MDATA_KEY_MD5'] = self.md_manager.get_md5(filepath)
|
||||||
data = md
|
self.api_client.update_media_metadata(md, mode)
|
||||||
elif (mode == self.config.MODE_DELETE):
|
|
||||||
data = md
|
elif (mode == self.config.MODE_DELETE):
|
||||||
|
self.api_client.update_media_metadata(md, mode)
|
||||||
|
|
||||||
if data is not None:
|
|
||||||
self.api_client.update_media_metadata(data, mode)
|
|
||||||
|
|
||||||
def process_file_events(self, queue):
|
def process_file_events(self, queue):
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,8 @@ class AirtimeProcessEvent(ProcessEvent):
|
||||||
|
|
||||||
storage_directory = self.config.storage_directory
|
storage_directory = self.config.storage_directory
|
||||||
|
|
||||||
|
is_recorded_show = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
#will be in the format .ext
|
#will be in the format .ext
|
||||||
file_ext = os.path.splitext(imported_filepath)[1]
|
file_ext = os.path.splitext(imported_filepath)[1]
|
||||||
|
@ -165,10 +167,17 @@ class AirtimeProcessEvent(ProcessEvent):
|
||||||
md[m] = orig_md[m]
|
md[m] = orig_md[m]
|
||||||
|
|
||||||
filepath = None
|
filepath = None
|
||||||
if(md['MDATA_KEY_TRACKNUMBER'] == u'unknown'.encode('utf-8')):
|
#file is recorded by Airtime
|
||||||
filepath = '%s/%s/%s/%s-%s%s' % (storage_directory, md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
#/srv/airtime/stor/recorded/year/month/year-month-day-time-showname-bitrate.ext
|
||||||
|
if(md['MDATA_KEY_CREATOR'] == "AIRTIMERECORDERSOURCEFABRIC".encode('utf-8')):
|
||||||
|
#yyyy-mm-dd-hh-MM-ss
|
||||||
|
y = orig_md['MDATA_KEY_YEAR'].split("-")
|
||||||
|
filepath = '%s/%s/%s/%s/%s-%s-%s%s' % (storage_directory, "recorded".encode('utf-8'), y[0], y[1], orig_md['MDATA_KEY_YEAR'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
||||||
|
is_recorded_show = True
|
||||||
|
elif(md['MDATA_KEY_TRACKNUMBER'] == u'unknown'.encode('utf-8')):
|
||||||
|
filepath = '%s/%s/%s/%s/%s-%s%s' % (storage_directory, "imported".encode('utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
||||||
else:
|
else:
|
||||||
filepath = '%s/%s/%s/%s-%s-%s%s' % (storage_directory, md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TRACKNUMBER'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
filepath = '%s/%s/%s/%s/%s-%s-%s%s' % (storage_directory, "imported".encode('utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TRACKNUMBER'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
||||||
|
|
||||||
self.logger.info('Created filepath: %s', filepath)
|
self.logger.info('Created filepath: %s', filepath)
|
||||||
filepath = self.create_unique_filename(filepath, imported_filepath)
|
filepath = self.create_unique_filename(filepath, imported_filepath)
|
||||||
|
@ -178,7 +187,7 @@ class AirtimeProcessEvent(ProcessEvent):
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.logger.error('Exception: %s', e)
|
self.logger.error('Exception: %s', e)
|
||||||
|
|
||||||
return filepath, orig_md
|
return filepath, orig_md, is_recorded_show
|
||||||
|
|
||||||
def process_IN_CREATE(self, event):
|
def process_IN_CREATE(self, event):
|
||||||
|
|
||||||
|
@ -194,11 +203,11 @@ class AirtimeProcessEvent(ProcessEvent):
|
||||||
if self.is_audio_file(event.pathname):
|
if self.is_audio_file(event.pathname):
|
||||||
if self.is_parent_directory(event.pathname, storage_directory):
|
if self.is_parent_directory(event.pathname, storage_directory):
|
||||||
self.set_needed_file_permissions(event.pathname, event.dir)
|
self.set_needed_file_permissions(event.pathname, event.dir)
|
||||||
filepath, file_md = self.create_file_path(event.pathname)
|
filepath, file_md, is_recorded_show = self.create_file_path(event.pathname)
|
||||||
self.move_file(event.pathname, filepath)
|
self.move_file(event.pathname, filepath)
|
||||||
self.file_events.put({'mode': self.config.MODE_CREATE, 'filepath': filepath, 'data': file_md})
|
self.file_events.put({'mode': self.config.MODE_CREATE, 'filepath': filepath, 'data': file_md, 'is_recorded_show': is_recorded_show})
|
||||||
else:
|
else:
|
||||||
self.file_events.put({'mode': self.config.MODE_CREATE, 'filepath': event.pathname})
|
self.file_events.put({'mode': self.config.MODE_CREATE, 'filepath': event.pathname, 'is_recorded_show': False})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if self.is_parent_directory(event.pathname, storage_directory):
|
if self.is_parent_directory(event.pathname, storage_directory):
|
||||||
|
@ -233,13 +242,14 @@ class AirtimeProcessEvent(ProcessEvent):
|
||||||
del self.moved_files[event.cookie]
|
del self.moved_files[event.cookie]
|
||||||
self.file_events.put({'filepath': event.pathname, 'mode': self.config.MODE_MOVED})
|
self.file_events.put({'filepath': event.pathname, 'mode': self.config.MODE_MOVED})
|
||||||
else:
|
else:
|
||||||
|
# show dragged from unwatched folder into a watched folder.
|
||||||
storage_directory = self.config.storage_directory
|
storage_directory = self.config.storage_directory
|
||||||
if self.is_parent_directory(event.pathname, storage_directory):
|
if self.is_parent_directory(event.pathname, storage_directory):
|
||||||
filepath, file_md = self.create_file_path(event.pathname)
|
filepath, file_md, is_recorded_show = self.create_file_path(event.pathname)
|
||||||
self.move_file(event.pathname, filepath)
|
self.move_file(event.pathname, filepath)
|
||||||
self.file_events.put({'mode': self.config.MODE_CREATE, 'filepath': filepath, 'data': file_md})
|
self.file_events.put({'mode': self.config.MODE_CREATE, 'filepath': filepath, 'data': file_md, 'is_recorded_show': False})
|
||||||
else:
|
else:
|
||||||
self.file_events.put({'mode': self.config.MODE_CREATE, 'filepath': event.pathname})
|
self.file_events.put({'mode': self.config.MODE_CREATE, 'filepath': event.pathname, 'is_recorded_show': False})
|
||||||
|
|
||||||
def process_IN_DELETE(self, event):
|
def process_IN_DELETE(self, event):
|
||||||
self.logger.info("%s: %s", event.maskname, event.pathname)
|
self.logger.info("%s: %s", event.maskname, event.pathname)
|
||||||
|
|
|
@ -22,8 +22,8 @@ version_url = 'version/api_key/%%api_key%%'
|
||||||
# URL to setup the media monitor
|
# URL to setup the media monitor
|
||||||
media_setup_url = 'media-monitor-setup/format/json/api_key/%%api_key%%'
|
media_setup_url = 'media-monitor-setup/format/json/api_key/%%api_key%%'
|
||||||
|
|
||||||
# URL to check Airtime's status of a file
|
# Tell Airtime the file id associated with a show instance.
|
||||||
media_status_url = 'media-item-status/format/json/api_key/%%api_key%%/md5/%%md5%%'
|
upload_recorded = 'upload-recorded/format/json/api_key/%%api_key%%/fileid/%%fileid%%/showinstanceid/%%showinstanceid%%'
|
||||||
|
|
||||||
# URL to tell Airtime to update file's meta data
|
# URL to tell Airtime to update file's meta data
|
||||||
update_media_url = 'reload-metadata/format/json/api_key/%%api_key%%/mode/%%mode%%'
|
update_media_url = 'reload-metadata/format/json/api_key/%%api_key%%/mode/%%mode%%'
|
||||||
|
|
|
@ -26,7 +26,7 @@ version_url = 'version/api_key/%%api_key%%'
|
||||||
show_schedule_url = 'recorded-shows/format/json/api_key/%%api_key%%'
|
show_schedule_url = 'recorded-shows/format/json/api_key/%%api_key%%'
|
||||||
|
|
||||||
# URL to upload the recorded show's file to Airtime
|
# URL to upload the recorded show's file to Airtime
|
||||||
upload_file_url = 'upload-recorded/format/json/api_key/%%api_key%%'
|
upload_file_url = 'upload-file/format/json/api_key/%%api_key%%'
|
||||||
|
|
||||||
#number of retries to upload file if connection problem
|
#number of retries to upload file if connection problem
|
||||||
upload_retries = 3
|
upload_retries = 3
|
||||||
|
|
|
@ -7,6 +7,7 @@ import time
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
|
|
||||||
from configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
|
|
||||||
|
@ -17,6 +18,8 @@ import urllib2
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
import mutagen
|
||||||
|
|
||||||
# For RabbitMQ - to be implemented in the future
|
# For RabbitMQ - to be implemented in the future
|
||||||
#from kombu.connection import BrokerConnection
|
#from kombu.connection import BrokerConnection
|
||||||
#from kombu.messaging import Exchange, Queue, Consumer, Producer
|
#from kombu.messaging import Exchange, Queue, Consumer, Producer
|
||||||
|
@ -48,13 +51,14 @@ def getDateTimeObj(time):
|
||||||
|
|
||||||
class ShowRecorder(Thread):
|
class ShowRecorder(Thread):
|
||||||
|
|
||||||
def __init__ (self, show_instance, filelength, start_time, filetype):
|
def __init__ (self, show_instance, show_name, filelength, start_time, filetype):
|
||||||
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.filelength = filelength
|
self.filelength = filelength
|
||||||
self.start_time = start_time
|
self.start_time = start_time
|
||||||
self.filetype = filetype
|
self.filetype = filetype
|
||||||
self.show_instance = show_instance
|
self.show_instance = show_instance
|
||||||
|
self.show_name = show_name
|
||||||
self.logger = logging.getLogger('root')
|
self.logger = logging.getLogger('root')
|
||||||
self.p = None
|
self.p = None
|
||||||
|
|
||||||
|
@ -70,24 +74,24 @@ class ShowRecorder(Thread):
|
||||||
|
|
||||||
self.logger.info("starting record")
|
self.logger.info("starting record")
|
||||||
self.logger.info("command " + command)
|
self.logger.info("command " + command)
|
||||||
|
|
||||||
self.p = Popen(args)
|
self.p = Popen(args)
|
||||||
|
|
||||||
#blocks at the following line until the child process
|
#blocks at the following line until the child process
|
||||||
#quits
|
#quits
|
||||||
code = self.p.wait()
|
code = self.p.wait()
|
||||||
self.p = None
|
self.p = None
|
||||||
|
|
||||||
self.logger.info("finishing record, return code %s", code)
|
self.logger.info("finishing record, return code %s", code)
|
||||||
return code, filepath
|
return code, filepath
|
||||||
|
|
||||||
def cancel_recording(self):
|
def cancel_recording(self):
|
||||||
#send signal interrupt (2)
|
#send signal interrupt (2)
|
||||||
self.logger.info("Show manually cancelled!")
|
self.logger.info("Show manually cancelled!")
|
||||||
if (self.p is not None):
|
if (self.p is not None):
|
||||||
self.p.terminate()
|
self.p.terminate()
|
||||||
self.p = None
|
self.p = None
|
||||||
|
|
||||||
#if self.p is defined, then the child process ecasound is recording
|
#if self.p is defined, then the child process ecasound is recording
|
||||||
def is_recording(self):
|
def is_recording(self):
|
||||||
return (self.p is not None)
|
return (self.p is not None)
|
||||||
|
@ -110,6 +114,29 @@ class ShowRecorder(Thread):
|
||||||
|
|
||||||
if code == 0:
|
if code == 0:
|
||||||
self.logger.info("Preparing to upload %s" % filepath)
|
self.logger.info("Preparing to upload %s" % filepath)
|
||||||
|
|
||||||
|
try:
|
||||||
|
date = self.start_time
|
||||||
|
md = date.split(" ")
|
||||||
|
time = md[1].replace(":", "-")
|
||||||
|
self.logger.info("time: %s" % time)
|
||||||
|
|
||||||
|
name = time+"-"+self.show_name
|
||||||
|
name.encode('utf-8')
|
||||||
|
artist = "AIRTIMERECORDERSOURCEFABRIC".encode('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)
|
||||||
|
|
||||||
|
|
||||||
self.upload_file(filepath)
|
self.upload_file(filepath)
|
||||||
else:
|
else:
|
||||||
self.logger.info("problem recording show")
|
self.logger.info("problem recording show")
|
||||||
|
@ -154,8 +181,9 @@ class Record():
|
||||||
|
|
||||||
show_length = self.shows_to_record[start_time][0]
|
show_length = self.shows_to_record[start_time][0]
|
||||||
show_instance = self.shows_to_record[start_time][1]
|
show_instance = self.shows_to_record[start_time][1]
|
||||||
|
show_name = self.shows_to_record[start_time][2]
|
||||||
|
|
||||||
self.sr = ShowRecorder(show_instance, show_length.seconds, start_time, filetype="mp3")
|
self.sr = ShowRecorder(show_instance, show_name, show_length.seconds, start_time, filetype="mp3")
|
||||||
self.sr.start()
|
self.sr.start()
|
||||||
|
|
||||||
#remove show from shows to record.
|
#remove show from shows to record.
|
||||||
|
@ -170,9 +198,9 @@ class Record():
|
||||||
if self.sr is not None:
|
if self.sr is not None:
|
||||||
if not response['is_recording'] and self.sr.is_recording():
|
if not response['is_recording'] and self.sr.is_recording():
|
||||||
self.sr.cancel_recording()
|
self.sr.cancel_recording()
|
||||||
|
|
||||||
shows = response[u'shows']
|
shows = response[u'shows']
|
||||||
|
|
||||||
if len(shows):
|
if len(shows):
|
||||||
self.process_shows(shows)
|
self.process_shows(shows)
|
||||||
self.check_record()
|
self.check_record()
|
||||||
|
|
Loading…
Reference in New Issue