Patch Detail
Show a patch.
GET /api/1.1/patches/15992/?format=api
{ "id": 15992, "url": "https://patchwork.libcamera.org/api/1.1/patches/15992/?format=api", "web_url": "https://patchwork.libcamera.org/patch/15992/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20220520165234.7642-2-laurent.pinchart@ideasonboard.com>", "date": "2022-05-20T16:52:32", "name": "[libcamera-devel,v2,1/3] cam: Add a parser for capture scripts", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "cdece44248ded51d4d5784b4cfb3b6f08e98edc3", "submitter": { "id": 2, "url": "https://patchwork.libcamera.org/api/1.1/people/2/?format=api", "name": "Laurent Pinchart", "email": "laurent.pinchart@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/15992/mbox/", "series": [ { "id": 3131, "url": "https://patchwork.libcamera.org/api/1.1/series/3131/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3131", "date": "2022-05-20T16:52:31", "name": "cam: Add support for capture scripts", "version": 2, "mbox": "https://patchwork.libcamera.org/series/3131/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/15992/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/15992/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id BE620C3256\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 20 May 2022 16:52:48 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8C07C65663;\n\tFri, 20 May 2022 18:52:47 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 62FD761FB9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 20 May 2022 18:52:45 +0200 (CEST)", "from localhost.localdomain (unknown [45.131.31.124])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id F0A826D1;\n\tFri, 20 May 2022 18:52:44 +0200 (CEST)" ], "DKIM-Signature": [ "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1653065567;\n\tbh=hEJJe5w8QJJooa3hPsDePFHdlUX+koyav8Jth3XZw0Q=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=afsyK0abCyeGLFNTSa+utl8S+/ZDH7NEBi34ok2uL4klL+D6KAPLdxYgxU/2XcD13\n\t0LKKB7u7hvSQEXx3iW8N2Zd8Y5LocIv8Emt4FMJLshNpbsXbypsTLvMb5ClnZ91UHb\n\tf1KHXIRzto3fNDrkpNwr2O6cyDW2Ocr6fihKT6s+ax1G4oiXgqfqgCRGG4GGIIT6cP\n\tbJB8O+mvvdYpt/jsyZDrNv3SXBd8h892ZdDZnAf2r/IIhIjpUAc9tvJbuQz7osMfPD\n\tenh8jemMIRmTD0Nr0hRr1AW8XZJYoRJdIACUZahyqJ7jBBIXdoUw3dgS4ORhDhh6xd\n\tvzgDeNKH9WQiQ==", "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1653065565;\n\tbh=hEJJe5w8QJJooa3hPsDePFHdlUX+koyav8Jth3XZw0Q=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=RibV+j8cat45zTyGzvOzMesNDCuXYh21TMaZw/jHW6nIRZLZJsw5N7AxOId0uDBwk\n\tSXGg87Rm/GZfVNmM9GZJAndHMczc/DsBHmeN0JRTlf6PYm3BYSj7n1L+cmZct8MTLo\n\tG5gpKmbmLVkOgZ4BLhGhDTu70qYDxgatb69A6Lvc=" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"RibV+j8c\"; dkim-atps=neutral", "To": "libcamera-devel@lists.libcamera.org", "Date": "Fri, 20 May 2022 19:52:32 +0300", "Message-Id": "<20220520165234.7642-2-laurent.pinchart@ideasonboard.com>", "X-Mailer": "git-send-email 2.35.1", "In-Reply-To": "<20220520165234.7642-1-laurent.pinchart@ideasonboard.com>", "References": "<20220520165234.7642-1-laurent.pinchart@ideasonboard.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "Subject": "[libcamera-devel] [PATCH v2 1/3] cam: Add a parser for capture\n\tscripts", "X-BeenThere": "libcamera-devel@lists.libcamera.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<libcamera-devel.lists.libcamera.org>", "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>", "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>", "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>", "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>", "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>", "From": "Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>", "Reply-To": "Laurent Pinchart <laurent.pinchart@ideasonboard.com>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "From: Jacopo Mondi <jacopo@jmondi.org>\n\nAdd a parser class to the cam test application to control the capture\noperations through a yaml script.\n\nThe capture script currently allow to specify a list of controls and\ntheir associated values to be applied per-frame.\n\nAlso add a trivial capture script example to showcase the intended\nscript structure.\n\nSigned-off-by: Jacopo Mondi <jacopo@jmondi.org>\n---\nChanges since v1:\n\n- Add SPDX license identifier to capture-script.yaml\n- Drop gcc 7 support as libcamera requires gcc 8 or newer\n- Make scriptPath_ a local variable in CaptureScript constructor\n- Drop file check as we want to accept symlinks too\n- Pass const reference to fileName to CaptureScript constructor\n- Avoid manual deletes of tokens\n- Simplify and improve error message handling\n- Print line and column number in error message\n- Simplify handling of key/value parsing\n- Simplify parser deletion\n- Don't consume last tokens as that's not needed.\n- Simplify CaptureScript::unpackFailure()\n- Switch to the simpler event API\n- Typo fixes\n---\n src/cam/capture-script.yaml | 46 +++++\n src/cam/capture_script.cpp | 334 ++++++++++++++++++++++++++++++++++++\n src/cam/capture_script.h | 60 +++++++\n src/cam/meson.build | 2 +\n 4 files changed, 442 insertions(+)\n create mode 100644 src/cam/capture-script.yaml\n create mode 100644 src/cam/capture_script.cpp\n create mode 100644 src/cam/capture_script.h", "diff": "diff --git a/src/cam/capture-script.yaml b/src/cam/capture-script.yaml\nnew file mode 100644\nindex 000000000000..6a749bc60cf7\n--- /dev/null\n+++ b/src/cam/capture-script.yaml\n@@ -0,0 +1,46 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+# Capture script example\n+#\n+# A capture script allows to associate a list of controls and their values\n+# to frame numbers.\n+\n+# \\todo Formally define the capture script structure with a schema\n+\n+# Notes:\n+# - Controls have to be specified by name, as defined in the\n+# libcamera::controls:: enumeration\n+# - Controls not supported by the camera currently operated are ignored\n+# - Frame numbers shall be monotonically incrementing, gaps are allowed\n+\n+# Example:\n+frames:\n+ - 1:\n+ Brightness: 0.0\n+\n+ - 40:\n+ Brightness: 0.2\n+\n+ - 80:\n+ Brightness: 0.4\n+\n+ - 120:\n+ Brightness: 0.8\n+\n+ - 160:\n+ Brightness: 0.4\n+\n+ - 200:\n+ Brightness: 0.2\n+\n+ - 240:\n+ Brightness: 0.0\n+\n+ - 280:\n+ Brightness: -0.2\n+\n+ - 300:\n+ Brightness: -0.4\n+\n+ - 340:\n+ Brightness: -0.8\ndiff --git a/src/cam/capture_script.cpp b/src/cam/capture_script.cpp\nnew file mode 100644\nindex 000000000000..8e9120321a25\n--- /dev/null\n+++ b/src/cam/capture_script.cpp\n@@ -0,0 +1,334 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * capture_script.cpp - Capture session configuration script\n+ */\n+\n+#include \"capture_script.h\"\n+\n+#include <iostream>\n+#include <stdio.h>\n+#include <stdlib.h>\n+\n+using namespace libcamera;\n+\n+CaptureScript::CaptureScript(std::shared_ptr<Camera> camera,\n+\t\t\t const std::string &fileName)\n+\t: camera_(camera), valid_(false)\n+{\n+\tFILE *fh = fopen(fileName.c_str(), \"r\");\n+\tif (!fh) {\n+\t\tint ret = -errno;\n+\t\tstd::cerr << \"Failed to open capture script \" << fileName\n+\t\t\t << \": \" << strerror(-ret) << std::endl;\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * Map the camera's controls to their name so that they can be\n+\t * easily identified when parsing the script file.\n+\t */\n+\tfor (const auto &[control, info] : camera_->controls())\n+\t\tcontrols_[control->name()] = control;\n+\n+\tint ret = parseScript(fh);\n+\tfclose(fh);\n+\tif (ret)\n+\t\treturn;\n+\n+\tvalid_ = true;\n+}\n+\n+/* Retrieve the control list associated with a frame number. */\n+const ControlList &CaptureScript::frameControls(unsigned int frame)\n+{\n+\tstatic ControlList controls{};\n+\n+\tauto it = frameControls_.find(frame);\n+\tif (it == frameControls_.end())\n+\t\treturn controls;\n+\n+\treturn it->second;\n+}\n+\n+CaptureScript::EventPtr CaptureScript::nextEvent(yaml_event_type_t expectedType)\n+{\n+\tEventPtr event(new yaml_event_t);\n+\n+\tif (!yaml_parser_parse(&parser_, event.get()))\n+\t\treturn nullptr;\n+\n+\tif (expectedType != YAML_NO_EVENT && !checkEvent(event, expectedType))\n+\t\treturn nullptr;\n+\n+\treturn event;\n+}\n+\n+bool CaptureScript::checkEvent(const EventPtr &event, yaml_event_type_t expectedType) const\n+{\n+\tif (event->type != expectedType) {\n+\t\tstd::cerr << \"Capture script error on line \" << event->start_mark.line\n+\t\t\t << \" column \" << event->start_mark.column << \": \"\n+\t\t\t << \"Expected \" << eventTypeName(expectedType)\n+\t\t\t << \" event, got \" << eventTypeName(event->type)\n+\t\t\t << std::endl;\n+\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+std::string CaptureScript::eventScalarValue(const EventPtr &event)\n+{\n+\treturn std::string(reinterpret_cast<char *>(event->data.scalar.value),\n+\t\t\t event->data.scalar.length);\n+}\n+\n+std::string CaptureScript::eventTypeName(yaml_event_type_t type)\n+{\n+\tstatic const std::map<yaml_event_type_t, std::string> typeNames = {\n+\t\t{ YAML_STREAM_START_EVENT, \"stream-start\" },\n+\t\t{ YAML_STREAM_END_EVENT, \"stream-end\" },\n+\t\t{ YAML_DOCUMENT_START_EVENT, \"document-start\" },\n+\t\t{ YAML_DOCUMENT_END_EVENT, \"document-end\" },\n+\t\t{ YAML_ALIAS_EVENT, \"alias\" },\n+\t\t{ YAML_SCALAR_EVENT, \"scalar\" },\n+\t\t{ YAML_SEQUENCE_START_EVENT, \"sequence-start\" },\n+\t\t{ YAML_SEQUENCE_END_EVENT, \"sequence-end\" },\n+\t\t{ YAML_MAPPING_START_EVENT, \"mapping-start\" },\n+\t\t{ YAML_MAPPING_END_EVENT, \"mapping-end\" },\n+\t};\n+\n+\tauto it = typeNames.find(type);\n+\tif (it == typeNames.end())\n+\t\treturn \"[type \" + std::to_string(type) + \"]\";\n+\n+\treturn it->second;\n+}\n+\n+int CaptureScript::parseScript(FILE *script)\n+{\n+\tint ret = yaml_parser_initialize(&parser_);\n+\tif (!ret) {\n+\t\tstd::cerr << \"Failed to initialize yaml parser\" << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\t/* Delete the parser upon function exit. */\n+\tstruct ParserDeleter {\n+\t\tParserDeleter(yaml_parser_t *parser) : parser_(parser) { }\n+\t\t~ParserDeleter() { yaml_parser_delete(parser_); }\n+\t\tyaml_parser_t *parser_;\n+\t} deleter(&parser_);\n+\n+\tyaml_parser_set_input_file(&parser_, script);\n+\n+\tEventPtr event = nextEvent(YAML_STREAM_START_EVENT);\n+\tif (!event)\n+\t\treturn -EINVAL;\n+\n+\tevent = nextEvent(YAML_DOCUMENT_START_EVENT);\n+\tif (!event)\n+\t\treturn -EINVAL;\n+\n+\tevent = nextEvent(YAML_MAPPING_START_EVENT);\n+\tif (!event)\n+\t\treturn -EINVAL;\n+\n+\twhile (1) {\n+\t\tevent = nextEvent();\n+\t\tif (!event)\n+\t\t\treturn -EINVAL;\n+\n+\t\tif (event->type == YAML_MAPPING_END_EVENT)\n+\t\t\treturn 0;\n+\n+\t\tif (!checkEvent(event, YAML_SCALAR_EVENT))\n+\t\t\treturn -EINVAL;\n+\n+\t\tstd::string section = eventScalarValue(event);\n+\n+\t\tif (section == \"frames\") {\n+\t\t\tparseFrames();\n+\t\t} else {\n+\t\t\tstd::cerr << \"Unsupported section '\" << section << \"'\"\n+\t\t\t\t << std::endl;\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+}\n+\n+int CaptureScript::parseFrames()\n+{\n+\tEventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);\n+\tif (!event)\n+\t\treturn -EINVAL;\n+\n+\twhile (1) {\n+\t\tevent = nextEvent();\n+\t\tif (!event)\n+\t\t\treturn -EINVAL;\n+\n+\t\tif (event->type == YAML_SEQUENCE_END_EVENT)\n+\t\t\treturn 0;\n+\n+\t\tint ret = parseFrame(std::move(event));\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+}\n+\n+int CaptureScript::parseFrame(EventPtr event)\n+{\n+\tif (!checkEvent(event, YAML_MAPPING_START_EVENT))\n+\t\treturn -EINVAL;\n+\n+\tstd::string key = parseScalar();\n+\tif (key.empty())\n+\t\treturn -EINVAL;\n+\n+\tunsigned int frameId = atoi(key.c_str());\n+\n+\tevent = nextEvent(YAML_MAPPING_START_EVENT);\n+\tif (!event)\n+\t\treturn -EINVAL;\n+\n+\tControlList controls{};\n+\n+\twhile (1) {\n+\t\tevent = nextEvent();\n+\t\tif (!event)\n+\t\t\treturn -EINVAL;\n+\n+\t\tif (event->type == YAML_MAPPING_END_EVENT)\n+\t\t\tbreak;\n+\n+\t\tint ret = parseControl(std::move(event), controls);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\tframeControls_[frameId] = std::move(controls);\n+\n+\tevent = nextEvent(YAML_MAPPING_END_EVENT);\n+\tif (!event)\n+\t\treturn -EINVAL;\n+\n+\treturn 0;\n+}\n+\n+int CaptureScript::parseControl(EventPtr event, ControlList &controls)\n+{\n+\t/* We expect a value after a key. */\n+\tstd::string name = eventScalarValue(event);\n+\tif (name.empty())\n+\t\treturn -EINVAL;\n+\n+\t/* If the camera does not support the control just ignore it. */\n+\tauto it = controls_.find(name);\n+\tif (it == controls_.end()) {\n+\t\tstd::cerr << \"Unsupported control '\" << name << \"'\" << std::endl;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tstd::string value = parseScalar();\n+\tif (value.empty())\n+\t\treturn -EINVAL;\n+\n+\tconst ControlId *controlId = it->second;\n+\tControlValue val = unpackControl(controlId, value);\n+\tcontrols.set(controlId->id(), val);\n+\n+\treturn 0;\n+}\n+\n+std::string CaptureScript::parseScalar()\n+{\n+\tEventPtr event = nextEvent(YAML_SCALAR_EVENT);\n+\tif (!event)\n+\t\treturn \"\";\n+\n+\treturn eventScalarValue(event);\n+}\n+\n+void CaptureScript::unpackFailure(const ControlId *id, const std::string &repr)\n+{\n+\tstatic const std::map<unsigned int, const char *> typeNames = {\n+\t\t{ ControlTypeNone, \"none\" },\n+\t\t{ ControlTypeBool, \"bool\" },\n+\t\t{ ControlTypeByte, \"byte\" },\n+\t\t{ ControlTypeInteger32, \"int32\" },\n+\t\t{ ControlTypeInteger64, \"int64\" },\n+\t\t{ ControlTypeFloat, \"float\" },\n+\t\t{ ControlTypeString, \"string\" },\n+\t\t{ ControlTypeRectangle, \"Rectangle\" },\n+\t\t{ ControlTypeSize, \"Size\" },\n+\t};\n+\n+\tconst char *typeName;\n+\tauto it = typeNames.find(id->type());\n+\tif (it != typeNames.end())\n+\t\ttypeName = it->second;\n+\telse\n+\t\ttypeName = \"unknown\";\n+\n+\tstd::cerr << \"Unsupported control '\" << repr << \"' for \"\n+\t\t << typeName << \" control \" << id->name() << std::endl;\n+}\n+\n+ControlValue CaptureScript::unpackControl(const ControlId *id,\n+\t\t\t\t\t const std::string &repr)\n+{\n+\tControlValue value{};\n+\n+\tswitch (id->type()) {\n+\tcase ControlTypeNone:\n+\t\tbreak;\n+\tcase ControlTypeBool: {\n+\t\tbool val;\n+\n+\t\tif (repr == \"true\") {\n+\t\t\tval = true;\n+\t\t} else if (repr == \"false\") {\n+\t\t\tval = false;\n+\t\t} else {\n+\t\t\tunpackFailure(id, repr);\n+\t\t\treturn value;\n+\t\t}\n+\n+\t\tvalue.set<bool>(val);\n+\t\tbreak;\n+\t}\n+\tcase ControlTypeByte: {\n+\t\tuint8_t val = strtol(repr.c_str(), NULL, 10);\n+\t\tvalue.set<uint8_t>(val);\n+\t\tbreak;\n+\t}\n+\tcase ControlTypeInteger32: {\n+\t\tint32_t val = strtol(repr.c_str(), NULL, 10);\n+\t\tvalue.set<int32_t>(val);\n+\t\tbreak;\n+\t}\n+\tcase ControlTypeInteger64: {\n+\t\tint64_t val = strtoll(repr.c_str(), NULL, 10);\n+\t\tvalue.set<int64_t>(val);\n+\t\tbreak;\n+\t}\n+\tcase ControlTypeFloat: {\n+\t\tfloat val = strtof(repr.c_str(), NULL);\n+\t\tvalue.set<float>(val);\n+\t\tbreak;\n+\t}\n+\tcase ControlTypeString: {\n+\t\tvalue.set<std::string>(repr);\n+\t\tbreak;\n+\t}\n+\tcase ControlTypeRectangle:\n+\t\t/* \\todo Parse rectangles. */\n+\t\tbreak;\n+\tcase ControlTypeSize:\n+\t\t/* \\todo Parse Sizes. */\n+\t\tbreak;\n+\t}\n+\n+\treturn value;\n+}\ndiff --git a/src/cam/capture_script.h b/src/cam/capture_script.h\nnew file mode 100644\nindex 000000000000..44368f63f34c\n--- /dev/null\n+++ b/src/cam/capture_script.h\n@@ -0,0 +1,60 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * capture_script.h - Capture session configuration script\n+ */\n+\n+#pragma once\n+\n+#include <map>\n+#include <memory>\n+#include <string>\n+\n+#include <libcamera/camera.h>\n+#include <libcamera/controls.h>\n+\n+#include <yaml.h>\n+\n+class CaptureScript\n+{\n+public:\n+\tCaptureScript(std::shared_ptr<libcamera::Camera> camera,\n+\t\t const std::string &fileName);\n+\n+\tbool valid() const { return valid_; }\n+\n+\tconst libcamera::ControlList &frameControls(unsigned int frame);\n+\n+private:\n+\tstruct EventDeleter {\n+\t\tvoid operator()(yaml_event_t *event) const\n+\t\t{\n+\t\t\tyaml_event_delete(event);\n+\t\t\tdelete event;\n+\t\t}\n+\t};\n+\tusing EventPtr = std::unique_ptr<yaml_event_t, EventDeleter>;\n+\n+\tstd::map<std::string, const libcamera::ControlId *> controls_;\n+\tstd::map<unsigned int, libcamera::ControlList> frameControls_;\n+\tstd::shared_ptr<libcamera::Camera> camera_;\n+\tyaml_parser_t parser_;\n+\tbool valid_;\n+\n+\tEventPtr nextEvent(yaml_event_type_t expectedType = YAML_NO_EVENT);\n+\tbool checkEvent(const EventPtr &event, yaml_event_type_t expectedType) const;\n+\tstatic std::string eventScalarValue(const EventPtr &event);\n+\tstatic std::string eventTypeName(yaml_event_type_t type);\n+\n+\tint parseScript(FILE *script);\n+\n+\tint parseFrames();\n+\tint parseFrame(EventPtr event);\n+\tint parseControl(EventPtr event, libcamera::ControlList &controls);\n+\n+\tstd::string parseScalar();\n+\n+\tvoid unpackFailure(const libcamera::ControlId *id,\n+\t\t\t const std::string &repr);\n+\tlibcamera::ControlValue unpackControl(const libcamera::ControlId *id,\n+\t\t\t\t\t const std::string &repr);\n+};\ndiff --git a/src/cam/meson.build b/src/cam/meson.build\nindex 5bab8c9e331b..c47a85592478 100644\n--- a/src/cam/meson.build\n+++ b/src/cam/meson.build\n@@ -11,6 +11,7 @@ cam_enabled = true\n \n cam_sources = files([\n 'camera_session.cpp',\n+ 'capture_script.cpp',\n 'event_loop.cpp',\n 'file_sink.cpp',\n 'frame_sink.cpp',\n@@ -38,6 +39,7 @@ cam = executable('cam', cam_sources,\n libcamera_public,\n libdrm,\n libevent,\n+ libyaml,\n ],\n cpp_args : cam_cpp_args,\n install : true)\n", "prefixes": [ "libcamera-devel", "v2", "1/3" ] }