# # hailcamsnap: Part of "hailcam" that snaps pictures and loads into S3 # # Copyright (C) 2010 Red Hat, Inc. # # requires: boto, iniparse # ## Parameters in hailcamsnap.ini: # [snap] # sleep = 3 # cmd = fswebcam -p BGR24 -d /dev/video0 --no-banner fs0.jpeg # file = fs0.jpeg (has to be a JPEG file for now; is deleted, careful) # s3host = niphredil.zaitcev.lan # s3user = hailcamsnapuser # s3pass = hailcamsnappass # s3bucket = hailcamtest # prefix = stream0/ # # If the command is a webcam capture, this script may need to run as root. import sys import os import time import shlex, subprocess from iniparse import ConfigParser from ConfigParser import NoSectionError, NoOptionError from boto.s3.connection import S3Connection, S3ResponseError class SnapScript: def __init__(self, cfg): self.cfg = cfg def fetch(self): try: os.unlink(self.cfg["file"]) except OSError: pass cmd = self.cfg["cmd"] args = shlex.split(cmd) try: rc = subprocess.call(args) except OSError, e: print >>sys.stderr, "Command", cmd, "failed:", e sys.exit(1) if rc != 0: print >>sys.stderr, "Bad exit code from command", cmd, ":", rc # Add a test for the file; old fswebcam often exits with zero on error return rc def upload(self): tag = str(int(time.time())) c = S3Connection(aws_access_key_id=self.cfg["s3user"], aws_secret_access_key=self.cfg["s3pass"], is_secure=False, host=self.cfg["s3host"]) # socket.error: [Errno 111] Connection refused # If we create a bucket here, it will be owned by the uploader, so # the expiration process may fail unless it uses same ID/key. XXX # Anyhow, make sure hailcampack is running somewhere, or else # the below with fail with a missing bucket. bucketname = self.cfg["bucket"] try: bucket = c.get_bucket(bucketname) except S3ResponseError, e: # code.message is deprecated, spews a warning to stderr # print >>sys.stderr, "S3ResponseError:", "code", getattr(e, 'code') print >>sys.stderr, "Bucket", bucketname, "access error: %s" % e sys.exit(1) key = bucket.new_key() key.name = self.cfg["prefix"] + '/' + "i" + tag mimetype = "image/jpeg" headers = { "Content-Type": mimetype } # key.set_contents_from_filename(self.cfg["file"]) fp = open(self.cfg["file"], 'rb') key.set_contents_from_file(fp, headers) fp.close() key.set_acl('public-read') # redundant? what's the default? # and now we just return and hope for c to be garbage-collected? # config() class ConfigError(Exception): pass # This is what cool people would do, but I don't know how to catch # improper syntax on this case. Se we just use ConfigParser mode. # from iniparse import INIConfig # cfgpr = INIConfig(open(cfgname)) def config(cfgname, inisect): cfg = { } cfgpr = ConfigParser() try: cfgpr.read(cfgname) cfg["sleep"] = cfgpr.get(inisect, "sleep") cfg["cmd"] = cfgpr.get(inisect, "cmd") cfg["file"] = cfgpr.get(inisect, "file") cfg["s3host"] = cfgpr.get(inisect, "s3host") cfg["s3user"] = cfgpr.get(inisect, "s3user") cfg["s3pass"] = cfgpr.get(inisect, "s3pass") cfg["bucket"] = cfgpr.get(inisect, "s3bucket") cfg["prefix"] = cfgpr.get(inisect, "prefix") except NoSectionError: # Unfortunately if the file does not exist, we end here. raise ConfigError("Unable to open or find section " + inisect) except NoOptionError, e: raise ConfigError(str(e)) try: cfg["sleepval"] = float(cfg["sleep"]) except ValueError: raise ConfigError("Invalid sleep value " + cfg["sleep"]) return cfg # main() argc = len(sys.argv) if argc == 1: cfgname = "hailcamsnap.ini" elif argc == 2: cfgname = sys.argv[1] else: print >>sys.stderr, "Usage: hailcamsnap [hailcamsnap.ini]" sys.exit(1) try: cfg = config(cfgname, "snap") except ConfigError, e: # This is our exception. Other types traceback. print >>sys.stderr, "Error in config file " + cfgname + ":", e sys.exit(1) t = SnapScript(cfg) while 1: rc = t.fetch() if rc == 0: t.upload() print "Sleeping %(ss)gs" % { 'ss' : cfg["sleepval"] } time.sleep(cfg["sleepval"])