Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
 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)
Ejemplo n.º 6
0
 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
Ejemplo n.º 7
0
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')))
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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))
Ejemplo n.º 13
0
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