| 1 |
# Written by Sudharshan 'Sup3rkiddo' S |
| 2 |
# released under WTFPL |
| 3 |
|
| 4 |
# I know a simple mirror would suffice. But screw mirrors. |
| 5 |
# The sound file taken from http://www.freesound.org/samplesViewSingle.php?id=72557 |
| 6 |
|
| 7 |
import datetime |
| 8 |
import time |
| 9 |
from datetime import datetime |
| 10 |
from optparse import OptionParser |
| 11 |
|
| 12 |
|
| 13 |
NOTIFICATIONS = True |
| 14 |
try: |
| 15 |
import pynotify |
| 16 |
pynotify.init("Boss Alert") |
| 17 |
except: |
| 18 |
print "Pynotify not installed. Notifications will be disabled" |
| 19 |
NOTIFICATIONS = False |
| 20 |
|
| 21 |
|
| 22 |
import pygame |
| 23 |
import pygame.camera |
| 24 |
import pygame.font |
| 25 |
import pygame.mixer |
| 26 |
|
| 27 |
from pygame.locals import * |
| 28 |
|
| 29 |
|
| 30 |
class BossAlert(object): |
| 31 |
|
| 32 |
def __init__(self, soundpath=None, waitfor=0): |
| 33 |
self.reference_color = None |
| 34 |
size = (640,480) |
| 35 |
pygame.camera.init() |
| 36 |
|
| 37 |
self.display = pygame.display.set_mode(size, 0) |
| 38 |
cams = pygame.camera.list_cameras() |
| 39 |
assert cams, "No Cameras detected. Get a mirror instead" |
| 40 |
|
| 41 |
self.notify = None |
| 42 |
if NOTIFICATIONS: |
| 43 |
self.notify = pynotify.Notification("BossAlert", "Your Boss just walked down the hallway. Back to work") |
| 44 |
self.notify.set_urgency(pynotify.URGENCY_CRITICAL) |
| 45 |
self.notify.set_timeout(5) |
| 46 |
|
| 47 |
self.cam = pygame.camera.Camera(cams[0], size, "RGB24") |
| 48 |
self.cam.start() |
| 49 |
print "Initializing Camera" |
| 50 |
|
| 51 |
self.surface = pygame.surface.Surface(size, 0, self.display) |
| 52 |
self.font = pygame.font.SysFont("Courier New", 30, bold=True) |
| 53 |
|
| 54 |
self.sound = None |
| 55 |
if soundpath: |
| 56 |
print "Init pygame mixer" |
| 57 |
pygame.mixer.init() |
| 58 |
self.sound = pygame.mixer.Sound(soundpath) |
| 59 |
self.waitfor = waitfor |
| 60 |
self.starttime = time.time() |
| 61 |
self.refresh() |
| 62 |
|
| 63 |
|
| 64 |
def check_movement(self, rect, threshold=30): |
| 65 |
"""Really dumb way to check for movement. |
| 66 |
Find out the average color within the rectangular area. If it differs from |
| 67 |
the reference colour by a certain threshold, we have movement. |
| 68 |
Simple but effective in cases where the background colour is uniform. |
| 69 |
""" |
| 70 |
color = pygame.transform.average_color(self.surface, rect) |
| 71 |
diff = [a - b for a,b in zip(color, self.reference_color)] |
| 72 |
for d in diff: |
| 73 |
if abs(d) > threshold: |
| 74 |
return True |
| 75 |
return False |
| 76 |
|
| 77 |
|
| 78 |
def get_image(self, surface): |
| 79 |
success = self.cam.query_image() |
| 80 |
if success: |
| 81 |
return self.cam.get_image(surface) |
| 82 |
return None |
| 83 |
|
| 84 |
|
| 85 |
def refresh(self): |
| 86 |
snapshot = self.get_image(self.surface) |
| 87 |
if not snapshot: |
| 88 |
return |
| 89 |
# Change the position of the rectangular window here. Or position your webcam accordingly |
| 90 |
rect = pygame.draw.rect(snapshot, (255, 0, 0), (10, 40, 100, 100), 2) |
| 91 |
|
| 92 |
if not self.reference_color: |
| 93 |
if time.time() - self.starttime < self.waitfor: |
| 94 |
return |
| 95 |
self.reference_color = pygame.transform.average_color(snapshot, rect) |
| 96 |
|
| 97 |
# Play a sound and pop up a desktop notification if any motion is detected within the rect window |
| 98 |
if self.check_movement(rect): |
| 99 |
print "Movement detected at ", datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S") |
| 100 |
if not pygame.mixer.get_busy() and self.sound: |
| 101 |
self.sound.play(loops=-1) |
| 102 |
if NOTIFICATIONS: |
| 103 |
self.notify.show() |
| 104 |
snapshot.fill((255, 0, 0), rect) |
| 105 |
ren = self.font.render("Boss Alert", 30, (255, 0, 0)) |
| 106 |
snapshot.blit(ren, (400, 200)) |
| 107 |
else: |
| 108 |
if self.sound: |
| 109 |
self.sound.fadeout(500) |
| 110 |
self.display.blit(snapshot, (0,0)) |
| 111 |
self.surface = snapshot |
| 112 |
pygame.display.flip() |
| 113 |
|
| 114 |
|
| 115 |
def startloop(self): |
| 116 |
while True: |
| 117 |
events = pygame.event.get() |
| 118 |
for e in events: |
| 119 |
if e.type == QUIT: |
| 120 |
self.cam.stop() |
| 121 |
exit() |
| 122 |
self.refresh() |
| 123 |
|
| 124 |
|
| 125 |
if __name__ == '__main__': |
| 126 |
pygame.init() |
| 127 |
oparser = OptionParser() |
| 128 |
oparser.add_option("-s", "--sound", dest="soundpath", |
| 129 |
help="Path to the Sound file to be played when movement is detected", metavar="FILE") |
| 130 |
oparser.add_option("-x", "--disable-notifications", dest="no_notify", action="store_true", default=False, |
| 131 |
help="Disable desktop notifications (Need pynotify to be installed)") |
| 132 |
oparser.add_option("-w", "--wait-for", dest="wait_for", default=0, |
| 133 |
help="The time (milliseconds) after which the reference color should be captured") |
| 134 |
(options, args) = oparser.parse_args() |
| 135 |
NOTIFICATIONS = not options.no_notify |
| 136 |
alert = BossAlert(soundpath=options.soundpath, waitfor=int(options.wait_for)) |
| 137 |
alert.startloop() |