From patchwork Mon Aug 5 09:28:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jaslo Ziska X-Patchwork-Id: 20768 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id BDD3AC323E for ; Mon, 5 Aug 2024 10:01:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5E08A63381; Mon, 5 Aug 2024 12:01:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=ziska.de header.i=@ziska.de header.b="Cf78g8L8"; dkim=permerror (0-bit key) header.d=ziska.de header.i=@ziska.de header.b="HebAEFhc"; dkim-atps=neutral Received: from mo4-p00-ob.smtp.rzone.de (mo4-p00-ob.smtp.rzone.de [81.169.146.217]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0A9696337E for ; Mon, 5 Aug 2024 12:01:47 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; t=1722852106; cv=none; d=strato.com; s=strato-dkim-0002; b=B7qNlkFUSfPdm5rdszkqvJSL8RyGs09TCKWZQrB3vF+6dBtwdLurTtfv171/BJTUD4 Yb90Qo3Qs79wR5ItMUK0iv1HnVGr0Zc4byN3zSvc1xa+O5o8SliNyo5eGbwox2WeJ7h6 8r3RLYuhgvtMexNrfyEuN1qkio8Q7UPmeVlpA3R3dAOnjV7V3ueXsTuXKd/DcixEZxWz MYoKWZX8Cow7pIv0GsLxjISlsddT/SRdjLmVdkSfOX0FTuNesurY5lJC29lz/MhbNE07 Jwub1AQfOdIAetgE4yxvRBUorQZW6YNrRnJR1dovMgqsPDKCJ8Od65maxFvEPtljv9qO G03Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; t=1722852106; s=strato-dkim-0002; d=strato.com; h=References:In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Cc:Date: From:Subject:Sender; bh=3SHd+fgDM0zwoJ6gc3Yv4Pl6CBD3qYQKnUY+lVuijqk=; b=KLH7Ek7ii4WRJnnJM4uc/MYPTjfTkH4oqBtRjsf74vw+MeoIg8ASq2nK2U2NfXWPkw HGRVh1U48jNe8kbDF6HF3TRi8b+Q/dqzHv4dzBI/tINLnrA2j74cdj1Xzf/U074bN12b IcET2ivuz1oa0iq9coImZheajzg11X/+OtGStTJEqlubO6Aj5tEoAh9xVxYHj2nH3NVq JN19Xj2qyfaCU4wsDzFS1J0g3Cj2g+lS/+8Yqi5caX/B08vnY+Z8jTDwcOkb+odSjISo SzLyBlPjy1u2Wg2XD957jTjPt9w2n3o2dZWDfK6fzZXHh2M9qjD+Z/aY68dtaiP6sIeQ 0O2g== ARC-Authentication-Results: i=1; strato.com; arc=none; dkim=none X-RZG-CLASS-ID: mo00 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; t=1722852106; s=strato-dkim-0002; d=ziska.de; h=References:In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Cc:Date: From:Subject:Sender; bh=3SHd+fgDM0zwoJ6gc3Yv4Pl6CBD3qYQKnUY+lVuijqk=; b=Cf78g8L8hMcH2tx5mU3oDlC767K/zpU5mX4yQNMC+yiWZJn72eEmpZSCke9DKEsxqb XQfCyoDk42CcPabA9dsalIvDxXB1/iKJClxqfgfLwcIPaW0iixClGTGFwixkMBff62vY gMRKdFVdVUMD4DtQ3kjCc1lzPg7Y5UqPlXkS/TkTZzPajjHSQ/X1q0EgF9OKw4xQY/Ju 1Jp0jtf4H8q0ydPe+B1HYantStwFBjvrmoukPIDDr3Z51pFWrRssBzyjt9TidWSIODbF uuQz/i3PYIfGEBxzigRNBpHW9YH3xwSbdxdlTFjg8WOTjHrgNS2mODXRTCXAQeLlO3fq jWew== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; t=1722852106; s=strato-dkim-0003; d=ziska.de; h=References:In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Cc:Date: From:Subject:Sender; bh=3SHd+fgDM0zwoJ6gc3Yv4Pl6CBD3qYQKnUY+lVuijqk=; b=HebAEFhc0FgnjX0Kbfn4YAYz+XFNhu/xef5EbDJjXAs9UbIiIEcBIqr/3yqP5fow4j i9Oufk7ssmBYzsZNp6DQ== X-RZG-AUTH: ":Jm0XeU+IYfb0x77LHmrjN5Wlb7TBwusDqIM6Hizy8VdfzvKi4yoFC9cCg4qxBvJaP2L5sFjJoIK+3CsR3+pCW/FVb/tK" Received: from archlinux.fritz.box by smtp.strato.de (RZmta 51.1.0 AUTH) with ESMTPSA id zb9f0a075A1kvkK (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256 bits)) (Client did not present a certificate); Mon, 5 Aug 2024 12:01:46 +0200 (CEST) From: Jaslo Ziska To: libcamera-devel@lists.libcamera.org Cc: Jaslo Ziska Subject: [PATCH 2/3] gstreamer: Generate controls from control_ids_*.yaml files Date: Mon, 5 Aug 2024 11:28:37 +0200 Message-ID: <20240805100038.11972-3-jaslo@ziska.de> X-Mailer: git-send-email 2.46.0 In-Reply-To: <20240805100038.11972-1-jaslo@ziska.de> References: <20240805100038.11972-1-jaslo@ziska.de> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" This commit implements gstreamer controls for the libcamera element by generating the controls from the control_ids_*.yaml files using a new gen-gst-controls.py script. The appropriate meson files are also changed to automatically run the script when building. The gen-gst-controls.py script works similar to the gen-controls.py script by parsing the control_ids_*.yaml files and generating C++ code for each control. For the controls to be used as gstreamer properties the type for each control needs to be translated to the appropriate glib type and a GEnumValue is generated for each enum control. Then a g_object_install_property(), _get_property() and _set_property() function is generated for each control. The vendor controls get prefixed with "$vendor-" in the final gstreamer property name. The C++ code generated by the gen-gst-controls.py script is written into the template gstlibcamerasrc-controls.cpp.in file. The matching gstlibcamerasrc-controls.h header defines the GstCameraControls class which handles the installation of the gstreamer properties as well as keeping track of the control values and setting and getting the controls. The content of these functions is generated in the Python script. Finally the libcamerasrc element itself is edited to make use of the new GstCameraControls class. The way this works is by defining a PROP_LAST enum variant which is passed to the installProperties() function so the properties are defined with the appropriate offset. When getting or setting a property PROP_LAST is subtracted from the requested property to translate the control back into a libcamera::controls:: enum variant. Signed-off-by: Jaslo Ziska --- src/gstreamer/gstlibcamera-controls.cpp.in | 46 +++ src/gstreamer/gstlibcamera-controls.h | 36 ++ src/gstreamer/gstlibcamerasrc.cpp | 17 +- src/gstreamer/meson.build | 14 + utils/gen-gst-controls.py | 398 +++++++++++++++++++++ utils/meson.build | 1 + 6 files changed, 509 insertions(+), 3 deletions(-) create mode 100644 src/gstreamer/gstlibcamera-controls.cpp.in create mode 100644 src/gstreamer/gstlibcamera-controls.h create mode 100755 utils/gen-gst-controls.py diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in new file mode 100644 index 00000000..ff93f5c3 --- /dev/null +++ b/src/gstreamer/gstlibcamera-controls.cpp.in @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Collabora Ltd. + * Author: Nicolas Dufresne + * + * gstlibcamera-controls.cpp - GStreamer Camera Controls + * + * This file is auto-generated. Do not edit. + */ + +#include "gstlibcamera-controls.h" + +#include + +using namespace libcamera; + +${enum_def} + +void GstCameraControls::installProperties(GObjectClass *klass, int lastPropId) +{ +${install_properties} +} + +bool GstCameraControls::getProperty(guint propId, GValue *value, GParamSpec *pspec) +{ + switch (propId) { +${get_properties} + default: + return false; + } +} + +bool GstCameraControls::setProperty(guint propId, const GValue *value, + [[maybe_unused]] GParamSpec *pspec) +{ + switch (propId) { +${set_properties} + default: + return false; + } +} + +void GstCameraControls::applyControls(std::unique_ptr &request) +{ + request->controls().merge(controls_); +} diff --git a/src/gstreamer/gstlibcamera-controls.h b/src/gstreamer/gstlibcamera-controls.h new file mode 100644 index 00000000..4e1d5bf9 --- /dev/null +++ b/src/gstreamer/gstlibcamera-controls.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Collabora Ltd. + * Author: Nicolas Dufresne + * + * gstlibcamera-controls.h - GStreamer Camera Controls + */ + +#pragma once + +#include +#include + +#include "gstlibcamerasrc.h" + +namespace libcamera { + +class GstCameraControls +{ +public: + GstCameraControls() {}; + ~GstCameraControls() {}; + + static void installProperties(GObjectClass *klass, int lastProp); + + bool getProperty(guint propId, GValue *value, GParamSpec *pspec); + bool setProperty(guint propId, const GValue *value, GParamSpec *pspec); + + void applyControls(std::unique_ptr &request); + +private: + /* set of user modified controls */ + ControlList controls_; +}; + +} /* namespace libcamera */ diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp index 5a3e2989..85dab67f 100644 --- a/src/gstreamer/gstlibcamerasrc.cpp +++ b/src/gstreamer/gstlibcamerasrc.cpp @@ -37,10 +37,11 @@ #include +#include "gstlibcamera-controls.h" +#include "gstlibcamera-utils.h" #include "gstlibcameraallocator.h" #include "gstlibcamerapad.h" #include "gstlibcamerapool.h" -#include "gstlibcamera-utils.h" using namespace libcamera; @@ -128,6 +129,7 @@ struct GstLibcameraSrcState { ControlList initControls_; guint group_id_; + GstCameraControls controls_; int queueRequest(); void requestCompleted(Request *request); @@ -153,6 +155,7 @@ struct _GstLibcameraSrc { enum { PROP_0, PROP_CAMERA_NAME, + PROP_LAST }; static void gst_libcamera_src_child_proxy_init(gpointer g_iface, @@ -183,6 +186,9 @@ int GstLibcameraSrcState::queueRequest() if (!request) return -ENOMEM; + /* Apply controls */ + controls_.applyControls(request); + std::unique_ptr wrap = std::make_unique(std::move(request)); @@ -722,6 +728,7 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id, { GLibLocker lock(GST_OBJECT(object)); GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object); + GstLibcameraSrcState *state = self->state; switch (prop_id) { case PROP_CAMERA_NAME: @@ -729,7 +736,8 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id, self->camera_name = g_value_dup_string(value); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + if (!state->controls_.setProperty(prop_id - PROP_LAST, value, pspec)) + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } @@ -740,13 +748,15 @@ gst_libcamera_src_get_property(GObject *object, guint prop_id, GValue *value, { GLibLocker lock(GST_OBJECT(object)); GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object); + GstLibcameraSrcState *state = self->state; switch (prop_id) { case PROP_CAMERA_NAME: g_value_set_string(value, self->camera_name); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + if (!state->controls_.getProperty(prop_id - PROP_LAST, value, pspec)) + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } @@ -947,6 +957,7 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec); + GstCameraControls::installProperties(object_class, PROP_LAST); } /* GstChildProxy implementation */ diff --git a/src/gstreamer/meson.build b/src/gstreamer/meson.build index c2a01e7b..e6c20124 100644 --- a/src/gstreamer/meson.build +++ b/src/gstreamer/meson.build @@ -25,6 +25,20 @@ libcamera_gst_sources = [ 'gstlibcamerasrc.cpp', ] +# Generate gstreamer control properties + +gen_gst_controls_input_files = [] +gen_gst_controls_template = files('gstlibcamera-controls.cpp.in') +foreach file : controls_files + gen_gst_controls_input_files += files('../libcamera/' + file) +endforeach + +libcamera_gst_sources += custom_target('gstlibcamera-controls.cpp', + input : gen_gst_controls_input_files, + output : 'gstlibcamera-controls.cpp', + command : [gen_gst_controls, '-o', '@OUTPUT@', + '-t', gen_gst_controls_template, '@INPUT@']) + libcamera_gst_cpp_args = [ '-DVERSION="@0@"'.format(libcamera_git_version), '-DPACKAGE="@0@"'.format(meson.project_name()), diff --git a/utils/gen-gst-controls.py b/utils/gen-gst-controls.py new file mode 100755 index 00000000..d0c12b50 --- /dev/null +++ b/utils/gen-gst-controls.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 + +import argparse +import re +import string +import sys +import yaml + + +class ControlEnum(object): + def __init__(self, data): + self.__data = data + + @property + def description(self): + """The enum description""" + return self.__data.get('description') + + @property + def name(self): + """The enum name""" + return self.__data.get('name') + + @property + def value(self): + """The enum value""" + return self.__data.get('value') + + +class Control(object): + def __init__(self, name, data, vendor): + self.__name = name + self.__data = data + self.__enum_values = None + self.__size = None + self.__vendor = vendor + + enum_values = data.get('enum') + if enum_values is not None: + self.__enum_values = [ControlEnum(enum) for enum in enum_values] + + size = self.__data.get('size') + if size is not None: + if len(size) == 0: + raise RuntimeError(f'Control `{self.__name}` size must have at least one dimension') + + # Compute the total number of elements in the array. If any of the + # array dimension is a string, the array is variable-sized. + num_elems = 1 + for dim in size: + if type(dim) is str: + num_elems = 0 + break + + dim = int(dim) + if dim <= 0: + raise RuntimeError(f'Control `{self.__name}` size must have positive values only') + + num_elems *= dim + + self.__size = num_elems + + @property + def description(self): + """The control description""" + return self.__data.get('description') + + @property + def enum_values(self): + """The enum values, if the control is an enumeration""" + if self.__enum_values is None: + return + for enum in self.__enum_values: + yield enum + + @property + def is_enum(self): + """Is the control an enumeration""" + return self.__enum_values is not None + + @property + def vendor(self): + """The vendor string, or None""" + return self.__vendor + + @property + def name(self): + """The control name (CamelCase)""" + return self.__name + + @property + def type(self): + typ = self.__data.get('type') + size = self.__data.get('size') + + if typ == 'string': + return 'std::string' + + if self.__size is None: + return typ + + if self.__size: + return f"Span" + else: + return f"Span" + + @property + def element_type(self): + typ = self.__data.get('type') + return typ + + @property + def size(self): + return self.__size + + +def find_common_prefix(strings): + prefix = strings[0] + + for string in strings[1:]: + while string[:len(prefix)] != prefix and prefix: + prefix = prefix[:len(prefix) - 1] + if not prefix: + break + + return prefix + + +def format_description(description, indent = 0): + # Substitute doxygen keywords \sa (see also) and \todo + description = re.sub(r'\\sa((?: \w+)+)', + lambda match: 'See also: ' + ', '.join(map(kebab_case, match.group(1).strip().split(' '))) + '.', description) + description = re.sub(r'\\todo', 'Todo:', description) + + description = description.strip().split('\n') + return '\n'.join([indent * '\t' + '"' + line.replace('\\', r'\\').replace('"', r'\"') + ' "' for line in description if line]).rstrip() + + +def snake_case(s): + return ''.join([c.isupper() and ('_' + c.lower()) or c for c in s]).strip('_') + + +def kebab_case(s): + return snake_case(s).replace('_', '-') + + +def indent(s, n): + lines = s.split('\n') + return '\n'.join([n * '\t' + line for line in lines]) + + +def generate_cpp(controls): + # Beginning of a GEnumValue definition for enum controls + enum_values_start = string.Template('static const GEnumValue ${name_snake_case}_types[] = {') + # Definition of the GEnumValue variant for each enum control variant + # Because the description might have multiple lines it will get indented below + enum_values_values = string.Template('''{ +\tcontrols${vendor_namespace}::${name}, +${description}, +\t"${nick}" +},''') + # End of a GEnumValue definition and definition of the matching _get_type() function + enum_values_end = string.Template('''\t{0, NULL, NULL} +}; + +#define TYPE_${name_upper} (${name_snake_case}_get_type()) +static GType ${name_snake_case}_get_type(void) +{ +\tstatic GType ${name_snake_case}_type = 0; + +\tif (!${name_snake_case}_type) +\t\t${name_snake_case}_type = g_enum_register_static("${name}", ${name_snake_case}_types); + +\treturn ${name_snake_case}_type; +} +''') + + # Creation of the type spec for the different types of controls + # The description (and the element_spec for the array) might have multiple lines and will get indented below + spec_array = string.Template('''gst_param_spec_array( +\t"${spec_name}", +\t"${nick}", +${description}, +${element_spec}, +\t(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) +)''') + spec_bool = string.Template('''g_param_spec_boolean( +\t"${spec_name}", +\t"${nick}", +${description}, +\t${default}, +\t(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) +)''') + spec_enum = string.Template('''g_param_spec_enum( +\t"${spec_name}", +\t"${nick}", +${description}, +\tTYPE_${enum}, +\t${default}, +\t(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) +)''') + spec_numeric = string.Template('''g_param_spec_${gtype}( +\t"${spec_name}", +\t"${nick}", +${description}, +\t${min}, +\t${max}, +\t${default}, +\t(GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS) +)''') + + # The g_object_class_install_property() function call for each control + install_property = string.Template('''\tg_object_class_install_property( +\t\tklass, +\t\tlastPropId + controls${vendor_namespace}::${name_upper}, +${spec} +\t);''') + + # The _get_property() switch cases for each control + get_property = string.Template('''case controls${vendor_namespace}::${name_upper}: { +\tauto val = controls_.get(controls${vendor_namespace}::${name}); +\tauto spec = G_PARAM_SPEC_${gtype_upper}(pspec); +\tg_value_set_${gtype}(value, val.value_or(spec->default_value)); +\treturn true; +}''') + get_array_property = string.Template('''case controls${vendor_namespace}::${name_upper}: { +\tauto val = controls_.get(controls${vendor_namespace}::${name}); +\tif (val) { +\t\tfor (size_t i = 0; i < val->size(); ++i) { +\t\t\tGValue v = G_VALUE_INIT; +\t\t\tg_value_init(&v, G_TYPE_${gtype_upper}); +\t\t\tg_value_set_${gtype}(&v, (*val)[i]); +\t\t\tgst_value_array_append_and_take_value(value, &v); +\t\t} +\t} +\treturn true; +}''') + + # The _set_property() switch cases for each control + set_property = string.Template('''case controls${vendor_namespace}::${name_upper}: +\tcontrols_.set(controls${vendor_namespace}::${name}, g_value_get_${gtype}(value)); +\treturn true;''') + set_array_property = string.Template('''case controls${vendor_namespace}::${name_upper}: { +\tsize_t size = gst_value_array_get_size(value); +\tstd::vector<${type}> val(size); +\tfor (size_t i = 0; i < size; ++i) { +\t\tconst GValue *v = gst_value_array_get_value(value, i); +\t\tval[i] = g_value_get_${gtype}(v); +\t} +\tcontrols_.set(controls${vendor_namespace}::${name}, Span(val.data(), size)); +\treturn true; +}''') + + enum_def = [] + install_properties = [] + get_properties = [] + set_properties = [] + + for ctrl in controls: + is_array = ctrl.size is not None + size = ctrl.size + if size == 0: + size = 'dynamic_extent' + + # Determine the matching glib type for each C++ type used in the controls + gtype = '' + if ctrl.is_enum: + gtype = 'enum' + elif ctrl.element_type == 'bool': + gtype = 'boolean' + elif ctrl.element_type == 'float': + gtype = 'float' + elif ctrl.element_type == 'int32_t': + gtype = 'int' + elif ctrl.element_type == 'int64_t': + gtype = 'int64' + elif ctrl.element_type == 'uint8_t': + gtype = 'uchar' + elif ctrl.element_type == 'Rectangle': + # TODO: Handle Rectangle + continue + else: + raise RuntimeError(f'The type `{ctrl.element_type}` is unknown') + + vendor_prefix = '' + vendor_namespace = '' + if ctrl.vendor != 'libcamera': + vendor_prefix = ctrl.vendor + '-' + vendor_namespace = '::' + ctrl.vendor + + name_snake_case = snake_case(ctrl.name) + name_upper = name_snake_case.upper() + + info = { + 'name': ctrl.name, + 'vendor_namespace': vendor_namespace, + 'name_snake_case': name_snake_case, + 'name_upper': name_upper, + 'spec_name': vendor_prefix + kebab_case(ctrl.name), + 'nick': ''.join([c.isupper() and (' ' + c) or c for c in ctrl.name]).strip(' '), + 'description': format_description(ctrl.description, indent=1), + 'gtype': gtype, + 'gtype_upper': gtype.upper(), + 'type': ctrl.element_type, + 'size': size, + } + + if ctrl.is_enum: + enum_def.append(enum_values_start.substitute(info)) + + common_prefix = find_common_prefix([enum.name for enum in ctrl.enum_values]) + + for enum in ctrl.enum_values: + values_info = { + 'name': enum.name, + 'vendor_namespace': vendor_namespace, + 'description': format_description(enum.description, indent=1), + 'nick': kebab_case(enum.name.removeprefix(common_prefix)), + } + enum_def.append(indent(enum_values_values.substitute(values_info), 1)) + + enum_def.append(enum_values_end.substitute(info)) + + spec = '' + if ctrl.is_enum: + spec = spec_enum.substitute({'enum': name_upper, 'default': 0, **info}) + elif gtype == 'boolean': + spec = spec_bool.substitute({'default': 'false', **info}) + elif gtype == 'float': + spec = spec_numeric.substitute({'min': '-G_MAXFLOAT', 'max': 'G_MAXFLOAT', 'default': 0, **info}) + elif gtype == 'int': + spec = spec_numeric.substitute({'min': 'G_MININT', 'max': 'G_MAXINT', 'default': 0, **info}) + elif gtype == 'int64': + spec = spec_numeric.substitute({'min': 'G_MININT64', 'max': 'G_MAXINT64', 'default': 0, **info}) + elif gtype == 'uchar': + spec = spec_numeric.substitute({'min': '0', 'max': 'G_MAXUINT8', 'default': 0, **info}) + + if is_array: + spec = spec_array.substitute({'element_spec': indent(spec, 1), **info}) + + install_properties.append(install_property.substitute({'spec': indent(spec, 2), **info})) + + if is_array: + get_properties.append(indent(get_array_property.substitute(info), 1)) + set_properties.append(indent(set_array_property.substitute(info), 1)) + else: + get_properties.append(indent(get_property.substitute(info), 1)) + set_properties.append(indent(set_property.substitute(info), 1)) + + return { + 'enum_def': '\n'.join(enum_def), + 'install_properties': '\n'.join(install_properties), + 'get_properties': '\n'.join(get_properties), + 'set_properties': '\n'.join(set_properties), + } + + +def fill_template(template, data): + template = open(template, 'rb').read() + template = template.decode('utf-8') + template = string.Template(template) + return template.substitute(data) + + +def main(argv): + # Parse command line arguments + parser = argparse.ArgumentParser() + parser.add_argument('--output', '-o', metavar='file', type=str, + help='Output file name. Defaults to standard output if not specified.') + parser.add_argument('--template', '-t', dest='template', type=str, required=True, + help='Template file name.') + parser.add_argument('input', type=str, nargs='+', + help='Input file name.') + args = parser.parse_args(argv[1:]) + + controls = [] + for input in args.input: + data = open(input, 'rb').read() + vendor = yaml.safe_load(data)['vendor'] + ctrls = yaml.safe_load(data)['controls'] + controls = controls + [Control(*ctrl.popitem(), vendor) for ctrl in ctrls] + + data = generate_cpp(controls) + + data = fill_template(args.template, data) + + if args.output: + output = open(args.output, 'wb') + output.write(data.encode('utf-8')) + output.close() + else: + sys.stdout.write(data) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/utils/meson.build b/utils/meson.build index 8e28ada7..89597f7f 100644 --- a/utils/meson.build +++ b/utils/meson.build @@ -9,6 +9,7 @@ py_modules += ['yaml'] gen_controls = files('gen-controls.py') gen_formats = files('gen-formats.py') gen_header = files('gen-header.sh') +gen_gst_controls = files('gen-gst-controls.py') ## Module signing gen_ipa_priv_key = files('gen-ipa-priv-key.sh')