diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index b9812d95e94b5e2af57850e44cc753fcb58575f0..3413d23d97f5d03eb5efea85dfc8ae9150946fba 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -137,6 +137,7 @@ ftl::gui::Camera::Camera(ftl::gui::Screen *screen, ftl::rgbd::Source *src) : scr
 	sdepth_ = false;
 	ftime_ = (float)glfwGetTime();
 	pause_ = false;
+	videowriter_ = std::nullopt;
 
 	channel_ = Channel::Left;
 
@@ -488,6 +489,12 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 			//imageSize = Vector2f(rgb.cols,rgb.rows);
 			texture1_.update(im1_);
 		}
+
+		if (videowriter_) {
+			cv::Mat image;
+			cv::flip(im1_, image, 0);
+			videowriter_.value().write(image);
+		}
 	}
 
 	return texture1_;
@@ -503,6 +510,21 @@ void ftl::gui::Camera::snapshot() {
 	cv::imwrite(std::string(timestamp) + ".png", image);
 }
 
+void ftl::gui::Camera::toggleVideoRecording() {
+	if (videowriter_) {
+		videowriter_.value().release();
+		videowriter_ = std::nullopt;
+	} else {
+		char timestamp[18];
+		std::time_t t = std::time(NULL);
+		std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
+		videowriter_ = std::optional<cv::VideoWriter>(cv::VideoWriter(std::string(timestamp) + ".avi",
+													cv::VideoWriter::fourcc('M','J','P','G'),
+													screen_->root()->value("fps", 20),
+													cv::Size(width(), height())));
+	}
+}
+
 nlohmann::json ftl::gui::Camera::getMetaData() {
 	return nlohmann::json();
 }
diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp
index cc874305c36daa9918ce3360f07f2da64a71b6e9..156d8bb35b3b071388e551114a5afa884a57b08d 100644
--- a/applications/gui/src/camera.hpp
+++ b/applications/gui/src/camera.hpp
@@ -53,6 +53,8 @@ class Camera {
 
 	void snapshot();
 
+	void toggleVideoRecording();
+
 	nlohmann::json getMetaData();
 
 	StatisticsImage *stats_ = nullptr;
@@ -87,6 +89,7 @@ class Camera {
 	ftl::codecs::Channels channels_;
 	cv::Mat im1_; // first channel (left)
 	cv::Mat im2_; // second channel ("right")
+	std::optional<cv::VideoWriter> videowriter_;
 
 	MUTEX mutex_;
 
diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp
index 639b1c50de9b0ee74f71f673323097e07f09c2fc..fa2fa18bd3c335d576ea9cead9f555ca0e921fd2 100644
--- a/applications/gui/src/media_panel.cpp
+++ b/applications/gui/src/media_panel.cpp
@@ -47,6 +47,15 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen) : nanogui::Window(screen, ""),
 		screen_->activeCamera()->snapshot();
 	});
 	itembutton = new Button(recordpopup, "2D video recording");
+	itembutton->setFlags(Button::ToggleButton);
+	itembutton->setChangeCallback([this,recordbutton](bool state) {
+		screen_->activeCamera()->toggleVideoRecording();
+		if (state) {
+			recordbutton->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
+		} else {
+			recordbutton->setTextColor(nanogui::Color(1.0f,1.0f,1.0f,1.0f));
+		}
+	});
 	itembutton = new Button(recordpopup, "Depth video recording");
 	itembutton->setFlags(Button::ToggleButton);
 	itembutton->setChangeCallback([this,recordbutton](bool state) {