diff --git a/octoprint/server.py b/octoprint/server.py index 4db9ba5..6d552d9 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -442,7 +442,9 @@ def getSettings(): "snapshotUrl": s.get(["webcam", "snapshot"]), "ffmpegPath": s.get(["webcam", "ffmpeg"]), "bitrate": s.get(["webcam", "bitrate"]), - "watermark": s.getBoolean(["webcam", "watermark"]) + "watermark": s.getBoolean(["webcam", "watermark"]), + "flipH": s.getBoolean(["webcam", "flipH"]), + "flipV": s.getBoolean(["webcam", "flipV"]) }, "feature": { "gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]), @@ -490,6 +492,8 @@ def setSettings(): if "ffmpegPath" in data["webcam"].keys(): s.set(["webcam", "ffmpeg"], data["webcam"]["ffmpegPath"]) if "bitrate" in data["webcam"].keys(): s.set(["webcam", "bitrate"], data["webcam"]["bitrate"]) if "watermark" in data["webcam"].keys(): s.setBoolean(["webcam", "watermark"], data["webcam"]["watermark"]) + if "flipH" in data["webcam"].keys(): s.setBoolean(["webcam", "flipH"], data["webcam"]["flipH"]) + if "flipV" in data["webcam"].keys(): s.setBoolean(["webcam", "flipV"], data["webcam"]["flipV"]) if "feature" in data.keys(): if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"]) diff --git a/octoprint/settings.py b/octoprint/settings.py index f6fed77..4efd483 100644 --- a/octoprint/settings.py +++ b/octoprint/settings.py @@ -35,7 +35,9 @@ default_settings = { "snapshot": None, "ffmpeg": None, "bitrate": "5000k", - "watermark": True + "watermark": True, + "flipH": False, + "flipV": False }, "feature": { "gCodeVisualizer": True, diff --git a/octoprint/static/css/octoprint.less b/octoprint/static/css/octoprint.less index 41f4367..d2de9d8 100644 --- a/octoprint/static/css/octoprint.less +++ b/octoprint/static/css/octoprint.less @@ -358,6 +358,21 @@ ul.dropdown-menu li a { #webcam_container { width: 100%; + + .flipH { + -webkit-transform: scaleX(-1); + -moz-transform: scaleX(-1); + } + + .flipV { + -webkit-transform: scaleY(-1); + -moz-transform: scaleY(-1); + } + + .flipH.flipV { + -webkit-transform: scaleX(-1) scaleY(-1); + -moz-transform: scaleX(-1) scaleY(-1); + } } /** GCODE file manager */ @@ -577,4 +592,4 @@ ul.dropdown-menu li a { background: url("../img/icon-sd-black-14.png") 0 3px no-repeat; width: 11px; height: 17px; -} \ No newline at end of file +} diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js index 9d2c27c..9d0c11b 100644 --- a/octoprint/static/js/ui.js +++ b/octoprint/static/js/ui.js @@ -509,10 +509,11 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) { } } -function ControlViewModel(loginStateViewModel) { +function ControlViewModel(loginStateViewModel, settingsViewModel) { var self = this; self.loginState = loginStateViewModel; + self.settings = settingsViewModel; self.isErrorOrClosed = ko.observable(undefined); self.isOperational = ko.observable(undefined); @@ -1325,6 +1326,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.webcam_ffmpegPath = ko.observable(undefined); self.webcam_bitrate = ko.observable(undefined); self.webcam_watermark = ko.observable(undefined); + self.webcam_flipH = ko.observable(undefined); + self.webcam_flipV = ko.observable(undefined); self.feature_gcodeViewer = ko.observable(undefined); self.feature_waitForStart = ko.observable(undefined); @@ -1372,6 +1375,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { self.webcam_ffmpegPath(response.webcam.ffmpegPath); self.webcam_bitrate(response.webcam.bitrate); self.webcam_watermark(response.webcam.watermark); + self.webcam_flipH(response.webcam.flipH); + self.webcam_flipV(response.webcam.flipV); self.feature_gcodeViewer(response.feature.gcodeViewer); self.feature_waitForStart(response.feature.waitForStart); @@ -1406,14 +1411,15 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) { "snapshotUrl": self.webcam_snapshotUrl(), "ffmpegPath": self.webcam_ffmpegPath(), "bitrate": self.webcam_bitrate(), - "watermark": self.webcam_watermark() + "watermark": self.webcam_watermark(), + "flipH": self.webcam_flipH(), + "flipV": self.webcam_flipV() }, "feature": { "gcodeViewer": self.feature_gcodeViewer(), "waitForStart": self.feature_waitForStart(), "alwaysSendChecksum": self.feature_alwaysSendChecksum(), "resetLineNumbersWithPrefixedN": self.feature_resetLineNumbersWithPrefixedN(), - "waitForStart": self.feature_waitForStart(), "sdSupport": self.feature_sdSupport() }, "folder": { @@ -1833,7 +1839,7 @@ $(function() { var settingsViewModel = new SettingsViewModel(loginStateViewModel, usersViewModel); var appearanceViewModel = new AppearanceViewModel(settingsViewModel); var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel); - var controlViewModel = new ControlViewModel(loginStateViewModel); + var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel); var terminalViewModel = new TerminalViewModel(loginStateViewModel); var gcodeFilesViewModel = new GcodeFilesViewModel(loginStateViewModel); var timelapseViewModel = new TimelapseViewModel(loginStateViewModel); diff --git a/octoprint/templates/index.jinja2 b/octoprint/templates/index.jinja2 index 5fa53f3..d1bcbb6 100644 --- a/octoprint/templates/index.jinja2 +++ b/octoprint/templates/index.jinja2 @@ -311,7 +311,7 @@
{% if webcamStream %}
- +
{% endif %} diff --git a/octoprint/templates/settings.jinja2 b/octoprint/templates/settings.jinja2 index c6a3652..b247cdf 100644 --- a/octoprint/templates/settings.jinja2 +++ b/octoprint/templates/settings.jinja2 @@ -89,6 +89,18 @@
+
+
+ +
+
+ +
+
diff --git a/octoprint/timelapse.py b/octoprint/timelapse.py index 7038738..8a5415a 100644 --- a/octoprint/timelapse.py +++ b/octoprint/timelapse.py @@ -141,14 +141,37 @@ class Timelapse(object): ffmpeg, '-i', input, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', '25', '-y', '-b:v', bitrate, '-f', 'vob'] + filters = [] + + # flip video if configured + if settings().getBoolean(["webcam", "flipX"]): + filters.append('hflip') + if settings().getBoolean(["webcam", "flipY"]): + filters.append('vflip') + # add watermark if configured + watermarkFilter = None if settings().getBoolean(["webcam", "watermark"]): watermark = os.path.join(os.path.dirname(__file__), "static", "img", "watermark.png") if sys.platform == "win32": # Because ffmpeg hiccups on windows' drive letters and backslashes we have to give the watermark # path a special treatment. Yeah, I couldn't believe it either... watermark = watermark.replace("\\", "/").replace(":", "\\\\:") - command.extend(['-vf', 'movie=%s [wm]; [in][wm] overlay=10:main_h-overlay_h-10 [out]' % watermark]) + + watermarkFilter = "movie=%s [wm]; [%%(inputName)s][wm] overlay=10:main_h-overlay_h-10" % watermark + + filterstring = None + if len(filters) > 0: + if watermarkFilter is not None: + filterstring = "[in] %s [postprocessed]; %s [out]" % (",".join(filters), watermarkFilter % {"inputName": "postprocessed"}) + else: + filterstring = "[in] %s [out]" % ",".join(filters) + elif watermarkFilter is not None: + filterstring = watermarkFilter % {"inputName": "in"} + " [out]" + + if filterstring is not None: + self._logger.debug("Applying videofilter chain: %s" % filterstring) + command.extend(["-vf", filterstring]) # finalize command with output file self._logger.debug("Rendering movie to %s" % output)