def connect(reconnect=False): global _connection if _connection is None or reconnect: from rsab.listenagain import config, ListenAgainConfigError for key in ['domain', 'port', 'username', 'password']: if not config.has_option('ftp', key): raise ListenAgainConfigError('Missing config value', 'ftp', key) domain = config.get('ftp', 'domain') port = config.get('ftp', 'port') if port: try: port = int(port) except ValueError: raise ListenAgainConfigError('FTP port not an integer', port) else: port = 0 import socket socket_default_timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(20) _connection = ListenAgainFTP() _connection.connect(domain, port) _connection.login(config.get('ftp', 'username'), config.get('ftp', 'password')) socket.setdefaulttimeout(socket_default_timeout) if DEBUG_CONNECTION: _connection.set_debuglevel(DEBUG_CONNECTION) return _connection
def get_message(message_name, message_type, default=_absent_parameter): from rsab.listenagain import config import os if not config.has_option('messages', message_type): if default is _absent_parameter: raise ValueError('Message file not configured', message_type) return default fname = config.get('messages', message_type) if not os.path.exists(fname): if default is _absent_parameter: raise ValueError('Message file not found', message_type, fname) return default f = open(fname, 'r') found_message = None for line in f: line = line.strip() if not line or line.startswith('#') or line.startswith(';'): continue if '=' in line: key, value = line.split('=', 1) else: continue key = key.strip() value = value.strip() if key == message_name: found_message = value break f.close() if found_message is None: if default is _absent_parameter: raise ValueError('Message not found', message_type, message_name) return default return found_message
def make_playlist_item(audio_fname): import datetime import os import urllib from rsab.listenagain import config, ListenAgainConfigError import schedule import utils if not config.has_option('ftp', 'audio_path'): raise ListenAgainConfigError('Missing option', 'ftp', 'audio_path') audio_path = config.get('ftp', 'audio_path') if not audio_path.endswith('/'): audio_path += '/' details = schedule.schedule_from_audio_file_name(audio_fname) if details is None or details['show'].startswith('xx'): return '' template = Template('playlist-item') template.title = utils.get_show_title_string( details['show'], details.get('presenters'), ) template.date = details['date'].strftime('%a %d %b %Y') template.date_compact = details['date'].strftime('%Y%m%d') template.day_short = details['date'].strftime('%a').lower() template.start_time = details['start'].strftime('%H:%M') template.start_compact = details['start'].strftime('%H%M') template.end_time = details['end'].strftime('%H:%M') template.end_compact = details['end'].strftime('%H%M') duration = ( datetime.datetime.combine(details['date'], details['end']) - datetime.datetime.combine(details['date'], details['start']) ).seconds / 60.0 # approx minutes ROUND_TO_MINUTES = 1 if duration % ROUND_TO_MINUTES < (ROUND_TO_MINUTES / 2.0): duration = divmod(duration, ROUND_TO_MINUTES)[0] * ROUND_TO_MINUTES else: duration = (divmod(duration, ROUND_TO_MINUTES)[0] + 1) * ROUND_TO_MINUTES duration_h, duration_m = divmod(duration, 60) if duration_m: duration_string = '%dh %dm' % (duration_h, duration_m) else: duration_string = '%dh' % (duration_h,) template.duration = duration_string hidden_input = '<input type="hidden" disabled="disabled" name="%s" value="%s" />' template.show_names = hidden_input % ('show', details['show']) template.presenter_names = '\n'.join([ hidden_input % ('presenter', presenter) for presenter in details.get('presenters', []) if presenter and presenter != details['show'] ]) template.url = audio_path + urllib.quote(os.path.split(audio_fname)[1]) return template
def _send(message_list): from rsab.listenagain import config import email.Utils import smtplib if config.has_option('email', 'from'): email_from = config.get('email', 'from') else: email_from = DEFAULT_FROM if config.has_option('email', 'smtphost'): smtphost = config.get('email', 'smtphost') else: smtphost = '' if not smtphost: raise ListenAgainConfigError('[email]/smtphost not set') if config.has_option('email', 'smtpport'): smtpport = config.getint('email', 'smtpport') else: smtpport = None if not smtpport: smtpport = 25 if config.has_option('email', 'smtpusername'): smtpusername = config.get('email', 'smtpusername') else: smtpusername = '' if config.has_option('email', 'smtppassword'): smtppassword = config.get('email', 'smtppassword') else: smtppassword = '' sent = 0 smtp = smtplib.SMTP(smtphost, smtpport) if smtpusername: try: smtp.login(smtpusername, smtppassword) except smtplib.SMTPException, e: print 'ERROR: Could not log in to SMTP server.' print e return sent
def __init__(self, *args, **kwargs): _FTP.__init__(self, *args, **kwargs) self._laftp_config = {} from rsab.listenagain import config, ListenAgainConfigError SECTION_NAME = 'ftp' for config_key, key in [ ('audio_path', 'audio_path'), ]: if not config.has_option(SECTION_NAME, config_key): raise ListenAgainConfigError('Missing option', SECTION_NAME, config_key) self._laftp_config[key] = config.get(SECTION_NAME, config_key)
def _find_template(self, name): import glob import os from rsab.listenagain import config, ListenAgainConfigError if not config.has_option('main', 'templates'): raise ListenAgainConfigError('No [main]/templates config defined') template_dir = config.get('main', 'templates') fname = os.path.join(template_dir, name) if os.path.exists(fname): return fname fnames = glob.glob(os.path.join(template_dir, name + '.*')) if fnames: return fnames[0] # XXX Better choice? return None
def get_files_for_date(date): import glob import os from rsab.listenagain import config folder = None if config.has_option('rec.aircheck', 'path'): folder = config.get('rec.aircheck', 'path') if folder is None or not os.path.isdir(folder): from rsab.listenagain import ListenAgainConfigError raise ListenAgainConfigError('Cannot find AirCheck recordings folder', folder) return glob.glob(os.path.join(folder, date.strftime('%Y%m%d*.wav')))
def init_module(): global _module if _module is None: from rsab.listenagain import config if config.has_option('main', 'recorder'): recorder_name = config.get('main', 'recorder') else: recorder_name = 'aircheck' # XXX Surely there's a nicer way to do this? try: recorder_package = __import__('rsab.listenagain.recorder', locals(), globals(), [recorder_name]) except ImportError: from rsab.listenagain import ListenAgainConfigError raise ListenAgainConfigError('Invalid recorder module', recorder_name) else: _module = getattr(recorder_package, recorder_name) for name in dir(_module): if not name.startswith('_'): globals()[name] = getattr(_module, name) return _module
def make_index_file(date, audio_fname_list, output_fname=None): import datetime import os import schedule import utils from rsab.listenagain import config, ListenAgainConfigError if not config.has_option('DEFAULT', 'output'): raise ListenAgainConfigError('No [DEFAULT]/output config defined') if output_fname is None: output_fname = 'index.html' output_fname = os.path.join(config.get('DEFAULT', 'output'), output_fname) output_file = open(output_fname, 'w') template = Template('player') playlist_items = [] details_for_audio_files = [] show_name_mapping = {} presenter_name_mapping = {} for audio_fname in audio_fname_list: playlist_items.append(str(make_playlist_item(audio_fname))) details_for_audio_files.append(schedule.schedule_from_audio_file_name(audio_fname)) live_schedule = schedule.get_schedule(date + datetime.timedelta(days=1)) for details in details_for_audio_files + live_schedule: if details is None: continue show_name = details['show'] show_title = utils.get_message(show_name, 'show', default=None) if show_title is None: show_title = utils.get_message(show_name, 'presenter', default=show_name) if show_name and show_name not in show_name_mapping: show_name_mapping[show_name] = show_title for presenter in details.get('presenters', []): if not presenter or presenter == show_name: continue if presenter not in presenter_name_mapping: presenter_name_mapping[presenter] = utils.get_message(presenter, 'presenter', default=presenter) template.playlist_items = '\n'.join(filter(None, playlist_items)) hidden_input = '<input type="hidden" disabled="disabled" name="%s" value="%s" />' template.show_name_mapping = '\n'.join([ hidden_input % ('showname', '%s:%s' % pair) for pair in show_name_mapping.items() ]) template.presenter_name_mapping = '\n'.join([ hidden_input % ('presentername', '%s:%s' % pair) for pair in presenter_name_mapping.items() ]) template.live_schedule = '\n'.join([ hidden_input % ( 'live_schedule', '%s:%s' % ( details['start'].strftime('%H:%M'), ','.join([details['show']] + details.get('presenters', [])), ), ) for details in schedule.get_schedule(date + datetime.timedelta(days=1)) ]) output_file.write(str(template)) output_file.close() return output_fname
def make_wav_files(bounds_and_files, schedule_list): import datetime import os import wave from rsab.listenagain import config, schedule, utils if config.has_option('main', 'wavs'): output_dir = config.get('main', 'wavs') else: output_dir = os.getcwd() if not os.path.exists(output_dir): os.makedirs(output_dir) if not bounds_and_files: return None wav_reader = wave.open(bounds_and_files[0][2], 'rb') output_wav_params = wav_reader.getparams() output_wav_params = list(output_wav_params) output_wav_params[3] = 0 output_wav_params = tuple(output_wav_params) wav_reader.close() frame_rate = output_wav_params[2] frame_size = output_wav_params[0] + output_wav_params[1] if config.has_option('main', 'framechunksize'): frame_chunk = config.getint('main', 'framechunksize') else: frame_chunk = 65536 print 'Reading audio data in chunks of', utils.format_large_number(frame_chunk), 'frames' print 'Output directory:', output_dir output_wav_files = [] bounds_and_files.sort() for details in schedule_list: if not details['record']: continue actual_time_start = utils.apply_padding(details['start'], details.get('pad_start', 0), subtract=True) actual_time_start = datetime.datetime.combine(details['date'], actual_time_start) actual_time_end = utils.apply_padding(details['end'], details.get('pad_end', 0), subtract=False) actual_time_end = datetime.datetime.combine(details['date'], actual_time_end) print print 'Schedule:', schedule.get_schedule_item_as_string(details) output_file_name = os.path.join(output_dir, make_output_file_name(details, 'wav')) print 'Output file:', os.path.split(output_file_name)[1] wav_writer = wave.open(output_file_name, 'wb') wav_writer.setparams(output_wav_params) for wav_file_start, wav_file_end, wav_file_path in bounds_and_files: if wav_file_start.date() != details['date'] \ or wav_file_end.date() != details['date'] \ or wav_file_end <= actual_time_start \ or wav_file_start >= actual_time_end: continue wav_reader = wave.open(wav_file_path, 'rb') start_frame = end_frame = None if wav_file_start < actual_time_start: start_frame = (actual_time_start - wav_file_start).seconds * frame_rate if wav_file_end > actual_time_end: end_frame = (actual_time_end - wav_file_start).seconds * frame_rate if start_frame is None: start_frame = 0 if end_frame is None: end_frame = wav_reader.getnframes() to_read = start_frame print 'Skipping', utils.format_large_number(to_read), 'frames in', os.path.split(wav_file_path)[1] while to_read > 0: data = wav_reader.readframes(min(to_read, frame_chunk)) if not data: break to_read -= (len(data) / frame_size) to_read = end_frame - start_frame print 'Copying', utils.format_large_number(to_read), 'frames from', os.path.split(wav_file_path)[1] while to_read > 0: data = wav_reader.readframes(min(to_read, frame_chunk)) if not data: break to_read -= (len(data) / frame_size) wav_writer.writeframes(data) wav_reader.close() wrote_frames = wav_writer.getnframes() wav_writer.close() if wrote_frames: print 'Wrote', utils.format_large_number(wrote_frames), 'frames' output_wav_files.append(output_file_name) else: print 'Wrote no frames; deleting empty file' os.unlink(output_file_name) return output_wav_files
def encode_file(path, details=None): from rsab.listenagain import config, ListenAgainConfigError import schedule import utils import os if details is None: details = schedule.schedule_from_audio_file_name(path) if details is None: print 'No details available from path name; skipping:', os.path.split(path)[1] return None # Get items from encoder section. Find any called "command". commands = [] if config.has_section('encoder'): for key, value in config.items('encoder', raw=True): if key == 'command' or key.startswith('command.'): if key == 'command': after_first = False n = 0 else: after_first = True try: n = int(key.split('.')[1]) except ValueError: raise ListenAgainConfigError('[encoder] command not of form "command.N"', key) commands.append( (after_first, n, value) ) commands.sort() if not commands: raise ListenAgainConfigError('No encoding commands defined') def escape(s): return s.replace('"', '\\"') if config.has_option('main', 'mp3s'): output_dir = config.get('main', 'mp3s') else: output_dir = os.getcwd() if not os.path.exists(output_dir): os.makedirs(output_dir) print 'Encoding', os.path.split(path)[1] if config.has_option('encoder', 'artist'): artist = config.get('encoder', 'artist') else: artist = 'Radio St Austell Bay' show_name = '%s, %s' % ( utils.get_show_title_string(details['show'], details.get('presenters', [])), details['date'].strftime('%A %d %B %Y'), ) input_file_name = path output_file_name = os.path.splitext(os.path.split(path)[1])[0] + '.mp3' output_file_path = os.path.join(output_dir, output_file_name) option_vars = { 'artist': escape(artist), 'title': escape(show_name), 'year': escape(str(details['date'].year)), 'input': escape(input_file_name), 'output': escape(output_file_path), } if config.has_option('encoder', 'image'): option_vars['image'] = config.get('encoder', 'image') for dummy, dummy, command in commands: print ' -> ', command % option_vars pipe = os.popen(command % option_vars) exit_status = pipe.close() # All commands after the first must operate on the output from the # first command. option_vars['input'] = option_vars['output'] print 'file done.' print return output_file_path
def notify_all(uploaded_files): from rsab.listenagain import config import html import schedule from email.MIMEText import MIMEText import os import sets if uploaded_files is None: return mapping = { None: sets.Set(uploaded_files), } for fname in uploaded_files: details = schedule.schedule_from_audio_file_name(fname) for name in [details['show']] + details.get('presenters', []): if name not in mapping: mapping[name] = sets.Set() mapping[name].add(fname) config_sections = [ sname for sname in config.sections() if sname == 'notify' or sname.startswith('notify.') ] if config.has_option('ftp', 'keep_days'): keep_days = config.getint('ftp', 'keep_days') else: keep_days = 7 if config.has_option('main', 'userfacingserveraddress'): remote_server = config.get('main', 'userfacingserveraddress') else: remote_server = 'http://listenagain.rsab.org' if config.has_option('ftp', 'audio_path'): remote_path = config.get('ftp', 'audio_path') else: remote_path = '/audio' if config.has_option('email', 'from'): email_from = config.get('email', 'from') else: email_from = DEFAULT_FROM print print 'Notification emails:' messages = [] for section in config_sections: print print 'Section name:', section if config.has_option(section, 'email'): addresses = filter(None, [x.strip() for x in config.get(section, 'email').split(',')]) else: addresses = [] print 'Addresses:', (addresses and ', '.join(addresses) or '(none)') if not addresses: print 'Skipping' continue if config.has_option(section, 'match'): match_names = filter(None, [x.strip() for x in config.get(section, 'match').split(',')]) else: match_names = [] print 'Requested:', ('*' in match_names and '(all)' or ', '.join(match_names)) if '*' in match_names: notify_files = list(mapping[None]) elif match_names: notify_files = sets.Set() for name in match_names: notify_files.update(mapping.get(name, [])) notify_files = list(notify_files) else: notify_files = [] if not notify_files: print 'No files matched.' continue notify_files.sort() if config.has_option(section, 'subject'): subject = config.get(section, 'subject') else: subject = 'New files uploaded by Radio St Austell Bay' if config.has_option(section, 'template'): template_name = config.get(section, 'template') else: template_name = 'mail-notify-default.txt' template = html.Template(template_name) # Not actually HTML! template.keep_days = keep_days files_string = '\n'.join([ ' %s%s/%s' % ( remote_server, remote_path, os.path.split(fname)[1], ) for fname in notify_files ]) template.files = files_string print 'Files:' print files_string message = MIMEText(str(template)) message['Subject'] = subject message['From'] = email_from message['To'] = ', '.join(addresses) messages.append(message) print print 'Sending messages...' print sent = _send(messages) print print 'Done. Sent %s of %s messages' % (sent, len(messages))
def get_schedule(date, filter_items=None): import datetime import glob import os from rsab.listenagain import \ config, \ utils, \ ListenAgainConfigError schedules_folder = None if config.has_option('main', 'schedules'): schedules_folder = config.get('main', 'schedules') if not os.path.isdir(schedules_folder): raise ListenAgainConfigError('Cannot find schedules folder', schedules_folder) schedule_file_path = None require_dow = True override_file_path = os.path.join(schedules_folder, 'override', date.strftime('%Y-%m-%d.csv')) if os.path.isfile(override_file_path): schedule_file_path = override_file_path require_dow = False if schedule_file_path is None: override_file_path = os.path.join(schedules_folder, 'override', date.strftime('%Y-%m-%d.txt')) if os.path.isfile(override_file_path): schedule_file_path = override_file_path require_dow = False if schedule_file_path is None: schedule_files_by_date = [] for fname in glob.glob(os.path.join(schedules_folder, '*.csv')) + glob.glob(os.path.join(schedules_folder, '*.txt')): fname_base = os.path.splitext(os.path.split(fname)[1])[0] date_for_fname = utils.parse_date(fname_base) if date_for_fname is None: print 'schedule.get_schedule: Could not interpret filename as date:', fname continue schedule_files_by_date.append( (date_for_fname, fname) ) schedule_files_by_date.sort() schedule_file_path = None for date_for_fname, try_schedule_file_path in schedule_files_by_date: # Later date: don't change the file we've already remembered, as # it's the latest one before the current date. Break. if date_for_fname > date: break # The file's date is less than or equal to the date we want. Keep # this file as a candidate for the schedule. schedule_file_path = try_schedule_file_path # Exact date: keep the file and break. XXX I suspect we don't need # this clause as it will all work anyway, but let's be explicit... if date_for_fname == date: break if schedule_file_path is None: schedule_from_file = [] else: schedule_from_file = read_schedule_file( date, schedule_file_path, dow=date.weekday(), require_dow=require_dow, ) pad_start = pad_end = 0 if config.has_option('main', 'padstart'): pad_start = config.getint('main', 'padstart') if config.has_option('main', 'padend'): pad_end = config.getint('main', 'padend') schedule = [] for details in schedule_from_file: if filter_items: for filter_item in filter_items: if filter_item == details['show'] \ or filter_item in details.get('presenters', []): break else: continue if details.get('pad_start') is None: details['pad_start'] = pad_start if details.get('pad_end') is None: details['pad_end'] = pad_end schedule.append(details) return schedule