]> www.vanbest.org Git - motioneye-debian/commitdiff
added continuous movie recording mode; better movie quality adjustment
authorCalin Crisan <ccrisan@gmail.com>
Sun, 4 Oct 2015 15:20:01 +0000 (18:20 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sun, 4 Oct 2015 15:20:01 +0000 (18:20 +0300)
settings

motioneye/config.py
motioneye/static/js/main.js
motioneye/templates/main.html

index c17b5c9097387d903b23f6f6390e6b7d950ef6f5..e8c069002ccf4c0fabe61346c1db97dae89aeed9 100644 (file)
@@ -653,14 +653,22 @@ def motion_camera_ui_to_dict(ui, old_config=None):
 
         # still images
         'output_pictures': False,
-        'emulate_motion': False,
         'snapshot_interval': 0,
         'picture_filename': '',
         'snapshot_filename': '',
+        'quality': max(1, int(ui['image_quality'])),
         '@preserve_pictures': int(ui['preserve_pictures']),
         
+        # movies
+        'ffmpeg_output_movies': False,
+        'movie_filename': ui['movie_file_name'],
+        'ffmpeg_bps': 44000, # a quality of about 85% for 320x240x2fps
+        'max_movie_time': ui['max_movie_length'],
+        '@preserve_movies': int(ui['preserve_movies']),
+    
         # motion detection
         '@motion_detection': ui['motion_detection'],
+        'emulate_motion': False,
         'text_changes': ui['show_frame_changes'],
         'locate_motion_mode': ui['show_frame_changes'],
         'noise_tune': ui['auto_noise_detect'],
@@ -670,13 +678,6 @@ def motion_camera_ui_to_dict(ui, old_config=None):
         'post_capture': int(ui['post_capture']),
         'minimum_motion_frames': int(ui['minimum_motion_frames']),
         
-        # movies
-        'ffmpeg_output_movies': ui['motion_movies'],
-        'movie_filename': ui['movie_file_name'],
-        'ffmpeg_bps': 44000, # a quality of about 85% for 320x240x2fps
-        'max_movie_time': ui['max_movie_length'],
-        '@preserve_movies': int(ui['preserve_movies']),
-    
         # working schedule
         '@working_schedule': '',
     
@@ -812,17 +813,24 @@ def motion_camera_ui_to_dict(ui, old_config=None):
             data['emulate_motion'] = True
             data['picture_filename'] = ui['image_file_name']
             
-        data['quality'] = max(1, int(ui['image_quality']))
-    
+    if ui['movies']:
+        data['ffmpeg_output_movies'] = True
+        recording_mode = ui['recording_mode']
+        if recording_mode == 'motion-triggered':
+            data['emulate_motion'] = False  
+
+        elif recording_mode == 'continuous':
+            data['emulate_motion'] = True
+
     if proto == 'v4l2':
-        max_val = data['width'] * data['height'] * data['framerate'] / 3
+        max_val = data['width'] * data['height'] * data['framerate']
     
-    else: # always assume a netcam image size of 640x480, since we have no means to know it at this point
-        max_val = 640 * 480 * data['framerate'] / 3
-        
+    else: # assume a netcam image size of 640x480, since we have no means to know it at this point
+        max_val = 640 * 480 * data['framerate']
+
     max_val = min(max_val, 9999999)
-    
-    data['ffmpeg_bps'] = int(ui['movie_quality']) * max_val / 100
+
+    data['ffmpeg_bps'] = int(int(ui['movie_quality']) * max_val / 100)
     
     # working schedule
     if ui['working_schedule']:
@@ -939,10 +947,17 @@ def motion_camera_dict_to_ui(data):
         'still_images': False,
         'capture_mode': 'motion-triggered',
         'image_file_name': '%Y-%m-%d/%H-%M-%S',
-        'image_quality': 85,
+        'image_quality': data['quality'],
         'snapshot_interval': 0,
         'preserve_pictures': data['@preserve_pictures'],
         
+        # movies
+        'movies': False,
+        'recording_mode': 'motion-triggered',
+        'movie_file_name': data['movie_filename'],
+        'max_movie_length': data['max_movie_time'],
+        'preserve_movies': data['@preserve_movies'],
+
         # motion detection
         'motion_detection': data['@motion_detection'],
         'show_frame_changes': data['text_changes'] or data['locate_motion_mode'],
@@ -953,12 +968,6 @@ def motion_camera_dict_to_ui(data):
         'post_capture': int(data['post_capture']),
         'minimum_motion_frames': int(data['minimum_motion_frames']),
         
-        # motion movies
-        'motion_movies': data['ffmpeg_output_movies'],
-        'movie_file_name': data['movie_filename'],
-        'max_movie_length': data['max_movie_time'],
-        'preserve_movies': data['@preserve_movies'],
-
         # motion notifications
         'email_notifications_enabled': False,
         'web_hook_notifications_enabled': False,
@@ -1102,33 +1111,41 @@ def motion_camera_dict_to_ui(data):
     snapshot_interval = data['snapshot_interval']
     snapshot_filename = data['snapshot_filename']
     
-    if (((emulate_motion or output_pictures) and picture_filename) or
-        (snapshot_interval and snapshot_filename)):
-        
-        ui['still_images'] = True
+    ui['still_images'] = (((emulate_motion or output_pictures) and picture_filename) or
+            (snapshot_interval and snapshot_filename))
         
-        if emulate_motion:
-            ui['capture_mode'] = 'all-frames'
+    if emulate_motion:
+        ui['capture_mode'] = 'all-frames'
+        if picture_filename:
             ui['image_file_name'] = picture_filename
-            
-        elif snapshot_interval:
-            ui['capture_mode'] = 'interval-snapshots'
+
+    elif snapshot_interval:
+        ui['capture_mode'] = 'interval-snapshots'
+        ui['snapshot_interval'] = snapshot_interval
+        if snapshot_filename:
             ui['image_file_name'] = snapshot_filename
-            ui['snapshot_interval'] = snapshot_interval
-            
-        elif output_pictures:
-            ui['capture_mode'] = 'motion-triggered'
-            ui['image_file_name'] = picture_filename  
-            
-        ui['image_quality'] = data['quality']
+        
+    elif output_pictures:
+        ui['capture_mode'] = 'motion-triggered'
+        if picture_filename:
+            ui['image_file_name'] = picture_filename
+        
+    if data['ffmpeg_output_movies']:
+        ui['movies'] = True
+        
+    if emulate_motion:
+        ui['recording_mode'] = 'continuous'  
 
+    else:
+        ui['recording_mode'] = 'motion-triggered'
+            
     ffmpeg_bps = data['ffmpeg_bps']
     if ffmpeg_bps is not None:
         if utils.v4l2_camera(data):
-            max_val = data['width'] * data['height'] * data['framerate'] / 3
+            max_val = data['width'] * data['height'] * data['framerate']
         
         else: # net camera
-            max_val = 640 * 480 * data['framerate'] / 3
+            max_val = 640 * 480 * data['framerate']
             
         max_val = min(max_val, 9999999)
         
index 0fe6ca794aaf1568b968175a96a27129127e9b47..540f25b48b6e941e849e5309219ec958bf3f444e 100644 (file)
@@ -561,35 +561,35 @@ function initUI() {
     }
 
     /* ui elements that enable/disable other ui elements */
-    $('#motionEyeSwitch').change(updateConfigUi);
-    $('#showAdvancedSwitch').change(updateConfigUi);
-    $('#storageDeviceSelect').change(updateConfigUi);
-    $('#resolutionSelect').change(updateConfigUi);
-    $('#leftTextSelect').change(updateConfigUi);
-    $('#rightTextSelect').change(updateConfigUi);
-    $('#captureModeSelect').change(updateConfigUi);
-    $('#autoNoiseDetectSwitch').change(updateConfigUi);
-    $('#videoDeviceSwitch').change(checkMinimizeSection).change(updateConfigUi);
-    $('#textOverlaySwitch').change(checkMinimizeSection).change(updateConfigUi);
-    $('#videoStreamingSwitch').change(checkMinimizeSection).change(updateConfigUi);
-    $('#streamingServerResizeSwitch').change(updateConfigUi);
-    $('#stillImagesSwitch').change(checkMinimizeSection).change(updateConfigUi);
-    $('#preservePicturesSelect').change(updateConfigUi);
-    $('#motionDetectionSwitch').change(checkMinimizeSection).change(updateConfigUi);
-    $('#motionMoviesSwitch').change(checkMinimizeSection).change(updateConfigUi);
-    $('#preserveMoviesSelect').change(updateConfigUi);
-    $('#emailNotificationsSwitch').change(updateConfigUi);
-    $('#webHookNotificationsSwitch').change(updateConfigUi);
-    $('#commandNotificationsSwitch').change(updateConfigUi);
-    $('#workingScheduleSwitch').change(checkMinimizeSection).change(updateConfigUi);
-    
-    $('#mondayEnabledSwitch').change(updateConfigUi);
-    $('#tuesdayEnabledSwitch').change(updateConfigUi);
-    $('#wednesdayEnabledSwitch').change(updateConfigUi);
-    $('#thursdayEnabledSwitch').change(updateConfigUi);
-    $('#fridayEnabledSwitch').change(updateConfigUi);
-    $('#saturdayEnabledSwitch').change(updateConfigUi);
-    $('#sundayEnabledSwitch').change(updateConfigUi);
+    $('#motionEyeSwitch').change(updateConfigUI);
+    $('#showAdvancedSwitch').change(updateConfigUI);
+    $('#storageDeviceSelect').change(updateConfigUI);
+    $('#resolutionSelect').change(updateConfigUI);
+    $('#leftTextSelect').change(updateConfigUI);
+    $('#rightTextSelect').change(updateConfigUI);
+    $('#captureModeSelect').change(updateConfigUI);
+    $('#autoNoiseDetectSwitch').change(updateConfigUI);
+    $('#videoDeviceSwitch').change(checkMinimizeSection).change(updateConfigUI);
+    $('#textOverlaySwitch').change(checkMinimizeSection).change(updateConfigUI);
+    $('#videoStreamingSwitch').change(checkMinimizeSection).change(updateConfigUI);
+    $('#streamingServerResizeSwitch').change(updateConfigUI);
+    $('#stillImagesSwitch').change(checkMinimizeSection).change(updateConfigUI);
+    $('#preservePicturesSelect').change(updateConfigUI);
+    $('#moviesSwitch').change(checkMinimizeSection).change(updateConfigUI);
+    $('#motionDetectionSwitch').change(checkMinimizeSection).change(updateConfigUI);
+    $('#preserveMoviesSelect').change(updateConfigUI);
+    $('#emailNotificationsSwitch').change(updateConfigUI);
+    $('#webHookNotificationsSwitch').change(updateConfigUI);
+    $('#commandNotificationsSwitch').change(updateConfigUI);
+    $('#workingScheduleSwitch').change(checkMinimizeSection).change(updateConfigUI);
+    
+    $('#mondayEnabledSwitch').change(updateConfigUI);
+    $('#tuesdayEnabledSwitch').change(updateConfigUI);
+    $('#wednesdayEnabledSwitch').change(updateConfigUI);
+    $('#thursdayEnabledSwitch').change(updateConfigUI);
+    $('#fridayEnabledSwitch').change(updateConfigUI);
+    $('#saturdayEnabledSwitch').change(updateConfigUI);
+    $('#sundayEnabledSwitch').change(updateConfigUI);
     
     /* minimizable sections */
     $('span.minimize').click(function () {
@@ -608,7 +608,7 @@ function initUI() {
             }
         }
             
-        updateConfigUi();
+        updateConfigUI();
     });
 
     $('a.settings-section-title').click(function () {
@@ -631,7 +631,7 @@ function initUI() {
             seenDependNames[depend] = true;
 
             var control = $('#' + depend + 'Entry, #' + depend + 'Select, #' + depend + 'Slider, #' + depend + 'Switch');
-            control.change(updateConfigUi);
+            control.change(updateConfigUI);
         });
     });
     
@@ -673,7 +673,7 @@ function initUI() {
     });
     
     /* streaming framerate must be >= device framerate */
-    $('#framerateSlider').change(function (val) {
+    $('#framerateSlider').change(function () {
         var value = Number($('#framerateSlider').val());
         var streamingValue = Number($('#streamingFramerateSlider').val());
         
@@ -682,6 +682,33 @@ function initUI() {
         }
     });
     
+    /* capture mode and recording mode are not completely independent:
+     * all frames capture mode implies continuous recording (and vice-versa) */
+    $('#captureModeSelect').change(function (val) {
+        if ($('#captureModeSelect').val() == 'all-frames') {
+            $('#recordingModeSelect').val('continuous');
+        }
+        else {
+            if ($('#recordingModeSelect').val() == 'continuous') {
+                $('#recordingModeSelect').val('motion-triggered');
+            }
+        }
+        
+        updateConfigUI();
+    });
+    $('#recordingModeSelect').change(function (val) {
+        if ($('#recordingModeSelect').val() == 'continuous') {
+            $('#captureModeSelect').val('all-frames');
+        }
+        else {
+            if ($('#captureModeSelect').val() == 'all-frames') {
+                $('#captureModeSelect').val('motion-triggered');
+            }
+        }
+        
+        updateConfigUI();
+    });
+    
     /* preview controls */
     $('#brightnessSlider').change(function () {pushPreview('brightness');});
     $('#contrastSlider').change(function () {pushPreview('contrast');});
@@ -749,7 +776,7 @@ function openSettings(cameraId) {
     $('div.page-container').addClass('stretched');
     $('div.settings-top-bar').addClass('open').removeClass('closed');
     
-    updateConfigUi();
+    updateConfigUI();
 }
 
 function closeSettings() {
@@ -766,7 +793,7 @@ function isSettingsOpen() {
     return $('div.settings').hasClass('open');   
 }
 
-function updateConfigUi() {
+function updateConfigUI() {
     var objs = $('tr.settings-item, div.advanced-setting, table.advanced-setting, div.settings-section-title, table.settings, ' +
             'div.check-box.camera-config, div.check-box.main-config');
     
@@ -885,14 +912,20 @@ function updateConfigUi() {
         $('#picturesLifetimeEntry').parents('tr:eq(0)').each(markHideLogic);
     }
     
+    /* movies switch */
+    if (!$('#moviesSwitch').get(0).checked) {
+        $('#moviesSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
+    }
+    
+    /* preserve movies */
+    if ($('#preserveMoviesSelect').val() != '-1') {
+        $('#moviesLifetimeEntry').parents('tr:eq(0)').each(markHideLogic);
+    }
+    
     /* motion detection switch */
     if (!$('#motionDetectionSwitch').get(0).checked) {
         $('#motionDetectionSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
         
-        /* hide the entire motion movies section */
-        $('#motionMoviesSwitch').parent().each(markHideLogic);
-        $('#motionMoviesSwitch').parent().next('table.settings').each(markHideLogic);
-        
         /* hide the entire notifications section */
         $('#emailNotificationsSwitch').parents('table.settings').prev().each(markHideLogic);
         $('#emailNotificationsSwitch').parents('table.settings').each(markHideLogic);
@@ -902,16 +935,6 @@ function updateConfigUi() {
         $('#workingScheduleSwitch').parent().next('table.settings').each(markHideLogic);
     }
     
-    /* motion movies switch */
-    if (!$('#motionMoviesSwitch').get(0).checked) {
-        $('#motionMoviesSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
-    }
-    
-    /* preserve movies */
-    if ($('#preserveMoviesSelect').val() != '-1') {
-        $('#moviesLifetimeEntry').parents('tr:eq(0)').each(markHideLogic);
-    }
-    
     /* event notifications */
     if (!$('#emailNotificationsSwitch').get(0).checked) {
         $('#emailAddressesEntry').parents('tr:eq(0)').each(markHideLogic);
@@ -1233,7 +1256,7 @@ function dict2MainUi(dict) {
         markHideIfNull('_' + name, id);
     });
 
-    updateConfigUi();
+    updateConfigUI();
 }
 
 function cameraUi2Dict() {
@@ -1308,6 +1331,14 @@ function cameraUi2Dict() {
         'snapshot_interval': $('#snapshotIntervalEntry').val(),
         'preserve_pictures': $('#preservePicturesSelect').val() >= 0 ? $('#preservePicturesSelect').val() : $('#picturesLifetimeEntry').val(),
         
+        /* movies */
+        'movies': $('#moviesSwitch')[0].checked,
+        'movie_file_name': $('#movieFileNameEntry').val(),
+        'movie_quality': $('#movieQualitySlider').val(),
+        'recording_mode': $('#recordingModeSelect').val(),
+        'max_movie_length': $('#maxMovieLengthEntry').val(),
+        'preserve_movies': $('#preserveMoviesSelect').val() >= 0 ? $('#preserveMoviesSelect').val() : $('#moviesLifetimeEntry').val(),
+        
         /* motion detection */
         'motion_detection': $('#motionDetectionSwitch')[0].checked,
         'show_frame_changes': $('#showFrameChangesSwitch')[0].checked,
@@ -1319,13 +1350,6 @@ function cameraUi2Dict() {
         'post_capture': $('#postCaptureEntry').val(),
         'minimum_motion_frames': $('#minimumMotionFramesEntry').val(),
         
-        /* motion movies */
-        'motion_movies': $('#motionMoviesSwitch')[0].checked,
-        'movie_file_name': $('#movieFileNameEntry').val(),
-        'movie_quality': $('#movieQualitySlider').val(),
-        'max_movie_length': $('#maxMovieLengthEntry').val(),
-        'preserve_movies': $('#preserveMoviesSelect').val() >= 0 ? $('#preserveMoviesSelect').val() : $('#moviesLifetimeEntry').val(),
-        
         /* motion notifications */
         'email_notifications_enabled': $('#emailNotificationsSwitch')[0].checked,
         'email_notifications_addresses': $('#emailAddressesEntry').val(),
@@ -1428,7 +1452,7 @@ function dict2CameraUi(dict) {
         
         $('#videoDeviceSwitch')[0].error = true;
         $('#videoDeviceSwitch')[0].checked = true; /* so that the user can explicitly disable the camera */
-        updateConfigUi();
+        updateConfigUI();
         
         return;
     }
@@ -1610,6 +1634,19 @@ function dict2CameraUi(dict) {
     markHideIfNull('preserve_pictures', 'preservePicturesSelect');
     $('#picturesLifetimeEntry').val(dict['preserve_pictures']); markHideIfNull('preserve_pictures', 'picturesLifetimeEntry');
     
+    /* movies */
+    $('#moviesSwitch')[0].checked = dict['movies']; markHideIfNull('movies', 'moviesSwitch');
+    $('#movieFileNameEntry').val(dict['movie_file_name']); markHideIfNull('movie_file_name', 'movieFileNameEntry');
+    $('#movieQualitySlider').val(dict['movie_quality']); markHideIfNull('movie_quality', 'movieQualitySlider');
+    $('#recordingModeSelect').val(dict['recording_mode']); markHideIfNull('recording_mode', 'recordingModeSelect');
+    $('#maxMovieLengthEntry').val(dict['max_movie_length']); markHideIfNull('max_movie_length', 'maxMovieLengthEntry');
+    $('#preserveMoviesSelect').val(dict['preserve_movies']);
+    if ($('#preserveMoviesSelect').val() == null) {
+        $('#preserveMoviesSelect').val('-1');
+    }
+    markHideIfNull('preserve_movies', 'preserveMoviesSelect');
+    $('#moviesLifetimeEntry').val(dict['preserve_movies']); markHideIfNull('preserve_movies', 'moviesLifetimeEntry');
+    
     /* motion detection */
     $('#motionDetectionSwitch')[0].checked = dict['motion_detection']; markHideIfNull('motion_detection', 'motionDetectionSwitch');
     $('#showFrameChangesSwitch')[0].checked = dict['show_frame_changes']; markHideIfNull('show_frame_changes', 'showFrameChangesSwitch');
@@ -1621,18 +1658,6 @@ function dict2CameraUi(dict) {
     $('#postCaptureEntry').val(dict['post_capture']); markHideIfNull('post_capture', 'postCaptureEntry');
     $('#minimumMotionFramesEntry').val(dict['minimum_motion_frames']); markHideIfNull('minimum_motion_frames', 'minimumMotionFramesEntry');
     
-    /* motion movies */
-    $('#motionMoviesSwitch')[0].checked = dict['motion_movies']; markHideIfNull('motion_movies', 'motionMoviesSwitch');
-    $('#movieFileNameEntry').val(dict['movie_file_name']); markHideIfNull('movie_file_name', 'movieFileNameEntry');
-    $('#movieQualitySlider').val(dict['movie_quality']); markHideIfNull('movie_quality', 'movieQualitySlider');
-    $('#maxMovieLengthEntry').val(dict['max_movie_length']); markHideIfNull('max_movie_length', 'maxMovieLengthEntry');
-    $('#preserveMoviesSelect').val(dict['preserve_movies']);
-    if ($('#preserveMoviesSelect').val() == null) {
-        $('#preserveMoviesSelect').val('-1');
-    }
-    markHideIfNull('preserve_movies', 'preserveMoviesSelect');
-    $('#moviesLifetimeEntry').val(dict['preserve_movies']); markHideIfNull('preserve_movies', 'moviesLifetimeEntry');
-    
     /* motion notifications */
     $('#emailNotificationsSwitch')[0].checked = dict['email_notifications_enabled']; markHideIfNull('email_notifications_enabled', 'emailNotificationsSwitch');
     $('#emailAddressesEntry').val(dict['email_notifications_addresses']);
@@ -1721,7 +1746,7 @@ function dict2CameraUi(dict) {
         markHideIfNull('_' + name, id);
     });
 
-    updateConfigUi();
+    updateConfigUI();
 }
 
     
@@ -2294,7 +2319,7 @@ function fetchCurrentConfig(onFetch) {
                     }
                 }
 
-                updateConfigUi();
+                updateConfigUI();
             }
             else { /* normal user */
                 if (!cameras.length) {
index 4a7390e49d15d89fe21736ce4684876a587527ca..18e7137c17446395a2df19c40aae2ddff20dae24 100644 (file)
@@ -98,6 +98,8 @@
     <div class="page">
         <div class="settings closed">
             <div class="settings-container">
+                
+                <!-- General Settings -->
                 <div class="settings-section-title">
                     <input type="checkbox" class="styled section general main-config" id="motionEyeSwitch">
                     <span class="help-mark" title="general settings, not related to any camera">?</span>
                     </tr>
                 </table>
 
+                <!-- Additional Main Sections -->
                 {% for section in main_sections.values() %}
                 {% if section.get('label') and section.get('configs') %}
                 <div class="settings-section-title {% if section['advanced'] %}advanced-setting{% endif %}">
                     <td colspan="100"><div class="settings-item-separator" style="margin: 0px 0px 1.5em 0px;"></div></td>
                 </tr>
                 
+                <!-- Video Device -->
                 <div class="settings-section-title">
                     <input type="checkbox" class="styled section device camera-config" id="videoDeviceSwitch">
                     <span class="help-mark" title="enable this if you want to use this camera device">?</span>
                     {% endfor %}
                 </table>
                 
+                <!-- File Storage -->
                 <div class="settings-section-title advanced-setting">
                     <span class="help-mark" title="choose where and how your media files are saved">?</span>
                     <a class="settings-section-title">File Storage</a>
                     {% endfor %}
                 </table>
                 
+                <!-- Text Overlay -->
                 <div class="settings-section-title advanced-setting">
                     <input type="checkbox" class="styled section text-overlay camera-config" id="textOverlaySwitch">
                     <span class="help-mark" title="choose what information is displayed on the captured frames">?</span>
                     {% endfor %}
                 </table>
 
+                <!-- Video Streaming -->
                 <div class="settings-section-title" minimize-switch-independent="true">
                     <input type="checkbox" class="styled section streaming camera-config" id="videoStreamingSwitch">
                     <span class="help-mark" title="enable this if you want video streaming for this camera">?</span>
                     {% endfor %}
                 </table>
                 
+                <!-- Still Images -->
                 <div class="settings-section-title">
                     <input type="checkbox" class="styled section still-images camera-config" id="stillImagesSwitch">
                     <span class="help-mark" title="enable this if you want to capture still images (pictures)">?</span>
                                 <option value="all-frames">All Frames</option>
                             </select>
                         </td>
-                        <td><span class="help-mark" title="sets the image capture mode: Motion Triggered = an image captured whenever motion is detected, Automated Snapshots = an image captured every x seconds, All Frames = saves each frame into an image file">?</span></td>
+                        <td><span class="help-mark" title="sets the image capture mode: Motion Triggered = an image captured whenever motion is detected, Interval Snapshots = an image captured every x seconds, All Frames = saves each frame to an image file">?</span></td>
                     </tr>
                     <tr class="settings-item advanced-setting" min="1" max="86400" required="true">
                         <td class="settings-item-label"><span class="settings-item-label">Snapshot Interval</span></td>
                         <td class="settings-item-value"><input type="text" class="styled number still-images camera-config" id="snapshotIntervalEntry"><span class="settings-item-unit">seconds</span></td>
-                        <td><span class="help-mark" title="sets the interval (in seconds) for the automated snapshots">?</span></td>
+                        <td><span class="help-mark" title="sets the interval (in seconds) for the snapshots">?</span></td>
                     </tr>
                     <tr class="settings-item">
                         <td class="settings-item-label"><span class="settings-item-label">Preserve Pictures</span></td>
                                 <option value="-1">Custom</option>
                             </select>
                         </td>
-                        <td><span class="help-mark" title="images older than the specified duration are automatically deleted to free storage space">?</span></td>
+                        <td><span class="help-mark" title="images older than the specified duration are automatically deleted to free up storage space">?</span></td>
                     </tr>
                     <tr class="settings-item" min="1" max="3650" required="true">
                         <td class="settings-item-label"><span class="settings-item-label">Pictures Lifetime</span></td>
                     {% endfor %}
                 </table>
                 
+                <!-- Movies -->
+                <div class="settings-section-title">
+                    <input type="checkbox" class="styled section movies camera-config" id="moviesSwitch">
+                    <span class="help-mark" title="enable this if you want to record movies">?</span>
+                    <a class="settings-section-title">Movies</a>
+                    <span class="minimize"></span>
+                </div>
+                <table class="settings">
+                    <tr class="settings-item advanced-setting" required="true" strip="true">
+                        <td class="settings-item-label"><span class="settings-item-label">Movie File Name</span></td>
+                        <td class="settings-item-value"><input type="text" class="styled movies camera-config" id="movieFileNameEntry" placeholder="file name pattern..."></td>
+                        <td><span class="help-mark" title="sets the name pattern for the movie (MPEG) files; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number, / = subfolder">?</span></td>
+                    </tr>
+                    <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+                        <td class="settings-item-label"><span class="settings-item-label">Movie Quality</span></td>
+                        <td class="settings-item-value"><input type="text" class="range styled movies camera-config" id="movieQualitySlider"></td>
+                        <td><span class="help-mark" title="sets the MPEG video quality (higher values produce a better video quality but require more storage space)">?</span></td>
+                    </tr>
+                    <tr class="settings-item advanced-setting">
+                        <td class="settings-item-label"><span class="settings-item-label">Recording Mode</span></td>
+                        <td class="settings-item-value">
+                            <select class="styled movies camera-config" id="recordingModeSelect">
+                                <option value="motion-triggered">Motion Triggered</option>
+                                <option value="continuous">Continuous Recording</option>
+                            </select>
+                        </td>
+                        <td><span class="help-mark" title="sets the recording mode: Motion Triggered = a new movie created whenever motion is detected, Continuous Recording = one big movie file">?</span></td>
+                    </tr>
+                    <tr class="settings-item advanced-setting" min="0" max="86400" required="true">
+                        <td class="settings-item-label"><span class="settings-item-label">Maximum Movie Length</span></td>
+                        <td class="settings-item-value"><input type="text" class="number styled movies camera-config" id="maxMovieLengthEntry"><span class="settings-item-unit">seconds</span></td>
+                        <td><span class="help-mark" title="sets the maximum length of movies, in seconds; if the motion event lasts longer, a new movie file is created; use 0 for unlimited length">?</span></td>
+                    </tr>
+                    <tr class="settings-item">
+                        <td class="settings-item-label"><span class="settings-item-label">Preserve Movies</span></td>
+                        <td class="settings-item-value">
+                            <select class="styled movies camera-config" id="preserveMoviesSelect">
+                                <option value="1">For One Day</option>
+                                <option value="7">For One Week</option>
+                                <option value="30">For One Month</option>
+                                <option value="365">For One Year</option>
+                                <option value="0">Forever</option>
+                                <option value="-1">Custom</option>
+                            </select>
+                        </td>
+                        <td><span class="help-mark" title="movies older than the specified duration are automatically deleted to free up storage space">?</span></td>
+                    </tr>
+                    <tr class="settings-item" min="1" max="3650" required="true">
+                        <td class="settings-item-label"><span class="settings-item-label">Movies Lifetime</span></td>
+                        <td class="settings-item-value"><input type="text" class="styled number movies camera-config" id="moviesLifetimeEntry"><span class="settings-item-unit">days</span></td>
+                        <td><span class="help-mark" title="sets the number of days after which the movies will be deleted automatically">?</span></td>
+                    </tr>
+                    {% for config in camera_sections.get('movies', {}).get('configs', []) %}
+                        {{config_item(config)}}
+                    {% endfor %}
+                </table>
+
+                <!-- Motion Detection -->
                 <div class="settings-section-title advanced-setting">
                     <input type="checkbox" class="styled section motion-detection camera-config" id="motionDetectionSwitch">
                     <span class="help-mark" title="enable this to use and configure the motion detection mechanism">?</span>
                     {% endfor %}
                 </table>
                 
-                <div class="settings-section-title">
-                    <input type="checkbox" class="styled section motion-movies camera-config" id="motionMoviesSwitch">
-                    <span class="help-mark" title="enable this if you want to record motion movies">?</span>
-                    <a class="settings-section-title">Motion Movies</a>
-                    <span class="minimize"></span>
-                </div>
-                <table class="settings">
-                    <tr class="settings-item advanced-setting" required="true" strip="true">
-                        <td class="settings-item-label"><span class="settings-item-label">Movie File Name</span></td>
-                        <td class="settings-item-value"><input type="text" class="styled motion-movies camera-config" id="movieFileNameEntry" placeholder="file name pattern..."></td>
-                        <td><span class="help-mark" title="sets the name pattern for the movie (MPEG) files; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number, / = subfolder">?</span></td>
-                    </tr>
-                    <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
-                        <td class="settings-item-label"><span class="settings-item-label">Movie Quality</span></td>
-                        <td class="settings-item-value"><input type="text" class="range styled motion-movies camera-config" id="movieQualitySlider"></td>
-                        <td><span class="help-mark" title="sets the MPEG video quality (higher values produce a better video quality but require more storage space)">?</span></td>
-                    </tr>
-                    <tr class="settings-item advanced-setting" min="0" max="86400" required="true">
-                        <td class="settings-item-label"><span class="settings-item-label">Maximum Movie Length</span></td>
-                        <td class="settings-item-value"><input type="text" class="number styled motion-movies camera-config" id="maxMovieLengthEntry"><span class="settings-item-unit">seconds</span></td>
-                        <td><span class="help-mark" title="sets the maximum length of motion movies, in seconds; if the motion event lasts longer, a new movie file is created; use 0 for infinite length">?</span></td>
-                    </tr>
-                    <tr class="settings-item">
-                        <td class="settings-item-label"><span class="settings-item-label">Preserve Movies</span></td>
-                        <td class="settings-item-value">
-                            <select class="styled motion-movies camera-config" id="preserveMoviesSelect">
-                                <option value="1">For One Day</option>
-                                <option value="7">For One Week</option>
-                                <option value="30">For One Month</option>
-                                <option value="365">For One Year</option>
-                                <option value="0">Forever</option>
-                                <option value="-1">Custom</option>
-                            </select>
-                        </td>
-                        <td><span class="help-mark" title="movies older than the specified duration are automatically deleted to free storage space">?</span></td>
-                    </tr>
-                    <tr class="settings-item" min="1" max="3650" required="true">
-                        <td class="settings-item-label"><span class="settings-item-label">Movies Lifetime</span></td>
-                        <td class="settings-item-value"><input type="text" class="styled number motion-movies camera-config" id="moviesLifetimeEntry"><span class="settings-item-unit">days</span></td>
-                        <td><span class="help-mark" title="sets the number of days after which the movies will be deleted automatically">?</span></td>
-                    </tr>
-                    {% for config in camera_sections.get('motion-movies', {}).get('configs', []) %}
-                        {{config_item(config)}}
-                    {% endfor %}
-                </table>
+                <!-- Motion Notifications -->
                 <div class="settings-section-title advanced-setting">
                     <span class="help-mark" title="enable this if you want to be notified when motion is detected">?</span>
                     <a class="settings-section-title">Motion Notifications</a>
                     {% endfor %}
                 </table>
 
+                <!-- Working Schedule -->
                 <div class="settings-section-title">
                     <input type="checkbox" class="styled section working-schedule camera-config" id="workingScheduleSwitch">
                     <span class="help-mark" title="enable this if you want to define a weekly working schedule for motion detection">?</span>
                     {% endfor %}
                 </table>
 
+                <!-- Additional Camera Sections -->
                 {% for section in camera_sections.values() %}
                 {% if section.get('label') and section.get('configs') %}
                 <div class="settings-section-title {% if section['advanced'] %}advanced-setting{% endif %}">