]> www.vanbest.org Git - motioneye-debian/commitdiff
mask editing should now be fully functional
authorCalin Crisan <ccrisan@gmail.com>
Sat, 17 Sep 2016 18:34:14 +0000 (21:34 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sat, 17 Sep 2016 18:34:14 +0000 (21:34 +0300)
motioneye/static/css/main.css
motioneye/static/js/main.js
motioneye/templates/main.html
motioneye/utils.py

index d5621d7a1bc99ae0e1014ab5b640ae61b6fa421c..48034172cb56cdedb32a9ccf2b4a45c50ddce5cf 100644 (file)
@@ -476,7 +476,7 @@ input[type=text].working-schedule.number {
     width: 90%;
 }
 
-#editableMaskEntry {
+#maskLinesEntry {
     display: none;
 }
 
index e2e475df8d9c7d6aac80025a1d0c6e0f8ee66655..c9871a934f815aa123bd1c18f37764b8640bc238 100644 (file)
@@ -811,6 +811,50 @@ function initUI() {
     $('#saturationSlider').change(function () {pushPreview('saturation');});
     $('#hueSlider').change(function () {pushPreview('hue');});
     
+    /* whenever the window is resized,
+     * if a modal dialog is visible, it should be repositioned */
+    $(window).resize(updateModalDialogPosition);
+    
+    /* autoselect urls in read-only entries */
+    $('#streamingSnapshotUrlEntry:text, #streamingMjpgUrlEntry:text, #streamingEmbedUrlEntry:text').click(function () {
+        this.select();
+    });
+
+    /* show a warning when enabling media files removal */
+    var preserveSelects = $('#preservePicturesSelect, #preserveMoviesSelect');
+    var rootDirectoryEntry = $('#rootDirectoryEntry');
+    preserveSelects.focus(function () {
+        this._prevValue = $(this).val();
+    }).change(function () {
+        var value = $(this).val();
+        if (value != '0' && this._prevValue == '0') {
+            var rootDir = rootDirectoryEntry.val();
+            runAlertDialog(('This will recursively remove all old media files present in the directory "' + rootDir + 
+                    '", not just those created by motionEye!'));
+        }
+    });
+    
+    /* disable mask editor when mask gets disabled */
+    $('#maskSwitch').change(function () {
+       if (!this.checked) {
+           disableMaskEdit();
+       } 
+    });
+    
+    /* disable mask editor when mask gets disabled */
+    $('#maskSwitch').change(function () {
+       if (!this.checked) {
+           disableMaskEdit();
+       } 
+    });
+    
+    /* disable mask editor when mask type is no longer editable */
+    $('#maskTypeSelect').change(function () {
+        if ($(this).val() != 'editable') {
+            disableMaskEdit();
+        }
+    });
+    
     /* apply button */
     $('#applyButton').click(function () {
         if ($(this).hasClass('progress')) {
@@ -830,34 +874,60 @@ function initUI() {
         doReboot();
     });
     
-    /* whenever the window is resized,
-     * if a modal dialog is visible, it should be repositioned */
-    $(window).resize(updateModalDialogPosition);
-    
     /* remove camera button */
     $('div.button.rem-camera-button').click(doRemCamera);
     
     /* logout button */
     $('div.button.logout-button').click(doLogout);
     
-    /* autoselect urls in read-only entries */
-    $('#streamingSnapshotUrlEntry:text, #streamingMjpgUrlEntry:text, #streamingEmbedUrlEntry:text').click(function () {
-        this.select();
-    });
-
-    /* show a warning when enabling media files removal */
-    var preserveSelects = $('#preservePicturesSelect, #preserveMoviesSelect');
-    var rootDirectoryEntry = $('#rootDirectoryEntry');
-    preserveSelects.focus(function () {
-        this._prevValue = $(this).val();
-    }).change(function () {
-        var value = $(this).val();
-        if (value != '0' && this._prevValue == '0') {
-            var rootDir = rootDirectoryEntry.val();
-            runAlertDialog(('This will recursively remove all old media files present in the directory "' + rootDir + 
-                    '", not just those created by motionEye!'));
+    /* software update button */
+    $('div#updateButton').click(doUpdate);
+    
+    /* backup/restore */
+    $('div#backupButton').click(doBackup);
+    $('div#restoreButton').click(doRestore);
+    
+    /* test buttons */
+    $('div#uploadTestButton').click(doTestUpload);
+    $('div#emailTestButton').click(doTestEmail);
+    $('div#networkShareTestButton').click(doTestNetworkShare);
+    
+    /* mask editor buttons */
+    $('div#editMaskButton').click(function () {
+        var cameraId = $('#cameraSelect').val();
+        var resolution = $('#resolutionSelect').val();
+        if (!cameraId) {
+            return;
         }
+        
+        if (!resolution) {
+            /*
+             * motion requires the mask file to be the same size as the
+             * captured images; however for netcams we have no means to know in
+             * advance the size of the stream; therefore, for netcams, we impose
+             * here a standard fixed mask size, which WILL NOT WORK for netcam
+             * streams of a different resolution, unless motion is patched accordingly
+             */
+            resolution = maskDefaultResolution; 
+        }
+
+        resolution = resolution.split('x');
+        var width = resolution[0];
+        var height = resolution[1];
+
+        enableMaskEdit(cameraId, width, height);
     });
+    $('div#saveMaskButton').click(function () {
+        disableMaskEdit();
+    });
+    $('div#clearMaskButton').click(function () {
+        var cameraId = $('#cameraSelect').val();
+        if (!cameraId) {
+            return;
+        }
+
+        clearMask(cameraId);
+    });    
 }
 
 function getPageContainer() {
@@ -989,7 +1059,7 @@ function enableMaskEdit(cameraId, width, height) {
     var cameraFrame = getCameraFrame(cameraId);
     var overlayDiv = cameraFrame.find('div.camera-overlay');
     var maskDiv = cameraFrame.find('div.camera-overlay-mask');
-    
+
     if (overlayDiv.hasClass('mask-edit')) {
         return; /* already enabled */
     }
@@ -1023,10 +1093,59 @@ function enableMaskEdit(cameraId, width, height) {
     
     var mouseDown = false;
     var currentState = false;
+    var elementsMatrix = Array.apply(null, Array(maskHeight)).map(function(){return []});
+
+    function matrixToMaskLines() {
+        var maskLines = [];
+        var bits, line;
+        
+        for (y = 0; y < ny; y++) {
+            bits = [];
+            for (x = 0; x < nx; x++) { 
+                bits.push(elementsMatrix[y][x].hasClass('on'));
+            }
+
+            if (rx) {
+                bits.push(elementsMatrix[y][nx].hasClass('on'));
+            }
+        
+            line = 0;
+            bits.forEach(function (bit, i) {
+                if (bit) {
+                    line |= 1 << (maskWidth - 1 - i);
+                }
+            });
+        
+            maskLines.push(line);
+        }
+
+        if (ry) {
+            bits = [];
+            for (x = 0; x < nx; x++) {
+                bits.push(elementsMatrix[ny][x].hasClass('on'));
+            }
+
+            if (rx) {
+                bits.push(elementsMatrix[ny][nx].hasClass('on'));
+            }
+
+            line = 0;
+            bits.forEach(function (bit, i) {
+                if (bit) {
+                    line |= 1 << (maskWidth - 1 - i);
+                }
+            });
+        }
+        
+        maskLines.push(line);
+
+        $('#maskLinesEntry').val(maskLines.join(',')).change();
+    }
     
     function handleMouseUp() {
         mouseDown = false;
         $('html').unbind('mouseup', handleMouseUp);
+        matrixToMaskLines();
     }
     
     function makeMaskElement(x, y, px, py, pw, ph) {
@@ -1047,6 +1166,8 @@ function enableMaskEdit(cameraId, width, height) {
             el.addClass('last-line');
         }
         maskDiv.append(el);
+        
+        elementsMatrix[y][x] = el; 
 
         el.mousedown(function () {
             mouseDown = true;
@@ -1063,6 +1184,8 @@ function enableMaskEdit(cameraId, width, height) {
             el.toggleClass('on', currentState);
         });
     }
+    
+    maskDiv[0]._matrixToMaskLines = matrixToMaskLines;
 
     /* make sure the mask is empty */
     maskDiv.html('');
@@ -1079,17 +1202,46 @@ function enableMaskEdit(cameraId, width, height) {
         }
 
         if (rx) {
-            makeMaskElement(x, y, nx * rw, y * rh, rx, rh);
+            makeMaskElement(nx, y, nx * rw, y * rh, rx, rh);
         }
     }
 
     if (ry) {
         for (x = 0; x < nx; x++) {
-            makeMaskElement(x, y, x * rw, ny * rh, rw, ry);
+            makeMaskElement(x, ny, x * rw, ny * rh, rw, ry);
         }
 
         if (rx) {
-            makeMaskElement(x, y, nx * rw, ny * rh, rx, ry);
+            makeMaskElement(nx, ny, nx * rw, ny * rh, rx, ry);
+        }
+    }
+    
+    /* use mask lines to initialize the element matrix */
+    var line;
+    var maskLines = $('#maskLinesEntry').val() ? $('#maskLinesEntry').val().split(',').map(function (v) {return parseInt(v);}) : [];
+    
+    for (y = 0; y < ny; y++) {
+        line = maskLines[y];
+        for (x = 0; x < nx; x++) { 
+            if (line & (1 << (maskWidth - 1 - x))) {
+                elementsMatrix[y][x].addClass('on');
+            }
+        }
+        if (rx && (line & 1)) {
+            elementsMatrix[y][nx].addClass('on');
+        }
+    }
+
+    if (ry) {
+        line = maskLines[ny];
+        for (x = 0; x < nx; x++) {
+            if (line & (1 << (maskWidth - 1 - x))) {
+                elementsMatrix[ny][x].addClass('on');
+            }
+        }
+
+        if (rx && (line & 1)) {
+            elementsMatrix[ny][nx].addClass('on');
         }
     }
 
@@ -1134,6 +1286,7 @@ function clearMask(cameraId) {
     var maskDiv = cameraFrame.find('div.camera-overlay-mask');
 
     maskDiv.find('div.mask-element').removeClass('on');
+    maskDiv[0]._matrixToMaskLines();
 }
 
 
@@ -1715,7 +1868,7 @@ function cameraUi2Dict() {
         'mask': $('#maskSwitch')[0].checked,
         'mask_type': $('#maskTypeSelect').val(),
         'smart_mask_slugginess': $('#smartMaskSlugginessSlider').val(),
-        'mask_lines': $('#maskLinesEntry').val().split(','),
+        'mask_lines': $('#maskLinesEntry').val() ? $('#maskLinesEntry').val().split(',').map(function (l) {return parseInt(l);}) : [],
 
         /* motion notifications */
         'email_notifications_enabled': $('#emailNotificationsEnabledSwitch')[0].checked,
@@ -2059,7 +2212,7 @@ function dict2CameraUi(dict) {
     $('#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');
-    $('#maskLinesEntry').val((dict['mask_lines'] or []).join(',')); markHideIfNull('mask_lines', 'maskLinesEntry');
+    $('#maskLinesEntry').val((dict['mask_lines'] || []).join(',')); markHideIfNull('mask_lines', 'maskLinesEntry');
 
     /* motion notifications */
     $('#emailNotificationsEnabledSwitch')[0].checked = dict['email_notifications_enabled']; markHideIfNull('email_notifications_enabled', 'emailNotificationsEnabledSwitch');
@@ -4761,55 +4914,6 @@ $(document).ready(function () {
         }
     });
     
-    /* software update button */
-    $('div#updateButton').click(doUpdate);
-    
-    /* backup/restore */
-    $('div#backupButton').click(doBackup);
-    $('div#restoreButton').click(doRestore);
-    
-    /* test buttons */
-    $('div#uploadTestButton').click(doTestUpload);
-    $('div#emailTestButton').click(doTestEmail);
-    $('div#networkShareTestButton').click(doTestNetworkShare);
-    
-    /* mask editor buttons */
-    $('div#editMaskButton').click(function () {
-        var cameraId = $('#cameraSelect').val();
-        var resolution = $('#resolutionSelect').val();
-        if (!cameraId) {
-            return;
-        }
-        
-        if (!resolution) {
-            /*
-             * TODO motion requires the mask file to be the same size as the
-             * captured images; however for netcams we have no means to know in
-             * advance the size of the stream; therefore, for netcams, we impose
-             * here a standard fixed mask size, which WILL NOT WORK for netcam
-             * streams of a different resolution
-             */
-            resolution = maskDefaultResolution; 
-        }
-
-        resolution = resolution.split('x');
-        var width = resolution[0];
-        var height = resolution[1];
-
-        enableMaskEdit(cameraId, width, height);
-    });
-    $('div#saveMaskButton').click(function () {
-        disableMaskEdit();
-    });
-    $('div#clearMaskButton').click(function () {
-        var cameraId = $('#cameraSelect').val();
-        if (!cameraId) {
-            return;
-        }
-
-        clearMask(cameraId);
-    });
-    
     initUI();
     beginProgress();
     
index 28a679a2db23faa8d005348adfa646f8c4a1a7dd..8192b7385b8aa5ec43befa5e81afc1bc2562da1a 100644 (file)
                         <td class="settings-item-value">
                             <div class="button normal-button edit-mask-button" id="editMaskButton">Edit Mask</div>
                             <div class="button normal-button save-mask-button" id="saveMaskButton">Save Mask</div>
-                            <input type="text" id="maskLinesEntry">
+                            <input type="text" class="motion-detection camera-config" id="maskLinesEntry">
                         </td>
                         <td><span class="help-mark" title="click this button to enable/disable the mask editor">?</span></td>
                     </tr>
index b14e6e7efd86b56fadbd8614941bfc7094f9caa4..fbeb0919a384027ada57429ba64e4c5389a08aaa 100644 (file)
@@ -814,18 +814,26 @@ def build_editable_mask_file(camera_id, width, height, mask_lines):
     else:
         ry = 0
 
+    # if mask not present, generate an empty mask    
+    if not mask_lines:
+        mask_lines = [0] * mask_height
+
+    # scale the mask vertically in case the aspect ratio has changed
+    # since the last time the mask has been generated
+    if mask_height == len(mask_lines):
+        line_index_func = lambda y: y
+        
+    else:
+        line_index_func = lambda y: (len(mask_lines) - 1) * y / (mask_height - 1)
+
     rh = height / ny # rectangle height
 
     # draw the actual mask image content
     im = Image.new('L', (width, height), 255) # all white
     dr = ImageDraw.Draw(im)
     
-    while len(mask_lines) < mask_height:
-        # add empty mask lines until mask is tall enough
-        mask_lines.append(0)
-
     for y in xrange(ny):
-        line = mask_lines[y]
+        line = mask_lines[line_index_func(y)]
         for x in xrange(nx):
             if line & (1 << (MASK_WIDTH - 1 - x)):
                 dr.rectangle((x * rw, y * rh, (x + 1) * rw - 1, (y + 1) * rh - 1), fill=0)
@@ -834,7 +842,7 @@ def build_editable_mask_file(camera_id, width, height, mask_lines):
             dr.rectangle((nx * rw, y * rh, nx * rw + rx - 1, (y + 1) * rh - 1), fill=0)
 
     if ry:
-        line = mask_lines[ny]
+        line = mask_lines[line_index_func(ny)]
         for x in xrange(nx):
             if line & (1 << (MASK_WIDTH - 1 - x)):
                 dr.rectangle((x * rw, ny * rh, (x + 1) * rw - 1, ny * rh + ry - 1), fill=0)