diff --git a/airtime_mvc/application/Bootstrap.php b/airtime_mvc/application/Bootstrap.php index d7ba2d192..8e9f875e8 100644 --- a/airtime_mvc/application/Bootstrap.php +++ b/airtime_mvc/application/Bootstrap.php @@ -25,8 +25,7 @@ date_default_timezone_set(Application_Model_Preference::GetTimezone()); global $CC_CONFIG; $airtime_version = Application_Model_Preference::GetAirtimeVersion(); $uniqueid = Application_Model_Preference::GetUniqueId(); -$CC_CONFIG['airtime_version'] = md5($airtime_version + $uniqueid); - +$CC_CONFIG['airtime_version'] = md5($airtime_version.$uniqueid); require_once __DIR__."/configs/navigation.php"; Zend_Validate::setDefaultNamespaces("Zend"); diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 296cfcdaf..52843c738 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -129,6 +129,12 @@ class ApiController extends Zend_Controller_Action if(is_file($filepath)){ $full_path = $media->getPropelOrm()->getDbFilepath(); $file_base_name = strrchr($full_path, '/'); + /* If $full_path does not contain a '/', strrchr will return false, + * in which case we can use $full_path as the base name. + */ + if (!$file_base_name) { + $file_base_name = $full_path; + } $file_base_name = substr($file_base_name, 1); // possibly use fileinfo module here in the future. // http://www.php.net/manual/en/book.fileinfo.php diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index c8edc4453..5c80013ef 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -548,10 +548,11 @@ class Application_Model_Schedule { $data["media"][$kick_start]['end'] = $kick_start; $data["media"][$kick_start]['event_type'] = "kick_out"; $data["media"][$kick_start]['type'] = "event"; - + if($kick_time !== $switch_off_time){ - $data["media"][$switch_start]['start'] = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time); - $data["media"][$switch_start]['end'] = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time); + $switch_start = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time); + $data["media"][$switch_start]['start'] = $switch_start; + $data["media"][$switch_start]['end'] = $switch_start; $data["media"][$switch_start]['event_type'] = "switch_off"; $data["media"][$switch_start]['type'] = "event"; } diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 5e286fbc9..2f6910978 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -202,13 +202,19 @@ var AIRTIME = (function(AIRTIME) { */ mod.selectCurrentPage = function() { $.fn.reverse = [].reverse; - var $trs = $libTable.find("tbody input:checkbox").parents("tr").reverse(); + var $inputs = $libTable.find("tbody input:checkbox"), + $trs = $inputs.parents("tr").reverse(); + + $inputs.attr("checked", true); + $trs.addClass(LIB_SELECTED_CLASS); $trs.each(function(i, el){ $el = $(this); - - mod.selectItem($el); + mod.addToChosen($el); }); + + mod.checkToolBarIcons(); + }; /* @@ -216,14 +222,20 @@ var AIRTIME = (function(AIRTIME) { * (behaviour taken from gmail) */ mod.deselectCurrentPage = function() { + var $inputs = $libTable.find("tbody input:checkbox"), + $trs = $inputs.parents("tr"), + id; - var $trs = $libTable.find("tbody input:checkbox").filter(":checked").parents("tr"); + $inputs.attr("checked", false); + $trs.removeClass(LIB_SELECTED_CLASS); $trs.each(function(i, el){ $el = $(this); - - mod.deselectItem($el); - }); + id = $el.attr("id"); + delete chosenItems[id]; + }); + + mod.checkToolBarIcons(); }; mod.selectNone = function() { diff --git a/airtime_mvc/public/js/airtime/schedule/add-show.js b/airtime_mvc/public/js/airtime/schedule/add-show.js index 5c161e097..d85742e4b 100644 --- a/airtime_mvc/public/js/airtime/schedule/add-show.js +++ b/airtime_mvc/public/js/airtime/schedule/add-show.js @@ -91,6 +91,34 @@ function onEndTimeSelect(){ $("#add_show_end_time").trigger('input'); } +function padZeroes(number, length) +{ + var str = '' + number; + while (str.length < length) {str = '0' + str;} + return str; +} + +function hashCode(str) { // java String#hashCode + var hash = 0; + for (var i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + return hash; +} + +function intToRGB(i){ + return (padZeroes(((i>>16)&0xFF).toString(16), 2) + + padZeroes(((i>>8)&0xFF).toString(16), 2)+ + padZeroes((i&0xFF).toString(16), 2) + ); +} + +function stringToColor(s) +{ + return intToRGB(hashCode(s)); +} + + function setAddShowEvents() { var form = $("#add-show-form"); @@ -548,6 +576,12 @@ function setAddShowEvents() { loadingIcon.hide(); }); } + + var bgColorEle = $("#add_show_background_color"); + $('#add_show_name').bind('input', 'change', function(){ + var colorCode = stringToColor($(this).val()); + bgColorEle.val(colorCode); + }); } function showErrorSections() { diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index c4a58cc07..b5ed74369 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -210,7 +210,8 @@ var AIRTIME = (function(AIRTIME){ mod.fnItemCallback = function(json) { checkError(json); - + + cursorIds = []; cursors = $(".cursor-selected-row"); for (i = 0; i < cursors.length; i++) { cursorIds.push(($(cursors.get(i)).attr("id"))); @@ -422,8 +423,6 @@ var AIRTIME = (function(AIRTIME){ $nRow.addClass(sClass); }; - - $nRow.attr("id", aData.id); if (aData.header === true) { //remove the column classes from all tds. @@ -583,11 +582,12 @@ var AIRTIME = (function(AIRTIME){ $nRow.addClass("sb-future"); } - if (aData.allowed !== true) { + if (aData.allowed !== true || aData.header === true) { $nRow.addClass("sb-not-allowed"); } else { $nRow.addClass("sb-allowed"); + $nRow.attr("id", aData.id); } //status used to colour tracks. @@ -601,7 +601,7 @@ var AIRTIME = (function(AIRTIME){ if (aData.currentShow === true) { $nRow.addClass("sb-current-show"); } - + //call the context menu so we can prevent the event from propagating. $nRow.find('td:gt(1)').click(function(e){ @@ -673,7 +673,6 @@ var AIRTIME = (function(AIRTIME){ $tr = $table.find("tr[id="+cursorIds[i]+"]"); mod.selectCursor($tr); } - cursorIds = []; //if there is only 1 cursor on the page highlight it by default. if ($cursorRows.length === 1) { @@ -699,7 +698,7 @@ var AIRTIME = (function(AIRTIME){ if (temp.length > 0) { aData = temp.data("aData"); // max time interval - // setTimeout allow only up to 2^21 millisecs timeout value + // setTimeout allows only up to (2^31)-1 millisecs timeout value maxRefreshInterval = Math.pow(2, 31) - 1; refreshInterval = aData.refresh * 1000; if(refreshInterval > maxRefreshInterval){ diff --git a/install_minimal/DoctrineMigrations/Version20110711161043.php b/install_minimal/DoctrineMigrations/Version20110711161043.php index 7c0de0ee3..388d31772 100644 --- a/install_minimal/DoctrineMigrations/Version20110711161043.php +++ b/install_minimal/DoctrineMigrations/Version20110711161043.php @@ -15,7 +15,7 @@ class Version20110711161043 extends AbstractMigration { public function up(Schema $schema) { - $ini = parse_ini_file(__DIR__."../include/airtime-install.ini"); + $ini = parse_ini_file(__DIR__."/../include/airtime-install.ini"); $stor_dir = $ini["storage_dir"]; /* 1) update cc_files table to include to "directory" column */ diff --git a/install_minimal/include/airtime-install.php b/install_minimal/include/airtime-install.php index cb9189a4f..6fddb7bfa 100644 --- a/install_minimal/include/airtime-install.php +++ b/install_minimal/include/airtime-install.php @@ -61,6 +61,16 @@ AirtimeInstall::InstallStorageDirectory(); $db_install = getenv("nodb")!="t"; if ($db_install) { + + echo "* Checking database for correct encoding".PHP_EOL; + exec('su -c \'psql -t -c "SHOW SERVER_ENCODING"\' postgres | grep -i "UTF.*8"', $out, $return_code); + if ($return_code != 0){ + echo " * Unfortunately your postgresql database has not been created using a UTF-8 encoding.".PHP_EOL; + echo " * As of Airtime 2.1, installs will fail unless the encoding has been set to UTF-8. Please verify this is the case".PHP_EOL; + echo " * and try the install again".PHP_EOL; + exit(1); + } + if($newInstall) { //call external script. "y" argument means force creation of database tables. passthru('php '.__DIR__.'/airtime-db-install.php y'); diff --git a/python_apps/pypo/install/pypo-initialize.py b/python_apps/pypo/install/pypo-initialize.py index bbefe7ddb..12eb870ca 100644 --- a/python_apps/pypo/install/pypo-initialize.py +++ b/python_apps/pypo/install/pypo-initialize.py @@ -109,20 +109,6 @@ try: print e sys.exit(1) - """ - logging.basicConfig(format='%(message)s') - - #generate liquidsoap config file - #access the DB and generate liquidsoap.cfg under /etc/airtime/ - ac = api_client.api_client_factory(config, logging.getLogger()) - ss = ac.get_stream_setting() - - if ss is not None: - generate_liquidsoap_config(ss) - else: - print "Unable to connect to the Airtime server." - """ - #initialize init.d scripts subprocess.call("update-rc.d airtime-playout defaults >/dev/null 2>&1", shell=True) diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index efbd221de..482a60a5e 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -130,21 +130,21 @@ end def append_dj_inputs(master_harbor_input_port, master_harbor_input_mount_point, dj_harbor_input_port, dj_harbor_input_mount_point, s) = if master_harbor_input_port != 0 and master_harbor_input_mount_point != "" and dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then - master_dj = mksafe(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, - max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect)) - dj_live = mksafe(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, - max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect)) + master_dj = mksafe(audio_to_stereo(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, + max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect))) + dj_live = mksafe(audio_to_stereo(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, + max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect))) ignore(output.dummy(master_dj, fallible=true)) ignore(output.dummy(dj_live, fallible=true)) switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition, transition], [({!master_dj_enabled},master_dj), ({!live_dj_enabled},dj_live), ({true}, s)]) elsif master_harbor_input_port != 0 and master_harbor_input_mount_point != "" then - master_dj = mksafe(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, - max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect)) + master_dj = mksafe(audio_to_stereo(input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, + max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect))) ignore(output.dummy(master_dj, fallible=true)) switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!master_dj_enabled},master_dj), ({true}, s)]) elsif dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then - dj_live = mksafe(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, - max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect)) + dj_live = mksafe(audio_to_stereo(input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, + max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect))) ignore(output.dummy(dj_live, fallible=true)) switch(id="live_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!live_dj_enabled},dj_live), ({true}, s)]) else diff --git a/python_apps/pypo/liquidsoap_scripts/notify.sh b/python_apps/pypo/liquidsoap_scripts/notify.sh index 848428cee..fa69cd1e8 100755 --- a/python_apps/pypo/liquidsoap_scripts/notify.sh +++ b/python_apps/pypo/liquidsoap_scripts/notify.sh @@ -10,4 +10,4 @@ SCRIPT=`readlink -f $0` # Absolute path this script is in SCRIPTPATH=`dirname $SCRIPT` -cd ${SCRIPTPATH}/../ && python pypo-notify.py "$@" +cd ${SCRIPTPATH}/../ && python pyponotify.py "$@" diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index 1cc8ff2c8..e11de3047 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -2,13 +2,13 @@ Python part of radio playout (pypo) """ +from optparse import OptionParser +from datetime import datetime + import time -from optparse import * import sys import signal import logging -import logging.config -import logging.handlers import locale import os from Queue import Queue @@ -53,11 +53,11 @@ parser.add_option("-c", "--check", help="Check the cached schedule and exit", de def configure_locale(): logger.debug("Before %s", locale.nl_langinfo(locale.CODESET)) current_locale = locale.getlocale() - + if current_locale[1] is None: logger.debug("No locale currently set. Attempting to get default locale.") default_locale = locale.getdefaultlocale() - + if default_locale[1] is None: logger.debug("No default locale exists. Let's try loading from /etc/default/locale") if os.path.exists("/etc/default/locale"): @@ -69,17 +69,17 @@ def configure_locale(): sys.exit(1) else: new_locale = default_locale - + logger.info("New locale set to: %s", locale.setlocale(locale.LC_ALL, new_locale)) - - - + + + reload(sys) sys.setdefaultencoding("UTF-8") current_locale_encoding = locale.getlocale()[1].lower() logger.debug("sys default encoding %s", sys.getdefaultencoding()) logger.debug("After %s", locale.nl_langinfo(locale.CODESET)) - + if current_locale_encoding not in ['utf-8', 'utf8']: logger.error("Need a UTF-8 locale. Currently '%s'. Exiting..." % current_locale_encoding) sys.exit(1) @@ -92,7 +92,7 @@ try: except Exception, e: print "Couldn't configure logging" sys.exit() - + configure_locale() # loading config file @@ -105,42 +105,14 @@ except Exception, e: class Global: def __init__(self): self.api_client = api_client.api_client_factory(config) - + def selfcheck(self): self.api_client = api_client.api_client_factory(config) return self.api_client.is_server_compatible() - + def test_api(self): self.api_client.test() -""" - def check_schedule(self): - logger = logging.getLogger() - - try: - schedule_file = open(self.schedule_file, "r") - schedule = pickle.load(schedule_file) - schedule_file.close() - - except Exception, e: - logger.error("%s", e) - schedule = None - - for pkey in sorted(schedule.iterkeys()): - playlist = schedule[pkey] - print '*****************************************' - print '\033[0;32m%s %s\033[m' % ('scheduled at:', str(pkey)) - print 'cached at : ' + self.cache_dir + str(pkey) - print 'played: ' + str(playlist['played']) - print 'schedule id: ' + str(playlist['schedule_id']) - print 'duration: ' + str(playlist['duration']) - print 'source id: ' + str(playlist['x_ident']) - print '-----------------------------------------' - - for media in playlist['medias']: - print media -""" - def keyboardInterruptHandler(signum, frame): logger = logging.getLogger() logger.info('\nKeyboard Interrupt\n') @@ -154,13 +126,19 @@ if __name__ == '__main__': logger.info('# Liquidsoap Scheduled Playout System #') logger.info('###########################################') + #Although all of our calculations are in UTC, it is useful to know what timezone + #the local machine is, so that we have a reference for what time the actual + #log entries were made + logger.info("Timezone: %s" % time.tzname) + logger.info("UTC time: %s" % datetime.utcnow()) + signal.signal(signal.SIGINT, keyboardInterruptHandler) # initialize g = Global() while not g.selfcheck(): time.sleep(5) - + logger = logging.getLogger() if options.test: @@ -173,9 +151,9 @@ if __name__ == '__main__': pypoFetch_q = Queue() recorder_q = Queue() pypoPush_q = Queue() - + telnet_lock = Lock() - + """ This queue is shared between pypo-fetch and pypo-file, where pypo-file is the receiver. Pypo-fetch will send every schedule it gets to pypo-file @@ -183,19 +161,19 @@ if __name__ == '__main__': priority, and will retrieve it. """ media_q = Queue() - + pmh = PypoMessageHandler(pypoFetch_q, recorder_q) pmh.daemon = True pmh.start() - + pfile = PypoFile(media_q) pfile.daemon = True pfile.start() - + pf = PypoFetch(pypoFetch_q, pypoPush_q, media_q, telnet_lock) pf.daemon = True pf.start() - + pp = PypoPush(pypoPush_q, telnet_lock) pp.daemon = True pp.start() @@ -204,23 +182,12 @@ if __name__ == '__main__': recorder.daemon = True recorder.start() - # all join() are commented out becase we want to exit entire pypo + # all join() are commented out because we want to exit entire pypo # if pypofetch is exiting #pmh.join() #recorder.join() #pp.join() pf.join() - + logger.info("pypo fetch exit") sys.exit() -""" - if options.check: - try: g.check_schedule() - except Exception, e: - print e - - if options.cleanup: - try: pf.cleanup('scheduler') - except Exception, e: - print e -""" diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index b9cb6e76b..c62c6ca57 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -3,14 +3,14 @@ import os import sys import time -import logging import logging.config -import shutil import json import telnetlib import copy from threading import Thread +from Queue import Empty + from api_clients import api_client from std_err_override import LogWriter @@ -29,7 +29,9 @@ try: config = ConfigObj('/etc/airtime/pypo.cfg') LS_HOST = config['ls_host'] LS_PORT = config['ls_port'] - POLL_INTERVAL = int(config['poll_interval']) + #POLL_INTERVAL = int(config['poll_interval']) + POLL_INTERVAL = 1800 + except Exception, e: logger.error('Error loading config file: %s', e) @@ -43,12 +45,12 @@ class PypoFetch(Thread): self.push_queue = pypoPush_q self.media_prepare_queue = media_q self.last_update_schedule_timestamp = time.time() - self.listener_timeout = 3600 - + self.listener_timeout = POLL_INTERVAL + self.telnet_lock = telnet_lock - + self.logger = logging.getLogger(); - + self.cache_dir = os.path.join(config["cache_dir"], "scheduler") self.logger.debug("Cache dir %s", self.cache_dir) @@ -63,24 +65,24 @@ class PypoFetch(Thread): os.makedirs(dir) except Exception, e: pass - + self.schedule_data = [] self.logger.info("PypoFetch: init complete") - + """ Handle a message from RabbitMQ, put it into our yucky global var. Hopefully there is a better way to do this. """ def handle_message(self, message): - try: + try: self.logger.info("Received event from Pypo Message Handler: %s" % message) - - m = json.loads(message) + + m = json.loads(message) command = m['event_type'] self.logger.info("Handling command: " + command) - + if command == 'update_schedule': - self.schedule_data = m['schedule'] + self.schedule_data = m['schedule'] self.process_schedule(self.schedule_data) elif command == 'update_stream_setting': self.logger.info("Updating stream setting...") @@ -100,12 +102,12 @@ class PypoFetch(Thread): elif command == 'disconnect_source': self.logger.info("disconnect_on_source show command received...") self.disconnect_source(self.logger, self.telnet_lock, m['sourcename']) - + # update timeout value if command == 'update_schedule': - self.listener_timeout = 3600 + self.listener_timeout = POLL_INTERVAL else: - self.listener_timeout = self.last_update_schedule_timestamp - time.time() + 3600 + self.listener_timeout = self.last_update_schedule_timestamp - time.time() + POLL_INTERVAL if self.listener_timeout < 0: self.listener_timeout = 0 self.logger.info("New timeout: %s" % self.listener_timeout) @@ -115,7 +117,7 @@ class PypoFetch(Thread): self.logger.error('Exception: %s', e) self.logger.error("traceback: %s", top) self.logger.error("Exception in handling Message Handler message: %s", e) - + @staticmethod def disconnect_source(logger, lock, sourcename): logger.debug('Disconnecting source: %s', sourcename) @@ -124,7 +126,7 @@ class PypoFetch(Thread): command += "master_harbor.kick\n" elif(sourcename == "live_dj"): command += "live_dj_harbor.kick\n" - + lock.acquire() try: tn = telnetlib.Telnet(LS_HOST, LS_PORT) @@ -135,7 +137,7 @@ class PypoFetch(Thread): logger.error(str(e)) finally: lock.release() - + @staticmethod def switch_source(logger, lock, sourcename, status): logger.debug('Switching source: %s to "%s" status', sourcename, status) @@ -146,12 +148,12 @@ class PypoFetch(Thread): command += "live_dj_" elif(sourcename == "scheduled_play"): command += "scheduled_play_" - + if(status == "on"): command += "start\n" else: command += "stop\n" - + lock.acquire() try: tn = telnetlib.Telnet(LS_HOST, LS_PORT) @@ -162,7 +164,7 @@ class PypoFetch(Thread): logger.error(str(e)) finally: lock.release() - + """ grabs some information that are needed to be set on bootstrap time and configures them @@ -171,16 +173,16 @@ class PypoFetch(Thread): self.logger.debug('Getting information needed on bootstrap from Airtime') info = self.api_client.get_bootstrap_info() if info == None: - self.logger.error('Unable to get bootstrap info.. Existing pypo...') - sys.exit(0) + self.logger.error('Unable to get bootstrap info.. Exiting pypo...') + sys.exit(1) else: - self.logger.debug('info:%s',info) + self.logger.debug('info:%s', info) for k, v in info['switch_status'].iteritems(): self.switch_source(self.logger, self.telnet_lock, k, v) self.update_liquidsoap_stream_format(info['stream_label']) self.update_liquidsoap_station_name(info['station_name']) self.update_liquidsoap_transition_fade(info['transition_fade']) - + def write_liquidsoap_config(self, setting): fh = open('/etc/airtime/liquidsoap.cfg', 'w') self.logger.info("Rewriting liquidsoap.cfg...") @@ -197,7 +199,7 @@ class PypoFetch(Thread): if temp == "": temp = "0" buffer_str += temp - + buffer_str += "\n" fh.write(api_client.encode_to(buffer_str)) fh.write("log_file = \"/var/log/airtime/pypo-liquidsoap/