data['ffmpeg_variable_bitrate'] = int(vbr)
# motion detection
- if ui['mask']:
+ if ui['mask'] and data.get('width') and data.get('height'):
if ui['mask_type'] == 'smart':
data['smart_mask_speed'] = 10 - int(ui['smart_mask_slugginess'])
elif ui['mask_type'] == 'editable':
- data['mask_file'] = utils.build_editable_mask_file(ui['editable_mask'])
+ data['mask_file'] = utils.build_editable_mask_file(old_config['@id'], data['width'], data['height'], ui['mask_lines'])
# working schedule
if ui['working_schedule']:
'mask': False,
'mask_type': 'smart',
'smart_mask_slugginess': 5,
- 'editable_mask': '',
+ 'mask_lines': [],
# motion notifications
'email_notifications_enabled': False,
ui['movie_quality'] = int(q)
# mask
- if data['mask_file']:
+ if data['mask_file'] and data.get('width') and data.get('height'):
ui['mask'] = True
ui['mask_type'] = 'editable'
- ui['editable_mask'] = utils.parse_editable_mask_file(data['mask_file'])
+ ui['mask_lines'] = utils.parse_editable_mask_file(data['@id'], data['width'], data['height'])
elif data['smart_mask_speed']:
ui['mask'] = True
admin_username=config.get_main().get('@admin_username'),
has_streaming_auth=motionctl.has_streaming_auth(),
has_new_movie_format_support=motionctl.has_new_movie_format_support(),
- has_motion=bool(motionctl.find_motion()))
-
+ has_motion=bool(motionctl.find_motion()),
+ mask_width=utils.MASK_WIDTH)
+
class ConfigHandler(BaseHandler):
@asynchronous
transition: background-color 0.1s ease;
left: 0px;
width: 100%;
+ z-index: 1;
}
div.camera-frame:HOVER div.camera-overlay-top,
white-space: nowrap;
}
+div.camera-overlay-mask {
+ background: #888;
+ opacity: 0;
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ left: 0px;
+ z-index: 0;
+}
+
+div.camera-overlay.mask-edit > div.camera-overlay-top,
+div.camera-overlay.mask-edit > div.camera-overlay-bottom {
+ display: none;
+}
+
+div.camera-overlay.mask-edit > div.camera-overlay-mask {
+ opacity: 0.3;
+}
+
+div.mask-element {
+ position: absolute;
+ box-sizing: border-box;
+ border: 1px solid #aaa;
+ border-width: 0px 1px 1px 0px;
+ background: transparent;
+}
+
+div.mask-element.last-row {
+ border-right-width: 0px;
+}
+
+div.mask-element.last-line {
+ border-bottom-width: 0px;
+}
+
+div.mask-element.on {
+ background: #444;
+}
+
div.camera-name {
display: inline-block;
font-size: 1.2em;
}, 300);
}
+function enableMaskEdit(cameraId, width, height) {
+ var cameraFrame = getCameraFrame(cameraId);
+ var overlayDiv = cameraFrame.find('div.camera-overlay');
+ var maskDiv = cameraFrame.find('div.camera-overlay-mask');
+
+ overlayDiv.addClass('mask-edit');
+
+ var nx = maskWidth; /* number of rectangles */
+ var rx, rw;
+ if (width % nx) {
+ nx--;
+ rx = width % nx; /* remainder */
+ }
+ else {
+ rx = 0;
+ }
+
+ rw = parseInt(width / nx); /* rectangle width */
+
+ var maskHeight;
+ var ny = maskHeight = parseInt(height * maskWidth / width); /* number of rectangles */
+ var ry, rh;
+ if (height % ny) {
+ ny--;
+ ry = height % ny; /* remainder */
+ }
+ else {
+ ry = 0;
+ }
+
+ rh = parseInt(height / ny); /* rectangle height */
+
+ function makeMaskElement(x, y, px, py, pw, ph) {
+ px = px * 100 / width;
+ py = py * 100 / height;
+ pw = pw * 100 / width;
+ ph = ph * 100 / height;
+
+ var el = $('<div class="mask-element"></div>');
+ el.css('left', px + '%');
+ el.css('top', py + '%');
+ el.css('width', pw + '%');
+ el.css('height', ph + '%');
+ if (x == maskWidth - 1) {
+ el.addClass('last-row');
+ }
+ if (y == maskHeight - 1) {
+ el.addClass('last-line');
+ }
+ maskDiv.append(el);
+ }
+
+ /* make sure the mask is empty */
+ maskDiv.html('');
+
+ var x, y;
+ for (y = 0; y < ny; y++) {
+ for (x = 0; x < nx; x++) {
+ makeMaskElement(x, y, x * rw, y * rh, rw, rh);
+ }
+
+ if (rx) {
+ makeMaskElement(x, y, nx * rw, y * rh, rx, rh);
+ }
+ }
+
+ if (ry) {
+ for (x = 0; x < nx; x++) {
+ makeMaskElement(x, y, x * rw, ny * rh, rw, ry);
+ }
+
+ if (rx) {
+ makeMaskElement(x, y, nx * rw, ny * rh, rx, ry);
+ }
+ }
+}
+
+function disableMaskEdit(cameraId) {
+ var cameraFrame = getCameraFrame(cameraId);
+ var overlayDiv = cameraFrame.find('div.camera-overlay');
+ var maskDiv = cameraFrame.find('div.camera-overlay-mask');
+
+ overlayDiv.removeClass('mask-edit');
+ maskDiv.html('');
+}
+
/* settings */
'mask': $('#maskSwitch')[0].checked,
'mask_type': $('#maskTypeSelect').val(),
'smart_mask_slugginess': $('#smartMaskSlugginessSlider').val(),
- 'mask_file': $('#maskFileEntry').val(),
+ 'mask_lines': [], // TODO generate mask lines
/* motion notifications */
'email_notifications_enabled': $('#emailNotificationsEnabledSwitch')[0].checked,
$('#maskSwitch')[0].checked = dict['mask']; markHideIfNull('mask', 'maskSwitch');
$('#maskTypeSelect').val(dict['mask_type']); markHideIfNull('mask_type', 'maskTypeSelect');
$('#smartMaskSlugginessSlider').val(dict['smart_mask_slugginess']); markHideIfNull('smart_mask_slugginess', 'smartMaskSlugginessSlider');
- $('#maskFileEntry').val(dict['mask_file']); markHideIfNull('mask_file', 'maskFileEntry');
+ //TODO use dict['mask_lines']; markHideIfNull('mask_file', 'maskFileEntry');
/* motion notifications */
$('#emailNotificationsEnabledSwitch')[0].checked = dict['email_notifications_enabled']; markHideIfNull('email_notifications_enabled', 'emailNotificationsEnabledSwitch');
'<div class="button icon camera-top-button mouse-effect configure" title="configure this camera"></div>' +
'</div>' +
'</div>' +
+ '<div class="camera-overlay-mask"></div>' +
'<div class="camera-overlay-bottom">' +
'<div class="camera-info">' +
'<span class="camera-info" title="streaming/capture frame rate"></span>' +
var frame = {% if frame %}true{% else %}false{% endif %};
var hasLocalCamSupport = {% if has_motion %}true{% else %}false{% endif %};
var hasNetCamSupport = {% if has_motion %}true{% else %}false{% endif %};
+ {% if mask_width %}var maskWidth = {{mask_width}};{% endif %}
</script>
{% endblock %}
_SIGNATURE_REGEX = re.compile('[^a-zA-Z0-9/?_.=&{}\[\]":, _-]')
_SPECIAL_COOKIE_NAMES = {'expires', 'domain', 'path', 'secure', 'httponly'}
-_MASK_WIDTH = 32
+MASK_WIDTH = 32
DEV_NULL = open('/dev/null', 'w')
def build_editable_mask_file(camera_id, width, height, mask_lines):
# horizontal rectangles
- nx = _MASK_WIDTH # number of rectangles
- rw = width / nx # rectangle width
- rx = width % nx # remainder
- if rx:
+ nx = MASK_WIDTH # number of rectangles
+ if width % nx:
nx -= 1
+ rx = width % nx # remainder
+
+ else:
+ rx = 0
+
+ rw = width / nx # rectangle width
# vertical rectangles
- ny = height * _MASK_WIDTH / width # number of rectangles
- rh = height / ny # rectangle height
- ry = height % ny # remainder
- if ry:
+ ny = height * MASK_WIDTH / width # number of rectangles
+ if height % ny:
ny -= 1
+ ry = height % ny # remainder
+
+ else:
+ ry = 0
+
+ rh = height / ny # rectangle height
# draw the actual mask image content
im = Image.new('L', (width, height), 255) # all white
for y in xrange(ny):
line = mask_lines[y]
for x in xrange(nx):
- if line & (_MASK_WIDTH - 1 - x):
- dr.rectangle((x * rw, y * rh, (x + 1) * rw, (y + 1) * rh), fill=0)
+ if line & (1 << (MASK_WIDTH - 1 - x)):
+ dr.rectangle((x * rw, y * rh, (x + 1) * rw - 1, (y + 1) * rh - 1), fill=0)
- if rx and line & (nx + 1):
- dr.rectangle((nx * rw, y * rh, nx * rw + rx, (y + 1) * rh), fill=0)
+ if rx and line & 1:
+ dr.rectangle((nx * rw, y * rh, nx * rw + rx - 1, (y + 1) * rh - 1), fill=0)
if ry:
line = mask_lines[ny]
for x in xrange(nx):
- if line & (_MASK_WIDTH - 1 - x):
- dr.rectangle((x * rw, ny * rh, (x + 1) * rw, ny * rh + ry), fill=0)
+ if line & (1 << (MASK_WIDTH - 1 - x)):
+ dr.rectangle((x * rw, ny * rh, (x + 1) * rw - 1, ny * rh + ry - 1), fill=0)
- if rx and line & (nx + 1):
- dr.rectangle((nx * rw, ny * rh, nx * rw + rx, ny * rh + ry), fill=0)
+ if rx and line & 1:
+ dr.rectangle((nx * rw, ny * rh, nx * rw + rx - 1, ny * rh + ry - 1), fill=0)
file_name = os.path.join(settings.CONF_PATH, 'mask_%s.pgm' % camera_id)
im.save(file_name, 'ppm')
# as it might be different from that of the associated mask
# horizontal rectangles
- nx = _MASK_WIDTH # number of rectangles
- rw = width / nx # rectangle width
- rx = width % nx # remainder
- if rx:
+ nx = MASK_WIDTH # number of rectangles
+ if width % nx:
nx -= 1
+ rx = width % nx # remainder
+
+ else:
+ rx = 0
+
+ rw = width / nx # rectangle width
# vertical rectangles
- ny = mask_height = height * _MASK_WIDTH / width # number of rectangles
- rh = height / ny # rectangle height
- ry = height % ny # remainder
- if ry:
+ ny = mask_height = height * MASK_WIDTH / width # number of rectangles
+ if height % ny:
ny -= 1
+ ry = height % ny # remainder
+
+ else:
+ ry = 0
+
+ rh = height / ny # rectangle height
file_name = os.path.join(settings.CONF_PATH, 'mask_%s.pgm' % camera_id)
logging.error('failed to read mask file %s: %s' % (file_name, e))
# empty mask
- return [2 ** _MASK_WIDTH - 1] * mask_height
+ return [2 ** MASK_WIDTH - 1] * mask_height
# resize the image if necessary
if im.size != (width, height):
for y in xrange(ny):
bits = []
for x in xrange(nx):
- px = (x + 0.5) * rw
- py = (y + 0.5) * rh
- pixel = pixels[py * height + px]
- if pixel == 0:
- bits.append(not bool(pixel))
+ px = int((x + 0.5) * rw)
+ py = int((y + 0.5) * rh)
+ pixel = pixels[py * width + px]
+ bits.append(not bool(pixel))
if rx:
- px = nx * rw + rx / 2
- py = (y + 0.5) * rh
- pixel = pixels[py * height + px]
- if pixel == 0:
- bits.append(not bool(pixel))
+ px = int(nx * rw + rx / 2)
+ py = int((y + 0.5) * rh)
+ pixel = pixels[py * width + px]
+ bits.append(not bool(pixel))
# build the binary packed mask line
line = 0
for i, bit in enumerate(bits):
if bit:
- line |= 1 << (_MASK_WIDTH - 1 - i)
+ line |= 1 << (MASK_WIDTH - 1 - i)
mask_lines.append(line)
if ry:
bits = []
for x in xrange(nx):
- px = (x + 0.5) * rw
- py = ny * rh + ry / 2
- pixel = pixels[py * height + px]
- if pixel == 0:
- bits.append(not bool(pixel))
+ px = int((x + 0.5) * rw)
+ py = int(ny * rh + ry / 2)
+ pixel = pixels[py * width + px]
+ bits.append(not bool(pixel))
if rx:
- px = nx * rw + rx / 2
- py = ny * rh + ry / 2
- pixel = pixels[py * height + px]
- if pixel == 0:
- bits.append(not bool(pixel))
+ px = int(nx * rw + rx / 2)
+ py = int(ny * rh + ry / 2)
+ pixel = pixels[py * width + px]
+ bits.append(not bool(pixel))
# build the binary packed mask line
line = 0
for i, bit in enumerate(bits):
if bit:
- line |= 1 << (_MASK_WIDTH - 1 - i)
+ line |= 1 << (MASK_WIDTH - 1 - i)
mask_lines.append(line)