From 0597f5a3d330755096396dd28187873be248d3f1 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Tue, 19 Aug 2014 13:55:44 +0300 Subject: [PATCH] email notifications have now event pictures attached --- motioneye.py | 4 +- sendmail.py | 110 ++++++++++++++++++++++++++++++++++---------- settings_default.py | 3 ++ src/config.py | 6 +-- src/mediafiles.py | 7 +-- templates/main.html | 2 +- 6 files changed, 99 insertions(+), 33 deletions(-) diff --git a/motioneye.py b/motioneye.py index 8a3b492..0da299c 100755 --- a/motioneye.py +++ b/motioneye.py @@ -60,7 +60,9 @@ def _configure_settings(): set_default_setting('WPA_SUPPLICANT_CONF', None) set_default_setting('LOCAL_TIME_FILE', None) set_default_setting('ENABLE_REBOOT', False) - + set_default_setting('SMTP_TIMEOUT', 60) + set_default_setting('NOTIFY_MEDIA_TIMESPAN', 5) + length = len(sys.argv) - 1 for i in xrange(length): arg = sys.argv[i + 1] diff --git a/sendmail.py b/sendmail.py index 3216867..de0dda8 100755 --- a/sendmail.py +++ b/sendmail.py @@ -19,20 +19,28 @@ import datetime import logging import os +import re import smtplib import socket import sys +import time +from email import Encoders from email.mime.text import MIMEText +from email.MIMEMultipart import MIMEMultipart +from email.MIMEBase import MIMEBase +from tornado.ioloop import IOLoop import settings -from motioneye import _configure_settings, _configure_logging +from motioneye import _configure_settings, _configure_logging, _configure_signals _configure_settings() +_configure_signals() _configure_logging() import config +import mediafiles import tzctl @@ -45,7 +53,7 @@ subjects = { } -def send_mail(server, port, account, password, tls, to, subject, message): +def send_mail(server, port, account, password, tls, to, subject, message, files): conn = smtplib.SMTP(server, port, timeout=getattr(settings, 'SMTP_TIMEOUT', 60)) if tls: conn.starttls() @@ -55,40 +63,65 @@ def send_mail(server, port, account, password, tls, to, subject, message): _from = account or 'motioneye@' + socket.gethostname() - email = MIMEText(message) + email = MIMEMultipart() email['Subject'] = subject email['From'] = _from email['To'] = to + email.attach(MIMEText(message)) + for file in reversed(files): + part = MIMEBase('application', 'image/jpg') + with open(file, 'rb') as f: + part.set_payload(f.read()) + + Encoders.encode_base64(part) + part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file)) + email.attach(part) + + if files: + logging.debug('attached %d pictures' % len(files)) + conn.sendmail(_from, to, email.as_string()) conn.quit() -def format_message(subject, message, camera_id, moment): - format_dict = { - 'camera': config.get_camera(camera_id)['@name'], - 'hostname': socket.gethostname(), - 'moment': moment.strftime('%Y-%m-%d %H:%M:%S'), - } +def make_message(subject, message, camera_id, moment, callback): + camera_config = config.get_camera(camera_id) - if settings.LOCAL_TIME_FILE: - format_dict['timezone'] = tzctl.get_time_zone() + def on_media_files(media_files): + timestamp = time.mktime(moment.timetuple()) + + media_files = [m for m in media_files if abs(m['timestamp'] - timestamp) < settings.NOTIFY_MEDIA_TIMESPAN] # filter out non-recent media files + media_files.sort(key=lambda m: m['timestamp'], reverse=True) + media_files = [os.path.join(camera_config['target_dir'], re.sub('^/', '', m['path'])) for m in media_files] + + format_dict = { + 'camera': camera_config['@name'], + 'hostname': socket.gethostname(), + 'moment': moment.strftime('%Y-%m-%d %H:%M:%S'), + } + + if settings.LOCAL_TIME_FILE: + format_dict['timezone'] = tzctl.get_time_zone() + + else: + format_dict['timezone'] = 'local time' - else: - format_dict['timezone'] = 'local time' - - message = message % format_dict - subject = subject % format_dict - subject = subject.replace('\n', ' ') - - message += '\n\n' - message += 'motionEye.' + m = message % format_dict + s = subject % format_dict + s = s.replace('\n', ' ') + + m += '\n\n' + m += 'motionEye.' + + callback(s, m, media_files) - return (subject, message) + time.sleep(settings.NOTIFY_MEDIA_TIMESPAN) + mediafiles.list_media(camera_config, media_type='picture', callback=on_media_files) def print_usage(): - print 'Usage: sendmail.py ' + print 'Usage: sendmail.py ' if __name__ == '__main__': @@ -113,8 +146,35 @@ if __name__ == '__main__': sys.exit(-1) moment = datetime.datetime.strptime(moment, '%Y-%m-%dT%H:%M:%S') - subject, message = format_message(subject, message, camera_id, moment) - send_mail(server, port, account, password, tls, to, subject, message) + logging.debug('server = %s' % server) + logging.debug('port = %s' % port) + logging.debug('account = %s' % account) + logging.debug('password = ******') + logging.debug('server = %s' % server) + logging.debug('tls = %s' % tls) + logging.debug('to = %s' % to) + logging.debug('msg_id = %s' % msg_id) + logging.debug('camera_id = %s' % camera_id) + logging.debug('moment = %s' % moment.strftime('%Y-%m-%d %H:%M:%S')) + logging.debug('smtp timeout = %d' % settings.SMTP_TIMEOUT) - logging.info('message sent.') + io_loop = IOLoop.instance() + + def on_message(subject, message, files): + try: + send_mail(server, port, account, password, tls, to, subject, message, files) + logging.info('email sent') + + except Exception as e: + logging.error('failed to send mail: %s' % e, exc_info=True) + + io_loop.stop() + + def ioloop_timeout(): + io_loop.stop() + + make_message(subject, message, camera_id, moment, on_message) + + io_loop.add_timeout(datetime.timedelta(seconds=settings.SMTP_TIMEOUT), ioloop_timeout) + io_loop.start() diff --git a/settings_default.py b/settings_default.py index 9617388..54cc924 100644 --- a/settings_default.py +++ b/settings_default.py @@ -70,3 +70,6 @@ ENABLE_REBOOT = False # the timeout in seconds to use when talking to a SMTP server SMTP_TIMEOUT = 60 + +# the interval in seconds to consider around the moment of the event when attaching media files to notifications +NOTIFY_MEDIA_TIMESPAN = 5 diff --git a/src/config.py b/src/config.py index d27371a..1bc5668 100644 --- a/src/config.py +++ b/src/config.py @@ -700,8 +700,8 @@ def camera_ui_to_dict(ui): send_mail_path = os.path.abspath(send_mail_path) emails = re.sub('\\s', '', ui['motion_notifications_emails']) - - on_event_start.append('%(script)s %(server)s %(port)s %(account)s %(password)s %(tls)s %(to)s motion_start %%t %%Y-%%m-%%dT%%H:%%M:%%S' % { + + on_event_start.append('%(script)s "%(server)s" "%(port)s" "%(account)s" "%(password)s" "%(tls)s" "%(to)s" "motion_start" "%%t" "%%Y-%%m-%%dT%%H:%%M:%%S"' % { 'script': send_mail_path, 'server': ui['smtp_server'], 'port': ui['smtp_port'], @@ -933,7 +933,7 @@ def camera_dict_to_ui(data): for e in on_event_start: if e.count('sendmail.py') and e.count('motion_start'): - e = e.split(' ') + e = e.replace('"', '').split(' ') if len(e) != 10: continue diff --git a/src/mediafiles.py b/src/mediafiles.py index d3b5ab0..1b09d62 100644 --- a/src/mediafiles.py +++ b/src/mediafiles.py @@ -27,7 +27,6 @@ import tornado from PIL import Image import config -import mjpgclient import settings import utils @@ -94,7 +93,7 @@ def _list_media_files(dir, exts, prefix=None): continue media_files.append((full_path, st)) - + return media_files @@ -288,7 +287,7 @@ def list_media(camera_config, media_type, callback, prefix=None): callback(media_list) poll_process() - + def get_media_content(camera_config, path, media_type): target_dir = camera_config.get('target_dir') @@ -344,6 +343,8 @@ def get_media_preview(camera_config, path, media_type, width, height): def get_current_picture(camera_config, width, height): + import mjpgclient + jpg = mjpgclient.get_jpg(camera_config['@id']) if jpg is None: diff --git a/templates/main.html b/templates/main.html index 61f38f8..62484dd 100644 --- a/templates/main.html +++ b/templates/main.html @@ -453,7 +453,7 @@ Use TLS - ? + ? -- 2.39.5