diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index 38f6c5f779ec2c23d334bd31ee3f5c5a65727777..9d8dac421d0189c978dbc0312302f2b34349945c 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -524,11 +524,8 @@ const GLTexture &ftl::gui::Camera::captureFrame() {
 	return texture1_;
 }
 
-void ftl::gui::Camera::snapshot() {
+void ftl::gui::Camera::snapshot(const std::string &filename) {
 	UNIQUE_LOCK(mutex_, lk);
-	char timestamp[18];
-	std::time_t t = std::time(NULL);
-	std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
 	cv::Mat blended;
 	cv::Mat visualized = visualizeActiveChannel();
 	if (!visualized.empty()) {
@@ -539,7 +536,7 @@ void ftl::gui::Camera::snapshot() {
 	}
 	cv::Mat flipped;
 	cv::flip(blended, flipped, 0);
-	cv::imwrite(std::string(timestamp) + ".png", flipped);
+	cv::imwrite(filename, flipped);
 }
 
 void ftl::gui::Camera::startVideoRecording(const std::string &filename) {
diff --git a/applications/gui/src/camera.hpp b/applications/gui/src/camera.hpp
index fe477f624ed42c8fdca4535355b914a4676540e4..90914cf599942107f8d7bbba8b8f5a9505f815e9 100644
--- a/applications/gui/src/camera.hpp
+++ b/applications/gui/src/camera.hpp
@@ -52,7 +52,7 @@ class Camera {
 
 	bool thumbnail(cv::Mat &thumb);
 
-	void snapshot();
+	void snapshot(const std::string &filename);
 
 	void startVideoRecording(const std::string &filename);
 
diff --git a/applications/gui/src/media_panel.cpp b/applications/gui/src/media_panel.cpp
index c576f28f63cd4edc35a56768f50f27c2818631a2..1a6f288ce788f1b808c2eb0b42515b5b8ca37c83 100644
--- a/applications/gui/src/media_panel.cpp
+++ b/applications/gui/src/media_panel.cpp
@@ -47,23 +47,10 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 	recordpopup->setAnchorHeight(150);
 	auto itembutton = new Button(recordpopup, "2D snapshot (.png)");
 	itembutton->setCallback([this,recordbutton]() {
-		screen_->activeCamera()->snapshot();
-		recordbutton->setPushed(false);
-	});
-	itembutton = new Button(recordpopup, "3D snapshot (.ftl)");
-	itembutton->setCallback([this,recordbutton]() {
-		auto tag = screen_->activeCamera()->source()->get<std::string>("uri");
-		if (tag) {
-			auto tagvalue = tag.value();
-			auto configurables = ftl::config::findByTag(tagvalue);
-			if (configurables.size() > 0) {
-				ftl::Configurable *configurable = configurables[0];
-				char timestamp[18];
-				std::time_t t=std::time(NULL);
-				std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-				configurable->set("3D-snapshot", std::string(timestamp) + ".ftl");
-			}
-		}
+		char timestamp[18];
+		std::time_t t=std::time(NULL);
+		std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
+		screen_->activeCamera()->snapshot(std::string(timestamp) + ".png");
 		recordbutton->setPushed(false);
 	});
 	itembutton = new Button(recordpopup, "Virtual camera recording (.ftl)");
@@ -76,24 +63,26 @@ MediaPanel::MediaPanel(ftl::gui::Screen *screen, ftl::gui::SourceWindow *sourceW
 		recordbutton->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
 		recordbutton->setPushed(false);
 	});
+	itembutton = new Button(recordpopup, "3D scene snapshot (.ftl)");
+	itembutton->setCallback([this,recordbutton]() {
+		char timestamp[18];
+		std::time_t t=std::time(NULL);
+		std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
+		snapshot3D(screen_->activeCamera(), std::string(timestamp) + ".ftl");
+		recordbutton->setPushed(false);
+	});
 	itembutton = new Button(recordpopup, "3D scene recording (.ftl)");
 	itembutton->setCallback([this,recordbutton]() {
-		auto tag = screen_->activeCamera()->source()->get<std::string>("uri");
-		if (tag) {
-			auto tagvalue = tag.value();
-			auto configurables = ftl::config::findByTag(tagvalue);
-			if (configurables.size() > 0) {
-				ftl::Configurable *configurable = configurables[0];
-				configurable->set("record", true);
-				recordbutton->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
-				sceneRecording_ = std::optional<ftl::Configurable*>(configurable);
-			}
-		}
+		char timestamp[18];
+		std::time_t t=std::time(NULL);
+		std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
+		startRecording3D(screen_->activeCamera(), std::string(timestamp) + ".ftl");
+		recordbutton->setTextColor(nanogui::Color(1.0f,0.1f,0.1f,1.0f));
 		recordbutton->setPushed(false);
 	});
 	itembutton = new Button(recordpopup, "Detailed recording options");
 	itembutton->setCallback([this,sourceWindow,recordbutton] {
-		auto record_window = new RecordWindow(screen_, sourceWindow->getCameras(), this);
+		auto record_window = new RecordWindow(screen_, screen_, sourceWindow->getCameras(), this);
 		record_window->setTheme(screen_->windowtheme);
 		recordbutton->setPushed(false);
 	});
@@ -308,4 +297,30 @@ void MediaPanel::cameraChanged() {
 void MediaPanel::toggleVirtualCameraRecording(ftl::gui::Camera *camera, const std::string &filename) {
 	camera->startVideoRecording(filename);
 	virtualCameraRecording_ = std::optional<ftl::gui::Camera*>(camera);
+}
+
+void MediaPanel::snapshot3D(ftl::gui::Camera *camera, const std::string &filename) {
+	auto tag = camera->source()->get<std::string>("uri");
+	if (tag) {
+		auto tagvalue = tag.value();
+		auto configurables = ftl::config::findByTag(tagvalue);
+		if (configurables.size() > 0) {
+			ftl::Configurable *configurable = configurables[0];
+			configurable->set("3D-snapshot", filename);
+		}
+	}
+}
+
+void MediaPanel::startRecording3D(ftl::gui::Camera *camera, const std::string &filename) {
+	auto tag = camera->source()->get<std::string>("uri");
+	if (tag) {
+		auto tagvalue = tag.value();
+		auto configurables = ftl::config::findByTag(tagvalue);
+		if (configurables.size() > 0) {
+			ftl::Configurable *configurable = configurables[0];
+			configurable->set("record-name", filename);
+			configurable->set("record", true);
+			sceneRecording_ = std::optional<ftl::Configurable*>(configurable);
+		}
+	}
 }
\ No newline at end of file
diff --git a/applications/gui/src/media_panel.hpp b/applications/gui/src/media_panel.hpp
index b7e5957cca4e6c171972013448229df8bd364f4c..a71ee81838d87bd9af4183e60e45dd4c7dfd92b1 100644
--- a/applications/gui/src/media_panel.hpp
+++ b/applications/gui/src/media_panel.hpp
@@ -26,6 +26,10 @@ class MediaPanel : public nanogui::Window {
 
 	void toggleVirtualCameraRecording(ftl::gui::Camera *camera, const std::string &filename);
 
+	void snapshot3D(ftl::gui::Camera *camera, const std::string &filename);
+
+	void startRecording3D(ftl::gui::Camera *camera, const std::string &filename);
+
 	private:
 	ftl::gui::Screen *screen_;
 
diff --git a/applications/gui/src/record_window.cpp b/applications/gui/src/record_window.cpp
index 82b333bc9cd0b568697725b8b3e564a9df73c82f..50cdf9d3ed7e0a5277a52b976108deae4c82adc0 100644
--- a/applications/gui/src/record_window.cpp
+++ b/applications/gui/src/record_window.cpp
@@ -1,5 +1,7 @@
 #include "record_window.hpp"
 
+#include "screen.hpp"
+
 #include <ftl/codecs/channels.hpp>
 
 #include <nanogui/layout.h>
@@ -11,7 +13,7 @@
 
 using ftl::gui::RecordWindow;
 
-RecordWindow::RecordWindow(nanogui::Widget *parent, const std::vector<ftl::gui::Camera *> &streams, ftl::gui::MediaPanel *media_panel)
+RecordWindow::RecordWindow(nanogui::Widget *parent, ftl::gui::Screen *screen, const std::vector<ftl::gui::Camera *> &streams, ftl::gui::MediaPanel *media_panel)
         : nanogui::Window(parent, "Recording options") {
     using namespace nanogui;
 
@@ -54,23 +56,42 @@ RecordWindow::RecordWindow(nanogui::Widget *parent, const std::vector<ftl::gui::
     new Label(recording2D, "Select channel (in addition to Left)", "sans-bold");
     auto recordingChannel = recording2D->add<ComboBox>();
     streamSelect->setCallback([this,streams,snapshotChannel,recordingChannel](int ix) {
-        channels_ = std::vector<std::string>();
+        channels_ = std::vector<ftl::codecs::Channel>();
+        channel_names_ = std::vector<std::string>();
         ftl::codecs::Channels availableChannels = streams[ix]->availableChannels();
         for (auto c : availableChannels) {
-            channels_.push_back(ftl::codecs::name(c));
+            channels_.push_back(c);
+            channel_names_.push_back(ftl::codecs::name(c));
         }
-            snapshotChannel->setItems(channels_);
-            recordingChannel->setItems(channels_);
+        snapshotChannel->setItems(channel_names_);
+        recordingChannel->setItems(channel_names_);
     });
 
     Widget *actionButtons = new Widget(this);
     actionButtons->setLayout(new BoxLayout(Orientation::Horizontal));
     auto button = new Button(actionButtons, "Start");
-    button->setCallback([streams,streamSelect,media_panel,fileName]() {
+    button->setCallback([this,streams,streamSelect,screen,media_panel,fileName,tabWidget,snapshot2D,recording2D,snapshot3D,recording3D,snapshotChannel,recordingChannel]() {
         // Check the chosen stream type and channels, then record them.
         auto stream = streams[streamSelect->selectedIndex()];
-        // TODO: If the camera isn't active, no frames are currently received.
-        media_panel->toggleVirtualCameraRecording(stream, fileName->value());
+        auto tab = tabWidget->tab(tabWidget->activeTab());
+        if (tab == snapshot2D) {
+            // If the channel isn't the same as the one that was active
+            // previously, the channel won't have an image.
+            // One possible solution is to set the channels immediately
+            // as they are chosen in the recording window.
+            // Alternatively, 2D snapshots might not need a channel option at all.
+            stream->setChannel(channels_[snapshotChannel->selectedIndex()]);
+            stream->snapshot(fileName->value());
+        } else if (tab == recording2D) {
+            stream->setChannel(channels_[recordingChannel->selectedIndex()]);
+            screen->setActiveCamera(stream);
+            media_panel->toggleVirtualCameraRecording(stream, fileName->value());
+        } else if (tab == snapshot3D) {
+            media_panel->snapshot3D(stream, fileName->value());
+        } else if (tab == recording3D) {
+            media_panel->startRecording3D(stream, fileName->value());
+        }
+        dispose();
     });
     button = new Button(actionButtons, "Cancel");
     button->setCallback([this]() {
diff --git a/applications/gui/src/record_window.hpp b/applications/gui/src/record_window.hpp
index 4396c04569e908ee58bc64fdc9c0cca94a488c97..5a9b28fef85b5ef2832b7a43b2db1be2b09aa202 100644
--- a/applications/gui/src/record_window.hpp
+++ b/applications/gui/src/record_window.hpp
@@ -8,11 +8,12 @@ namespace gui {
 
 class RecordWindow : public nanogui::Window {
     public:
-    explicit RecordWindow(nanogui::Widget *parent, const std::vector<ftl::gui::Camera *> &streams, ftl::gui::MediaPanel *media_panel);
+    explicit RecordWindow(nanogui::Widget *parent, ftl::gui::Screen *screen, const std::vector<ftl::gui::Camera *> &streams, ftl::gui::MediaPanel *media_panel);
     ~RecordWindow();
 
     private:
-    std::vector<std::string> channels_;
+    std::vector<ftl::codecs::Channel> channels_;
+    std::vector<std::string> channel_names_;
 };
 
 }
diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp
index 66d6e3189eb3b2ed13dbe86b8f7f47a2cf4905cb..3a5807171ec9d83057291d32e6ba20c027609647 100644
--- a/applications/reconstruct/src/main.cpp
+++ b/applications/reconstruct/src/main.cpp
@@ -294,7 +294,7 @@ static void run(ftl::Configurable *root) {
 			char timestamp[18];
 			std::time_t t=std::time(NULL);
 			std::strftime(timestamp, sizeof(timestamp), "%F-%H%M%S", std::localtime(&t));
-			fileout.open(std::string(timestamp) + ".ftl");
+			fileout.open(e.entity->value<std::string>("record-name", std::string(timestamp) + ".ftl"));
 
 			writer.begin();
 			group->addRawCallback(std::function(recorder));