diff --git a/src/qcam/cam_select_dialog.h b/src/qcam/cam_select_dialog.h
index 6ba61cff..fc29b46a 100644
--- a/src/qcam/cam_select_dialog.h
+++ b/src/qcam/cam_select_dialog.h
@@ -18,16 +18,19 @@
 #include <QComboBox>
 #include <QDialog>
 #include <QDialogButtonBox>
+#include <QFileDialog>
 #include <QFormLayout>
 #include <QLabel>
+#include <QPushButton>
 #include <QString>
 
 class CamSelectDialog : public QDialog
 {
 	Q_OBJECT
 public:
-	CamSelectDialog(libcamera::CameraManager *cameraManager, QWidget *parent)
-		: QDialog(parent), cm_(cameraManager)
+	CamSelectDialog(libcamera::CameraManager *cameraManager,
+			bool isScriptRunning, QWidget *parent)
+		: QDialog(parent), cm_(cameraManager), isScriptRunning_(isScriptRunning)
 	{
 		/* Use a QFormLayout for the dialog. */
 		QFormLayout *camSelectDialogLayout = new QFormLayout(this);
@@ -48,6 +51,16 @@ public:
 					cameraIdComboBox_->currentText().toStdString()));
 			});
 
+		captureScriptButton_ = new QPushButton;
+		connect(captureScriptButton_, &QPushButton::clicked,
+			this, &CamSelectDialog::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 *dialogButtonBox =
 			new QDialogButtonBox(QDialogButtonBox::Ok |
@@ -62,6 +75,7 @@ public:
 		camSelectDialogLayout->addRow("Camera: ", cameraIdComboBox_);
 		camSelectDialogLayout->addRow("Location: ", cameraLocation_);
 		camSelectDialogLayout->addRow("Model: ", cameraModel_);
+		camSelectDialogLayout->addRow("Capture Script: ", captureScriptButton_);
 		camSelectDialogLayout->addWidget(dialogButtonBox);
 	}
 
@@ -72,6 +86,11 @@ public:
 		return cameraIdComboBox_->currentText().toStdString();
 	}
 
+	std::string getCaptureScript()
+	{
+		return scriptPath_;
+	}
+
 	/* Hotplug / Unplug Support. */
 	void cameraAdded(libcamera::Camera *camera)
 	{
@@ -122,11 +141,35 @@ public:
 			cameraModel_->setText(QString());
 	}
 
+	/* Capture script support. */
+	void handleCaptureScriptButton()
+	{
+		if (isScriptRunning_) {
+			Q_EMIT stopCaptureScript();
+			isScriptRunning_ = false;
+			captureScriptButton_->setText("Open");
+		} else {
+			scriptPath_ = QFileDialog::getOpenFileName(this, "Run Capture Script",
+								   QDir::currentPath(), "Capture Script (*.yaml)")
+					      .toStdString();
+
+			if (!scriptPath_.empty())
+				captureScriptButton_->setText("Loaded");
+		}
+	}
+
+Q_SIGNALS:
+	void stopCaptureScript();
+
 private:
 	libcamera::CameraManager *cm_;
 
+	bool isScriptRunning_;
+	std::string scriptPath_;
+
 	/* 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 549af024..e0219e5e 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>
@@ -151,6 +153,9 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
 		return;
 	}
 
+	/* Start capture script. */
+	loadCaptureScript();
+
 	startStopAction_->setChecked(true);
 }
 
@@ -290,14 +295,55 @@ void MainWindow::switchCamera()
 	startStopAction_->setChecked(true);
 }
 
+void MainWindow::stopCaptureScript()
+{
+	if (script_) {
+		script_.reset();
+	}
+}
+
+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();
+
+		QMessageBox::critical(this, "Invalid Script",
+				      "Couldn't load the capture script");
+	}
+
+	/* Start capture again if we were capturing before. */
+	if (wasCapturing)
+		toggleCapture(true);
+}
+
 std::string MainWindow::chooseCamera()
 {
-	camSelectDialog_ = new CamSelectDialog(cm_, this);
+	bool scriptRunning = script_ != nullptr;
+	camSelectDialog_ = new CamSelectDialog(cm_, scriptRunning, this);
+
+	connect(camSelectDialog_, &CamSelectDialog::stopCaptureScript,
+		this, &MainWindow::stopCaptureScript);
 
 	if (camSelectDialog_->exec() == QDialog::Accepted) {
 		std::string cameraId = camSelectDialog_->getCameraId();
 		cameraSwitchButton_->setText(QString::fromStdString(cameraId));
 
+		scriptPath_ = camSelectDialog_->getCaptureScript();
+		loadCaptureScript();
+
 		return cameraId;
 	} else
 		return std::string();
@@ -504,6 +550,7 @@ int MainWindow::startCapture()
 	previousFrames_ = 0;
 	framesCaptured_ = 0;
 	lastBufferTime_ = 0;
+	queueCount_ = 0;
 
 	ret = camera_->start();
 	if (ret) {
@@ -783,5 +830,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 7982939e..01466e28 100644
--- a/src/qcam/main_window.h
+++ b/src/qcam/main_window.h
@@ -28,6 +28,7 @@
 #include <QQueue>
 #include <QTimer>
 
+#include "../cam/capture_script.h"
 #include "../cam/stream_options.h"
 
 #include "cam_select_dialog.h"
@@ -90,6 +91,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 70615e92..80c97cc5 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',
@@ -38,6 +39,7 @@ qcam_resources = files([
 qcam_deps = [
     libatomic,
     libcamera_public,
+    libyaml,
     qt5_dep,
 ]
 
