[libcamera-devel,v8,6/8] qcam: CamSelectDialog: Add capture script button
diff mbox series

Message ID 20220810150349.414043-7-utkarsh02t@gmail.com
State Superseded
Headers show
Series
  • Introduce capture scripts to qcam
Related show

Commit Message

Utkarsh Tiwari Aug. 10, 2022, 3:03 p.m. UTC
Implement an Capture Script in CamSelectDialog button which would allow
the user to open a Capture Script (*.yaml).
This button has three states :
    - Open Capture Script
    - Loaded
    - Stop the execution of current capture script

When clicked in an open state, present the user with a QFileDialog to
allow selecting a single file. When the script is loaded the button
displays "Loaded", the script has not been verified yet. Verifying the
script and executing it happens after user presses Ok.

Introduce a queueCount_ to keep track of the requests queued.

When stopping the execution of the capture script the queueCount_ is not
reset and the capture continues as it is (i.e it is not stopped or
restarted).

Requests are queued with any controls the script matching the current
queueCount_.

Signed-off-by: Utkarsh Tiwari <utkarsh02t@gmail.com>
---
Difference from v7:
	1. Fix grammetical errors in the commit message
	2. Intialize the cameraSelectorDialog_ to nullptr in Construct
		so we construct the CameraSelectorDialog just once
 src/qcam/cam_select_dialog.cpp | 69 ++++++++++++++++++++++++++++++++--
 src/qcam/cam_select_dialog.h   | 20 +++++++++-
 src/qcam/main_window.cpp       | 59 ++++++++++++++++++++++++++++-
 src/qcam/main_window.h         |  7 ++++
 src/qcam/meson.build           |  2 +
 5 files changed, 152 insertions(+), 5 deletions(-)

Comments

Utkarsh Tiwari Aug. 11, 2022, 6:16 a.m. UTC | #1
On Wed, Aug 10, 2022 at 08:33:47PM +0530, Utkarsh Tiwari wrote:
> Implement an Capture Script in CamSelectDialog button which would allow
> the user to open a Capture Script (*.yaml).
> This button has three states :
>     - Open Capture Script
>     - Loaded
>     - Stop the execution of current capture script
> 
> When clicked in an open state, present the user with a QFileDialog to
> allow selecting a single file. When the script is loaded the button
> displays "Loaded", the script has not been verified yet. Verifying the
> script and executing it happens after user presses Ok.
> 
> Introduce a queueCount_ to keep track of the requests queued.
> 
> When stopping the execution of the capture script the queueCount_ is not
> reset and the capture continues as it is (i.e it is not stopped or
> restarted).
> 
> Requests are queued with any controls the script matching the current
> queueCount_.
> 
> Signed-off-by: Utkarsh Tiwari <utkarsh02t@gmail.com>
> ---
> Difference from v7:
> 	1. Fix grammetical errors in the commit message
> 	2. Intialize the cameraSelectorDialog_ to nullptr in Construct
> 		so we construct the CameraSelectorDialog just once
>  src/qcam/cam_select_dialog.cpp | 69 ++++++++++++++++++++++++++++++++--
>  src/qcam/cam_select_dialog.h   | 20 +++++++++-
>  src/qcam/main_window.cpp       | 59 ++++++++++++++++++++++++++++-
>  src/qcam/main_window.h         |  7 ++++
>  src/qcam/meson.build           |  2 +
>  5 files changed, 152 insertions(+), 5 deletions(-)
> 
> diff --git a/src/qcam/cam_select_dialog.cpp b/src/qcam/cam_select_dialog.cpp
> index f97ad6eb..0db0a5bd 100644
> --- a/src/qcam/cam_select_dialog.cpp
> +++ b/src/qcam/cam_select_dialog.cpp
> @@ -16,12 +16,13 @@
>  #include <QComboBox>
>  #include <QDialog>
>  #include <QDialogButtonBox>
> +#include <QFileDialog>
>  #include <QFormLayout>
>  #include <QString>
>  
>  CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManager,
> -					   QWidget *parent)
> -	: QDialog(parent), cm_(cameraManager)
> +					   bool isScriptRunning, QWidget *parent)
> +	: QDialog(parent), cm_(cameraManager), isScriptRunning_(isScriptRunning)
>  {
>  	/* Use a QFormLayout for the dialog. */
>  	QFormLayout *layout = new QFormLayout(this);
> @@ -39,6 +40,16 @@ CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManag
>  	connect(cameraIdComboBox_, &QComboBox::currentTextChanged,
>  		this, &CameraSelectorDialog::handleCameraChange);
>  
> +	captureScriptButton_ = new QPushButton;
> +	connect(captureScriptButton_, &QPushButton::clicked,
> +		this, &CameraSelectorDialog::handleCaptureScriptButton);
> +
> +	/* Display the action that would be performed when button is clicked. */
> +	if (isScriptRunning_)
> +		captureScriptButton_->setText("Stop");
> +	else
> +		captureScriptButton_->setText("Open");
> +
>  	/* Setup the QDialogButton Box */
>  	QDialogButtonBox *buttonBox =
>  		new QDialogButtonBox(QDialogButtonBox::Ok |
> @@ -50,10 +61,10 @@ CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManag
>  		this, &QDialog::reject);
>  
>  	/* Set the layout. */
> -
>  	layout->addRow("Camera:", cameraIdComboBox_);
>  	layout->addRow("Location:", cameraLocation_);
>  	layout->addRow("Model:", cameraModel_);
> +	layout->addRow("Capture Script:", captureScriptButton_);
>  	layout->addWidget(buttonBox);
>  }
>  
> @@ -62,6 +73,11 @@ std::string CameraSelectorDialog::getCameraId()
>  	return cameraIdComboBox_->currentText().toStdString();
>  }
>  
> +std::string CameraSelectorDialog::getCaptureScript()
> +{
> +	return scriptPath_;
> +}
> +
>  /* Hotplug / Unplug Support. */
>  void CameraSelectorDialog::cameraAdded(libcamera::Camera *camera)
>  {
> @@ -115,3 +131,50 @@ void CameraSelectorDialog::updateCamInfo(const std::shared_ptr<libcamera::Camera
>  
>  	cameraModel_->setText(QString::fromStdString(model));
>  }
> +
> +/* Capture script support. */
> +void CameraSelectorDialog::handleCaptureScriptButton()
> +{
> +	if (isScriptRunning_) {
> +		Q_EMIT stopCaptureScript();
> +		isScriptRunning_ = false;
> +		captureScriptButton_->setText("Open");
> +	} else {
> +		selectedScriptPath_ = QFileDialog::getOpenFileName(this,
> +								   "Run Capture Script", QDir::currentPath(),
> +								   "Capture Script (*.yaml)")
> +					      .toStdString();
> +
> +		if (!selectedScriptPath_.empty())
> +			captureScriptButton_->setText("Loaded");
> +		else
> +			captureScriptButton_->setText("Open");
> +	}
> +}
> +
> +void CameraSelectorDialog::accept()
> +{
> +	scriptPath_ = selectedScriptPath_;
> +	QDialog::accept();
> +}
> +
> +void CameraSelectorDialog::reject()
> +{
> +	if (isScriptRunning_)
> +		selectedScriptPath_ = scriptPath_;
> +	QDialog::reject();
> +}
> +
> +void CameraSelectorDialog::informScriptReset()
> +{
> +	isScriptRunning_ = false;
> +	scriptPath_.clear();

add this to also reset any selected files already this 
is necessary when dealing with interactive controls
	selectedScriptPath_.clear();
	captureWidgetLayout_->removeWidget(scriptPathLabel_);

> +	captureScriptButton_->setText("Open");
> +}
> +
> +void CameraSelectorDialog::informScriptRunning(std::string scriptPath)
> +{
> +	isScriptRunning_ = true;
> +	scriptPath_ = scriptPath;
> +	captureScriptButton_->setText("Stop");
> +}
> diff --git a/src/qcam/cam_select_dialog.h b/src/qcam/cam_select_dialog.h
> index 16475af6..bbdf897e 100644
> --- a/src/qcam/cam_select_dialog.h
> +++ b/src/qcam/cam_select_dialog.h
> @@ -17,18 +17,21 @@
>  #include <QComboBox>
>  #include <QDialog>
>  #include <QLabel>
> +#include <QPushButton>
>  
>  class CameraSelectorDialog : public QDialog
>  {
>  	Q_OBJECT
>  public:
>  	CameraSelectorDialog(libcamera::CameraManager *cameraManager,
> -			     QWidget *parent);
> +			     bool isScriptRunning, QWidget *parent);
>  
>  	~CameraSelectorDialog() = default;
>  
>  	std::string getCameraId();
>  
> +	std::string getCaptureScript();
> +
>  	/* Hotplug / Unplug Support. */
>  	void cameraAdded(libcamera::Camera *camera);
>  
> @@ -38,11 +41,26 @@ public:
>  	void updateCamInfo(const std::shared_ptr<libcamera::Camera> &camera);
>  	void handleCameraChange();
>  
> +	/* Capture script support. */
> +	void handleCaptureScriptButton();
> +	void informScriptReset();
> +	void informScriptRunning(std::string scriptPath);
> +	void accept() override;
> +	void reject() override;
> +
> +Q_SIGNALS:
> +	void stopCaptureScript();
> +
>  private:
>  	libcamera::CameraManager *cm_;
>  
> +	bool isScriptRunning_;
> +	std::string scriptPath_;
> +	std::string selectedScriptPath_;
> +
>  	/* UI elements. */
>  	QComboBox *cameraIdComboBox_;
>  	QLabel *cameraLocation_;
>  	QLabel *cameraModel_;
> +	QPushButton *captureScriptButton_;
>  };
> diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
> index bf40572a..3c7c3173 100644
> --- a/src/qcam/main_window.cpp
> +++ b/src/qcam/main_window.cpp
> @@ -9,6 +9,7 @@
>  
>  #include <assert.h>
>  #include <iomanip>
> +#include <memory>
>  #include <string>
>  
>  #include <libcamera/camera_manager.h>
> @@ -19,6 +20,7 @@
>  #include <QFileDialog>
>  #include <QImage>
>  #include <QImageWriter>
> +#include <QMessageBox>
>  #include <QMutexLocker>
>  #include <QStandardPaths>
>  #include <QStringList>
> @@ -152,6 +154,9 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
>  		return;
>  	}
>  
> +	/* Start capture script. */
> +	loadCaptureScript();
> +
>  	startStopAction_->setChecked(true);
>  }
>  
> @@ -290,11 +295,54 @@ void MainWindow::switchCamera()
>  	startStopAction_->setChecked(true);
>  }
>  
> +void MainWindow::stopCaptureScript()
> +{
> +	if (script_) {
> +		script_.reset();
> +		cameraSelectorDialog_->informScriptReset();
> +	}
> +}
> +
> +void MainWindow::loadCaptureScript()
> +{
> +	if (scriptPath_.empty() || camera_ == nullptr)
> +		return;
> +
> +	script_ = std::make_unique<CaptureScript>(camera_, scriptPath_);
> +
> +	/*
> +	 * If we are already capturing, stop so we don't have stuck image
> +	 * in viewfinder.
> +	 */
> +	bool wasCapturing = isCapturing_;
> +	if (isCapturing_)
> +		toggleCapture(false);
> +
> +	if (!script_->valid()) {
> +		script_.reset();
> +		cameraSelectorDialog_->informScriptReset();
> +
> +		QMessageBox::critical(this, "Invalid Script",
> +				      "Couldn't load the capture script");
> +
> +	} else
> +		cameraSelectorDialog_->informScriptRunning(scriptPath_);
> +
> +	/* Start capture again if we were capturing before. */
> +	if (wasCapturing)
> +		toggleCapture(true);
> +}
> +
>  std::string MainWindow::chooseCamera()
>  {
> +	bool scriptRunning = script_ != nullptr;
> +
>  	/* Construct the selection dialog, only the first time. */
>  	if (!cameraSelectorDialog_)
> -		cameraSelectorDialog_ = new CameraSelectorDialog(cm_, this);
> +		cameraSelectorDialog_ = new CameraSelectorDialog(cm_, scriptRunning, this);
> +
> +	connect(cameraSelectorDialog_, &CameraSelectorDialog::stopCaptureScript,
> +		this, &MainWindow::stopCaptureScript);
>  
>  	/*
>  	 * Use the camera specified on the command line, if any, or display the
> @@ -309,6 +357,9 @@ std::string MainWindow::chooseCamera()
>  		std::string cameraId = cameraSelectorDialog_->getCameraId();
>  		cameraSelectButton_->setText(QString::fromStdString(cameraId));
>  
> +		scriptPath_ = cameraSelectorDialog_->getCaptureScript();
> +		loadCaptureScript();
> +
>  		return cameraId;
>  	} else
>  		return std::string();
> @@ -506,6 +557,7 @@ int MainWindow::startCapture()
>  	previousFrames_ = 0;
>  	framesCaptured_ = 0;
>  	lastBufferTime_ = 0;
> +	queueCount_ = 0;
>  
>  	ret = camera_->start();
>  	if (ret) {
> @@ -783,5 +835,10 @@ void MainWindow::renderComplete(FrameBuffer *buffer)
>  
>  int MainWindow::queueRequest(Request *request)
>  {
> +	if (script_)
> +		request->controls() = script_->frameControls(queueCount_);
> +
> +	queueCount_++;
> +
>  	return camera_->queueRequest(request);
>  }
> diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
> index d161365a..10994b67 100644
> --- a/src/qcam/main_window.h
> +++ b/src/qcam/main_window.h
> @@ -27,6 +27,7 @@
>  #include <QQueue>
>  #include <QTimer>
>  
> +#include "../cam/capture_script.h"
>  #include "../cam/stream_options.h"
>  
>  #include "cam_select_dialog.h"
> @@ -89,6 +90,9 @@ private:
>  	void processHotplug(HotplugEvent *e);
>  	void processViewfinder(libcamera::FrameBuffer *buffer);
>  
> +	void loadCaptureScript();
> +	void stopCaptureScript();
> +
>  	/* UI elements */
>  	QToolBar *toolbar_;
>  	QAction *startStopAction_;
> @@ -130,6 +134,9 @@ private:
>  	QElapsedTimer frameRateInterval_;
>  	uint32_t previousFrames_;
>  	uint32_t framesCaptured_;
> +	uint32_t queueCount_;
>  
>  	std::vector<std::unique_ptr<libcamera::Request>> requests_;
> +	std::unique_ptr<CaptureScript> script_;
> +	std::string scriptPath_;
>  };
> diff --git a/src/qcam/meson.build b/src/qcam/meson.build
> index 61861ea6..70a18d7e 100644
> --- a/src/qcam/meson.build
> +++ b/src/qcam/meson.build
> @@ -15,6 +15,7 @@ endif
>  qcam_enabled = true
>  
>  qcam_sources = files([
> +    '../cam/capture_script.cpp',
>      '../cam/image.cpp',
>      '../cam/options.cpp',
>      '../cam/stream_options.cpp',
> @@ -39,6 +40,7 @@ qcam_resources = files([
>  qcam_deps = [
>      libatomic,
>      libcamera_public,
> +    libyaml,
>      qt5_dep,
>  ]
>  
> -- 
> 2.25.1
>

Patch
diff mbox series

diff --git a/src/qcam/cam_select_dialog.cpp b/src/qcam/cam_select_dialog.cpp
index f97ad6eb..0db0a5bd 100644
--- a/src/qcam/cam_select_dialog.cpp
+++ b/src/qcam/cam_select_dialog.cpp
@@ -16,12 +16,13 @@ 
 #include <QComboBox>
 #include <QDialog>
 #include <QDialogButtonBox>
+#include <QFileDialog>
 #include <QFormLayout>
 #include <QString>
 
 CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManager,
-					   QWidget *parent)
-	: QDialog(parent), cm_(cameraManager)
+					   bool isScriptRunning, QWidget *parent)
+	: QDialog(parent), cm_(cameraManager), isScriptRunning_(isScriptRunning)
 {
 	/* Use a QFormLayout for the dialog. */
 	QFormLayout *layout = new QFormLayout(this);
@@ -39,6 +40,16 @@  CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManag
 	connect(cameraIdComboBox_, &QComboBox::currentTextChanged,
 		this, &CameraSelectorDialog::handleCameraChange);
 
+	captureScriptButton_ = new QPushButton;
+	connect(captureScriptButton_, &QPushButton::clicked,
+		this, &CameraSelectorDialog::handleCaptureScriptButton);
+
+	/* Display the action that would be performed when button is clicked. */
+	if (isScriptRunning_)
+		captureScriptButton_->setText("Stop");
+	else
+		captureScriptButton_->setText("Open");
+
 	/* Setup the QDialogButton Box */
 	QDialogButtonBox *buttonBox =
 		new QDialogButtonBox(QDialogButtonBox::Ok |
@@ -50,10 +61,10 @@  CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManag
 		this, &QDialog::reject);
 
 	/* Set the layout. */
-
 	layout->addRow("Camera:", cameraIdComboBox_);
 	layout->addRow("Location:", cameraLocation_);
 	layout->addRow("Model:", cameraModel_);
+	layout->addRow("Capture Script:", captureScriptButton_);
 	layout->addWidget(buttonBox);
 }
 
@@ -62,6 +73,11 @@  std::string CameraSelectorDialog::getCameraId()
 	return cameraIdComboBox_->currentText().toStdString();
 }
 
+std::string CameraSelectorDialog::getCaptureScript()
+{
+	return scriptPath_;
+}
+
 /* Hotplug / Unplug Support. */
 void CameraSelectorDialog::cameraAdded(libcamera::Camera *camera)
 {
@@ -115,3 +131,50 @@  void CameraSelectorDialog::updateCamInfo(const std::shared_ptr<libcamera::Camera
 
 	cameraModel_->setText(QString::fromStdString(model));
 }
+
+/* Capture script support. */
+void CameraSelectorDialog::handleCaptureScriptButton()
+{
+	if (isScriptRunning_) {
+		Q_EMIT stopCaptureScript();
+		isScriptRunning_ = false;
+		captureScriptButton_->setText("Open");
+	} else {
+		selectedScriptPath_ = QFileDialog::getOpenFileName(this,
+								   "Run Capture Script", QDir::currentPath(),
+								   "Capture Script (*.yaml)")
+					      .toStdString();
+
+		if (!selectedScriptPath_.empty())
+			captureScriptButton_->setText("Loaded");
+		else
+			captureScriptButton_->setText("Open");
+	}
+}
+
+void CameraSelectorDialog::accept()
+{
+	scriptPath_ = selectedScriptPath_;
+	QDialog::accept();
+}
+
+void CameraSelectorDialog::reject()
+{
+	if (isScriptRunning_)
+		selectedScriptPath_ = scriptPath_;
+	QDialog::reject();
+}
+
+void CameraSelectorDialog::informScriptReset()
+{
+	isScriptRunning_ = false;
+	scriptPath_.clear();
+	captureScriptButton_->setText("Open");
+}
+
+void CameraSelectorDialog::informScriptRunning(std::string scriptPath)
+{
+	isScriptRunning_ = true;
+	scriptPath_ = scriptPath;
+	captureScriptButton_->setText("Stop");
+}
diff --git a/src/qcam/cam_select_dialog.h b/src/qcam/cam_select_dialog.h
index 16475af6..bbdf897e 100644
--- a/src/qcam/cam_select_dialog.h
+++ b/src/qcam/cam_select_dialog.h
@@ -17,18 +17,21 @@ 
 #include <QComboBox>
 #include <QDialog>
 #include <QLabel>
+#include <QPushButton>
 
 class CameraSelectorDialog : public QDialog
 {
 	Q_OBJECT
 public:
 	CameraSelectorDialog(libcamera::CameraManager *cameraManager,
-			     QWidget *parent);
+			     bool isScriptRunning, QWidget *parent);
 
 	~CameraSelectorDialog() = default;
 
 	std::string getCameraId();
 
+	std::string getCaptureScript();
+
 	/* Hotplug / Unplug Support. */
 	void cameraAdded(libcamera::Camera *camera);
 
@@ -38,11 +41,26 @@  public:
 	void updateCamInfo(const std::shared_ptr<libcamera::Camera> &camera);
 	void handleCameraChange();
 
+	/* Capture script support. */
+	void handleCaptureScriptButton();
+	void informScriptReset();
+	void informScriptRunning(std::string scriptPath);
+	void accept() override;
+	void reject() override;
+
+Q_SIGNALS:
+	void stopCaptureScript();
+
 private:
 	libcamera::CameraManager *cm_;
 
+	bool isScriptRunning_;
+	std::string scriptPath_;
+	std::string selectedScriptPath_;
+
 	/* UI elements. */
 	QComboBox *cameraIdComboBox_;
 	QLabel *cameraLocation_;
 	QLabel *cameraModel_;
+	QPushButton *captureScriptButton_;
 };
diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
index bf40572a..3c7c3173 100644
--- a/src/qcam/main_window.cpp
+++ b/src/qcam/main_window.cpp
@@ -9,6 +9,7 @@ 
 
 #include <assert.h>
 #include <iomanip>
+#include <memory>
 #include <string>
 
 #include <libcamera/camera_manager.h>
@@ -19,6 +20,7 @@ 
 #include <QFileDialog>
 #include <QImage>
 #include <QImageWriter>
+#include <QMessageBox>
 #include <QMutexLocker>
 #include <QStandardPaths>
 #include <QStringList>
@@ -152,6 +154,9 @@  MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
 		return;
 	}
 
+	/* Start capture script. */
+	loadCaptureScript();
+
 	startStopAction_->setChecked(true);
 }
 
@@ -290,11 +295,54 @@  void MainWindow::switchCamera()
 	startStopAction_->setChecked(true);
 }
 
+void MainWindow::stopCaptureScript()
+{
+	if (script_) {
+		script_.reset();
+		cameraSelectorDialog_->informScriptReset();
+	}
+}
+
+void MainWindow::loadCaptureScript()
+{
+	if (scriptPath_.empty() || camera_ == nullptr)
+		return;
+
+	script_ = std::make_unique<CaptureScript>(camera_, scriptPath_);
+
+	/*
+	 * If we are already capturing, stop so we don't have stuck image
+	 * in viewfinder.
+	 */
+	bool wasCapturing = isCapturing_;
+	if (isCapturing_)
+		toggleCapture(false);
+
+	if (!script_->valid()) {
+		script_.reset();
+		cameraSelectorDialog_->informScriptReset();
+
+		QMessageBox::critical(this, "Invalid Script",
+				      "Couldn't load the capture script");
+
+	} else
+		cameraSelectorDialog_->informScriptRunning(scriptPath_);
+
+	/* Start capture again if we were capturing before. */
+	if (wasCapturing)
+		toggleCapture(true);
+}
+
 std::string MainWindow::chooseCamera()
 {
+	bool scriptRunning = script_ != nullptr;
+
 	/* Construct the selection dialog, only the first time. */
 	if (!cameraSelectorDialog_)
-		cameraSelectorDialog_ = new CameraSelectorDialog(cm_, this);
+		cameraSelectorDialog_ = new CameraSelectorDialog(cm_, scriptRunning, this);
+
+	connect(cameraSelectorDialog_, &CameraSelectorDialog::stopCaptureScript,
+		this, &MainWindow::stopCaptureScript);
 
 	/*
 	 * Use the camera specified on the command line, if any, or display the
@@ -309,6 +357,9 @@  std::string MainWindow::chooseCamera()
 		std::string cameraId = cameraSelectorDialog_->getCameraId();
 		cameraSelectButton_->setText(QString::fromStdString(cameraId));
 
+		scriptPath_ = cameraSelectorDialog_->getCaptureScript();
+		loadCaptureScript();
+
 		return cameraId;
 	} else
 		return std::string();
@@ -506,6 +557,7 @@  int MainWindow::startCapture()
 	previousFrames_ = 0;
 	framesCaptured_ = 0;
 	lastBufferTime_ = 0;
+	queueCount_ = 0;
 
 	ret = camera_->start();
 	if (ret) {
@@ -783,5 +835,10 @@  void MainWindow::renderComplete(FrameBuffer *buffer)
 
 int MainWindow::queueRequest(Request *request)
 {
+	if (script_)
+		request->controls() = script_->frameControls(queueCount_);
+
+	queueCount_++;
+
 	return camera_->queueRequest(request);
 }
diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
index d161365a..10994b67 100644
--- a/src/qcam/main_window.h
+++ b/src/qcam/main_window.h
@@ -27,6 +27,7 @@ 
 #include <QQueue>
 #include <QTimer>
 
+#include "../cam/capture_script.h"
 #include "../cam/stream_options.h"
 
 #include "cam_select_dialog.h"
@@ -89,6 +90,9 @@  private:
 	void processHotplug(HotplugEvent *e);
 	void processViewfinder(libcamera::FrameBuffer *buffer);
 
+	void loadCaptureScript();
+	void stopCaptureScript();
+
 	/* UI elements */
 	QToolBar *toolbar_;
 	QAction *startStopAction_;
@@ -130,6 +134,9 @@  private:
 	QElapsedTimer frameRateInterval_;
 	uint32_t previousFrames_;
 	uint32_t framesCaptured_;
+	uint32_t queueCount_;
 
 	std::vector<std::unique_ptr<libcamera::Request>> requests_;
+	std::unique_ptr<CaptureScript> script_;
+	std::string scriptPath_;
 };
diff --git a/src/qcam/meson.build b/src/qcam/meson.build
index 61861ea6..70a18d7e 100644
--- a/src/qcam/meson.build
+++ b/src/qcam/meson.build
@@ -15,6 +15,7 @@  endif
 qcam_enabled = true
 
 qcam_sources = files([
+    '../cam/capture_script.cpp',
     '../cam/image.cpp',
     '../cam/options.cpp',
     '../cam/stream_options.cpp',
@@ -39,6 +40,7 @@  qcam_resources = files([
 qcam_deps = [
     libatomic,
     libcamera_public,
+    libyaml,
     qt5_dep,
 ]