From b26703648386d666d61cef0d5e2bdf169f51255f Mon Sep 17 00:00:00 2001 From: Lucas Bickel Date: Fri, 24 Mar 2017 15:12:06 +0100 Subject: [PATCH] Fix recording through ecasound This is part 2 of fixing ecasound recordings from line-in. Part 1 restored the User-Interface, part 2 takes care of getting to the point where ecasound gets started, records something and uploads it through rest when done. Part 3 will take care of making sure that the recorded file is mapped to the show and not just stored as a new track. I refactored api_clients to not use urllib2 for posting multipart data since I was loosing my sanity over it and requests seems to have a modern approach to doing this compared to what api_clients was previously doing. --- airtime_mvc/application/configs/conf.php | 4 -- .../application/controllers/ApiController.php | 19 ------- installer/vagrant/centos.sh | 5 ++ .../api_clients/api_clients/api_client.py | 54 ++++++++++++++++--- python_apps/pypo/pypo/recorder.py | 9 ++-- 5 files changed, 55 insertions(+), 36 deletions(-) diff --git a/airtime_mvc/application/configs/conf.php b/airtime_mvc/application/configs/conf.php index e6f0853c8..0a0339047 100644 --- a/airtime_mvc/application/configs/conf.php +++ b/airtime_mvc/application/configs/conf.php @@ -73,10 +73,6 @@ class Config { $CC_CONFIG['apiKey'] = array($values['general']['api_key']); - if (defined('APPLICATION_ENV') && APPLICATION_ENV == "development"){ - $CC_CONFIG['apiKey'][] = ""; - } - $CC_CONFIG['soundcloud-connection-retries'] = $values['soundcloud']['connection_retries']; $CC_CONFIG['soundcloud-connection-wait'] = $values['soundcloud']['time_between_retries']; diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index e4c5fe5da..c5f377512 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -642,25 +642,6 @@ class ApiController extends Zend_Controller_Action } } - public function uploadFileAction() - { - Logging::error("FIXME: Change the show recorder to use the File Upload API and remove this function."); // Albert - April 3, 2014 - /** - $upload_dir = ini_get("upload_tmp_dir"); - $tempFilePath = Application_Model_StoredFile::uploadFile($upload_dir); - $tempFileName = basename($tempFilePath); - - $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : ''; - $result = Application_Model_StoredFile::copyFileToStor($upload_dir, $fileName, $tempFileName); - - if (!is_null($result)) { - $this->_helper->json->sendJson( - array("jsonrpc" => "2.0", "error" => array("code" => $result['code'], "message" => $result['message'])) - ); - } - **/ - } - public function uploadRecordedAction() { $show_instance_id = $this->_getParam('showinstanceid'); diff --git a/installer/vagrant/centos.sh b/installer/vagrant/centos.sh index 2d8876021..48136e706 100644 --- a/installer/vagrant/centos.sh +++ b/installer/vagrant/centos.sh @@ -87,6 +87,8 @@ yum install -y \ fdk-aac \ liquidsoap \ silan \ + ecasound \ + alsa-utils \ icecast \ python-pip \ selinux-policy \ @@ -155,3 +157,6 @@ systemctl restart httpd # icecast needs to be available to everyone sed -i -e 's@127.0.0.1@0.0.0.0@' /etc/icecast.xml systemctl enable --now icecast + +# let em use alsa +usermod -a -G audio apache diff --git a/python_apps/api_clients/api_clients/api_client.py b/python_apps/api_clients/api_clients/api_client.py index e3a08e0b3..49bac65e1 100644 --- a/python_apps/api_clients/api_clients/api_client.py +++ b/python_apps/api_clients/api_clients/api_client.py @@ -10,6 +10,7 @@ import sys import time import urllib import urllib2 +import requests import socket import logging import json @@ -61,7 +62,7 @@ api_config['reload_metadata_group'] = 'reload-metadata-group/format/json/api_key api_config['handle_watched_dir_missing'] = 'handle-watched-dir-missing/format/json/api_key/%%api_key%%/dir/%%dir%%' #show-recorder api_config['show_schedule_url'] = 'recorded-shows/format/json/api_key/%%api_key%%' -api_config['upload_file_url'] = 'upload-file/format/json/api_key/%%api_key%%' +api_config['upload_file_url'] = 'rest/media' api_config['upload_retries'] = '3' api_config['upload_wait'] = '60' #pypo @@ -278,33 +279,57 @@ class AirtimeApiClient(object): self.logger.error(str(e)) return None - def upload_recorded_show(self, data, headers): + def upload_recorded_show(self, files, show_id): logger = self.logger response = '' retries = int(self.config["upload_retries"]) retries_wait = int(self.config["upload_wait"]) - url = self.construct_url("upload_file_url") + url = self.construct_rest_url("upload_file_url") logger.debug(url) for i in range(0, retries): logger.debug("Upload attempt: %s", i + 1) + logger.debug(files) + logger.debug(ApiRequest.API_HTTP_REQUEST_TIMEOUT) try: - request = urllib2.Request(url, data, headers) - response = urllib2.urlopen(request, timeout=ApiClient.API_HTTP_REQUEST_TIMEOUT).read().strip() + request = requests.post(url, files=files, timeout=float(ApiRequest.API_HTTP_REQUEST_TIMEOUT)) + response = request.json() + logger.debug(response) - logger.info("uploaded show result %s", response) + """ + FIXME: We need to tell LibreTime that the uploaded track was recorded for a specific show + + My issue here is that response does not yet have an id. The id gets generated at the point + where analyzer is done with it's work. We probably need to do what is below in analyzer + and also make sure that the show instance id is routed all the way through. + + It already gets uploaded by this but the RestController does not seem to care about it. In + the end analyzer doesn't have the info in it's rabbitmq message and imports the show as a + regular track. + + logger.info("uploaded show result as file id %s", response.id) + + url = self.construct_url("upload_recorded") + url = url.replace('%%fileid%%', response.id) + url = url.replace('%%showinstanceid%%', show_id) + request.get(url) + logger.info("associated uploaded file %s with show instance %s", response.id, show_id) + """ break - except urllib2.HTTPError, e: + except requests.exceptions.HTTPError, e: logger.error("Http error code: %s", e.code) - except urllib2.URLError, e: + logger.error("traceback: %s", traceback.format_exc()) + except requests.exceptions.ConnectionError, e: logger.error("Server is down: %s", e.args) + logger.error("traceback: %s", traceback.format_exc()) except Exception, e: logger.error("Exception: %s", e) + logger.error("traceback: %s", traceback.format_exc()) #wait some time before next retry time.sleep(retries_wait) @@ -332,6 +357,19 @@ class AirtimeApiClient(object): url = url.replace("%%api_key%%", self.config["general"]["api_key"]) return url + def construct_rest_url(self,config_action_key): + """Constructs the base url for RESTful requests""" + if self.config["general"]["base_dir"].startswith("/"): + self.config["general"]["base_dir"] = self.config["general"]["base_dir"][1:] + url = "%s://%s:@%s:%s/%s/%s" % \ + (str(("http", "https")[int(self.config["general"]["base_port"]) == 443]), + self.config["general"]["api_key"], + self.config["general"]["base_url"], str(self.config["general"]["base_port"]), + self.config["general"]["base_dir"], + self.config[config_action_key]) + return url + + """ Caller of this method needs to catch any exceptions such as ValueError thrown by json.loads or URLError by urllib2.urlopen diff --git a/python_apps/pypo/pypo/recorder.py b/python_apps/pypo/pypo/recorder.py index 73cf2585b..bc74f3c10 100644 --- a/python_apps/pypo/pypo/recorder.py +++ b/python_apps/pypo/pypo/recorder.py @@ -95,7 +95,7 @@ class ShowRecorder(Thread): self.logger.info("starting record") self.logger.info("command " + command) - self.p = Popen(args,stdout=PIPE) + self.p = Popen(args,stdout=PIPE,stderr=PIPE) #blocks at the following line until the child process #quits @@ -129,11 +129,10 @@ class ShowRecorder(Thread): # 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}) + # files is what requests actually expects + files = {'file': open(filepath, "rb"), 'name': filename, 'show_instance': str(self.show_instance)} - self.api_client.upload_recorded_show(datagen, headers) + self.api_client.upload_recorded_show(files, self.show_instance) def set_metadata_and_save(self, filepath): """