blind

suckless command-line video editing utility
git clone git://git.suckless.org/blind
Log | Files | Refs | README | LICENSE

blind-from-video.c (7228B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include "common.h"
      3 
      4 USAGE("[-F pixel-format] [-r frame-rate] [-w width -h height] [-dL] input-file [output-file]")
      5 
      6 static int draft = 0;
      7 static void (*convert_segment)(char *buf, size_t n, int fd, const char *file);
      8 
      9 static void
     10 read_metadata(FILE *fp, char *fname, size_t *width, size_t *height)
     11 {
     12 	char *line = NULL;
     13 	size_t size = 0;
     14 	ssize_t len;
     15 	char *p;
     16 
     17 	while ((len = getline(&line, &size, fp)) != -1) {
     18 		if (len && line[len - 1])
     19 			line[--len] = '\0';
     20 		p = strchr(line, '=') + 1;
     21 		if (strstr(line, "width=") == line) {
     22 			if (tozu(p, 1, SIZE_MAX, width))
     23 				eprintf("invalid width: %s\n", p);
     24 		} else if (strstr(line, "height=") == line) {
     25 			if (tozu(p, 1, SIZE_MAX, height))
     26 				eprintf("invalid height: %s\n", p);
     27 		}
     28 	}
     29 
     30 	if (ferror(fp))
     31 		eprintf("getline %s:", fname);
     32 	free(line);
     33 
     34 	if (!*width || !*height)
     35 		eprintf("could not get all required metadata\n");
     36 }
     37 
     38 static void
     39 get_metadata(char *file, size_t *width, size_t *height)
     40 {
     41 	FILE *fp;
     42 	int fd, pipe_rw[2];
     43 	pid_t pid;
     44 	int status;
     45 
     46 	epipe(pipe_rw);
     47 	pid = efork();
     48 
     49 	if (!pid) {
     50 		pdeath(SIGKILL);
     51 		fd = eopen(file, O_RDONLY);
     52 		edup2(fd, STDIN_FILENO);
     53 		close(fd);
     54 		close(pipe_rw[0]);
     55 		edup2(pipe_rw[1], STDOUT_FILENO);
     56 		close(pipe_rw[1]);
     57 		eexeclp("ffprobe", "ffprobe", "-v", "quiet", "-show_streams",
     58 			"-select_streams", "v", "-", NULL);
     59 	}
     60 
     61 	close(pipe_rw[1]);
     62 	fp = fdopen(pipe_rw[0], "rb");
     63 	if (!fp)
     64 		eprintf("fdopen <subprocess>:");
     65 	read_metadata(fp, file, width, height);
     66 	fclose(fp);
     67 	close(pipe_rw[0]);
     68 
     69 	ewaitpid(pid, &status, 0);
     70 	if (status)
     71 		exit(1);
     72 }
     73 
     74 #define CONVERT_SEGMENT(TYPE)\
     75 	do {\
     76 		typedef TYPE pixel_t[4];\
     77 		size_t i, ptr;\
     78 		TYPE y, u, v, max = (TYPE)0xFF00L, ymax = (TYPE)0xDAF4L;\
     79 		TYPE r, g, b;\
     80 		pixel_t pixels[1024];\
     81 		uint16_t *pix;\
     82 		if (draft) {\
     83 			for (ptr = i = 0; ptr < n; ptr += 8) {\
     84 				pix = (uint16_t *)(buf + ptr);\
     85 				pixels[i][3] = 1;\
     86 				y = (TYPE)((long int)(le16toh(pix[1])) - 0x1001L);\
     87 				u = (TYPE)((long int)(le16toh(pix[2])) - 0x8000L);\
     88 				v = (TYPE)((long int)(le16toh(pix[3])) - 0x8000L);\
     89 				scaled_yuv_to_ciexyz(y, u, v, pixels[i] + 0,\
     90 						     pixels[i] + 1, pixels[i] + 2);\
     91 				if (++i == 1024) {\
     92 					i = 0;\
     93 					ewriteall(fd, pixels, sizeof(pixels), file);\
     94 				}\
     95 			}\
     96 		} else {\
     97 			for (ptr = i = 0; ptr < n; ptr += 8) {\
     98 				pix = (uint16_t *)(buf + ptr);\
     99 				pixels[i][3] = le16toh(pix[0]) / max;\
    100 				pixels[i][3] = CLIP(0, pixels[i][3], 1);\
    101 				y = (TYPE)((long int)le16toh(pix[1]) - 0x1001L) / ymax;\
    102 				u = (TYPE)((long int)le16toh(pix[2]) - 0x8000L) / max;\
    103 				v = (TYPE)((long int)le16toh(pix[3]) - 0x8000L) / max;\
    104 				yuv_to_srgb(y, u, v, &r, &g, &b);\
    105 				r = srgb_decode(r);\
    106 				g = srgb_decode(g);\
    107 				b = srgb_decode(b);\
    108 				srgb_to_ciexyz(r, g, b, pixels[i] + 0, pixels[i] + 1, pixels[i] + 2);\
    109 				if (++i == 1024) {\
    110 					i = 0;\
    111 					ewriteall(fd, pixels, sizeof(pixels), file);\
    112 				}\
    113 			}\
    114 		}\
    115 		if (i)\
    116 			ewriteall(fd, pixels, i * sizeof(*pixels), file);\
    117 	} while (0)
    118 
    119 static void convert_segment_xyza (char *buf, size_t n, int fd, const char *file) {CONVERT_SEGMENT(double);}
    120 static void convert_segment_xyzaf(char *buf, size_t n, int fd, const char *file) {CONVERT_SEGMENT(float);}
    121 
    122 static void
    123 convert(const char *infile, int outfd, const char *outfile, size_t width, size_t height, const char *frame_rate)
    124 {
    125 	char geometry[2 * INTSTRLEN(size_t) + 2], buf[BUFSIZ];
    126 	const char *cmd[13];
    127 	int status, pipe_rw[2];
    128 	size_t i = 0, n, ptr;
    129 	pid_t pid;
    130 
    131 	cmd[i++] = "ffmpeg";
    132 	cmd[i++] = "-i", cmd[i++] = infile;
    133 	cmd[i++] = "-f", cmd[i++] = "rawvideo";
    134 	cmd[i++] = "-pix_fmt", cmd[i++] = "ayuv64le";
    135 	if (width && height) {
    136 		sprintf(geometry, "%zux%zu", width, height);
    137 		cmd[i++] = "-s:v", cmd[i++] = geometry;
    138 	}
    139 	if (frame_rate)
    140 		cmd[i++] = "-r", cmd[i++] = frame_rate;
    141 	cmd[i++] = "-";
    142 	cmd[i++] = NULL;
    143 
    144 	epipe(pipe_rw);
    145 	pid = efork();
    146 
    147 	if (!pid) {
    148 		pdeath(SIGKILL);
    149 		close(pipe_rw[0]);
    150 		edup2(pipe_rw[1], STDOUT_FILENO);
    151 		close(pipe_rw[1]);
    152 		eexecvp("ffmpeg", (char **)(void *)cmd);
    153 	}
    154 
    155 	close(pipe_rw[1]);
    156 
    157 	if (convert_segment) {
    158 		for (ptr = 0;;) {
    159 			if (!(n = eread(pipe_rw[0], buf + ptr, sizeof(buf) - ptr, "<subprocess>")))
    160 				break;
    161 			ptr += n;
    162 			n = ptr - (ptr % 8);
    163 			convert_segment(buf, n, outfd, outfile);
    164 			memmove(buf, buf + n, ptr -= n);
    165 		}
    166 		if (ptr)
    167 			eprintf("<subprocess>: incomplete frame\n");
    168 	} else {
    169 		while ((n = eread(pipe_rw[0], buf, sizeof(buf), "<subprocess>")))
    170 			ewriteall(outfd, buf, (size_t)n, outfile);
    171 	}
    172 
    173 	close(pipe_rw[0]);
    174 	ewaitpid(pid, &status, 0);
    175 	if (status)
    176 		exit(1);
    177 }
    178 
    179 int
    180 main(int argc, char *argv[])
    181 {
    182 	size_t width = 0, height = 0, frames;
    183 	char head[STREAM_HEAD_MAX];
    184 	char *frame_rate = NULL;
    185 	char *infile;
    186 	const char *outfile;
    187 	char *data;
    188 	const char *pixfmt = "xyza";
    189 	ssize_t headlen;
    190 	size_t length, frame_size, pixel_size;
    191 	int outfd, skip_length = 0;
    192 	struct stat st;
    193 
    194 	ARGBEGIN {
    195 	case 'd':
    196 		draft = 1;
    197 		break;
    198 	case 'L':
    199 		skip_length = 1;
    200 		break;
    201 	case 'F':
    202 		pixfmt = UARGF();
    203 		break;
    204 	case 'r':
    205 		frame_rate = UARGF();
    206 		break;
    207 	case 'w':
    208 		width = etozu_flag('w', UARGF(), 1, SIZE_MAX);
    209 		break;
    210 	case 'h':
    211 		height = etozu_flag('h', UARGF(), 1, SIZE_MAX);
    212 		break;
    213 	default:
    214 		usage();
    215 	} ARGEND;
    216 
    217 	if (argc < 1 || argc > 2 || !width != !height)
    218 		usage();
    219 
    220 	infile = argv[0];
    221 	outfile = argv[1] ? argv[1] : "-";
    222 
    223 	pixfmt = get_pixel_format(pixfmt, "xyza");
    224 	if (!strcmp(pixfmt, "xyza")) {
    225 		convert_segment = convert_segment_xyza;
    226 		pixel_size = 4 * sizeof(double);
    227 	} else if (!strcmp(pixfmt, "xyza f")) {
    228 		convert_segment = convert_segment_xyzaf;
    229 		pixel_size = 4 * sizeof(float);
    230 	} else if (!strcmp(pixfmt, "raw0")) {
    231 		convert_segment = NULL;
    232 		pixel_size = 4 * sizeof(uint16_t);
    233 	} else {
    234 		eprintf("pixel format %s is not supported, try xyza or raw0 and blind-convert\n", pixfmt);
    235 	}
    236 
    237 	if (!width)
    238 		get_metadata(infile, &width, &height);
    239 	if (width > SIZE_MAX / height)
    240 		eprintf("video frame too large\n");
    241 	frame_size = width * height;
    242 	if (pixel_size > SIZE_MAX / frame_size)
    243 		eprintf("video frame too large\n");
    244 	frame_size *= pixel_size;
    245 
    246 	if (!strcmp(outfile, "-")) {
    247 		outfile = "<stdout>";
    248 		outfd = STDOUT_FILENO;
    249 		if (!skip_length)
    250 			eprintf("standard out as output file is only allowed with -L\n");
    251 	} else {
    252 		outfd = eopen(outfile, O_RDWR | O_CREAT | O_TRUNC, 0666);
    253 	}
    254 
    255 	if (skip_length) {
    256 		SPRINTF_HEAD_ZN(head, 0, width, height, pixfmt, &headlen);
    257 		ewriteall(outfd, head, (size_t)headlen, outfile);
    258 	}
    259 
    260 	convert(infile, outfd, outfile, width, height, frame_rate);
    261 
    262 	if (outfd == STDOUT_FILENO)
    263 		return 0;
    264 
    265 	if (fstat(outfd, &st))
    266 		eprintf("fstat %s:", outfile);
    267 	length = (size_t)(st.st_size);
    268 
    269 	if (skip_length)
    270 		length -= (size_t)headlen;
    271 	if (length % frame_size)
    272 		eprintf("<subprocess>: incomplete frame\n");
    273 	frames = length / frame_size;
    274 
    275 	if (!skip_length) {
    276 		SPRINTF_HEAD_ZN(head, frames, width, height, pixfmt, &headlen);
    277 		ewriteall(outfd, head, (size_t)headlen, outfile);
    278 		data = mmap(0, length + (size_t)headlen, PROT_READ | PROT_WRITE, MAP_SHARED, outfd, 0);
    279 		memmove(data + headlen, data, length);
    280 		memcpy(data, head, (size_t)headlen);
    281 		munmap(data, length + (size_t)headlen);
    282 	}
    283 
    284 	close(outfd);
    285 	return 0;
    286 }