Bläddra i källkod

Implement external post processing

Martijn Braam 4 år sedan
förälder
incheckning
8863133fe2
4 ändrade filer med 165 tillägg och 5 borttagningar
  1. 21 0
      README.md
  2. 86 5
      main.c
  3. 4 0
      meson.build
  4. 54 0
      postprocess.sh

+ 21 - 0
README.md

@@ -48,3 +48,24 @@ These are the sections describing the sensors.
 * `rate=15` the refresh rate in fps to use for the sensor
 * `fmt=BGGR8` sets the pixel and bus formats used when capturing from the sensor, only BGGR8 is fully supported
 * `rotate=90` the rotation angle to make the sensor match the screen
+
+# Post processing
+
+Megapixels only captures raw frames and stores .dng files. It captures a 5 frame burst and saves it to a temporary
+location. Then the postprocessing script is run which will generate the final .jpg file and writes it into the 
+pictures directory. Megapixels looks for the post processing script in the following locations:
+
+* ./postprocess.sh
+* $XDG_CONFIG_DIR/megapixels/postprocess.sh
+* ~/.config/megapixels/postprocess.sh
+* /etc/megapixels/postprocess.sh
+* /usr/share/megapixels/postprocess.sh
+
+The bundled postprocess.sh script will copy the first frame of the burst into the picture directory as an DNG
+file and if dcraw and imagemagick are installed it will generate a JPG and also write that to the picture
+directory. It supports either the full dcraw or dcraw_emu from libraw.
+
+It is possible to write your own post processing pipeline my providing your own `postprocess.sh` script at
+one of the above locations. The first argument to the script is the directory containing the temporary 
+burst files and the second argument is the final path for the image without an extension. For more details
+see postprocess.sh in this repository.

+ 86 - 5
main.c

@@ -86,6 +86,9 @@ static int preview_height = -1;
 static char *last_path = NULL;
 static int auto_exposure = 1;
 static int auto_gain = 1;
+static int burst_length = 5;
+static char burst_dir[20];
+static char processing_script[512];
 
 // Widgets
 GtkWidget *preview;
@@ -404,6 +407,8 @@ process_image(const int *p, int size)
 	struct tm tim;
 	uint8_t *pixels;
 	char fname[255];
+	char fname_target[255];
+	char command[1024];
 	char timestamp[30];
 	char uniquecameramodel[255];
 	GdkPixbuf *pixbuf;
@@ -453,7 +458,9 @@ process_image(const int *p, int size)
 		tim = *(localtime(&rawtime));
 		strftime(timestamp, 30, "%Y%m%d%H%M%S", &tim);
 		strftime(datetime, 20, "%Y:%m:%d %H:%M:%S", &tim);
-		sprintf(fname, "%s/Pictures/IMG%s-%d.dng", getenv("HOME"), timestamp, capture);
+
+		sprintf(fname_target, "%s/Pictures/IMG%s", getenv("HOME"), timestamp);
+		sprintf(fname, "%s/%d.dng", burst_dir, burst_length - capture);
 
 		if(!(tif = TIFFOpen(fname, "w"))) {
 			printf("Could not open tiff\n");
@@ -516,7 +523,7 @@ process_image(const int *p, int size)
 			TIFFSetField(tif, TIFFTAG_BLACKLEVEL, 1, &current.blacklevel);
 		}
 		TIFFCheckpointDirectory(tif);
-		printf("Writing frame\n");
+		printf("Writing frame to %s\n", fname);
 		
 		unsigned char *pLine = (unsigned char*)malloc(current.width);
 		for(int row = 0; row < current.height; row++){
@@ -551,8 +558,8 @@ process_image(const int *p, int size)
 		TIFFClose(tif);
 
 
-		// Update the thumbnail if this is the last frame
 		if (capture == 0) {
+			// Update the thumbnail if this is the last frame
 			pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, current.width / (skip*2), current.height / (skip*2));
 			pixels = gdk_pixbuf_get_pixels(pixbuf);
 			quick_debayer_bggr8((const uint8_t *)p, pixels, current.width, current.height, skip);
@@ -573,6 +580,12 @@ process_image(const int *p, int size)
 				g_printerr("%s\n", error->message);
 				g_clear_error(&error);
 			}
+
+			// Start post-processing the captured burst
+			g_printerr("Post process %s to %s.ext\n", burst_dir, fname_target);
+			sprintf(command, "%s %s %s &", processing_script, burst_dir, fname_target);
+			system(command);
+
 		}
 	} 
 }
@@ -959,7 +972,18 @@ on_open_directory_clicked(GtkWidget *widget, gpointer user_data)
 void
 on_shutter_clicked(GtkWidget *widget, gpointer user_data)
 {
-	capture = 5;
+	char template[] = "/tmp/megapixels.XXXXXX";
+	char *tempdir;
+	tempdir = mkdtemp(template);
+
+	if (tempdir == NULL) {
+		g_printerr("Could not make capture directory %s\n", template);
+		exit (EXIT_FAILURE);
+	}
+
+	strcpy(burst_dir, tempdir);
+
+	capture = burst_length;
 }
 
 void
@@ -1065,12 +1089,69 @@ find_config(char *conffile)
 	return -1;
 }
 
+int
+find_processor(char *script)
+{
+	char *xdg_config_home;
+	char filename[] = "postprocess.sh";
+	wordexp_t exp_result;
+
+	// Resolve XDG stuff
+	if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
+		xdg_config_home = "~/.config";
+	}
+	wordexp(xdg_config_home, &exp_result, 0);
+	xdg_config_home = strdup(exp_result.we_wordv[0]);
+	wordfree(&exp_result);
+
+	// Check postprocess.h in the current working directory
+	sprintf(script, "%s", filename);
+	if(access(script, F_OK) != -1) {
+		sprintf(script, "./%s", filename);
+		printf("Found postprocessor script at %s\n", script);
+		return 0;
+	}
+
+	// Check for a script in XDG_CONFIG_HOME
+	sprintf(script, "%s/megapixels/%s", xdg_config_home, filename);
+	if(access(script, F_OK) != -1) {
+		printf("Found postprocessor script at %s\n", script);
+		return 0;
+	}
+
+	// Check user overridden /etc/megapixels/postprocessor.sh
+	sprintf(script, "%s/megapixels/%s", SYSCONFDIR, filename);
+	if(access(script, F_OK) != -1) {
+		printf("Found postprocessor script at %s\n", script);
+		return 0;
+	}
+
+	// Check packaged /usr/share/megapixels/postprocessor.sh
+	sprintf(script, "%s/megapixels/%s", DATADIR, filename);
+	if(access(script, F_OK) != -1) {
+		printf("Found postprocessor script at %s\n", script);
+		return 0;
+	}
+
+	return -1;
+}
+
 int
 main(int argc, char *argv[])
 {
+	int ret;
 	char conffile[512];
 
-	find_config(conffile);
+	ret = find_config(conffile);
+	if (ret) {
+		g_printerr("Could not find any config file\n");
+		return ret;
+	}
+	ret = find_processor(processing_script);
+	if (ret) {
+		g_printerr("Could not find any post-process script\n");
+		return ret;
+	}
 
 	TIFFSetTagExtender(register_custom_tiff_tags);
 

+ 4 - 0
meson.build

@@ -31,3 +31,7 @@ install_data([
   'config/pine64,pinetab.ini',
   ],
   install_dir : get_option('datadir') / 'megapixels/config/')
+
+install_data(['postprocess.sh'],
+  install_dir : get_option('datadir') / 'megapixels/',
+  install_mode: 'rwxr-xr-x')

+ 54 - 0
postprocess.sh

@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# The post-processing script gets called after taking a burst of
+# pictures into a temporary directory. The first argument is the
+# directory containing the raw files in the burst. The contents
+# are 1.dng, 2.dng.... up to the number of photos in the burst.
+#
+# The second argument is the filename for the final photo without
+# the extension, like "/home/user/Pictures/IMG202104031234" 
+#
+# The post-processing script is responsible for cleaning up
+# temporary directory for the burst.
+
+if [ "$#" -ne 2 ]; then
+	echo "Usage: $0 [burst-dir] [target-name]"
+	exit 2
+fi
+
+BURST_DIR="$1"
+TARGET_NAME="$2"
+
+# Copy the first frame of the burst as the raw photo
+cp "$BURST_DIR"/1.dng "$TARGET_NAME.dng"
+
+# Create a .jpg if raw processing tools are installed
+DCRAW=""
+if command -v "dcraw_emu" &> /dev/null
+then
+	DCRAW=dcraw_emu
+fi
+if command -v "dcraw" &> /dev/null
+then
+	DCRAW=dcraw
+fi
+
+if [ -n "$DCRAW" ]; then
+	# +M		use embedded color matrix
+	# -H 4		Recover highlights by rebuilding them
+	# -o 1		Output in sRGB colorspace
+	# -q 3		Debayer with AHD algorithm
+	# -T		Output TIFF
+	# -fbdd 1	Raw denoising with FBDD
+	$DCRAW +M -H 4 -o 1 -q 3 -T -fbdd 1 $BURST_DIR/1.dng
+
+	if command -v convert &> /dev/null
+	then
+		convert "$BURST_DIR"/1.dng.tiff "$TARGET_NAME.jpg"
+	else
+		cp "$BURST_DIR"/1.dng.tiff "$TARGET_NAME.tiff"
+	fi
+fi
+
+# Clean up the temp dir containing the burst
+rm -rf "$BURST_DIR"