Skip to content

Instantly share code, notes, and snippets.

@ssokolow
Created October 29, 2016 07:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssokolow/4df34619ba2134ca20163764532f211f to your computer and use it in GitHub Desktop.
Save ssokolow/4df34619ba2134ca20163764532f211f to your computer and use it in GitHub Desktop.
Quick script for setting an alarm on Linux
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""Wrapper script for scheduling an alarm within the desktop session so
MPV has access to the audio and video outputs.
See --help output for more details.
Requirements:
- python-dateutil
- pytimeparse
"""
from __future__ import (absolute_import, division, print_function,
with_statement, unicode_literals)
__author__ = "Stephan Sokolow (deitarion/SSokolow)"
__appname__ = "Alarm scheduler"
__version__ = "0.1"
__license__ = "MIT"
import subprocess, time
from contextlib import contextmanager
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from dateutil.parser import parse as dateparse
from pytimeparse import parse as deltaparse
SAFETY_LIMIT = timedelta(hours=15)
ALARM_CMD = ['mpv', '-loop=inf',
'/mnt/incoming/Videos/Danger alarm Sound Effect-gTJPpzo4jJ4.mp4']
MIXER_CMD = ['amixer', 'sset', 'Master']
@contextmanager
def loud():
"""Context manager to temporarily set master volume to 100%."""
# TODO: Actually store and restore the old volume rather than assuming 50
subprocess.call(MIXER_CMD + ['100'])
yield
subprocess.call(MIXER_CMD + ['50'])
def parse_in(timespec):
"""Parse timespecs like '5 hours'"""
return timedelta(seconds=deltaparse(timespec))
def parse_at(timespec):
"""Parse timespecs like '5am'"""
now = datetime.now()
date = dateparse(timespec)
# One day of drift is expected since pydateparse assumes "today" for
# omitted components
if date < now:
date += relativedelta(days=+1)
# More than one day of drift means something funny is going on
if date < now:
raise Exception("Didn't expect date more than 24 hours in the past. "
"Aborting to ensure user doesn't accidentally "
"oversleep.")
# Switch from dates to deltas as quickly as possible because a timezone
# mistake is far more dangerous than even a minute of inaccuracy due to
# swap thrashing would be and Python's ecosystem seems designed to punish
# anyone who needs a way to operate in "the desktop's local time" rather
# than using UTC and MAYBE manually-configured timezones at the edges.
return date - datetime.now()
def pformat_tdelta(delta):
"""A simple pretty-printer for time deltas which doesn't require Arrow"""
result = []
days, hours = delta.days, delta.seconds // 3600
minutes = (delta.seconds % 3600) // 60
seconds = delta.seconds % 60
if days > 0:
result.append('%s days' % days)
if hours > 0:
result.append('%s hours' % hours)
if minutes > 0:
result.append('%s minutes' % minutes)
if seconds > 0:
result.append('%s seconds' % seconds)
return ', '.join(result)
def main():
"""The main entry point, compatible with setuptools entry points."""
# If we're running on Python 2, take responsibility for preventing
# output from causing UnicodeEncodeErrors. (Done here so it should only
# happen when not being imported by some other program.)
import sys
if sys.version_info.major < 3:
reload(sys)
sys.setdefaultencoding('utf-8') # pylint: disable=no-member
from argparse import ArgumentParser, RawTextHelpFormatter, REMAINDER
parser = ArgumentParser(formatter_class=RawTextHelpFormatter,
description=__doc__.replace('\r\n', '\n').split('\n--snip--\n')[0])
parser.add_argument('--version', action='version',
version="%%(prog)s v%s" % __version__)
subparsers = parser.add_subparsers()
parser_in = subparsers.add_parser('in')
parser_in.set_defaults(parser=parse_in)
parser_in.add_argument('timespec_list', nargs=REMAINDER)
parser_at = subparsers.add_parser('at')
parser_at.set_defaults(parser=parse_at)
parser_at.add_argument('timespec_list', nargs=REMAINDER)
args = parser.parse_args()
delay = args.parser(' '.join(args.timespec_list))
# Now, we've got a timedelta, regardless of what we started with
if delay > SAFETY_LIMIT:
raise Exception("Delay exceeds safety limit (bad input?): %s" %
pformat_tdelta(delay))
print("Sleeping for %s" % pformat_tdelta(delay))
time.sleep(delay.total_seconds())
# FIXME: Wrap this up in something which handles being interrupted by a
# signal
with loud():
subprocess.call(ALARM_CMD)
if __name__ == '__main__':
main()
# vim: set sw=4 sts=4 expandtab :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment