#!/usr/bin/python3 # -*- coding: utf-8 -*- """A simple terminal and TTS countdown timer, in English and Spanish. For TTS use, it has a hardcoded variable granularity that gradually improves to one second as the end approaches, somewhat like NASA countdowns. """ from __future__ import print_function import argparse import math import time import sys def numeric_formatter(remaining): mm, ss = divmod(math.ceil(remaining), 60) return '{:02d}:{:02d} '.format(int(mm), int(ss)) def english_number(n): assert n == int(n) n = int(n) assert 0 <= n < 100 if n < 20: return '''zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen '''.split()[n] lastdigit = n % 10 if lastdigit == 0: return '''Babel plock twenty thirty forty fifty sixty seventy eighty ninety'''.split()[n // 10] return '{}-{}'.format(english_number(n - lastdigit), english_number(lastdigit)) def ok(a, b): assert a == b, (a, b) ok([english_number(i) for i in range(30)], [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty', 'twenty-one', 'twenty-two', 'twenty-three', 'twenty-four', 'twenty-five', 'twenty-six', 'twenty-seven', 'twenty-eight', 'twenty-nine', ]) ok([english_number(i) for i in [30, 47, 51, 63, 79, 82, 96]], [ 'thirty', 'forty-seven', 'fifty-one', 'sixty-three', 'seventy-nine', 'eighty-two', 'ninety-six', ]) def pluralize(noun, number): return noun if number == 1 else noun + 's' def round_for_speech(remaining_seconds): remaining = math.ceil(remaining_seconds) for threshold, granularity in [(15, 5), (30, 15), (60, 30), (150, 60), (1200, 300)]: if remaining > threshold and remaining % granularity != 0: remaining -= remaining % granularity - granularity return remaining def english_formatter(remaining): mm, ss = divmod(round_for_speech(remaining), 60) trailing = ' ' * 40 if not ss: return '{} {}{}'.format(english_number(mm), pluralize('minute', mm), trailing) if not mm: return '{}{}'.format(english_number(ss), trailing) return '{} {} {}{}'.format(english_number(mm), pluralize('minute', mm), english_number(ss), trailing) def spanish_number(n): # XXX this is a minimal hack to get correct output from espeak so # it doesn't say “uno minuto” return 'un' if n == 1 else n def spanish_formatter(remaining): mm, ss = divmod(round_for_speech(remaining), 60) trailing = ' ' * 40 if not ss: return '{} {}{}'.format(spanish_number(mm), pluralize('minuto', mm), trailing) if not mm: return '{}{}{}'.format(ss, ' segundos' if ss > 20 else '', trailing) return '{} {} y {} {}{}'.format(spanish_number(mm), pluralize('minuto', mm), spanish_number(ss), pluralize('segundo', ss), trailing) def main(): p = argparse.ArgumentParser(description="Simple terminal and TTS countdown timer.") p.add_argument('time', nargs='?', default=600, type=duration, help="how long to count down (default 10:00)") p.add_argument('-i', '--interval', type=float, default=.1, help="how often to poll the clock") p.add_argument('--english', action='store_true', help="format output times in English rather than digits") p.add_argument('--spanish', action='store_true', help="format output times in Spanish rather than digits") p.add_argument('--newlines', action='store_true', help="add newlines to output (useful for piping to espeak)") args = p.parse_args() formatter = (english_formatter if args.english else spanish_formatter if args.spanish else numeric_formatter) start = time.time() end = start + args.time last = None while True: remaining = end - time.time() if remaining <= 0: break out = formatter(remaining) if out != last: print(out, end='\n' if args.newlines else '\r') sys.stdout.flush() last = out time.sleep(args.interval) def duration(mmss): mm, ss = map(int, mmss.split(':')) return mm*60 + ss if __name__ == '__main__': main()