From b2ada6f3ec64dc97e7facdc32d99fbe7b76e0982 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Wed, 18 May 2022 16:13:26 +0300 Subject: py: Rename pyxyz to py_xyz Having the underscore makes the names more readable, especially when there are multiple words in the name. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham Signed-off-by: Laurent Pinchart --- src/py/libcamera/meson.build | 10 +- src/py/libcamera/py_enums.cpp | 34 ++ src/py/libcamera/py_enums_generated.cpp.in | 21 + src/py/libcamera/py_geometry.cpp | 119 ++++++ src/py/libcamera/py_main.cpp | 630 +++++++++++++++++++++++++++++ src/py/libcamera/pyenums.cpp | 34 -- src/py/libcamera/pyenums_generated.cpp.in | 21 - src/py/libcamera/pygeometry.cpp | 119 ------ src/py/libcamera/pymain.cpp | 630 ----------------------------- 9 files changed, 809 insertions(+), 809 deletions(-) create mode 100644 src/py/libcamera/py_enums.cpp create mode 100644 src/py/libcamera/py_enums_generated.cpp.in create mode 100644 src/py/libcamera/py_geometry.cpp create mode 100644 src/py/libcamera/py_main.cpp delete mode 100644 src/py/libcamera/pyenums.cpp delete mode 100644 src/py/libcamera/pyenums_generated.cpp.in delete mode 100644 src/py/libcamera/pygeometry.cpp delete mode 100644 src/py/libcamera/pymain.cpp (limited to 'src/py') diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index de66bb48..55957252 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -13,21 +13,21 @@ pybind11_proj = subproject('pybind11') pybind11_dep = pybind11_proj.get_variable('pybind11_dep') pycamera_sources = files([ - 'pyenums.cpp', - 'pygeometry.cpp', - 'pymain.cpp', + 'py_enums.cpp', + 'py_geometry.cpp', + 'py_main.cpp', ]) gen_input_files = files([ '../../libcamera/control_ids.yaml', - 'pyenums_generated.cpp.in', + 'py_enums_generated.cpp.in', ]) gen_py_control_enums = files('gen-py-control-enums.py') generated_sources = custom_target('py_gen_controls', input : gen_input_files, - output : ['pyenums_generated.cpp'], + output : ['py_enums_generated.cpp'], command : [gen_py_control_enums, '-o', '@OUTPUT@', '@INPUT@']) pycamera_sources += generated_sources diff --git a/src/py/libcamera/py_enums.cpp b/src/py/libcamera/py_enums.cpp new file mode 100644 index 00000000..e55318f1 --- /dev/null +++ b/src/py/libcamera/py_enums.cpp @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + * + * Python bindings - Enumerations + */ + +#include + +#include + +namespace py = pybind11; + +using namespace libcamera; + +void init_py_enums(py::module &m) +{ + py::enum_(m, "StreamRole") + .value("StillCapture", StreamRole::StillCapture) + .value("Raw", StreamRole::Raw) + .value("VideoRecording", StreamRole::VideoRecording) + .value("Viewfinder", StreamRole::Viewfinder); + + py::enum_(m, "ControlType") + .value("None", ControlType::ControlTypeNone) + .value("Bool", ControlType::ControlTypeBool) + .value("Byte", ControlType::ControlTypeByte) + .value("Integer32", ControlType::ControlTypeInteger32) + .value("Integer64", ControlType::ControlTypeInteger64) + .value("Float", ControlType::ControlTypeFloat) + .value("String", ControlType::ControlTypeString) + .value("Rectangle", ControlType::ControlTypeRectangle) + .value("Size", ControlType::ControlTypeSize); +} diff --git a/src/py/libcamera/py_enums_generated.cpp.in b/src/py/libcamera/py_enums_generated.cpp.in new file mode 100644 index 00000000..20e07528 --- /dev/null +++ b/src/py/libcamera/py_enums_generated.cpp.in @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + * + * Python bindings - Auto-generated enums + * + * This file is auto-generated. Do not edit. + */ + +#include + +#include + +namespace py = pybind11; + +using namespace libcamera; + +void init_py_enums_generated(py::module& m) +{ +${enums} +} diff --git a/src/py/libcamera/py_geometry.cpp b/src/py/libcamera/py_geometry.cpp new file mode 100644 index 00000000..84b0cb08 --- /dev/null +++ b/src/py/libcamera/py_geometry.cpp @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + * + * Python bindings - Geometry classes + */ + +#include + +#include +#include + +#include +#include +#include + +namespace py = pybind11; + +using namespace libcamera; + +void init_py_geometry(py::module &m) +{ + auto pyPoint = py::class_(m, "Point"); + auto pySize = py::class_(m, "Size"); + auto pySizeRange = py::class_(m, "SizeRange"); + auto pyRectangle = py::class_(m, "Rectangle"); + + pyPoint + .def(py::init<>()) + .def(py::init()) + .def_readwrite("x", &Point::x) + .def_readwrite("y", &Point::y) + .def(py::self == py::self) + .def(-py::self) + .def("__str__", &Point::toString) + .def("__repr__", [](const Point &self) { + return py::str("libcamera.Point({}, {})") + .format(self.x, self.y); + }); + + pySize + .def(py::init<>()) + .def(py::init()) + .def_readwrite("width", &Size::width) + .def_readwrite("height", &Size::height) + .def_property_readonly("is_null", &Size::isNull) + .def("align_down_to", &Size::alignDownTo) + .def("align_up_to", &Size::alignUpTo) + .def("bound_to", &Size::boundTo) + .def("expand_to", &Size::expandTo) + .def("grow_by", &Size::growBy) + .def("shrink_by", &Size::shrinkBy) + .def("aligned_up_to", &Size::alignedUpTo) + .def("aligned_up_to", &Size::alignedUpTo) + .def("bounded_to", &Size::boundedTo) + .def("expanded_to", &Size::expandedTo) + .def("grown_by", &Size::grownBy) + .def("shrunk_by", &Size::shrunkBy) + .def("bounded_to_aspect_ratio", &Size::boundedToAspectRatio) + .def("expanded_to_aspect_ratio", &Size::expandedToAspectRatio) + .def("centered_to", &Size::centeredTo) + .def(py::self == py::self) + .def(py::self < py::self) + .def(py::self <= py::self) + .def(py::self * float()) + .def(py::self / float()) + .def(py::self *= float()) + .def(py::self /= float()) + .def("__str__", &Size::toString) + .def("__repr__", [](const Size &self) { + return py::str("libcamera.Size({}, {})") + .format(self.width, self.height); + }); + + pySizeRange + .def(py::init<>()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def_readwrite("min", &SizeRange::min) + .def_readwrite("max", &SizeRange::max) + .def_readwrite("hStep", &SizeRange::hStep) + .def_readwrite("vStep", &SizeRange::vStep) + .def("contains", &SizeRange::contains) + .def(py::self == py::self) + .def("__str__", &SizeRange::toString) + .def("__repr__", [](const SizeRange &self) { + return py::str("libcamera.SizeRange(({}, {}), ({}, {}), {}, {})") + .format(self.min.width, self.min.height, + self.max.width, self.max.height, + self.hStep, self.vStep); + }); + + pyRectangle + .def(py::init<>()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def_readwrite("x", &Rectangle::x) + .def_readwrite("y", &Rectangle::y) + .def_readwrite("width", &Rectangle::width) + .def_readwrite("height", &Rectangle::height) + .def_property_readonly("is_null", &Rectangle::isNull) + .def_property_readonly("center", &Rectangle::center) + .def_property_readonly("size", &Rectangle::size) + .def_property_readonly("topLeft", &Rectangle::topLeft) + .def("scale_by", &Rectangle::scaleBy) + .def("translate_by", &Rectangle::translateBy) + .def("bounded_to", &Rectangle::boundedTo) + .def("enclosed_in", &Rectangle::enclosedIn) + .def("scaled_by", &Rectangle::scaledBy) + .def("translated_by", &Rectangle::translatedBy) + .def(py::self == py::self) + .def("__str__", &Rectangle::toString) + .def("__repr__", [](const Rectangle &self) { + return py::str("libcamera.Rectangle({}, {}, {}, {})") + .format(self.x, self.y, self.width, self.height); + }); +} diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp new file mode 100644 index 00000000..b05bbb22 --- /dev/null +++ b/src/py/libcamera/py_main.cpp @@ -0,0 +1,630 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + * + * Python bindings + */ + +/* + * \todo Add bindings for the ControlInfo class + */ + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace py = pybind11; + +using namespace libcamera; + +template +static py::object valueOrTuple(const ControlValue &cv) +{ + if (cv.isArray()) { + const T *v = reinterpret_cast(cv.data().data()); + auto t = py::tuple(cv.numElements()); + + for (size_t i = 0; i < cv.numElements(); ++i) + t[i] = v[i]; + + return std::move(t); + } + + return py::cast(cv.get()); +} + +static py::object controlValueToPy(const ControlValue &cv) +{ + switch (cv.type()) { + case ControlTypeBool: + return valueOrTuple(cv); + case ControlTypeByte: + return valueOrTuple(cv); + case ControlTypeInteger32: + return valueOrTuple(cv); + case ControlTypeInteger64: + return valueOrTuple(cv); + case ControlTypeFloat: + return valueOrTuple(cv); + case ControlTypeString: + return py::cast(cv.get()); + case ControlTypeRectangle: { + const Rectangle *v = reinterpret_cast(cv.data().data()); + return py::cast(v); + } + case ControlTypeSize: { + const Size *v = reinterpret_cast(cv.data().data()); + return py::cast(v); + } + case ControlTypeNone: + default: + throw std::runtime_error("Unsupported ControlValue type"); + } +} + +template +static ControlValue controlValueMaybeArray(const py::object &ob) +{ + if (py::isinstance(ob) || py::isinstance(ob)) { + std::vector vec = ob.cast>(); + return ControlValue(Span(vec)); + } + + return ControlValue(ob.cast()); +} + +static ControlValue pyToControlValue(const py::object &ob, ControlType type) +{ + switch (type) { + case ControlTypeBool: + return ControlValue(ob.cast()); + case ControlTypeByte: + return controlValueMaybeArray(ob); + case ControlTypeInteger32: + return controlValueMaybeArray(ob); + case ControlTypeInteger64: + return controlValueMaybeArray(ob); + case ControlTypeFloat: + return controlValueMaybeArray(ob); + case ControlTypeString: + return ControlValue(ob.cast()); + case ControlTypeRectangle: + return ControlValue(ob.cast()); + case ControlTypeSize: + return ControlValue(ob.cast()); + case ControlTypeNone: + default: + throw std::runtime_error("Control type not implemented"); + } +} + +static std::weak_ptr gCameraManager; +static int gEventfd; +static std::mutex gReqlistMutex; +static std::vector gReqList; + +static void handleRequestCompleted(Request *req) +{ + { + std::lock_guard guard(gReqlistMutex); + gReqList.push_back(req); + } + + uint64_t v = 1; + size_t s = write(gEventfd, &v, 8); + /* + * We should never fail, and have no simple means to manage the error, + * so let's use LOG(Fatal). + */ + if (s != 8) + LOG(Fatal) << "Unable to write to eventfd"; +} + +void init_py_enums(py::module &m); +void init_py_enums_generated(py::module &m); +void init_py_geometry(py::module &m); + +PYBIND11_MODULE(_libcamera, m) +{ + init_py_enums(m); + init_py_enums_generated(m); + init_py_geometry(m); + + /* Forward declarations */ + + /* + * We need to declare all the classes here so that Python docstrings + * can be generated correctly. + * https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings + */ + + auto pyCameraManager = py::class_(m, "CameraManager"); + auto pyCamera = py::class_(m, "Camera"); + auto pyCameraConfiguration = py::class_(m, "CameraConfiguration"); + auto pyCameraConfigurationStatus = py::enum_(pyCameraConfiguration, "Status"); + auto pyStreamConfiguration = py::class_(m, "StreamConfiguration"); + auto pyStreamFormats = py::class_(m, "StreamFormats"); + auto pyFrameBufferAllocator = py::class_(m, "FrameBufferAllocator"); + auto pyFrameBuffer = py::class_(m, "FrameBuffer"); + auto pyStream = py::class_(m, "Stream"); + auto pyControlId = py::class_(m, "ControlId"); + auto pyRequest = py::class_(m, "Request"); + auto pyRequestStatus = py::enum_(pyRequest, "Status"); + auto pyRequestReuse = py::enum_(pyRequest, "Reuse"); + auto pyFrameMetadata = py::class_(m, "FrameMetadata"); + auto pyFrameMetadataStatus = py::enum_(pyFrameMetadata, "Status"); + auto pyTransform = py::class_(m, "Transform"); + auto pyColorSpace = py::class_(m, "ColorSpace"); + auto pyColorSpacePrimaries = py::enum_(pyColorSpace, "Primaries"); + auto pyColorSpaceTransferFunction = py::enum_(pyColorSpace, "TransferFunction"); + auto pyColorSpaceYcbcrEncoding = py::enum_(pyColorSpace, "YcbcrEncoding"); + auto pyColorSpaceRange = py::enum_(pyColorSpace, "Range"); + auto pyPixelFormat = py::class_(m, "PixelFormat"); + + /* Global functions */ + m.def("log_set_level", &logSetLevel); + + /* Classes */ + pyCameraManager + .def_static("singleton", []() { + std::shared_ptr cm = gCameraManager.lock(); + if (cm) + return cm; + + int fd = eventfd(0, 0); + if (fd == -1) + throw std::system_error(errno, std::generic_category(), + "Failed to create eventfd"); + + cm = std::shared_ptr(new CameraManager, [](auto p) { + close(gEventfd); + gEventfd = -1; + delete p; + }); + + gEventfd = fd; + gCameraManager = cm; + + int ret = cm->start(); + if (ret) + throw std::system_error(-ret, std::generic_category(), + "Failed to start CameraManager"); + + return cm; + }) + + .def_property_readonly("version", &CameraManager::version) + + .def_property_readonly("efd", [](CameraManager &) { + return gEventfd; + }) + + .def("get_ready_requests", [](CameraManager &) { + std::vector v; + + { + std::lock_guard guard(gReqlistMutex); + swap(v, gReqList); + } + + std::vector ret; + + for (Request *req : v) { + py::object o = py::cast(req); + /* Decrease the ref increased in Camera.queue_request() */ + o.dec_ref(); + ret.push_back(o); + } + + return ret; + }) + + .def("get", py::overload_cast(&CameraManager::get), py::keep_alive<0, 1>()) + + /* Create a list of Cameras, where each camera has a keep-alive to CameraManager */ + .def_property_readonly("cameras", [](CameraManager &self) { + py::list l; + + for (auto &c : self.cameras()) { + py::object py_cm = py::cast(self); + py::object py_cam = py::cast(c); + py::detail::keep_alive_impl(py_cam, py_cm); + l.append(py_cam); + } + + return l; + }); + + pyCamera + .def_property_readonly("id", &Camera::id) + .def("acquire", &Camera::acquire) + .def("release", &Camera::release) + .def("start", [](Camera &self, py::dict controls) { + /* \todo What happens if someone calls start() multiple times? */ + + self.requestCompleted.connect(handleRequestCompleted); + + const ControlInfoMap &controlMap = self.controls(); + ControlList controlList(controlMap); + for (const auto& [hkey, hval]: controls) { + auto key = hkey.cast(); + + auto it = std::find_if(controlMap.begin(), controlMap.end(), + [&key](const auto &kvp) { + return kvp.first->name() == key; + }); + + if (it == controlMap.end()) + throw std::runtime_error("Control " + key + " not found"); + + const auto &id = it->first; + auto obj = py::cast(hval); + + controlList.set(id->id(), pyToControlValue(obj, id->type())); + } + + int ret = self.start(&controlList); + if (ret) { + self.requestCompleted.disconnect(handleRequestCompleted); + return ret; + } + + return 0; + }, py::arg("controls") = py::dict()) + + .def("stop", [](Camera &self) { + int ret = self.stop(); + if (ret) + return ret; + + self.requestCompleted.disconnect(handleRequestCompleted); + + return 0; + }) + + .def("__str__", [](Camera &self) { + return ""; + }) + + /* Keep the camera alive, as StreamConfiguration contains a Stream* */ + .def("generate_configuration", &Camera::generateConfiguration, py::keep_alive<0, 1>()) + .def("configure", &Camera::configure) + + .def("create_request", &Camera::createRequest, py::arg("cookie") = 0) + + .def("queue_request", [](Camera &self, Request *req) { + py::object py_req = py::cast(req); + + /* + * Increase the reference count, will be dropped in + * CameraManager.get_ready_requests(). + */ + + py_req.inc_ref(); + + int ret = self.queueRequest(req); + if (ret) + py_req.dec_ref(); + + return ret; + }) + + .def_property_readonly("streams", [](Camera &self) { + py::set set; + for (auto &s : self.streams()) { + py::object py_self = py::cast(self); + py::object py_s = py::cast(s); + py::detail::keep_alive_impl(py_s, py_self); + set.add(py_s); + } + return set; + }) + + .def("find_control", [](Camera &self, const std::string &name) { + const auto &controls = self.controls(); + + auto it = std::find_if(controls.begin(), controls.end(), + [&name](const auto &kvp) { + return kvp.first->name() == name; + }); + + if (it == controls.end()) + throw std::runtime_error("Control '" + name + "' not found"); + + return it->first; + }, py::return_value_policy::reference_internal) + + .def_property_readonly("controls", [](Camera &self) { + py::dict ret; + + for (const auto &[id, ci] : self.controls()) { + ret[id->name().c_str()] = std::make_tuple(controlValueToPy(ci.min()), + controlValueToPy(ci.max()), + controlValueToPy(ci.def())); + } + + return ret; + }) + + .def_property_readonly("properties", [](Camera &self) { + py::dict ret; + + for (const auto &[key, cv] : self.properties()) { + const ControlId *id = properties::properties.at(key); + py::object ob = controlValueToPy(cv); + + ret[id->name().c_str()] = ob; + } + + return ret; + }); + + pyCameraConfiguration + .def("__iter__", [](CameraConfiguration &self) { + return py::make_iterator(self); + }, py::keep_alive<0, 1>()) + .def("__len__", [](CameraConfiguration &self) { + return self.size(); + }) + .def("validate", &CameraConfiguration::validate) + .def("at", py::overload_cast(&CameraConfiguration::at), + py::return_value_policy::reference_internal) + .def_property_readonly("size", &CameraConfiguration::size) + .def_property_readonly("empty", &CameraConfiguration::empty) + .def_readwrite("transform", &CameraConfiguration::transform); + + pyCameraConfigurationStatus + .value("Valid", CameraConfiguration::Valid) + .value("Adjusted", CameraConfiguration::Adjusted) + .value("Invalid", CameraConfiguration::Invalid); + + pyStreamConfiguration + .def("__str__", &StreamConfiguration::toString) + .def_property_readonly("stream", &StreamConfiguration::stream, + py::return_value_policy::reference_internal) + .def_readwrite("size", &StreamConfiguration::size) + .def_readwrite("pixel_format", &StreamConfiguration::pixelFormat) + .def_readwrite("stride", &StreamConfiguration::stride) + .def_readwrite("frame_size", &StreamConfiguration::frameSize) + .def_readwrite("buffer_count", &StreamConfiguration::bufferCount) + .def_property_readonly("formats", &StreamConfiguration::formats, + py::return_value_policy::reference_internal) + .def_readwrite("color_space", &StreamConfiguration::colorSpace); + + pyStreamFormats + .def_property_readonly("pixel_formats", &StreamFormats::pixelformats) + .def("sizes", &StreamFormats::sizes) + .def("range", &StreamFormats::range); + + pyFrameBufferAllocator + .def(py::init>(), py::keep_alive<1, 2>()) + .def("allocate", &FrameBufferAllocator::allocate) + .def_property_readonly("allocated", &FrameBufferAllocator::allocated) + /* Create a list of FrameBuffers, where each FrameBuffer has a keep-alive to FrameBufferAllocator */ + .def("buffers", [](FrameBufferAllocator &self, Stream *stream) { + py::object py_self = py::cast(self); + py::list l; + for (auto &ub : self.buffers(stream)) { + py::object py_buf = py::cast(ub.get(), py::return_value_policy::reference_internal, py_self); + l.append(py_buf); + } + return l; + }); + + pyFrameBuffer + /* \todo implement FrameBuffer::Plane properly */ + .def(py::init([](std::vector> planes, unsigned int cookie) { + std::vector v; + for (const auto &t : planes) + v.push_back({ SharedFD(std::get<0>(t)), FrameBuffer::Plane::kInvalidOffset, std::get<1>(t) }); + return new FrameBuffer(v, cookie); + })) + .def_property_readonly("metadata", &FrameBuffer::metadata, py::return_value_policy::reference_internal) + .def_property_readonly("num_planes", [](const FrameBuffer &self) { + return self.planes().size(); + }) + .def("length", [](FrameBuffer &self, uint32_t idx) { + const FrameBuffer::Plane &plane = self.planes()[idx]; + return plane.length; + }) + .def("fd", [](FrameBuffer &self, uint32_t idx) { + const FrameBuffer::Plane &plane = self.planes()[idx]; + return plane.fd.get(); + }) + .def("offset", [](FrameBuffer &self, uint32_t idx) { + const FrameBuffer::Plane &plane = self.planes()[idx]; + return plane.offset; + }) + .def_property("cookie", &FrameBuffer::cookie, &FrameBuffer::setCookie); + + pyStream + .def_property_readonly("configuration", &Stream::configuration); + + pyControlId + .def_property_readonly("id", &ControlId::id) + .def_property_readonly("name", &ControlId::name) + .def_property_readonly("type", &ControlId::type); + + pyRequest + /* \todo Fence is not supported, so we cannot expose addBuffer() directly */ + .def("add_buffer", [](Request &self, const Stream *stream, FrameBuffer *buffer) { + return self.addBuffer(stream, buffer); + }, py::keep_alive<1, 3>()) /* Request keeps Framebuffer alive */ + .def_property_readonly("status", &Request::status) + .def_property_readonly("buffers", &Request::buffers) + .def_property_readonly("cookie", &Request::cookie) + .def_property_readonly("has_pending_buffers", &Request::hasPendingBuffers) + .def("set_control", [](Request &self, ControlId &id, py::object value) { + self.controls().set(id.id(), pyToControlValue(value, id.type())); + }) + .def_property_readonly("metadata", [](Request &self) { + py::dict ret; + + for (const auto &[key, cv] : self.metadata()) { + const ControlId *id = controls::controls.at(key); + py::object ob = controlValueToPy(cv); + + ret[id->name().c_str()] = ob; + } + + return ret; + }) + /* + * \todo As we add a keep_alive to the fb in addBuffers(), we + * can only allow reuse with ReuseBuffers. + */ + .def("reuse", [](Request &self) { self.reuse(Request::ReuseFlag::ReuseBuffers); }); + + pyRequestStatus + .value("Pending", Request::RequestPending) + .value("Complete", Request::RequestComplete) + .value("Cancelled", Request::RequestCancelled); + + pyRequestReuse + .value("Default", Request::ReuseFlag::Default) + .value("ReuseBuffers", Request::ReuseFlag::ReuseBuffers); + + pyFrameMetadata + .def_readonly("status", &FrameMetadata::status) + .def_readonly("sequence", &FrameMetadata::sequence) + .def_readonly("timestamp", &FrameMetadata::timestamp) + /* \todo Implement FrameMetadata::Plane properly */ + .def_property_readonly("bytesused", [](FrameMetadata &self) { + std::vector v; + v.resize(self.planes().size()); + transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; }); + return v; + }); + + pyFrameMetadataStatus + .value("Success", FrameMetadata::FrameSuccess) + .value("Error", FrameMetadata::FrameError) + .value("Cancelled", FrameMetadata::FrameCancelled); + + pyTransform + .def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) { + bool ok; + + Transform t = transformFromRotation(rotation, &ok); + if (!ok) + throw std::invalid_argument("Invalid rotation"); + + if (hflip) + t ^= Transform::HFlip; + if (vflip) + t ^= Transform::VFlip; + if (transpose) + t ^= Transform::Transpose; + return t; + }), py::arg("rotation") = 0, py::arg("hflip") = false, + py::arg("vflip") = false, py::arg("transpose") = false) + .def(py::init([](Transform &other) { return other; })) + .def("__str__", [](Transform &self) { + return ""; + }) + .def_property("hflip", + [](Transform &self) { + return !!(self & Transform::HFlip); + }, + [](Transform &self, bool hflip) { + if (hflip) + self |= Transform::HFlip; + else + self &= ~Transform::HFlip; + }) + .def_property("vflip", + [](Transform &self) { + return !!(self & Transform::VFlip); + }, + [](Transform &self, bool vflip) { + if (vflip) + self |= Transform::VFlip; + else + self &= ~Transform::VFlip; + }) + .def_property("transpose", + [](Transform &self) { + return !!(self & Transform::Transpose); + }, + [](Transform &self, bool transpose) { + if (transpose) + self |= Transform::Transpose; + else + self &= ~Transform::Transpose; + }) + .def("inverse", [](Transform &self) { return -self; }) + .def("invert", [](Transform &self) { + self = -self; + }) + .def("compose", [](Transform &self, Transform &other) { + self = self * other; + }); + + pyColorSpace + .def(py::init([](ColorSpace::Primaries primaries, + ColorSpace::TransferFunction transferFunction, + ColorSpace::YcbcrEncoding ycbcrEncoding, + ColorSpace::Range range) { + return ColorSpace(primaries, transferFunction, ycbcrEncoding, range); + }), py::arg("primaries"), py::arg("transferFunction"), + py::arg("ycbcrEncoding"), py::arg("range")) + .def(py::init([](ColorSpace &other) { return other; })) + .def("__str__", [](ColorSpace &self) { + return ""; + }) + .def_readwrite("primaries", &ColorSpace::primaries) + .def_readwrite("transferFunction", &ColorSpace::transferFunction) + .def_readwrite("ycbcrEncoding", &ColorSpace::ycbcrEncoding) + .def_readwrite("range", &ColorSpace::range) + .def_static("Raw", []() { return ColorSpace::Raw; }) + .def_static("Jpeg", []() { return ColorSpace::Jpeg; }) + .def_static("Srgb", []() { return ColorSpace::Srgb; }) + .def_static("Smpte170m", []() { return ColorSpace::Smpte170m; }) + .def_static("Rec709", []() { return ColorSpace::Rec709; }) + .def_static("Rec2020", []() { return ColorSpace::Rec2020; }); + + pyColorSpacePrimaries + .value("Raw", ColorSpace::Primaries::Raw) + .value("Smpte170m", ColorSpace::Primaries::Smpte170m) + .value("Rec709", ColorSpace::Primaries::Rec709) + .value("Rec2020", ColorSpace::Primaries::Rec2020); + + pyColorSpaceTransferFunction + .value("Linear", ColorSpace::TransferFunction::Linear) + .value("Srgb", ColorSpace::TransferFunction::Srgb) + .value("Rec709", ColorSpace::TransferFunction::Rec709); + + pyColorSpaceYcbcrEncoding + .value("Null", ColorSpace::YcbcrEncoding::None) + .value("Rec601", ColorSpace::YcbcrEncoding::Rec601) + .value("Rec709", ColorSpace::YcbcrEncoding::Rec709) + .value("Rec2020", ColorSpace::YcbcrEncoding::Rec2020); + + pyColorSpaceRange + .value("Full", ColorSpace::Range::Full) + .value("Limited", ColorSpace::Range::Limited); + + pyPixelFormat + .def(py::init<>()) + .def(py::init()) + .def(py::init<>([](const std::string &str) { + return PixelFormat::fromString(str); + })) + .def_property_readonly("fourcc", &PixelFormat::fourcc) + .def_property_readonly("modifier", &PixelFormat::modifier) + .def(py::self == py::self) + .def("__str__", &PixelFormat::toString) + .def("__repr__", [](const PixelFormat &self) { + return "libcamera.PixelFormat('" + self.toString() + "')"; + }); +} diff --git a/src/py/libcamera/pyenums.cpp b/src/py/libcamera/pyenums.cpp deleted file mode 100644 index b655e622..00000000 --- a/src/py/libcamera/pyenums.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2022, Tomi Valkeinen - * - * Python bindings - Enumerations - */ - -#include - -#include - -namespace py = pybind11; - -using namespace libcamera; - -void init_pyenums(py::module &m) -{ - py::enum_(m, "StreamRole") - .value("StillCapture", StreamRole::StillCapture) - .value("Raw", StreamRole::Raw) - .value("VideoRecording", StreamRole::VideoRecording) - .value("Viewfinder", StreamRole::Viewfinder); - - py::enum_(m, "ControlType") - .value("None", ControlType::ControlTypeNone) - .value("Bool", ControlType::ControlTypeBool) - .value("Byte", ControlType::ControlTypeByte) - .value("Integer32", ControlType::ControlTypeInteger32) - .value("Integer64", ControlType::ControlTypeInteger64) - .value("Float", ControlType::ControlTypeFloat) - .value("String", ControlType::ControlTypeString) - .value("Rectangle", ControlType::ControlTypeRectangle) - .value("Size", ControlType::ControlTypeSize); -} diff --git a/src/py/libcamera/pyenums_generated.cpp.in b/src/py/libcamera/pyenums_generated.cpp.in deleted file mode 100644 index 6aaf4795..00000000 --- a/src/py/libcamera/pyenums_generated.cpp.in +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2022, Tomi Valkeinen - * - * Python bindings - Auto-generated enums - * - * This file is auto-generated. Do not edit. - */ - -#include - -#include - -namespace py = pybind11; - -using namespace libcamera; - -void init_pyenums_generated(py::module& m) -{ -${enums} -} diff --git a/src/py/libcamera/pygeometry.cpp b/src/py/libcamera/pygeometry.cpp deleted file mode 100644 index d77de144..00000000 --- a/src/py/libcamera/pygeometry.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2022, Tomi Valkeinen - * - * Python bindings - Geometry classes - */ - -#include - -#include -#include - -#include -#include -#include - -namespace py = pybind11; - -using namespace libcamera; - -void init_pygeometry(py::module &m) -{ - auto pyPoint = py::class_(m, "Point"); - auto pySize = py::class_(m, "Size"); - auto pySizeRange = py::class_(m, "SizeRange"); - auto pyRectangle = py::class_(m, "Rectangle"); - - pyPoint - .def(py::init<>()) - .def(py::init()) - .def_readwrite("x", &Point::x) - .def_readwrite("y", &Point::y) - .def(py::self == py::self) - .def(-py::self) - .def("__str__", &Point::toString) - .def("__repr__", [](const Point &self) { - return py::str("libcamera.Point({}, {})") - .format(self.x, self.y); - }); - - pySize - .def(py::init<>()) - .def(py::init()) - .def_readwrite("width", &Size::width) - .def_readwrite("height", &Size::height) - .def_property_readonly("is_null", &Size::isNull) - .def("align_down_to", &Size::alignDownTo) - .def("align_up_to", &Size::alignUpTo) - .def("bound_to", &Size::boundTo) - .def("expand_to", &Size::expandTo) - .def("grow_by", &Size::growBy) - .def("shrink_by", &Size::shrinkBy) - .def("aligned_up_to", &Size::alignedUpTo) - .def("aligned_up_to", &Size::alignedUpTo) - .def("bounded_to", &Size::boundedTo) - .def("expanded_to", &Size::expandedTo) - .def("grown_by", &Size::grownBy) - .def("shrunk_by", &Size::shrunkBy) - .def("bounded_to_aspect_ratio", &Size::boundedToAspectRatio) - .def("expanded_to_aspect_ratio", &Size::expandedToAspectRatio) - .def("centered_to", &Size::centeredTo) - .def(py::self == py::self) - .def(py::self < py::self) - .def(py::self <= py::self) - .def(py::self * float()) - .def(py::self / float()) - .def(py::self *= float()) - .def(py::self /= float()) - .def("__str__", &Size::toString) - .def("__repr__", [](const Size &self) { - return py::str("libcamera.Size({}, {})") - .format(self.width, self.height); - }); - - pySizeRange - .def(py::init<>()) - .def(py::init()) - .def(py::init()) - .def(py::init()) - .def_readwrite("min", &SizeRange::min) - .def_readwrite("max", &SizeRange::max) - .def_readwrite("hStep", &SizeRange::hStep) - .def_readwrite("vStep", &SizeRange::vStep) - .def("contains", &SizeRange::contains) - .def(py::self == py::self) - .def("__str__", &SizeRange::toString) - .def("__repr__", [](const SizeRange &self) { - return py::str("libcamera.SizeRange(({}, {}), ({}, {}), {}, {})") - .format(self.min.width, self.min.height, - self.max.width, self.max.height, - self.hStep, self.vStep); - }); - - pyRectangle - .def(py::init<>()) - .def(py::init()) - .def(py::init()) - .def(py::init()) - .def_readwrite("x", &Rectangle::x) - .def_readwrite("y", &Rectangle::y) - .def_readwrite("width", &Rectangle::width) - .def_readwrite("height", &Rectangle::height) - .def_property_readonly("is_null", &Rectangle::isNull) - .def_property_readonly("center", &Rectangle::center) - .def_property_readonly("size", &Rectangle::size) - .def_property_readonly("topLeft", &Rectangle::topLeft) - .def("scale_by", &Rectangle::scaleBy) - .def("translate_by", &Rectangle::translateBy) - .def("bounded_to", &Rectangle::boundedTo) - .def("enclosed_in", &Rectangle::enclosedIn) - .def("scaled_by", &Rectangle::scaledBy) - .def("translated_by", &Rectangle::translatedBy) - .def(py::self == py::self) - .def("__str__", &Rectangle::toString) - .def("__repr__", [](const Rectangle &self) { - return py::str("libcamera.Rectangle({}, {}, {}, {})") - .format(self.x, self.y, self.width, self.height); - }); -} diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp deleted file mode 100644 index ef3f157a..00000000 --- a/src/py/libcamera/pymain.cpp +++ /dev/null @@ -1,630 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2022, Tomi Valkeinen - * - * Python bindings - */ - -/* - * \todo Add bindings for the ControlInfo class - */ - -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - -namespace py = pybind11; - -using namespace libcamera; - -template -static py::object valueOrTuple(const ControlValue &cv) -{ - if (cv.isArray()) { - const T *v = reinterpret_cast(cv.data().data()); - auto t = py::tuple(cv.numElements()); - - for (size_t i = 0; i < cv.numElements(); ++i) - t[i] = v[i]; - - return std::move(t); - } - - return py::cast(cv.get()); -} - -static py::object controlValueToPy(const ControlValue &cv) -{ - switch (cv.type()) { - case ControlTypeBool: - return valueOrTuple(cv); - case ControlTypeByte: - return valueOrTuple(cv); - case ControlTypeInteger32: - return valueOrTuple(cv); - case ControlTypeInteger64: - return valueOrTuple(cv); - case ControlTypeFloat: - return valueOrTuple(cv); - case ControlTypeString: - return py::cast(cv.get()); - case ControlTypeRectangle: { - const Rectangle *v = reinterpret_cast(cv.data().data()); - return py::cast(v); - } - case ControlTypeSize: { - const Size *v = reinterpret_cast(cv.data().data()); - return py::cast(v); - } - case ControlTypeNone: - default: - throw std::runtime_error("Unsupported ControlValue type"); - } -} - -template -static ControlValue controlValueMaybeArray(const py::object &ob) -{ - if (py::isinstance(ob) || py::isinstance(ob)) { - std::vector vec = ob.cast>(); - return ControlValue(Span(vec)); - } - - return ControlValue(ob.cast()); -} - -static ControlValue pyToControlValue(const py::object &ob, ControlType type) -{ - switch (type) { - case ControlTypeBool: - return ControlValue(ob.cast()); - case ControlTypeByte: - return controlValueMaybeArray(ob); - case ControlTypeInteger32: - return controlValueMaybeArray(ob); - case ControlTypeInteger64: - return controlValueMaybeArray(ob); - case ControlTypeFloat: - return controlValueMaybeArray(ob); - case ControlTypeString: - return ControlValue(ob.cast()); - case ControlTypeRectangle: - return ControlValue(ob.cast()); - case ControlTypeSize: - return ControlValue(ob.cast()); - case ControlTypeNone: - default: - throw std::runtime_error("Control type not implemented"); - } -} - -static std::weak_ptr gCameraManager; -static int gEventfd; -static std::mutex gReqlistMutex; -static std::vector gReqList; - -static void handleRequestCompleted(Request *req) -{ - { - std::lock_guard guard(gReqlistMutex); - gReqList.push_back(req); - } - - uint64_t v = 1; - size_t s = write(gEventfd, &v, 8); - /* - * We should never fail, and have no simple means to manage the error, - * so let's use LOG(Fatal). - */ - if (s != 8) - LOG(Fatal) << "Unable to write to eventfd"; -} - -void init_pyenums(py::module &m); -void init_pyenums_generated(py::module &m); -void init_pygeometry(py::module &m); - -PYBIND11_MODULE(_libcamera, m) -{ - init_pyenums(m); - init_pyenums_generated(m); - init_pygeometry(m); - - /* Forward declarations */ - - /* - * We need to declare all the classes here so that Python docstrings - * can be generated correctly. - * https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings - */ - - auto pyCameraManager = py::class_(m, "CameraManager"); - auto pyCamera = py::class_(m, "Camera"); - auto pyCameraConfiguration = py::class_(m, "CameraConfiguration"); - auto pyCameraConfigurationStatus = py::enum_(pyCameraConfiguration, "Status"); - auto pyStreamConfiguration = py::class_(m, "StreamConfiguration"); - auto pyStreamFormats = py::class_(m, "StreamFormats"); - auto pyFrameBufferAllocator = py::class_(m, "FrameBufferAllocator"); - auto pyFrameBuffer = py::class_(m, "FrameBuffer"); - auto pyStream = py::class_(m, "Stream"); - auto pyControlId = py::class_(m, "ControlId"); - auto pyRequest = py::class_(m, "Request"); - auto pyRequestStatus = py::enum_(pyRequest, "Status"); - auto pyRequestReuse = py::enum_(pyRequest, "Reuse"); - auto pyFrameMetadata = py::class_(m, "FrameMetadata"); - auto pyFrameMetadataStatus = py::enum_(pyFrameMetadata, "Status"); - auto pyTransform = py::class_(m, "Transform"); - auto pyColorSpace = py::class_(m, "ColorSpace"); - auto pyColorSpacePrimaries = py::enum_(pyColorSpace, "Primaries"); - auto pyColorSpaceTransferFunction = py::enum_(pyColorSpace, "TransferFunction"); - auto pyColorSpaceYcbcrEncoding = py::enum_(pyColorSpace, "YcbcrEncoding"); - auto pyColorSpaceRange = py::enum_(pyColorSpace, "Range"); - auto pyPixelFormat = py::class_(m, "PixelFormat"); - - /* Global functions */ - m.def("log_set_level", &logSetLevel); - - /* Classes */ - pyCameraManager - .def_static("singleton", []() { - std::shared_ptr cm = gCameraManager.lock(); - if (cm) - return cm; - - int fd = eventfd(0, 0); - if (fd == -1) - throw std::system_error(errno, std::generic_category(), - "Failed to create eventfd"); - - cm = std::shared_ptr(new CameraManager, [](auto p) { - close(gEventfd); - gEventfd = -1; - delete p; - }); - - gEventfd = fd; - gCameraManager = cm; - - int ret = cm->start(); - if (ret) - throw std::system_error(-ret, std::generic_category(), - "Failed to start CameraManager"); - - return cm; - }) - - .def_property_readonly("version", &CameraManager::version) - - .def_property_readonly("efd", [](CameraManager &) { - return gEventfd; - }) - - .def("get_ready_requests", [](CameraManager &) { - std::vector v; - - { - std::lock_guard guard(gReqlistMutex); - swap(v, gReqList); - } - - std::vector ret; - - for (Request *req : v) { - py::object o = py::cast(req); - /* Decrease the ref increased in Camera.queue_request() */ - o.dec_ref(); - ret.push_back(o); - } - - return ret; - }) - - .def("get", py::overload_cast(&CameraManager::get), py::keep_alive<0, 1>()) - - /* Create a list of Cameras, where each camera has a keep-alive to CameraManager */ - .def_property_readonly("cameras", [](CameraManager &self) { - py::list l; - - for (auto &c : self.cameras()) { - py::object py_cm = py::cast(self); - py::object py_cam = py::cast(c); - py::detail::keep_alive_impl(py_cam, py_cm); - l.append(py_cam); - } - - return l; - }); - - pyCamera - .def_property_readonly("id", &Camera::id) - .def("acquire", &Camera::acquire) - .def("release", &Camera::release) - .def("start", [](Camera &self, py::dict controls) { - /* \todo What happens if someone calls start() multiple times? */ - - self.requestCompleted.connect(handleRequestCompleted); - - const ControlInfoMap &controlMap = self.controls(); - ControlList controlList(controlMap); - for (const auto& [hkey, hval]: controls) { - auto key = hkey.cast(); - - auto it = std::find_if(controlMap.begin(), controlMap.end(), - [&key](const auto &kvp) { - return kvp.first->name() == key; - }); - - if (it == controlMap.end()) - throw std::runtime_error("Control " + key + " not found"); - - const auto &id = it->first; - auto obj = py::cast(hval); - - controlList.set(id->id(), pyToControlValue(obj, id->type())); - } - - int ret = self.start(&controlList); - if (ret) { - self.requestCompleted.disconnect(handleRequestCompleted); - return ret; - } - - return 0; - }, py::arg("controls") = py::dict()) - - .def("stop", [](Camera &self) { - int ret = self.stop(); - if (ret) - return ret; - - self.requestCompleted.disconnect(handleRequestCompleted); - - return 0; - }) - - .def("__str__", [](Camera &self) { - return ""; - }) - - /* Keep the camera alive, as StreamConfiguration contains a Stream* */ - .def("generate_configuration", &Camera::generateConfiguration, py::keep_alive<0, 1>()) - .def("configure", &Camera::configure) - - .def("create_request", &Camera::createRequest, py::arg("cookie") = 0) - - .def("queue_request", [](Camera &self, Request *req) { - py::object py_req = py::cast(req); - - /* - * Increase the reference count, will be dropped in - * CameraManager.get_ready_requests(). - */ - - py_req.inc_ref(); - - int ret = self.queueRequest(req); - if (ret) - py_req.dec_ref(); - - return ret; - }) - - .def_property_readonly("streams", [](Camera &self) { - py::set set; - for (auto &s : self.streams()) { - py::object py_self = py::cast(self); - py::object py_s = py::cast(s); - py::detail::keep_alive_impl(py_s, py_self); - set.add(py_s); - } - return set; - }) - - .def("find_control", [](Camera &self, const std::string &name) { - const auto &controls = self.controls(); - - auto it = std::find_if(controls.begin(), controls.end(), - [&name](const auto &kvp) { - return kvp.first->name() == name; - }); - - if (it == controls.end()) - throw std::runtime_error("Control '" + name + "' not found"); - - return it->first; - }, py::return_value_policy::reference_internal) - - .def_property_readonly("controls", [](Camera &self) { - py::dict ret; - - for (const auto &[id, ci] : self.controls()) { - ret[id->name().c_str()] = std::make_tuple(controlValueToPy(ci.min()), - controlValueToPy(ci.max()), - controlValueToPy(ci.def())); - } - - return ret; - }) - - .def_property_readonly("properties", [](Camera &self) { - py::dict ret; - - for (const auto &[key, cv] : self.properties()) { - const ControlId *id = properties::properties.at(key); - py::object ob = controlValueToPy(cv); - - ret[id->name().c_str()] = ob; - } - - return ret; - }); - - pyCameraConfiguration - .def("__iter__", [](CameraConfiguration &self) { - return py::make_iterator(self); - }, py::keep_alive<0, 1>()) - .def("__len__", [](CameraConfiguration &self) { - return self.size(); - }) - .def("validate", &CameraConfiguration::validate) - .def("at", py::overload_cast(&CameraConfiguration::at), - py::return_value_policy::reference_internal) - .def_property_readonly("size", &CameraConfiguration::size) - .def_property_readonly("empty", &CameraConfiguration::empty) - .def_readwrite("transform", &CameraConfiguration::transform); - - pyCameraConfigurationStatus - .value("Valid", CameraConfiguration::Valid) - .value("Adjusted", CameraConfiguration::Adjusted) - .value("Invalid", CameraConfiguration::Invalid); - - pyStreamConfiguration - .def("__str__", &StreamConfiguration::toString) - .def_property_readonly("stream", &StreamConfiguration::stream, - py::return_value_policy::reference_internal) - .def_readwrite("size", &StreamConfiguration::size) - .def_readwrite("pixel_format", &StreamConfiguration::pixelFormat) - .def_readwrite("stride", &StreamConfiguration::stride) - .def_readwrite("frame_size", &StreamConfiguration::frameSize) - .def_readwrite("buffer_count", &StreamConfiguration::bufferCount) - .def_property_readonly("formats", &StreamConfiguration::formats, - py::return_value_policy::reference_internal) - .def_readwrite("color_space", &StreamConfiguration::colorSpace); - - pyStreamFormats - .def_property_readonly("pixel_formats", &StreamFormats::pixelformats) - .def("sizes", &StreamFormats::sizes) - .def("range", &StreamFormats::range); - - pyFrameBufferAllocator - .def(py::init>(), py::keep_alive<1, 2>()) - .def("allocate", &FrameBufferAllocator::allocate) - .def_property_readonly("allocated", &FrameBufferAllocator::allocated) - /* Create a list of FrameBuffers, where each FrameBuffer has a keep-alive to FrameBufferAllocator */ - .def("buffers", [](FrameBufferAllocator &self, Stream *stream) { - py::object py_self = py::cast(self); - py::list l; - for (auto &ub : self.buffers(stream)) { - py::object py_buf = py::cast(ub.get(), py::return_value_policy::reference_internal, py_self); - l.append(py_buf); - } - return l; - }); - - pyFrameBuffer - /* \todo implement FrameBuffer::Plane properly */ - .def(py::init([](std::vector> planes, unsigned int cookie) { - std::vector v; - for (const auto &t : planes) - v.push_back({ SharedFD(std::get<0>(t)), FrameBuffer::Plane::kInvalidOffset, std::get<1>(t) }); - return new FrameBuffer(v, cookie); - })) - .def_property_readonly("metadata", &FrameBuffer::metadata, py::return_value_policy::reference_internal) - .def_property_readonly("num_planes", [](const FrameBuffer &self) { - return self.planes().size(); - }) - .def("length", [](FrameBuffer &self, uint32_t idx) { - const FrameBuffer::Plane &plane = self.planes()[idx]; - return plane.length; - }) - .def("fd", [](FrameBuffer &self, uint32_t idx) { - const FrameBuffer::Plane &plane = self.planes()[idx]; - return plane.fd.get(); - }) - .def("offset", [](FrameBuffer &self, uint32_t idx) { - const FrameBuffer::Plane &plane = self.planes()[idx]; - return plane.offset; - }) - .def_property("cookie", &FrameBuffer::cookie, &FrameBuffer::setCookie); - - pyStream - .def_property_readonly("configuration", &Stream::configuration); - - pyControlId - .def_property_readonly("id", &ControlId::id) - .def_property_readonly("name", &ControlId::name) - .def_property_readonly("type", &ControlId::type); - - pyRequest - /* \todo Fence is not supported, so we cannot expose addBuffer() directly */ - .def("add_buffer", [](Request &self, const Stream *stream, FrameBuffer *buffer) { - return self.addBuffer(stream, buffer); - }, py::keep_alive<1, 3>()) /* Request keeps Framebuffer alive */ - .def_property_readonly("status", &Request::status) - .def_property_readonly("buffers", &Request::buffers) - .def_property_readonly("cookie", &Request::cookie) - .def_property_readonly("has_pending_buffers", &Request::hasPendingBuffers) - .def("set_control", [](Request &self, ControlId &id, py::object value) { - self.controls().set(id.id(), pyToControlValue(value, id.type())); - }) - .def_property_readonly("metadata", [](Request &self) { - py::dict ret; - - for (const auto &[key, cv] : self.metadata()) { - const ControlId *id = controls::controls.at(key); - py::object ob = controlValueToPy(cv); - - ret[id->name().c_str()] = ob; - } - - return ret; - }) - /* - * \todo As we add a keep_alive to the fb in addBuffers(), we - * can only allow reuse with ReuseBuffers. - */ - .def("reuse", [](Request &self) { self.reuse(Request::ReuseFlag::ReuseBuffers); }); - - pyRequestStatus - .value("Pending", Request::RequestPending) - .value("Complete", Request::RequestComplete) - .value("Cancelled", Request::RequestCancelled); - - pyRequestReuse - .value("Default", Request::ReuseFlag::Default) - .value("ReuseBuffers", Request::ReuseFlag::ReuseBuffers); - - pyFrameMetadata - .def_readonly("status", &FrameMetadata::status) - .def_readonly("sequence", &FrameMetadata::sequence) - .def_readonly("timestamp", &FrameMetadata::timestamp) - /* \todo Implement FrameMetadata::Plane properly */ - .def_property_readonly("bytesused", [](FrameMetadata &self) { - std::vector v; - v.resize(self.planes().size()); - transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; }); - return v; - }); - - pyFrameMetadataStatus - .value("Success", FrameMetadata::FrameSuccess) - .value("Error", FrameMetadata::FrameError) - .value("Cancelled", FrameMetadata::FrameCancelled); - - pyTransform - .def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) { - bool ok; - - Transform t = transformFromRotation(rotation, &ok); - if (!ok) - throw std::invalid_argument("Invalid rotation"); - - if (hflip) - t ^= Transform::HFlip; - if (vflip) - t ^= Transform::VFlip; - if (transpose) - t ^= Transform::Transpose; - return t; - }), py::arg("rotation") = 0, py::arg("hflip") = false, - py::arg("vflip") = false, py::arg("transpose") = false) - .def(py::init([](Transform &other) { return other; })) - .def("__str__", [](Transform &self) { - return ""; - }) - .def_property("hflip", - [](Transform &self) { - return !!(self & Transform::HFlip); - }, - [](Transform &self, bool hflip) { - if (hflip) - self |= Transform::HFlip; - else - self &= ~Transform::HFlip; - }) - .def_property("vflip", - [](Transform &self) { - return !!(self & Transform::VFlip); - }, - [](Transform &self, bool vflip) { - if (vflip) - self |= Transform::VFlip; - else - self &= ~Transform::VFlip; - }) - .def_property("transpose", - [](Transform &self) { - return !!(self & Transform::Transpose); - }, - [](Transform &self, bool transpose) { - if (transpose) - self |= Transform::Transpose; - else - self &= ~Transform::Transpose; - }) - .def("inverse", [](Transform &self) { return -self; }) - .def("invert", [](Transform &self) { - self = -self; - }) - .def("compose", [](Transform &self, Transform &other) { - self = self * other; - }); - - pyColorSpace - .def(py::init([](ColorSpace::Primaries primaries, - ColorSpace::TransferFunction transferFunction, - ColorSpace::YcbcrEncoding ycbcrEncoding, - ColorSpace::Range range) { - return ColorSpace(primaries, transferFunction, ycbcrEncoding, range); - }), py::arg("primaries"), py::arg("transferFunction"), - py::arg("ycbcrEncoding"), py::arg("range")) - .def(py::init([](ColorSpace &other) { return other; })) - .def("__str__", [](ColorSpace &self) { - return ""; - }) - .def_readwrite("primaries", &ColorSpace::primaries) - .def_readwrite("transferFunction", &ColorSpace::transferFunction) - .def_readwrite("ycbcrEncoding", &ColorSpace::ycbcrEncoding) - .def_readwrite("range", &ColorSpace::range) - .def_static("Raw", []() { return ColorSpace::Raw; }) - .def_static("Jpeg", []() { return ColorSpace::Jpeg; }) - .def_static("Srgb", []() { return ColorSpace::Srgb; }) - .def_static("Smpte170m", []() { return ColorSpace::Smpte170m; }) - .def_static("Rec709", []() { return ColorSpace::Rec709; }) - .def_static("Rec2020", []() { return ColorSpace::Rec2020; }); - - pyColorSpacePrimaries - .value("Raw", ColorSpace::Primaries::Raw) - .value("Smpte170m", ColorSpace::Primaries::Smpte170m) - .value("Rec709", ColorSpace::Primaries::Rec709) - .value("Rec2020", ColorSpace::Primaries::Rec2020); - - pyColorSpaceTransferFunction - .value("Linear", ColorSpace::TransferFunction::Linear) - .value("Srgb", ColorSpace::TransferFunction::Srgb) - .value("Rec709", ColorSpace::TransferFunction::Rec709); - - pyColorSpaceYcbcrEncoding - .value("Null", ColorSpace::YcbcrEncoding::None) - .value("Rec601", ColorSpace::YcbcrEncoding::Rec601) - .value("Rec709", ColorSpace::YcbcrEncoding::Rec709) - .value("Rec2020", ColorSpace::YcbcrEncoding::Rec2020); - - pyColorSpaceRange - .value("Full", ColorSpace::Range::Full) - .value("Limited", ColorSpace::Range::Limited); - - pyPixelFormat - .def(py::init<>()) - .def(py::init()) - .def(py::init<>([](const std::string &str) { - return PixelFormat::fromString(str); - })) - .def_property_readonly("fourcc", &PixelFormat::fourcc) - .def_property_readonly("modifier", &PixelFormat::modifier) - .def(py::self == py::self) - .def("__str__", &PixelFormat::toString) - .def("__repr__", [](const PixelFormat &self) { - return "libcamera.PixelFormat('" + self.toString() + "')"; - }); -} -- cgit v1.2.1