From 07d4a8fd4f58018d38a108b8039f42080f36d7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 9 Mar 2013 00:23:52 +0100 Subject: [PATCH] Added (optional!) watermark addition to generated timelapse movies. --- octoprint/server.py | 4 +++- octoprint/settings.py | 3 ++- octoprint/static/img/watermark.png | Bin 0 -> 6822 bytes octoprint/static/js/ui.js | 5 ++++- octoprint/templates/settings.html | 7 +++++++ octoprint/timelapse.py | 24 ++++++++++++++++++++---- 6 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 octoprint/static/img/watermark.png diff --git a/octoprint/server.py b/octoprint/server.py index 03665a7..60c7374 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -349,7 +349,8 @@ def getSettings(): "streamUrl": s.get(["webcam", "stream"]), "snapshotUrl": s.get(["webcam", "snapshot"]), "ffmpegPath": s.get(["webcam", "ffmpeg"]), - "bitrate": s.get(["webcam", "bitrate"]) + "bitrate": s.get(["webcam", "bitrate"]), + "watermark": s.getBoolean(["webcam", "watermark"]) }, "feature": { "gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]), @@ -387,6 +388,7 @@ def setSettings(): if "snapshotUrl" in data["webcam"].keys(): s.set(["webcam", "snapshot"], data["webcam"]["snapshotUrl"]) if "ffmpeg" in data["webcam"].keys(): s.set(["webcam", "ffmpeg"], data["webcam"]["ffmpeg"]) 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 "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 8bb4480..2b096bc 100644 --- a/octoprint/settings.py +++ b/octoprint/settings.py @@ -32,7 +32,8 @@ old_default_settings = { "stream": None, "snapshot": None, "ffmpeg": None, - "bitrate": "5000k" + "bitrate": "5000k", + "watermark": True }, "folder": { "uploads": None, diff --git a/octoprint/static/img/watermark.png b/octoprint/static/img/watermark.png new file mode 100644 index 0000000000000000000000000000000000000000..18d559f1426dc87c5eeeabf554669b6ed7e56b59 GIT binary patch literal 6822 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 U$G>~(TmS$707*qoM6N<$f_(u;0RR91 literal 0 HcmV?d00001 diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js index eb7f54c..70a49f9 100644 --- a/octoprint/static/js/ui.js +++ b/octoprint/static/js/ui.js @@ -977,6 +977,7 @@ function SettingsViewModel() { self.webcam_snapshotUrl = ko.observable(undefined); self.webcam_ffmpegPath = ko.observable(undefined); self.webcam_bitrate = ko.observable(undefined); + self.webcam_watermark = ko.observable(undefined); self.feature_gcodeViewer = ko.observable(undefined); self.feature_waitForStart = ko.observable(undefined); @@ -1018,6 +1019,7 @@ function SettingsViewModel() { self.webcam_snapshotUrl(response.webcam.snapshotUrl); self.webcam_ffmpegPath(response.webcam.ffmpegPath); self.webcam_bitrate(response.webcam.bitrate); + self.webcam_watermark(response.webcam.watermark); self.feature_gcodeViewer(response.feature.gcodeViewer); self.feature_waitForStart(response.feature.waitForStart); @@ -1046,7 +1048,8 @@ function SettingsViewModel() { "streamUrl": self.webcam_streamUrl(), "snapshotUrl": self.webcam_snapshotUrl(), "ffmpegPath": self.webcam_ffmpegPath(), - "bitrate": self.webcam_bitrate() + "bitrate": self.webcam_bitrate(), + "watermark": self.webcam_watermark() }, "feature": { "gcodeViewer": self.feature_gcodeViewer(), diff --git a/octoprint/templates/settings.html b/octoprint/templates/settings.html index 883cc59..9005fce 100644 --- a/octoprint/templates/settings.html +++ b/octoprint/templates/settings.html @@ -81,6 +81,13 @@ +
+
+ +
+
diff --git a/octoprint/timelapse.py b/octoprint/timelapse.py index 1c85254..d8dcdce 100644 --- a/octoprint/timelapse.py +++ b/octoprint/timelapse.py @@ -13,6 +13,8 @@ import subprocess import fnmatch import datetime +import sys + def getFinishedTimelapses(): files = [] basedir = settings().getBaseFolder("timelapse") @@ -91,10 +93,24 @@ class Timelapse(object): input = os.path.join(self._captureDir, "tmp_%05d.jpg") output = os.path.join(self._movieDir, "%s_%s.mpg" % (os.path.splitext(self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S"))) - subprocess.call([ - ffmpeg, '-i', input, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', '25', '-y', - '-b:v', bitrate, '-f', 'vob', output - ]) + + # prepare ffmpeg command + command = [ + ffmpeg, '-i', input, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', '25', '-y', '-b:v', bitrate, + '-f', 'vob'] + + # add watermark if configured + 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)]) + + # finalize command with output file + command.append(output) + subprocess.call(command) def cleanCaptureDir(self): if not os.path.isdir(self._captureDir):