diff --git a/debian/control b/debian/control index b5ea18954..19e4adf93 100644 --- a/debian/control +++ b/debian/control @@ -41,7 +41,7 @@ Depends: apache2, pwgen, python, rabbitmq-server, - silan, + silan (>= 0.3.0~), sudo, sysv-rc, tar (>= 1.22), diff --git a/python_apps/media-monitor2/media/monitor/pure.py b/python_apps/media-monitor2/media/monitor/pure.py index 31863ac2b..86b9a1f69 100644 --- a/python_apps/media-monitor2/media/monitor/pure.py +++ b/python_apps/media-monitor2/media/monitor/pure.py @@ -415,6 +415,7 @@ def file_playable(pathname): """ Returns True if 'pathname' is playable by liquidsoap. False otherwise. """ + #currently disabled because this confuses inotify.... return True #remove all write permissions. This is due to stupid taglib library bug #where all files are opened in write mode. The only way around this is to diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index 566df0d9e..4a8a0b0e0 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -42,13 +42,13 @@ queue = amplify(1., override="replay_gain", queue) # the crossfade function controls fade in/out queue = crossfade_airtime(queue) queue = on_metadata(notify, queue) -queue = map_metadata(update=false, append_title, queue) output.dummy(fallible=true, queue) http = input.http_restart(id="http") http = cross_http(http_input_id="http",http) output.dummy(fallible=true, http) stream_queue = http_fallback(http_input_id="http", http=http, default=queue) +stream_queue = map_metadata(update=false, append_title, stream_queue) ignore(output.dummy(stream_queue, fallible=true)) diff --git a/python_apps/pypo/media/update/replaygain.py b/python_apps/pypo/media/update/replaygain.py index 5af7cd4a1..329a1e6a0 100644 --- a/python_apps/pypo/media/update/replaygain.py +++ b/python_apps/pypo/media/update/replaygain.py @@ -14,14 +14,14 @@ def get_process_output(command): Run subprocess and return stdout """ logger.debug(command) - p = Popen(command, shell=True, stdout=PIPE) + p = Popen(command, stdout=PIPE, stderr=PIPE) return p.communicate()[0].strip() def run_process(command): """ Run subprocess and return "return code" """ - p = Popen(command, shell=True) + p = Popen(command, stdout=PIPE, stderr=PIPE) return os.waitpid(p.pid, 0)[1] def get_mime_type(file_path): @@ -31,7 +31,8 @@ def get_mime_type(file_path): for files which do not have a mp3/ogg/flac extension. """ - return get_process_output("timeout 5 file -b --mime-type %s" % file_path) + command = ['timeout', '5', 'file', '-b', '--mime-type', file_path] + return get_process_output(command) def duplicate_file(file_path): """ @@ -89,41 +90,37 @@ def calculate_replay_gain(file_path): temp_file_path = duplicate_file(file_path) file_type = get_file_type(file_path) - nice_level = '15' + nice_level = '19' if file_type: if file_type == 'mp3': - if run_process("which mp3gain > /dev/null") == 0: - command = 'nice -n %s mp3gain -q "%s" 2> /dev/null' \ - % (nice_level, temp_file_path) + if run_process(['which', 'mp3gain']) == 0: + command = ['nice', '-n', nice_level, 'mp3gain', '-q', temp_file_path] out = get_process_output(command) search = re.search(r'Recommended "Track" dB change: (.*)', \ out) else: logger.warn("mp3gain not found") elif file_type == 'vorbis': - command = "which vorbisgain > /dev/null && which ogginfo > \ - /dev/null" - if run_process(command) == 0: - command = 'nice -n %s vorbisgain -q -f "%s" 2>/dev/null \ - >/dev/null' % (nice_level,temp_file_path) + if run_process(['which', 'ogginfo']) == 0 and \ + run_process(['which', 'vorbisgain']) == 0: + command = ['nice', '-n', nice_level, 'vorbisgain', '-q', '-f', temp_file_path] run_process(command) - out = get_process_output('ogginfo "%s"' % temp_file_path) + out = get_process_output(['ogginfo', temp_file_path]) search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out) else: logger.warn("vorbisgain/ogginfo not found") elif file_type == 'flac': - if run_process("which metaflac > /dev/null") == 0: + if run_process(['which', 'metaflac']) == 0: - command = 'nice -n %s metaflac --add-replay-gain "%s"' \ - % (nice_level, temp_file_path) + command = ['nice', '-n', nice_level, 'metaflac', \ + '--add-replay-gain', temp_file_path] run_process(command) - command = 'nice -n %s metaflac \ - --show-tag=REPLAYGAIN_TRACK_GAIN "%s"' \ - % (nice_level, temp_file_path) - + command = ['nice', '-n', nice_level, 'metaflac', \ + '--show-tag=REPLAYGAIN_TRACK_GAIN', \ + temp_file_path] out = get_process_output(command) search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out) else: logger.warn("metaflac not found") diff --git a/python_apps/pypo/media/update/silananalyzer.py b/python_apps/pypo/media/update/silananalyzer.py new file mode 100644 index 000000000..02b11079a --- /dev/null +++ b/python_apps/pypo/media/update/silananalyzer.py @@ -0,0 +1,84 @@ +from threading import Thread + +import traceback +import time +import subprocess +import json + + +class SilanAnalyzer(Thread): + """ + The purpose of the class is to query the server for a list of files which + do not have a Silan value calculated. This class will iterate over the + list calculate the values, update the server and repeat the process until + the server reports there are no files left. + """ + + @staticmethod + def start_silan(apc, logger): + me = SilanAnalyzer(apc, logger) + me.start() + + def __init__(self, apc, logger): + Thread.__init__(self) + self.api_client = apc + self.logger = logger + + def main(self): + while True: + # keep getting few rows at a time for current music_dir (stor + # or watched folder). + total = 0 + + # return a list of pairs where the first value is the + # file's database row id and the second value is the + # filepath + files = self.api_client.get_files_without_silan_value() + total_files = len(files) + if total_files == 0: return + processed_data = [] + for f in files: + full_path = f['fp'] + # silence detect(set default queue in and out) + try: + command = ['nice', '-n', '19', 'silan', '-b', '-f', 'JSON', full_path] + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + out = proc.communicate()[0].strip('\r\n') + info = json.loads(out) + data = {} + data['cuein'] = str('{0:f}'.format(info['sound'][0][0])) + data['cueout'] = str('{0:f}'.format(info['sound'][-1][1])) + processed_data.append((f['id'], data)) + total += 1 + if total % 5 == 0: + self.logger.info("Total %s / %s files has been processed.." % (total, total_files)) + except Exception, e: + self.logger.error(e) + self.logger.error(traceback.format_exc()) + + try: + self.api_client.update_cue_values_by_silan(processed_data) + except Exception ,e: + self.logger.error(e) + self.logger.error(traceback.format_exc()) + + self.logger.info("Processed: %d songs" % total) + + def run(self): + while True: + try: + self.logger.info("Running Silan analyzer") + self.main() + except Exception, e: + self.logger.error('Silan Analyzer Exception: %s', traceback.format_exc()) + self.logger.error(e) + self.logger.info("Sleeping for 5...") + time.sleep(60 * 5) + +if __name__ == "__main__": + from api_clients import api_client + import logging + logging.basicConfig(level=logging.DEBUG) + api_client = api_client.AirtimeApiClient() + SilanAnalyzer.start_silan(api_client, logging) + diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index bebe802bf..b645a8bff 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -25,6 +25,7 @@ from listenerstat import ListenerStat from pypomessagehandler import PypoMessageHandler from media.update.replaygainupdater import ReplayGainUpdater +from media.update.silananalyzer import SilanAnalyzer from configobj import ConfigObj @@ -126,21 +127,21 @@ def keyboardInterruptHandler(signum, frame): def liquidsoap_running_test(telnet_lock, host, port, logger): logger.debug("Checking to see if Liquidsoap is running") - success = True try: telnet_lock.acquire() tn = telnetlib.Telnet(host, port) msg = "version\n" tn.write(msg) tn.write("exit\n") - logger.info("Found: %s", tn.read_all()) + response = tn.read_all() + logger.info("Found: %s", response) except Exception, e: logger.error(str(e)) - success = False + return False finally: telnet_lock.release() - return success + return "Liquidsoap" in response if __name__ == '__main__': @@ -178,6 +179,7 @@ if __name__ == '__main__': api_client = api_client.AirtimeApiClient() ReplayGainUpdater.start_reply_gain(api_client) + SilanAnalyzer.start_silan(api_client, logger) success = False while not success: diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index f438b3bb1..cd552cf52 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -372,7 +372,17 @@ class PypoPush(Thread): first_link = chain[0] self.modify_cue_point(first_link) - if float(first_link['cue_in']) >= float(first_link['cue_out']): + + #ATM, we should never have an exception here. However in the future, it + #would be nice to allow cue_out to be None which would then allow us to + #fallback to the length of the track as the end point. + try: + end = first_link['cue_out'] + except TypeError: + #cue_out is type None + end = first_link['length'] + + if float(first_link['cue_in']) >= float(end): chain = chain [1:] return chain @@ -498,6 +508,11 @@ class PypoPush(Thread): self.logger.debug(msg) tn.write(msg) + show_name = media_item['show_name'] + msg = 'vars.show_name %s\n' % show_name.encode('utf-8') + tn.write(msg) + self.logger.debug(msg) + tn.write("exit\n") self.logger.debug(tn.read_all()) diff --git a/utils/airtime-silan.py b/utils/airtime-silan.py index fcaa6e88e..00e94d77e 100644 --- a/utils/airtime-silan.py +++ b/utils/airtime-silan.py @@ -3,8 +3,6 @@ from api_clients import api_client as apc import logging import json -import shutil -import commands import os import sys import subprocess