Add option to repeat last captured frame instead of capturing new frames for the end of a timelapse. Defaults to current behavior (capturing new frames for the time after). Implements foosel/OctoPrint#1422

This commit is contained in:
Dattas Moonchaser 2016-10-20 19:06:26 -06:00
parent b38d1810dc
commit 7e2382fed4
5 changed files with 67 additions and 9 deletions

View file

@ -66,7 +66,8 @@ def getTimelapseData():
config = dict(type="timed",
postRoll=timelapse.post_roll,
fps=timelapse.fps,
interval=timelapse.interval)
interval=timelapse.interval,
capturePostRoll=timelapse.capture_post_roll)
else:
config = dict(type="off")
@ -204,6 +205,16 @@ def setTimelapseConfig():
else:
return make_response("Invalid value for interval: %d" % interval)
if "capturePostRoll" in data:
config["options"]["capturePostRoll"] = True
try:
capturePostRoll = bool(data["capturePostRoll"])
except ValueError:
return make_response("Invalid value for capturePostRoll: %r" % data["capturePostRoll"])
else:
config["options"]["capturePostRoll"] = capturePostRoll
if "retractionZHop" in request.values:
config["options"] = {
"retractionZHop": 0
@ -219,6 +230,7 @@ def setTimelapseConfig():
else:
return make_response("Invalid value for retraction Z-Hop: %d" % retractionZHop)
if admin_permission.can() and "save" in data and data["save"] in valid_boolean_trues:
octoprint.timelapse.configure_timelapse(config, True)
else:

View file

@ -166,7 +166,8 @@ default_settings = {
"type": "off",
"options": {},
"postRoll": 0,
"fps": 25
"fps": 25,
"capturePostRoll": True
},
"cleanTmpAfterDays": 7
},

View file

@ -10,12 +10,14 @@ $(function() {
self.defaultPostRoll = 0;
self.defaultInterval = 10;
self.defaultRetractionZHop = 0;
self.defaultCapturePostroll = true;
self.timelapseType = ko.observable(undefined);
self.timelapseTimedInterval = ko.observable(self.defaultInterval);
self.timelapsePostRoll = ko.observable(self.defaultPostRoll);
self.timelapseFps = ko.observable(self.defaultFps);
self.timelapseRetractionZHop = ko.observable(self.defaultRetractionZHop);
self.timelapseCapturePostRoll = ko.observable(self.defaultCapturePostRoll);
self.persist = ko.observable(false);
self.isDirty = ko.observable(false);
@ -61,6 +63,9 @@ $(function() {
self.timelapseRetractionZHop.subscribe(function(newValue) {
self.isDirty(true);
});
self.timelapseCapturePostRoll.subscribe(function() {
self.isDirty(true);
});
// initialize list helper
self.listHelper = new ItemListHelper(
@ -167,6 +172,12 @@ $(function() {
self.timelapseFps(self.defaultFps);
}
if (config.capturePostRoll != undefined){
self.timelapseCapturePostRoll(config.capturePostRoll);
} else {
self.timelapseCapturePostRoll(self.defaultCapturePostRoll);
}
self.persist(false);
self.isDirty(false);
};
@ -214,6 +225,7 @@ $(function() {
if (self.timelapseType() == "timed") {
payload["interval"] = self.timelapseTimedInterval();
payload["capturePostRoll"] = self.timelapseCapturePostRoll();
}
if (self.timelapseType() == "zchange") {

View file

@ -24,6 +24,12 @@
<input type="text" class="input-mini" id="webcam_timelapse_postRoll" data-bind="value: timelapsePostRoll, valueUpdate: 'afterkeydown', enable: isOperational() && !isPrinting() && loginState.isUser() && timelapseTypeSelected()">
<span class="add-on">{{ _('sec') }}</span>
</div>
<div id="webcam_timelapse_timed_postRoll" data-bind="visible: timelapseType() == 'timed'">
<label class="checkbox">
<input type="checkbox" data-bind="checked: timelapseCapturePostRoll, valueUpdate: 'afterkeydown', enable: isOperational() && !isPrinting() && loginState.isUser()"> {{ _('Capture postroll images. If disabled, we will simply repeat the last frame for the post roll') }}
</label>
</div>
<div id="webcam_timelapse_timedsettings" data-bind="visible: intervalInputEnabled">
<label for="webcam_timelapse_interval">{{ _('Interval') }}</label>

View file

@ -303,7 +303,10 @@ def configure_timelapse(config=None, persist=False):
interval = 10
if "options" in config and "interval" in config["options"] and config["options"]["interval"] > 0:
interval = config["options"]["interval"]
current = TimedTimelapse(post_roll=postRoll, interval=interval, fps=fps)
capture_post_roll = True
if "options" in config and "capturePostRoll" in config["options"] and isinstance(config["options"]["capturePostRoll"], bool):
capture_post_roll = config["options"]["capturePostRoll"]
current = TimedTimelapse(post_roll=postRoll, interval=interval, fps=fps, capture_post_roll=capture_post_roll)
notify_callbacks(current)
@ -628,11 +631,12 @@ class ZTimelapse(Timelapse):
class TimedTimelapse(Timelapse):
def __init__(self, post_roll=0, interval=1, fps=25):
def __init__(self, post_roll=0, interval=1, fps=25, capture_post_roll=True):
Timelapse.__init__(self, post_roll=post_roll, fps=fps)
self._interval = interval
if self._interval < 1:
self._interval = 1 # force minimum interval of 1s
self._capture_post_roll = capture_post_roll
self._postroll_captures = 0
self._timer = None
self._logger.debug("TimedTimelapse initialized")
@ -641,11 +645,16 @@ class TimedTimelapse(Timelapse):
def interval(self):
return self._interval
@property
def capture_post_roll(self):
return self._capture_post_roll
def config_data(self):
return {
"type": "timed",
"options": {
"interval": self._interval
"interval": self._interval,
"capture_post_roll": self._capture_post_roll
}
}
@ -662,14 +671,31 @@ class TimedTimelapse(Timelapse):
self._timer.start()
def on_print_done(self, event, payload):
self._postroll_captures = self._post_roll * self._fps
if self._capture_post_roll:
self._postroll_captures = self._post_roll * self._fps
Timelapse.on_print_done(self, event, payload)
def calculate_post_roll(self):
return self._post_roll * self._fps * self._interval
if self._capture_post_roll:
return self._post_roll * self._fps * self._interval
else:
return Timelapse.calculate_post_roll(self)
def process_post_roll(self):
pass
if self._capture_post_roll:
return
with self._capture_mutex:
filename = os.path.join(self._capture_dir, _capture_format.format(prefix=self._file_prefix) % self._image_number)
self._image_number += 1
if self._perform_capture(filename):
for _ in range(self._post_roll * self._fps):
newFile = os.path.join(self._capture_dir, _capture_format.format(prefix=self._file_prefix) % self._image_number)
self._image_number += 1
shutil.copyfile(filename, newFile)
self.post_roll_finished()
def post_roll_finished(self):
Timelapse.post_roll_finished(self)
@ -684,7 +710,8 @@ class TimedTimelapse(Timelapse):
self._postroll_captures -= 1
def _on_timer_finished(self):
self.post_roll_finished()
if self._capture_post_roll:
self.post_roll_finished()
class TimelapseRenderJob(object):