def _process_snapshot(self, snapshot_path, pixfmt="yuv420p"): hflip = self._settings.global_get_boolean(["webcam", "flipH"]) vflip = self._settings.global_get_boolean(["webcam", "flipV"]) rotate = self._settings.global_get_boolean(["webcam", "rotate90"]) ffmpeg = self._settings.global_get(["webcam", "ffmpeg"]) if not ffmpeg or not os.access(ffmpeg, os.X_OK) or (not vflip and not hflip and not rotate): return ffmpeg_command = [ffmpeg, "-y", "-i", snapshot_path] rotate_params = ["format={}".format(pixfmt)] # workaround for foosel/OctoPrint#1317 if rotate: rotate_params.append("transpose=2") # 90 degrees counter clockwise if hflip: rotate_params.append("hflip") # horizontal flip if vflip: rotate_params.append("vflip") # vertical flip ffmpeg_command += ["-vf", sarge.shell_quote(",".join(rotate_params)), snapshot_path] self._logger.info("Running: {}".format(" ".join(ffmpeg_command))) p = sarge.run(ffmpeg_command, stdout=sarge.Capture(), stderr=sarge.Capture()) if p.returncode == 0: self._logger.info("Rotated/flipped image with ffmpeg") else: self._logger.warn("Failed to rotate/flip image with ffmpeg, " "got return code {}: {}, {}".format(p.returncode, p.stdout.text, p.stderr.text))
def _render(self, template_filename): render_config = self.render_config(template_filename) if self.badge_instance: url = reverse( "main:instance-filename", kwargs={ "slug": self.badge_template.slug, "pk": self.badge_instance.pk, "filename": template_filename, }, ) else: url = reverse( "main:preview-filename", kwargs={ "slug": self.badge_template.slug, "filename": template_filename, }, ) file_format = render_config.get("format", "png") if file_format == "pdf": f = tempfile.NamedTemporaryFile(delete=False) tmppdf = f.name f.close() cmd = ( "node {chrome_pdf_bin} " "--paper-width 8.27 --paper-height 11.69" "--no-margins --landscape --include-background --url {url} --pdf {tmppdf}" .format( chrome_pdf_bin=settings.CHROME_PDF_BIN, tmppdf=tmppdf, url="{}{}".format(settings.RENDER_PREFIX_URL, url), )) sarge.run(cmd, stdout=sarge.Capture()) f = open(tmppdf, "rb") image = f.read() f.close() os.unlink(f.name) else: cmd = ("{capture_bin} {url} --width={width} --height={height} " "--type={type} --element={element} --no-default-background". format( width=render_config.get("screen_width", 1000), height=render_config.get("screen_height", 1000), type=file_format, element=sarge.shell_quote( render_config.get("element", ".screenshot")), url="{}{}".format(settings.RENDER_PREFIX_URL, url), capture_bin=settings.CAPTURE_BIN, )) p = sarge.run(cmd, stdout=sarge.Capture()) image = p.stdout.bytes mime = magic.from_buffer(image, mime=True) return image, mime
def test_quote(self): self.assertEqual(shell_quote(""), "''") self.assertEqual(shell_quote("a"), "a") self.assertEqual(shell_quote("*"), "'*'") self.assertEqual(shell_quote("foo"), "foo") self.assertEqual(shell_quote("'*.py'"), "''\\''*.py'\\'''") self.assertEqual(shell_quote("'a'; rm -f b; true 'c'"), "''\\''a'\\''; rm -f b; true '\\''c'\\'''") self.assertEqual(shell_quote("*.py"), "'*.py'") self.assertEqual(shell_quote("'*.py"), "''\\''*.py'")
def _create_ffmpeg_command_string(cls, ffmpeg, fps, bitrate, threads, input, output, hflip=False, vflip=False, rotate=False, watermark=None, pixfmt="yuv420p"): """ Create ffmpeg command string based on input parameters. Arguments: ffmpeg (str): Path to ffmpeg fps (int): Frames per second for output bitrate (str): Bitrate of output threads (int): Number of threads to use for rendering input (str): Absolute path to input files including file mask output (str): Absolute path to output file hflip (bool): Perform horizontal flip on input material. vflip (bool): Perform vertical flip on input material. rotate (bool): Perform 90° CCW rotation on input material. watermark (str): Path to watermark to apply to lower left corner. pixfmt (str): Pixel format to use for output. Default of yuv420p should usually fit the bill. Returns: (str): Prepared command string to render `input` to `output` using ffmpeg. """ ### See unit tests in test/timelapse/test_timelapse_renderjob.py logger = logging.getLogger(__name__) command = [ ffmpeg, '-framerate', str(fps), '-loglevel', 'error', '-i', '"{}"'.format(input), '-vcodec', 'mpeg2video', '-threads', str(threads), '-r', "25", '-y', '-b', str(bitrate), '-f', 'vob' ] filter_string = cls._create_filter_string(hflip=hflip, vflip=vflip, rotate=rotate, watermark=watermark) if filter_string is not None: logger.debug( "Applying videofilter chain: {}".format(filter_string)) command.extend(["-vf", sarge.shell_quote(filter_string)]) # finalize command with output file logger.debug("Rendering movie to {}".format(output)) command.append('"{}"'.format(output)) return " ".join(command)
def test_quote(self): self.assertEqual(shell_quote(''), "''") self.assertEqual(shell_quote('a'), 'a') self.assertEqual(shell_quote('*'), "'*'") self.assertEqual(shell_quote('foo'), 'foo') self.assertEqual(shell_quote("'*.py'"), "''\\''*.py'\\'''") self.assertEqual(shell_quote("'a'; rm -f b; true 'c'"), "''\\''a'\\''; rm -f b; true '\\''c'\\'''") self.assertEqual(shell_quote("*.py"), "'*.py'") self.assertEqual(shell_quote("'*.py"), "''\\''*.py'")
def shell_quote(s): if platform.system() == "Windows": assert isinstance(s, str) if not s: result = '""' elif '"' not in s: result = s else: escaped = s.replace('"', r"\"") result = f'"{escaped}"' return result else: return sarge.shell_quote(s)
def _install_plugin(cls, plugin, force_user=False, pip_command=None, pip_args=None, on_log=None): if pip_args is None: pip_args = [] if on_log is None: on_log = logging.getLogger(__name__).info # prepare pip caller def log(prefix, *lines): for line in lines: on_log("{} {}".format(prefix, line.rstrip())) def log_call(*lines): log(">", *lines) def log_stdout(*lines): log("<", *lines) def log_stderr(*lines): log("!", *lines) if cls._pip_caller is None: cls._pip_caller = create_pip_caller(command=pip_command, force_user=force_user) cls._pip_caller.on_log_call = log_call cls._pip_caller.on_log_stdout = log_stdout cls._pip_caller.on_log_stderr = log_stderr # install plugin pip = [ "install", sarge.shell_quote(plugin["archive"]), "--no-cache-dir" ] if plugin.get("follow_dependency_links"): pip.append("--process-dependency-links") if force_user: pip.append("--user") if pip_args: pip += pip_args cls._pip_caller.execute(*pip)
def _create_ffmpeg_command_string(cls, ffmpeg, fps, bitrate, threads, input, output, hflip=False, vflip=False, rotate=False, watermark=None): """ Create ffmpeg command string based on input parameters. Examples: >>> TimelapseRenderJob._create_ffmpeg_command_string("/path/to/ffmpeg", 25, "10000k", 1, "/path/to/input/files_%d.jpg", "/path/to/output.mpg") '/path/to/ffmpeg -framerate 25 -loglevel error -i "/path/to/input/files_%d.jpg" -vcodec mpeg2video -threads 1 -pix_fmt yuv420p -r 25 -y -b 10000k -f vob "/path/to/output.mpg"' >>> TimelapseRenderJob._create_ffmpeg_command_string("/path/to/ffmpeg", 25, "10000k", 1, "/path/to/input/files_%d.jpg", "/path/to/output.mpg", hflip=True) '/path/to/ffmpeg -framerate 25 -loglevel error -i "/path/to/input/files_%d.jpg" -vcodec mpeg2video -threads 1 -pix_fmt yuv420p -r 25 -y -b 10000k -f vob -vf \\'[in] hflip [out]\\' "/path/to/output.mpg"' Arguments: ffmpeg (str): Path to ffmpeg fps (int): Frames per second for output bitrate (str): Bitrate of output threads (int): Number of threads to use for rendering input (str): Absolute path to input files including file mask output (str): Absolute path to output file hflip (bool): Perform horizontal flip on input material. vflip (bool): Perform vertical flip on input material. rotate (bool): Perform 90° CCW rotation on input material. watermark (str): Path to watermark to apply to lower left corner. Returns: (str): Prepared command string to render `input` to `output` using ffmpeg. """ logger = logging.getLogger(__name__) command = [ ffmpeg, '-framerate', str(fps), '-loglevel', 'error', '-i', '"{}"'.format(input), '-vcodec', 'mpeg2video', '-threads', str(threads), '-pix_fmt', 'yuv420p', '-r', str(fps), '-y', '-b', str(bitrate), '-f', 'vob'] filter_string = cls._create_filter_string(hflip=hflip, vflip=vflip, rotate=rotate, watermark=watermark) if filter_string is not None: logger.debug("Applying videofilter chain: {}".format(filter_string)) command.extend(["-vf", sarge.shell_quote(filter_string)]) # finalize command with output file logger.debug("Rendering movie to {}".format(output)) command.append('"{}"'.format(output)) return " ".join(command)
def test_quote_with_shell(self): from subprocess import PIPE, Popen if os.name != 'posix': raise unittest.SkipTest('This test works only on POSIX') workdir = tempfile.mkdtemp() try: s = "'\\\"; touch %s/foo #'" % workdir cmd = 'echo %s' % shell_quote(s) p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) p.communicate() self.assertEqual(p.returncode, 0) files = os.listdir(workdir) self.assertEqual(files, []) fn = "'ab?'" cmd = 'touch %s/%s' % (workdir, shell_quote(fn)) p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) p.communicate() self.assertEqual(p.returncode, 0) files = os.listdir(workdir) self.assertEqual(files, ["'ab?'"]) finally: shutil.rmtree(workdir)
def test_quote_with_shell(self): from subprocess import PIPE, Popen if os.name != 'posix': #pragma: no cover raise unittest.SkipTest('This test works only on POSIX') workdir = tempfile.mkdtemp() try: s = "'\\\"; touch %s/foo #'" % workdir cmd = 'echo %s' % shell_quote(s) p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) p.communicate() self.assertEqual(p.returncode, 0) files = os.listdir(workdir) self.assertEqual(files, []) fn = "'ab?'" cmd = 'touch %s/%s' % (workdir, shell_quote(fn)) p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) p.communicate() self.assertEqual(p.returncode, 0) files = os.listdir(workdir) self.assertEqual(files, ["'ab?'"]) finally: shutil.rmtree(workdir)
def _create_ffmpeg_command_string(cls, ffmpeg, fps, bitrate, threads, input, output, hflip=False, vflip=False, rotate=False, watermark=None, pixfmt="yuv420p"): """ Create ffmpeg command string based on input parameters. Arguments: ffmpeg (str): Path to ffmpeg fps (int): Frames per second for output bitrate (str): Bitrate of output threads (int): Number of threads to use for rendering input (str): Absolute path to input files including file mask output (str): Absolute path to output file hflip (bool): Perform horizontal flip on input material. vflip (bool): Perform vertical flip on input material. rotate (bool): Perform 90° CCW rotation on input material. watermark (str): Path to watermark to apply to lower left corner. pixfmt (str): Pixel format to use for output. Default of yuv420p should usually fit the bill. Returns: (str): Prepared command string to render `input` to `output` using ffmpeg. """ ### See unit tests in test/timelapse/test_timelapse_renderjob.py logger = logging.getLogger(__name__) command = [ ffmpeg, '-framerate', str(fps), '-loglevel', 'error', '-i', '"{}"'.format(input), '-vcodec', 'mpeg2video', '-threads', str(threads), '-r', "25", '-y', '-b', str(bitrate), '-f', 'vob'] filter_string = cls._create_filter_string(hflip=hflip, vflip=vflip, rotate=rotate, watermark=watermark) if filter_string is not None: logger.debug("Applying videofilter chain: {}".format(filter_string)) command.extend(["-vf", sarge.shell_quote(filter_string)]) # finalize command with output file logger.debug("Rendering movie to {}".format(output)) command.append('"{}"'.format(output)) return " ".join(command)
def _create_ffmpeg_command_string(self, input_file_format, output_file, watermark=None, pix_fmt="yuv420p"): """ Create ffmpeg command string based on input parameters. Arguments: input_file_format (str): Absolute path to input files including file mask output_file (str): Absolute path to output file watermark (str): Path to watermark to apply to lower left corner. pix_fmt (str): Pixel format to use for output. Default of yuv420p should usually fit the bill. Returns: (str): Prepared command string to render `input` to `output` using ffmpeg. """ logger = logging.getLogger(__name__) v_codec = self._get_vcodec_from_output_format( self._rendering.output_format) command = [ self._ffmpeg, '-framerate', str(self._fps), '-loglevel', 'error', '-i', '"{}"'.format(input_file_format) ] command.extend([ '-threads', str(self._threads), '-r', "25", '-y', '-b', str(self._rendering.bitrate), '-vcodec', v_codec ]) filter_string = self._create_filter_string(watermark=watermark, pix_fmt=pix_fmt) if filter_string is not None: logger.debug( "Applying video filter chain: {}".format(filter_string)) command.extend(["-vf", sarge.shell_quote(filter_string)]) # finalize command with output file logger.debug("Rendering movie to {}".format(output_file)) command.append('"{}"'.format(output_file)) return " ".join(command)
def sfdx( command, username=None, log_note=None, access_token=None, args=None, env=None, capture_output=True, check_return=False, ): """Call an sfdx command and capture its output. Be sure to quote user input that is part of the command using `sarge.shell_format`. Returns a `sarge` Command instance with returncode, stdout, stderr """ command = "sfdx {}".format(command) if args is not None: for arg in args: command += " " + sarge.shell_quote(arg) if username: command += sarge.shell_format(" -u {0}", username) if log_note: logger.info("{} with command: {}".format(log_note, command)) # Avoid logging access token if access_token: command += sarge.shell_format(" -u {0}", access_token) p = sarge.Command( command, stdout=sarge.Capture(buffer_size=-1) if capture_output else None, stderr=sarge.Capture(buffer_size=-1) if capture_output else None, shell=True, env=env, ) p.run() if capture_output: p.stdout_text = io.TextIOWrapper(p.stdout, encoding=sys.stdout.encoding) p.stderr_text = io.TextIOWrapper(p.stderr, encoding=sys.stdout.encoding) if check_return and p.returncode: raise Exception(f"Command exited with return code {p.returncode}") return p
def _install_plugin(cls, plugin, force_user=False, pip_args=None, on_log=None): if pip_args is None: pip_args = [] if on_log is None: on_log = logging.getLogger(__name__).info # prepare pip caller def log(prefix, *lines): for line in lines: on_log(u"{} {}".format(prefix, line.rstrip())) def log_call(*lines): log(u">", *lines) def log_stdout(*lines): log(u"<", *lines) def log_stderr(*lines): log(u"!", *lines) if cls._pip_caller is None: cls._pip_caller = LocalPipCaller(force_user=force_user) cls._pip_caller.on_log_call = log_call cls._pip_caller.on_log_stdout = log_stdout cls._pip_caller.on_log_stderr = log_stderr # install plugin pip = ["install", sarge.shell_quote(plugin["archive"]), '--no-cache-dir'] if plugin.get("follow_dependency_links"): pip.append("--process-dependency-links") if force_user: pip.append("--user") if pip_args: pip += pip_args cls._pip_caller.execute(*pip)
def command_install(self, url, force=False): # TODO need to solve issue of users externally modifying plugin folder, which could lead to more than # one plugin being found after installation of a package all_plugins_before = self._plugin_manager.plugins pip_args = ["install", sarge.shell_quote(url)] try: self._call_pip(pip_args) except: self._logger.exception("Could not install plugin from %s" % url) return make_response("Could not install plugin from url, see the log for more details", 500) else: if force: pip_args += ["--ignore-installed", "--force-reinstall", "--no-deps"] try: self._call_pip(pip_args) except: self._logger.exception("Could not install plugin from %s" % url) return make_response("Could not install plugin from url, see the log for more details", 500) self._plugin_manager.reload_plugins() all_plugins_after = self._plugin_manager.plugins new_plugins = set(all_plugins_after.keys()) - set(all_plugins_before.keys()) if len(new_plugins) == 0 or len(new_plugins) > 1: # no new plugin or more than one new plugin? Something must have gone wrong... return make_response("Installed plugin, but could not find it afterwards", 500) new_plugin_key = new_plugins.pop() new_plugin = all_plugins_after[new_plugin_key] needs_restart = new_plugin.implementation and isinstance(new_plugin.implementation, octoprint.plugin.core.RestartNeedingPlugin) needs_refresh = new_plugin.implementation and isinstance(new_plugin.implementation, octoprint.plugin.ReloadNeedingPlugin) self._plugin_manager.log_all_plugins() result = dict(result=True, url=url, needs_restart=needs_restart, needs_refresh=needs_refresh, plugin=self._to_external_representation(new_plugin)) self._send_result_notification("install", result) return jsonify(result)
def command_install(self, url=None, path=None, force=False, reinstall=None, dependency_links=False): if url is not None: pip_args = ["install", sarge.shell_quote(url)] elif path is not None: pip_args = ["install", sarge.shell_quote(path)] else: raise ValueError("Either URL or path must be provided") if dependency_links or self._settings.get_boolean(["dependency_links"]): pip_args.append("--process-dependency-links") all_plugins_before = self._plugin_manager.find_plugins() success_string = "Successfully installed" failure_string = "Could not install" try: returncode, stdout, stderr = self._call_pip(pip_args) except: self._logger.exception("Could not install plugin from %s" % url) return make_response("Could not install plugin from URL, see the log for more details", 500) else: if force: pip_args += ["--ignore-installed", "--force-reinstall", "--no-deps"] try: returncode, stdout, stderr = self._call_pip(pip_args) except: self._logger.exception("Could not install plugin from %s" % url) return make_response("Could not install plugin from URL, see the log for more details", 500) try: result_line = filter(lambda x: x.startswith(success_string) or x.startswith(failure_string), stdout)[-1] except IndexError: result = dict(result=False, reason="Could not parse output from pip") self._send_result_notification("install", result) return jsonify(result) # The final output of a pip install command looks something like this: # # Successfully installed OctoPrint-Plugin-1.0 Dependency-One-0.1 Dependency-Two-9.3 # # or this: # # Successfully installed OctoPrint-Plugin Dependency-One Dependency-Two # Cleaning up... # # So we'll need to fetch the "Successfully installed" line, strip the "Successfully" part, then split by whitespace # and strip to get all installed packages. # # We then need to iterate over all known plugins and see if either the package name or the package name plus # version number matches one of our installed packages. If it does, that's our installed plugin. # # Known issue: This might return the wrong plugin if more than one plugin was installed through this # command (e.g. due to pulling in another plugin as dependency). It should be safe for now though to # consider this a rare corner case. Once it becomes a real problem we'll just extend the plugin manager # so that it can report on more than one installed plugin. result_line = result_line.strip() if not result_line.startswith(success_string): result = dict(result=False, reason="Pip did not report successful installation") self._send_result_notification("install", result) return jsonify(result) installed = [x.strip() for x in result_line[len(success_string):].split(" ")] all_plugins_after = self._plugin_manager.find_plugins(existing=dict(), ignore_uninstalled=False) for key, plugin in list(all_plugins_after.items()): if plugin.origin is None or plugin.origin.type != "entry_point": continue package_name = plugin.origin.package_name package_version = plugin.origin.package_version versioned_package = "{package_name}-{package_version}".format(**locals()) if package_name in installed or versioned_package in installed: # exact match, we are done here new_plugin_key = key new_plugin = plugin break else: # it might still be a version that got stripped by python's package resources, e.g. 1.4.5a0 => 1.4.5a found = False for inst in installed: if inst.startswith(versioned_package): found = True break if found: new_plugin_key = key new_plugin = plugin break else: self._logger.warn("The plugin was installed successfully, but couldn't be found afterwards to initialize properly during runtime. Please restart OctoPrint.") result = dict(result=True, url=url, needs_restart=True, needs_refresh=True, was_reinstalled=False, plugin="unknown") self._send_result_notification("install", result) return jsonify(result) self._plugin_manager.reload_plugins() needs_restart = self._plugin_manager.is_restart_needing_plugin(new_plugin) or new_plugin_key in all_plugins_before or reinstall is not None needs_refresh = new_plugin.implementation and isinstance(new_plugin.implementation, octoprint.plugin.ReloadNeedingPlugin) is_reinstall = self._plugin_manager.is_plugin_marked(new_plugin_key, "uninstalled") self._plugin_manager.mark_plugin(new_plugin_key, uninstalled=False, installed=not is_reinstall and needs_restart) self._plugin_manager.log_all_plugins() result = dict(result=True, url=url, needs_restart=needs_restart, needs_refresh=needs_refresh, was_reinstalled=new_plugin_key in all_plugins_before or reinstall is not None, plugin=self._to_external_representation(new_plugin)) self._send_result_notification("install", result) return jsonify(result)
def _create_ffmpeg_command_string(cls, ffmpeg, fps, bitrate, threads, input_file, output_file, output_format='vob', h_flip=False, v_flip=False, rotate=False, watermark=None, pix_fmt="yuv420p", v_codec="mpeg2video"): """ Create ffmpeg command string based on input parameters. Arguments: ffmpeg (str): Path to ffmpeg fps (int): Frames per second for output bitrate (str): Bitrate of output threads (int): Number of threads to use for rendering input_file (str): Absolute path to input files including file mask output_file (str): Absolute path to output file h_flip (bool): Perform horizontal flip on input material. v_flip (bool): Perform vertical flip on input material. rotate (bool): Perform 90° CCW rotation on input material. watermark (str): Path to watermark to apply to lower left corner. pix_fmt (str): Pixel format to use for output. Default of yuv420p should usually fit the bill. Returns: (str): Prepared command string to render `input` to `output` using ffmpeg. """ # See unit tests in test/timelapse/test_timelapse_renderjob.py logger = logging.getLogger(__name__) ffmpeg = ffmpeg.strip() if sys.platform == "win32" and not (ffmpeg.startswith('"') and ffmpeg.endswith('"')): ffmpeg = "\"{0}\"".format(ffmpeg) command = [ ffmpeg, '-framerate', str(fps), '-loglevel', 'error', '-i', '"{}"'.format(input_file) ] # Umm yea, something about codecs and GIFS. # See https://stackoverflow.com/a/47502141. if output_format != 'GIF': command.extend(['-vcodec', v_codec]) command.extend([ '-threads', str(threads), '-r', "25", '-y', '-b', str(bitrate), '-f', str(output_format) ]) filter_string = cls._create_filter_string(hflip=h_flip, vflip=v_flip, rotate=rotate, watermark=watermark, pix_fmt=pix_fmt) if filter_string is not None: logger.debug( "Applying video filter chain: {}".format(filter_string)) command.extend(["-vf", sarge.shell_quote(filter_string)]) # finalize command with output file logger.debug("Rendering movie to {}".format(output_file)) command.append('"{}"'.format(output_file)) return " ".join(command)
def command_install(self, url=None, path=None, force=False, reinstall=None, dependency_links=False): if url is not None: pip_args = ["install", sarge.shell_quote(url)] elif path is not None: pip_args = ["install", path] else: raise ValueError("Either url or path must be provided") if dependency_links or self._settings.get_boolean(["dependency_links"]): pip_args.append("--process-dependency-links") all_plugins_before = self._plugin_manager.find_plugins() success_string = "Successfully installed" failure_string = "Could not install" try: returncode, stdout, stderr = self._call_pip(pip_args) except: self._logger.exception("Could not install plugin from %s" % url) return make_response("Could not install plugin from url, see the log for more details", 500) else: if force: pip_args += ["--ignore-installed", "--force-reinstall", "--no-deps"] try: returncode, stdout, stderr = self._call_pip(pip_args) except: self._logger.exception("Could not install plugin from %s" % url) return make_response("Could not install plugin from url, see the log for more details", 500) try: result_line = filter(lambda x: x.startswith(success_string) or x.startswith(failure_string), stdout)[-1] except IndexError: result = dict(result=False, reason="Could not parse output from pip") self._send_result_notification("install", result) return jsonify(result) # The final output of a pip install command looks something like this: # # Successfully installed OctoPrint-Plugin-1.0 Dependency-One-0.1 Dependency-Two-9.3 # # or this: # # Successfully installed OctoPrint-Plugin Dependency-One Dependency-Two # Cleaning up... # # So we'll need to fetch the "Successfully installed" line, strip the "Successfully" part, then split by whitespace # and strip to get all installed packages. # # We then need to iterate over all known plugins and see if either the package name or the package name plus # version number matches one of our installed packages. If it does, that's our installed plugin. # # Known issue: This might return the wrong plugin if more than one plugin was installed through this # command (e.g. due to pulling in another plugin as dependency). It should be safe for now though to # consider this a rare corner case. Once it becomes a real problem we'll just extend the plugin manager # so that it can report on more than one installed plugin. result_line = result_line.strip() if not result_line.startswith(success_string): result = dict(result=False, reason="Pip did not report successful installation") self._send_result_notification("install", result) return jsonify(result) installed = map(lambda x: x.strip(), result_line[len(success_string):].split(" ")) all_plugins_after = self._plugin_manager.find_plugins(existing=dict(), ignore_uninstalled=False) for key, plugin in all_plugins_after.items(): if plugin.origin is None or plugin.origin.type != "entry_point": continue package_name = plugin.origin.package_name package_version = plugin.origin.package_version versioned_package = "{package_name}-{package_version}".format(**locals()) if package_name in installed or versioned_package in installed: # exact match, we are done here new_plugin_key = key new_plugin = plugin break else: # it might still be a version that got stripped by python's package resources, e.g. 1.4.5a0 => 1.4.5a found = False for inst in installed: if inst.startswith(versioned_package): found = True break if found: new_plugin_key = key new_plugin = plugin break else: self._logger.warn("The plugin was installed successfully, but couldn't be found afterwards to initialize properly during runtime. Please restart OctoPrint.") result = dict(result=True, url=url, needs_restart=True, needs_refresh=True, was_reinstalled=False, plugin="unknown") self._send_result_notification("install", result) return jsonify(result) self._plugin_manager.mark_plugin(new_plugin_key, uninstalled=False) self._plugin_manager.reload_plugins() needs_restart = self._plugin_manager.is_restart_needing_plugin(new_plugin) or new_plugin_key in all_plugins_before or reinstall is not None needs_refresh = new_plugin.implementation and isinstance(new_plugin.implementation, octoprint.plugin.ReloadNeedingPlugin) self._plugin_manager.log_all_plugins() result = dict(result=True, url=url, needs_restart=needs_restart, needs_refresh=needs_refresh, was_reinstalled=new_plugin_key in all_plugins_before or reinstall is not None, plugin=self._to_external_representation(new_plugin)) self._send_result_notification("install", result) return jsonify(result)
def _create_movie(self, success=True): ffmpeg = settings().get(["webcam", "ffmpeg"]) bitrate = settings().get(["webcam", "bitrate"]) if ffmpeg is None or bitrate is None: self._logger.warn("Cannot create movie, path to ffmpeg or desired bitrate is unset") return input = os.path.join(self._capture_dir, "tmp_%05d.jpg") if success: output = os.path.join(self._movie_dir, "%s_%s.mpg" % (os.path.splitext(self._gcode_file)[0], time.strftime("%Y%m%d%H%M%S"))) else: output = os.path.join(self._movie_dir, "%s_%s-failed.mpg" % (os.path.splitext(self._gcode_file)[0], time.strftime("%Y%m%d%H%M%S"))) # prepare ffmpeg command command = [ ffmpeg, '-framerate', str(self._fps), '-loglevel', 'error', '-i', input, '-vcodec', 'mpeg2video', '-threads', str(self._ffmpeg_threads), '-pix_fmt', 'yuv420p', '-r', str(self._fps), '-y', '-b', bitrate, '-f', 'vob'] filters = [] # flip video if configured if settings().getBoolean(["webcam", "flipH"]): filters.append('hflip') if settings().getBoolean(["webcam", "flipV"]): filters.append('vflip') if settings().getBoolean(["webcam", "rotate90"]): filters.append('transpose=2') # 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(":", "\\\\:") 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", sarge.shell_quote(filterstring)]) # finalize command with output file self._logger.debug("Rendering movie to %s" % output) command.append("\"" + output + "\"") eventManager().fire(Events.MOVIE_RENDERING, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output)}) command_str = " ".join(command) self._logger.debug("Executing command: %s" % command_str) try: p = sarge.run(command_str, stderr=sarge.Capture()) if p.returncode == 0: eventManager().fire(Events.MOVIE_DONE, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output)}) else: returncode = p.returncode stderr_text = p.stderr.text self._logger.warn("Could not render movie, got return code %r: %s" % (returncode, stderr_text)) eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output), "returncode": returncode, "error": stderr_text}) except: self._logger.exception("Could not render movie due to unknown error") eventManager().fire(Events.MOVIE_FAILED, {"gcode": self._gcode_file, "movie": output, "movie_basename": os.path.basename(output), "returncode": 255, "error": "Unknown error"})
def _create_ffmpeg_command_string( cls, commandline, ffmpeg, fps, bitrate, threads, input, output, videocodec, hflip=False, vflip=False, rotate=False, watermark=None, pixfmt="yuv420p", ): """ Create ffmpeg command string based on input parameters. Arguments: commandline (str): Command line template to use ffmpeg (str): Path to ffmpeg fps (int): Frames per second for output bitrate (str): Bitrate of output threads (int): Number of threads to use for rendering videocodec (str): Videocodec to be used for encoding input (str): Absolute path to input files including file mask output (str): Absolute path to output file hflip (bool): Perform horizontal flip on input material. vflip (bool): Perform vertical flip on input material. rotate (bool): Perform 90° CCW rotation on input material. watermark (str): Path to watermark to apply to lower left corner. pixfmt (str): Pixel format to use for output. Default of yuv420p should usually fit the bill. Returns: (str): Prepared command string to render `input` to `output` using ffmpeg. """ ### See unit tests in test/timelapse/test_timelapse_renderjob.py logger = logging.getLogger(__name__) ### Not all players can handle non-mpeg2 in VOB format if not videocodec: videocodec = "libx264" if videocodec == "mpeg2video": containerformat = "vob" else: containerformat = "mp4" filter_string = cls._create_filter_string( hflip=hflip, vflip=vflip, rotate=rotate, watermark=watermark ) placeholders = { "ffmpeg": ffmpeg, "fps": str(fps if fps else "25"), "input": input, "output": output, "videocodec": videocodec, "threads": str(threads if threads else "1"), "bitrate": str(bitrate if bitrate else "10000k"), "containerformat": containerformat, "filters": ("-vf " + sarge.shell_quote(filter_string)) if filter_string else "", } logger.debug("Rendering movie to {}".format(output)) return commandline.format(**placeholders)
def command_install(self, url=None, path=None, force=False, reinstall=None, dependency_links=False): if url is not None: source = url source_type = "url" already_installed_check = lambda line: url in line elif path is not None: path = os.path.abspath(path) path_url = "file://" + path if os.sep != "/": # windows gets special handling path = path.replace(os.sep, "/").lower() path_url = "file:///" + path source = path source_type = "path" already_installed_check = lambda line: path_url in line.lower() # lower case in case of windows else: raise ValueError("Either URL or path must be provided") self._logger.info("Installing plugin from {}".format(source)) pip_args = ["install", sarge.shell_quote(source)] if dependency_links or self._settings.get_boolean(["dependency_links"]): pip_args.append("--process-dependency-links") all_plugins_before = self._plugin_manager.find_plugins(existing=dict()) already_installed_string = "Requirement already satisfied (use --upgrade to upgrade)" success_string = "Successfully installed" failure_string = "Could not install" try: returncode, stdout, stderr = self._call_pip(pip_args) # pip's output for a package that is already installed looks something like any of these: # # Requirement already satisfied (use --upgrade to upgrade): OctoPrint-Plugin==1.0 from \ # https://example.com/foobar.zip in <lib> # Requirement already satisfied (use --upgrade to upgrade): OctoPrint-Plugin in <lib> # Requirement already satisfied (use --upgrade to upgrade): OctoPrint-Plugin==1.0 from \ # file:///tmp/foobar.zip in <lib> # Requirement already satisfied (use --upgrade to upgrade): OctoPrint-Plugin==1.0 from \ # file:///C:/Temp/foobar.zip in <lib> # # If we detect any of these matching what we just tried to install, we'll need to trigger a second # install with reinstall flags. if not force and any(map(lambda x: x.strip().startswith(already_installed_string) and already_installed_check(x), stdout)): self._logger.info("Plugin to be installed from {} was already installed, forcing a reinstall".format(source)) self._log_message("Looks like the plugin was already installed. Forcing a reinstall.") force = True except: self._logger.exception("Could not install plugin from %s" % url) return make_response("Could not install plugin from URL, see the log for more details", 500) else: if force: # We don't use --upgrade here because that will also happily update all our dependencies - we'd rather # do that in a controlled manner pip_args += ["--ignore-installed", "--force-reinstall", "--no-deps"] try: returncode, stdout, stderr = self._call_pip(pip_args) except: self._logger.exception("Could not install plugin from {}".format(source)) return make_response("Could not install plugin from source {}, see the log for more details" .format(source), 500) try: result_line = filter(lambda x: x.startswith(success_string) or x.startswith(failure_string), stdout)[-1] except IndexError: self._logger.error("Installing the plugin from {} failed, could not parse output from pip. " "See plugin_pluginmanager_console.log for generated output".format(source)) result = dict(result=False, source=source, source_type=source_type, reason="Could not parse output from pip, see plugin_pluginmanager_console.log " "for generated output") self._send_result_notification("install", result) return jsonify(result) # The final output of a pip install command looks something like this: # # Successfully installed OctoPrint-Plugin-1.0 Dependency-One-0.1 Dependency-Two-9.3 # # or this: # # Successfully installed OctoPrint-Plugin Dependency-One Dependency-Two # Cleaning up... # # So we'll need to fetch the "Successfully installed" line, strip the "Successfully" part, then split # by whitespace and strip to get all installed packages. # # We then need to iterate over all known plugins and see if either the package name or the package name plus # version number matches one of our installed packages. If it does, that's our installed plugin. # # Known issue: This might return the wrong plugin if more than one plugin was installed through this # command (e.g. due to pulling in another plugin as dependency). It should be safe for now though to # consider this a rare corner case. Once it becomes a real problem we'll just extend the plugin manager # so that it can report on more than one installed plugin. result_line = result_line.strip() if not result_line.startswith(success_string): self._logger.error("Installing the plugin from {} failed, pip did not report successful installation" .format(source)) result = dict(result=False, source=source, source_type=source_type, reason="Pip did not report successful installation") self._send_result_notification("install", result) return jsonify(result) installed = map(lambda x: x.strip(), result_line[len(success_string):].split(" ")) all_plugins_after = self._plugin_manager.find_plugins(existing=dict(), ignore_uninstalled=False) new_plugin = self._find_installed_plugin(installed, plugins=all_plugins_after) if new_plugin is None: self._logger.warn("The plugin was installed successfully, but couldn't be found afterwards to " "initialize properly during runtime. Please restart OctoPrint.") result = dict(result=True, source=source, source_type=source_type, needs_restart=True, needs_refresh=True, was_reinstalled=False, plugin="unknown") self._send_result_notification("install", result) return jsonify(result) self._plugin_manager.reload_plugins() needs_restart = self._plugin_manager.is_restart_needing_plugin(new_plugin) \ or new_plugin.key in all_plugins_before \ or reinstall is not None needs_refresh = new_plugin.implementation \ and isinstance(new_plugin.implementation, octoprint.plugin.ReloadNeedingPlugin) is_reinstall = self._plugin_manager.is_plugin_marked(new_plugin.key, "uninstalled") self._plugin_manager.mark_plugin(new_plugin.key, uninstalled=False, installed=not is_reinstall and needs_restart) self._plugin_manager.log_all_plugins() self._logger.info("The plugin was installed successfully: {}, version {}".format(new_plugin.name, new_plugin.version)) result = dict(result=True, source=source, source_type=source_type, needs_restart=needs_restart, needs_refresh=needs_refresh, was_reinstalled=new_plugin.key in all_plugins_before or reinstall is not None, plugin=self._to_external_plugin(new_plugin)) self._send_result_notification("install", result) return jsonify(result)
def command_install(self, url=None, path=None, force=False, reinstall=None, dependency_links=False): if url is not None: if not any(map(lambda scheme: url.startswith(scheme + "://"), self.URL_SCHEMES)): raise ValueError("Invalid URL to pip install from") source = url source_type = "url" already_installed_check = lambda line: url in line elif path is not None: path = os.path.abspath(path) path_url = "file://" + path if os.sep != "/": # windows gets special handling path = path.replace(os.sep, "/").lower() path_url = "file:///" + path source = path source_type = "path" already_installed_check = lambda line: path_url in line.lower() # lower case in case of windows else: raise ValueError("Either URL or path must be provided") self._logger.info("Installing plugin from {}".format(source)) pip_args = ["install", sarge.shell_quote(source)] if dependency_links or self._settings.get_boolean(["dependency_links"]): pip_args.append("--process-dependency-links") all_plugins_before = self._plugin_manager.find_plugins(existing=dict()) already_installed_string = "Requirement already satisfied (use --upgrade to upgrade)" success_string = "Successfully installed" failure_string = "Could not install" try: returncode, stdout, stderr = self._call_pip(pip_args) # pip's output for a package that is already installed looks something like any of these: # # Requirement already satisfied (use --upgrade to upgrade): OctoPrint-Plugin==1.0 from \ # https://example.com/foobar.zip in <lib> # Requirement already satisfied (use --upgrade to upgrade): OctoPrint-Plugin in <lib> # Requirement already satisfied (use --upgrade to upgrade): OctoPrint-Plugin==1.0 from \ # file:///tmp/foobar.zip in <lib> # Requirement already satisfied (use --upgrade to upgrade): OctoPrint-Plugin==1.0 from \ # file:///C:/Temp/foobar.zip in <lib> # # If we detect any of these matching what we just tried to install, we'll need to trigger a second # install with reinstall flags. if not force and any(map(lambda x: x.strip().startswith(already_installed_string) and already_installed_check(x), stdout)): self._logger.info("Plugin to be installed from {} was already installed, forcing a reinstall".format(source)) self._log_message("Looks like the plugin was already installed. Forcing a reinstall.") force = True except: self._logger.exception("Could not install plugin from %s" % url) return make_response("Could not install plugin from URL, see the log for more details", 500) else: if force: # We don't use --upgrade here because that will also happily update all our dependencies - we'd rather # do that in a controlled manner pip_args += ["--ignore-installed", "--force-reinstall", "--no-deps"] try: returncode, stdout, stderr = self._call_pip(pip_args) except: self._logger.exception("Could not install plugin from {}".format(source)) return make_response("Could not install plugin from source {}, see the log for more details" .format(source), 500) try: result_line = filter(lambda x: x.startswith(success_string) or x.startswith(failure_string), stdout)[-1] except IndexError: self._logger.error("Installing the plugin from {} failed, could not parse output from pip. " "See plugin_pluginmanager_console.log for generated output".format(source)) result = dict(result=False, source=source, source_type=source_type, reason="Could not parse output from pip, see plugin_pluginmanager_console.log " "for generated output") self._send_result_notification("install", result) return jsonify(result) # The final output of a pip install command looks something like this: # # Successfully installed OctoPrint-Plugin-1.0 Dependency-One-0.1 Dependency-Two-9.3 # # or this: # # Successfully installed OctoPrint-Plugin Dependency-One Dependency-Two # Cleaning up... # # So we'll need to fetch the "Successfully installed" line, strip the "Successfully" part, then split # by whitespace and strip to get all installed packages. # # We then need to iterate over all known plugins and see if either the package name or the package name plus # version number matches one of our installed packages. If it does, that's our installed plugin. # # Known issue: This might return the wrong plugin if more than one plugin was installed through this # command (e.g. due to pulling in another plugin as dependency). It should be safe for now though to # consider this a rare corner case. Once it becomes a real problem we'll just extend the plugin manager # so that it can report on more than one installed plugin. result_line = result_line.strip() if not result_line.startswith(success_string): self._logger.error("Installing the plugin from {} failed, pip did not report successful installation" .format(source)) result = dict(result=False, source=source, source_type=source_type, reason="Pip did not report successful installation") self._send_result_notification("install", result) return jsonify(result) installed = map(lambda x: x.strip(), result_line[len(success_string):].split(" ")) all_plugins_after = self._plugin_manager.find_plugins(existing=dict(), ignore_uninstalled=False) new_plugin = self._find_installed_plugin(installed, plugins=all_plugins_after) if new_plugin is None: self._logger.warn("The plugin was installed successfully, but couldn't be found afterwards to " "initialize properly during runtime. Please restart OctoPrint.") result = dict(result=True, source=source, source_type=source_type, needs_restart=True, needs_refresh=True, needs_reconnect=True, was_reinstalled=False, plugin="unknown") self._send_result_notification("install", result) return jsonify(result) self._plugin_manager.reload_plugins() needs_restart = self._plugin_manager.is_restart_needing_plugin(new_plugin) \ or new_plugin.key in all_plugins_before \ or reinstall is not None needs_refresh = new_plugin.implementation \ and isinstance(new_plugin.implementation, octoprint.plugin.ReloadNeedingPlugin) needs_reconnect = self._plugin_manager.has_any_of_hooks(new_plugin, self._reconnect_hooks) and self._printer.is_operational() is_reinstall = self._plugin_manager.is_plugin_marked(new_plugin.key, "uninstalled") self._plugin_manager.mark_plugin(new_plugin.key, uninstalled=False, installed=not is_reinstall and needs_restart) self._plugin_manager.log_all_plugins() self._logger.info("The plugin was installed successfully: {}, version {}".format(new_plugin.name, new_plugin.version)) result = dict(result=True, source=source, source_type=source_type, needs_restart=needs_restart, needs_refresh=needs_refresh, needs_reconnect=needs_reconnect, was_reinstalled=new_plugin.key in all_plugins_before or reinstall is not None, plugin=self._to_external_plugin(new_plugin)) self._send_result_notification("install", result) return jsonify(result)
def _createMovie(self, success=True): ffmpeg = settings().get(["webcam", "ffmpeg"]) bitrate = settings().get(["webcam", "bitrate"]) if ffmpeg is None or bitrate is None: self._logger.warn( "Cannot create movie, path to ffmpeg or desired bitrate is unset" ) return input = os.path.join(self._captureDir, "tmp_%05d.jpg") if success: output = os.path.join( self._movieDir, "%s_%s.mpg" % (os.path.splitext( self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S"))) else: output = os.path.join( self._movieDir, "%s_%s-failed.mpg" % (os.path.splitext( self._gcodeFile)[0], time.strftime("%Y%m%d%H%M%S"))) # prepare ffmpeg command command = [ ffmpeg, '-loglevel', 'error', '-i', input, '-vcodec', 'mpeg2video', '-pix_fmt', 'yuv420p', '-r', str(self._fps), '-y', '-b:v', bitrate, '-f', 'vob' ] filters = [] # flip video if configured if settings().getBoolean(["webcam", "flipH"]): filters.append('hflip') if settings().getBoolean(["webcam", "flipV"]): 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(":", "\\\\:") 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", sarge.shell_quote(filterstring)]) # finalize command with output file self._logger.debug("Rendering movie to %s" % output) command.append(output) eventManager().fire( Events.MOVIE_RENDERING, { "gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output) }) command_str = " ".join(command) self._logger.debug("Executing command: %s" % command_str) p = sarge.run(command_str, stderr=sarge.Capture()) if p.returncode == 0: eventManager().fire( Events.MOVIE_DONE, { "gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output) }) else: returncode = p.returncode stderr_text = p.stderr.text self._logger.warn( "Could not render movie, got return code %r: %s" % (returncode, stderr_text)) eventManager().fire( Events.MOVIE_FAILED, { "gcode": self._gcodeFile, "movie": output, "movie_basename": os.path.basename(output), "returncode": returncode, "error": stderr_text })