blind

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

stream.c (22533B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include "common.h"
      3 
      4 static inline int
      5 get_dimension(int status, size_t *out, const char *s, const char *fname, const char *dim)
      6 {
      7 	char *end;
      8 	errno = 0;
      9 	*out = strtoul(s, &end, 10);
     10 	if (errno == ERANGE && *s != '-')
     11 		enprintf(status, "%s: video is too %s\n", fname, dim);
     12 	return -(errno || *end);
     13 }
     14 
     15 static inline int
     16 sread(int status, struct stream *stream)
     17 {
     18 	ssize_t r;
     19 	r = read(stream->fd, stream->buf + stream->ptr, sizeof(stream->buf) - stream->ptr);
     20 	if (r < 0)
     21 		enprintf(status, "read %s:", stream->file);
     22 	if (r == 0)
     23 		return 0;
     24 	stream->ptr += (size_t)r;
     25 	return 1;
     26 }
     27 
     28 void
     29 eninit_stream(int status, struct stream *stream)
     30 {
     31 	size_t n;
     32 	char *p = NULL, *w, *h, *f;
     33 
     34 	fadvise_sequential(stream->fd, 0, 0);
     35 
     36 	if (stream->fd >= 0) {
     37 		for (stream->ptr = 0; !p; p = memchr(stream->buf, '\n', stream->ptr))
     38 			if (!sread(status, stream))
     39 				goto bad_format;
     40 	} else {
     41 		p = memchr(stream->buf, '\n', stream->ptr);
     42 	}
     43 
     44 	*p = '\0';
     45 	if (!(w = strchr(stream->buf, ' ')) ||
     46 	    !(h = strchr(w + 1, ' ')) ||
     47 	    !(f = strchr(h + 1, ' ')))
     48 		goto bad_format;
     49 	*w++ = *h++ = *f++ = '\0';
     50 
     51 	if (strlen(f) >= sizeof(stream->pixfmt))
     52 		goto bad_format;
     53 	strcpy(stream->pixfmt, f);
     54 	if (get_dimension(status, &stream->frames, stream->buf, stream->file, "long") ||
     55 	    get_dimension(status, &stream->width,  w,           stream->file, "wide") ||
     56 	    get_dimension(status, &stream->height, h,           stream->file, "tall"))
     57 		goto bad_format;
     58 
     59 	if (!stream->width)
     60 		eprintf("%s: width is zero\n", stream->file);
     61 	if (!stream->height)
     62 		eprintf("%s: height is zero\n", stream->file);
     63 
     64 	n = (size_t)(p - stream->buf) + 1;
     65 	memmove(stream->buf, stream->buf + n, stream->ptr -= n);
     66 	while (stream->ptr < 5)
     67 		if (!sread(status, stream))
     68 			goto bad_format;
     69 	if (stream->buf[0] != '\0' ||
     70 	    stream->buf[1] != 'u' || stream->buf[2] != 'i' ||
     71 	    stream->buf[3] != 'v' || stream->buf[4] != 'f')
     72 		goto bad_format;
     73 	memmove(stream->buf, stream->buf + 5, stream->ptr -= 5);
     74 	stream->headlen = n + 5;
     75 
     76 	enset_pixel_format(status, stream, NULL);
     77 
     78 	stream->xptr = 0;
     79 
     80 	return;
     81 bad_format:
     82 	enprintf(status, "%s: file format not supported\n", stream->file);
     83 }
     84 
     85 
     86 void
     87 enopen_stream(int status, struct stream *stream, const char *file)
     88 {
     89 	stream->file = file ? file : "<stdin>";
     90 	stream->fd = file ? enopen(status, file, O_RDONLY) : STDIN_FILENO;
     91 	eninit_stream(status, stream);
     92 }
     93 
     94 
     95 int
     96 set_pixel_format(struct stream *stream, const char *pixfmt)
     97 {
     98 #define TEST_ENCODING_AGNOSTIC(FMT) (!strcmp(stream->pixfmt, FMT) || !strcmp(stream->pixfmt, FMT" f"))
     99 
    100 	if (pixfmt) {
    101 		pixfmt = get_pixel_format(pixfmt, stream->pixfmt[0] ? stream->pixfmt : "xyza");
    102 		if (strlen(pixfmt) >= sizeof(stream->pixfmt))
    103 			return -1;
    104 		strcpy(stream->pixfmt, pixfmt);
    105 	}
    106 
    107 	stream->n_chan = 4;
    108 	stream->alpha = UNPREMULTIPLIED;
    109 	stream->encoding = DOUBLE;
    110 	stream->endian = HOST;
    111 	stream->alpha_chan = 3;
    112 	stream->luma_chan = -1;
    113 
    114 	if (!strcmp(stream->pixfmt, "xyza")) {
    115 		stream->space = CIEXYZ;
    116 	} else if (!strcmp(stream->pixfmt, "xyza f")) {
    117 		stream->space = CIEXYZ;
    118 		stream->encoding = FLOAT;
    119 	} else if (!strcmp(stream->pixfmt, "raw0")) {
    120 		stream->space = YUV_NONLINEAR;
    121 		stream->encoding = UINT16;
    122 		stream->endian = LITTLE;
    123 		stream->alpha_chan = 0;
    124 		stream->luma_chan = 1;
    125 	} else if (!strcmp(stream->pixfmt, "raw1")) {
    126 		stream->space = YUV_NONLINEAR;
    127 		stream->encoding = UINT16;
    128 		stream->endian = LITTLE;
    129 	} else if (!strcmp(stream->pixfmt, "raw2a") || !strcmp(stream->pixfmt, "raw2")) {
    130 		stream->space = YUV_NONLINEAR;
    131 		stream->alpha = stream->pixfmt[4] == 'a' ? UNPREMULTIPLIED : NO_ALPHA;
    132 		stream->encoding = UINT16;
    133 	} else if (TEST_ENCODING_AGNOSTIC("raw3") || TEST_ENCODING_AGNOSTIC("raw3a")) {
    134 		stream->space = YUV_NONLINEAR;
    135 		stream->alpha = stream->pixfmt[4] == 'a' ? UNPREMULTIPLIED : NO_ALPHA;
    136 		stream->encoding = strlen(stream->pixfmt) > 5 ? FLOAT : DOUBLE;
    137 	} else if (TEST_ENCODING_AGNOSTIC("raw4") || TEST_ENCODING_AGNOSTIC("raw4a")) {
    138 		stream->space = SRGB_NONLINEAR;
    139 		stream->alpha = stream->pixfmt[4] == 'a' ? UNPREMULTIPLIED : NO_ALPHA;
    140 		stream->encoding = strlen(stream->pixfmt) > 5 ? FLOAT : DOUBLE;
    141 	} else if (TEST_ENCODING_AGNOSTIC("raw5") || TEST_ENCODING_AGNOSTIC("raw5a")) {
    142 		stream->space = SRGB;
    143 		stream->alpha = stream->pixfmt[4] == 'a' ? UNPREMULTIPLIED : NO_ALPHA;
    144 		stream->encoding = strlen(stream->pixfmt) > 5 ? FLOAT : DOUBLE;
    145 	} else {
    146 		return -1;
    147 	}
    148 
    149 	if (stream->alpha == NO_ALPHA) {
    150 		stream->n_chan -= 1;
    151 		stream->alpha_chan = -1;
    152 	}
    153 
    154 	if (stream->luma_chan == -1) {
    155 		if (stream->space == CIEXYZ)
    156 			stream->luma_chan = 1;
    157 		else if (stream->space == YUV_NONLINEAR)
    158 			stream->luma_chan = 0;
    159 	}
    160 
    161 	switch (stream->encoding) {
    162 	case FLOAT:
    163 		stream->chan_size = sizeof(float);
    164 		break;
    165 	case DOUBLE:
    166 		stream->chan_size = sizeof(double);
    167 		break;
    168 	case LONG_DOUBLE:
    169 		stream->chan_size = sizeof(long double);
    170 		break;
    171 	case UINT8:
    172 		stream->chan_size = sizeof(uint8_t);
    173 		break;
    174 	case UINT16:
    175 		stream->chan_size = sizeof(uint16_t);
    176 		break;
    177 	case UINT32:
    178 		stream->chan_size = sizeof(uint32_t);
    179 		break;
    180 	case UINT64:
    181 		stream->chan_size = sizeof(uint64_t);
    182 		break;
    183 	default:
    184 		abort();
    185 	}
    186 
    187 	stream->pixel_size = stream->n_chan * stream->chan_size;
    188 	stream->row_size   = stream->pixel_size * stream->width;
    189 	stream->col_size   = stream->pixel_size * stream->height;
    190 	stream->frame_size = stream->pixel_size * stream->height * stream->width;
    191 	return 0;
    192 
    193 #undef TEST_ENCODING_AGNOSTIC
    194 }
    195 
    196 void
    197 enset_pixel_format(int status, struct stream *stream, const char *pixfmt)
    198 {
    199 	if (set_pixel_format(stream, pixfmt)) {
    200 		if (pixfmt)
    201 			enprintf(status, "pixel format %s is not supported, try xyza\n", pixfmt);
    202 		else
    203 			enprintf(status, "%s: unsupported pixel format: %s\n",
    204 			         stream->file, stream->pixfmt);
    205 	}
    206 }
    207 
    208 
    209 void
    210 fprint_stream_head(FILE *fp, struct stream *stream)
    211 {
    212 	FPRINTF_HEAD(fp, stream->frames, stream->width, stream->height, stream->pixfmt);
    213 }
    214 
    215 
    216 int
    217 dprint_stream_head(int fd, struct stream *stream)
    218 {
    219 	return DPRINTF_HEAD(fd, stream->frames, stream->width, stream->height, stream->pixfmt);
    220 }
    221 
    222 
    223 size_t
    224 enread_stream(int status, struct stream *stream, size_t n)
    225 {
    226 	ssize_t r = read(stream->fd, stream->buf + stream->ptr,
    227 			 MIN(sizeof(stream->buf) - stream->ptr, n));
    228 	if (r < 0)
    229 		enprintf(status, "read %s:", stream->file);
    230 	stream->ptr += (size_t)r;
    231 	return (size_t)r;
    232 }
    233 
    234 
    235 void
    236 eninf_check_fd(int status, int fd, const char *file)
    237 {
    238 	struct stat st;
    239 	if (fstat(fd, &st))
    240 		enprintf(status, "fstat %s:", file);
    241 	if (S_ISREG(st.st_mode))
    242 		enprintf(status, "%s is a regular file, refusing infinite write\n", file);
    243 }
    244 
    245 
    246 void
    247 encheck_dimensions(int status, const struct stream *stream, enum dimension dimensions, const char *prefix)
    248 {
    249 	size_t n;
    250 
    251 	if (!stream->pixel_size)
    252 		enprintf(status, "%s: %s%svideo frame doesn't have a pixel size\n",
    253 			 stream->file, prefix ? prefix : "",
    254 			 (prefix && *prefix) ? " " : "");
    255 
    256 	n = SIZE_MAX / stream->pixel_size;
    257 
    258 	if ((dimensions & WIDTH) && stream->width > n)
    259 		enprintf(status, "%s: %s%svideo frame is too wide\n",
    260 			 stream->file, prefix ? prefix : "",
    261 			 (prefix && *prefix) ? " " : "");
    262 
    263 	if ((dimensions & HEIGHT) && stream->height > n)
    264 		enprintf(status, "%s: %s%svideo frame is too wide\n",
    265 			 stream->file, prefix ? prefix : "",
    266 			 (prefix && *prefix) ? " " : "");
    267 
    268 	if (!stream->width || !stream->height)
    269 		return;
    270 
    271 	if ((dimensions & (WIDTH | HEIGHT)) == (WIDTH | HEIGHT)) {
    272 		if (stream->width > n / stream->height)
    273 			enprintf(status, "%s: %s%svideo frame is too large\n",
    274 				 stream->file, prefix ? prefix : "",
    275 				 (prefix && *prefix) ? " " : "");
    276 	}
    277 
    278 	if (!(dimensions & LENGTH))
    279 		return;
    280 	if (dimensions & WIDTH)
    281 		n /= stream->width;
    282 	if (dimensions & HEIGHT)
    283 		n /= stream->height;
    284 
    285 	if (stream->frames > n)
    286 		enprintf(status, "%s: %s%svideo is too large\n",
    287 			 stream->file, prefix ? prefix : "",
    288 			 (prefix && *prefix) ? " " : "");
    289 }
    290 
    291 
    292 void
    293 encheck_compat(int status, const struct stream *a, const struct stream *b)
    294 {
    295 	if (a->width != b->width || a->height != b->height)
    296 		enprintf(status, "videos do not have the same geometry\n");
    297 	if (strcmp(a->pixfmt, b->pixfmt))
    298 		enprintf(status, "videos use incompatible pixel formats\n");
    299 }
    300 
    301 
    302 const char *
    303 get_pixel_format(const char *specified, const char *current)
    304 {
    305 	enum colour_space space = CIEXYZ;
    306 	enum alpha alpha = UNPREMULTIPLIED;
    307 	enum encoding encoding = UINT16;
    308 	int level = -1;
    309 	size_t n = strlen(specified);
    310 
    311 	if ((n >= 2 && !strcmp(specified - 2, " f")) ||
    312 	    !strcmp(specified, "raw0") || !strcmp(specified, "raw1") ||
    313 	    !strcmp(specified, "raw2") || !strcmp(specified, "raw2a"))
    314 		return specified;
    315 
    316 	if      (!strcmp(current, "xyza"))    space = CIEXYZ, encoding = DOUBLE;
    317 	else if (!strcmp(current, "xyza f"))  space = CIEXYZ, encoding = FLOAT;
    318 	else if (!strcmp(current, "raw0"))    level = 0;
    319 	else if (!strcmp(current, "raw1"))    level = 1;
    320 	else if (!strcmp(current, "raw2"))    level = 2, alpha = NO_ALPHA;
    321 	else if (!strcmp(current, "raw2a"))   level = 2;
    322 	else if (!strcmp(current, "raw3"))    level = 3, encoding = DOUBLE, alpha = NO_ALPHA;
    323 	else if (!strcmp(current, "raw3a"))   level = 3, encoding = DOUBLE;
    324 	else if (!strcmp(current, "raw3 f"))  level = 3, encoding = FLOAT, alpha = NO_ALPHA;
    325 	else if (!strcmp(current, "raw3a f")) level = 3, encoding = FLOAT;
    326 	else if (!strcmp(current, "raw4"))    level = 4, encoding = DOUBLE, alpha = NO_ALPHA;
    327 	else if (!strcmp(current, "raw4a"))   level = 4, encoding = DOUBLE;
    328 	else if (!strcmp(current, "raw4 f"))  level = 4, encoding = FLOAT, alpha = NO_ALPHA;
    329 	else if (!strcmp(current, "raw4a f")) level = 4, encoding = FLOAT;
    330 	else if (!strcmp(current, "raw5"))    level = 5, encoding = DOUBLE, alpha = NO_ALPHA;
    331 	else if (!strcmp(current, "raw5a"))   level = 5, encoding = DOUBLE;
    332 	else if (!strcmp(current, "raw5 f"))  level = 5, encoding = FLOAT, alpha = NO_ALPHA;
    333 	else if (!strcmp(current, "raw5a f")) level = 5, encoding = FLOAT;
    334 	else
    335 		return specified;
    336 
    337 	if      (!strcmp(specified, "f"))        encoding = FLOAT;
    338 	else if (!strcmp(specified, "!f"))       encoding = DOUBLE;
    339 	else if (!strcmp(specified, "xyza"))     level = -1, alpha = UNPREMULTIPLIED, space = CIEXYZ;
    340 	else if (!strcmp(specified, "raw3"))     level = 3, alpha = NO_ALPHA;
    341 	else if (!strcmp(specified, "raw3a"))    level = 3, alpha = UNPREMULTIPLIED;
    342 	else if (!strcmp(specified, "raw4"))     level = 4, alpha = NO_ALPHA;
    343 	else if (!strcmp(specified, "raw4a"))    level = 4, alpha = UNPREMULTIPLIED;
    344 	else if (!strcmp(specified, "raw5"))     level = 5, alpha = NO_ALPHA;
    345 	else if (!strcmp(specified, "raw5a"))    level = 5, alpha = UNPREMULTIPLIED;
    346 	else if (!strcmp(specified, "xyza !f"))  return "xyza";
    347 	else if (!strcmp(specified, "raw3 !f"))  return "raw3";
    348 	else if (!strcmp(specified, "raw3a !f")) return "raw3a";
    349 	else if (!strcmp(specified, "raw4 !f"))  return "raw4";
    350 	else if (!strcmp(specified, "raw4a !f")) return "raw4a";
    351 	else if (!strcmp(specified, "raw5 !f"))  return "raw5";
    352 	else if (!strcmp(specified, "raw5a !f")) return "raw5a";
    353 	else
    354 		return specified;
    355 
    356 	if      (level == 0 && encoding == UINT16) return "raw0";
    357 	else if (level == 1 && encoding == UINT16) return "raw1";
    358 	else if (level == 2 && encoding == UINT16) return alpha ? "raw2a"   : "raw2";
    359 	else if (level == 3 && encoding == DOUBLE) return alpha ? "raw3a"   : "raw3";
    360 	else if (level == 3 && encoding == FLOAT)  return alpha ? "raw3a f" : "raw3 f";
    361 	else if (level == 4 && encoding == DOUBLE) return alpha ? "raw4a"   : "raw4";
    362 	else if (level == 4 && encoding == FLOAT)  return alpha ? "raw4a f" : "raw4 f";
    363 	else if (level == 5 && encoding == DOUBLE) return alpha ? "raw5a"   : "raw5";
    364 	else if (level == 5 && encoding == FLOAT)  return alpha ? "raw5a f" : "raw5 f";
    365 	else if (level < 0 && space == CIEXYZ && alpha == UNPREMULTIPLIED)
    366 		return encoding == FLOAT ? "xyza f" : encoding == DOUBLE ? "xyza" : specified;
    367 	else
    368 		return specified;
    369 }
    370 
    371 
    372 const char *
    373 nselect_print_format(int status, const char *format, enum encoding encoding, const char *fmt)
    374 {
    375 	static char retbuf[512];
    376 	int with_plus = 0, inttyped = -1;
    377 	const char *f = "", *orig = fmt;
    378 	char *proto = alloca((fmt ? strlen(fmt) : 0) + sizeof("%+#.50llx")), *p;
    379 	char *ret = retbuf;
    380 	size_t n, len;
    381 
    382 	if (!orig)
    383 		goto check_done;
    384 
    385 	for (; *fmt == '+'; fmt++)
    386 		with_plus = 1;
    387 	f = fmt + strspn(fmt, "0123456789");
    388 	if (f[0] && f[1])
    389 		enprintf(status, "invalid format: %s\n", orig);
    390 
    391 	switch (*f) {
    392 	case '\0':
    393 		inttyped = -1;
    394 		break;
    395 	case 'd': case 'i':
    396 		inttyped = 1;
    397 		break;
    398 	case 'a': case 'A':
    399 	case 'e': case 'E':
    400 	case 'f': case 'F':
    401 	case 'g': case 'G':
    402 		inttyped = 0;
    403 		break;
    404 	default:
    405 		enprintf(status, "invalid format: %s\n", orig);
    406 	}
    407 
    408 	switch (encoding) {
    409 	case FLOAT:
    410 	case DOUBLE:
    411 	case LONG_DOUBLE:
    412 		if (inttyped == 1)
    413 			enprintf(status, "invalid format `%s' is incompatible with the video format\n", orig);
    414 		inttyped = 0;
    415 		break;
    416 	case UINT8:
    417 	case UINT16:
    418 	case UINT32:
    419 	case UINT64:
    420 		if (*f != *fmt)
    421 			enprintf(status, "invalid format: %s\n", orig);
    422 		if (inttyped == 0)
    423 			enprintf(status, "invalid format `%s' is incompatible with the video format\n", orig);
    424 		inttyped = 1;
    425 		break;
    426 	default:
    427 		abort();
    428 	}
    429 check_done:
    430 
    431 	p = proto;
    432 	*p++ = '%';
    433 	if (with_plus)
    434 		*p++ = '+';
    435 
    436 	if (orig && *f != *fmt) {
    437 		*p++ = '.';
    438 		p = stpncpy(p, fmt, (size_t)(f - fmt));
    439 	} else if (orig && inttyped && *f != 'a' && *f != 'A') {
    440 		*p++ = '.';
    441 		*p++ = '2';
    442 		*p++ = '5';
    443 	}
    444 
    445 	inttyped = 1;
    446 	switch (encoding) {
    447 	case FLOAT:
    448 		inttyped = 0;
    449 		break;
    450 	case DOUBLE:
    451 		*p++ = 'l';
    452 		inttyped = 0;
    453 		break;
    454 	case LONG_DOUBLE:
    455 		*p++ = 'L';
    456 		inttyped = 0;
    457 		break;
    458 	case UINT8:
    459 		fmt = PRIi8;
    460 		break;
    461 	case UINT16:
    462 		fmt = PRIi16;
    463 		break;
    464 	case UINT32:
    465 		fmt = PRIi32;
    466 		break;
    467 	case UINT64:
    468 		fmt = PRIi64;
    469 		break;
    470 	default:
    471 		abort();
    472 	}
    473 
    474 	if (inttyped)
    475 		while (*fmt == 'l' || *fmt == 'L')
    476 			*p++ = *fmt++;
    477 
    478 	switch (orig ? *f : '\0') {
    479 	case '\0':
    480 		*p++ = inttyped ? 'i' : 'f';
    481 		break;
    482 	case 'd': case 'i':
    483 		*p++ = 'i';
    484 		break;
    485 	case 'a': case 'A':
    486 		*p++ = 'a';
    487 		break;
    488 	case 'e': case 'E':
    489 		*p++ = 'e';
    490 		break;
    491 	case 'f': case 'F':
    492 		*p++ = 'f';
    493 		break;
    494 	case 'g': case 'G':
    495 		*p++ = 'g';
    496 		break;
    497 	}
    498 
    499 	*p = '\0';
    500 
    501 	len = strlen(proto);
    502 	for (n = 1, f = format; *f; f++) {
    503 		if (f[0] == '%' && f[1] == '!') {
    504 			f++;
    505 			n += len;
    506 		} else {
    507 			n++;
    508 		}
    509 	}
    510 
    511 	if (n > sizeof(retbuf))
    512 		ret = enmalloc(status, n);
    513 	for (p = ret, f = format; *f; f++) {
    514 		if (f[0] == '%' && f[1] == '!') {
    515 			f++;
    516 			p = stpcpy(p, proto);
    517 		} else {
    518 			*p++ = *f;
    519 		}
    520 	}
    521 
    522 	return ret;
    523 }
    524 
    525 
    526 int
    527 enread_segment(int status, struct stream *stream, void *buf, size_t n)
    528 {
    529 	char *buffer = buf;
    530 	ssize_t r;
    531 	size_t m;
    532 
    533 	if (stream->ptr) {
    534 		m = MIN(stream->ptr, n);
    535 		memcpy(buffer + stream->xptr, stream->buf, m);
    536 		memmove(stream->buf, stream->buf + m, stream->ptr -= m);
    537 		stream->xptr += m;
    538 	}
    539 
    540 	for (; stream->xptr < n; stream->xptr += (size_t)r) {
    541 		r = read(stream->fd, buffer + stream->xptr, n - stream->xptr);
    542 		if (r < 0) {
    543 			enprintf(status, "read %s:", stream->file);
    544 		} else if (r == 0) {
    545 			if (!stream->xptr)
    546 				break;
    547 			enprintf(status, "%s: incomplete frame", stream->file);
    548 		}
    549 	}
    550 
    551 	if (!stream->xptr)
    552 		return 0;
    553 	stream->xptr -= n;
    554 	return 1;
    555 }
    556 
    557 
    558 size_t
    559 ensend_frames(int status, struct stream *stream, int outfd, size_t frames, const char *outfname)
    560 {
    561 	size_t h, w, p, n, ret;
    562 
    563 	for (ret = 0; ret < frames; ret++) {
    564 		for (p = stream->pixel_size; p; p--) {
    565 			for (h = stream->height; h; h--) {
    566 				for (w = stream->width; w; w -= n) {
    567 					if (!stream->ptr && !enread_stream(status, stream, w))
    568 						goto done;
    569 					n = MIN(stream->ptr, w);
    570 					if (outfd >= 0)
    571 						enwriteall(status, outfd, stream->buf, n, outfname);
    572 					memmove(stream->buf, stream->buf + n, stream->ptr -= n);
    573 				}
    574 			}
    575 		}
    576 	}
    577 
    578 	return ret;
    579 done:
    580 	if (p != stream->pixel_size || h != stream->height || w != stream->width)
    581 		enprintf(status, "%s: incomplete frame", stream->file);
    582 	return ret;
    583 }
    584 
    585 
    586 size_t
    587 ensend_rows(int status, struct stream *stream, int outfd, size_t rows, const char *outfname)
    588 {
    589 	size_t w, p, n, ret;
    590 
    591 	for (ret = 0; ret < rows; ret++) {
    592 		for (p = stream->pixel_size; p; p--) {
    593 			for (w = stream->width; w; w -= n) {
    594 				if (!stream->ptr && !enread_stream(status, stream, w))
    595 					goto done;
    596 				n = MIN(stream->ptr, w);
    597 				if (outfd >= 0)
    598 					enwriteall(status, outfd, stream->buf, n, outfname);
    599 				memmove(stream->buf, stream->buf + n, stream->ptr -= n);
    600 			}
    601 		}
    602 	}
    603 
    604 	return ret;
    605 done:
    606 	if (p != stream->pixel_size || w != stream->width)
    607 		enprintf(status, "%s: incomplete row", stream->file);
    608 	return ret;
    609 }
    610 
    611 
    612 size_t
    613 ensend_pixels(int status, struct stream *stream, int outfd, size_t pixels, const char *outfname)
    614 {
    615 	size_t p, n, ret;
    616 
    617 	for (ret = 0; ret < pixels; ret++) {
    618 		for (p = stream->pixel_size; p; p -= n) {
    619 			if (!stream->ptr && !enread_stream(status, stream, p))
    620 				goto done;
    621 			n = MIN(stream->ptr, p);
    622 			if (outfd >= 0)
    623 				enwriteall(status, outfd, stream->buf, n, outfname);
    624 			memmove(stream->buf, stream->buf + n, stream->ptr -= n);
    625 		}
    626 	}
    627 
    628 	return ret;
    629 done:
    630 	if (p != stream->pixel_size)
    631 		enprintf(status, "%s: incomplete pixel", stream->file);
    632 	return ret;
    633 }
    634 
    635 
    636 int
    637 ensend_stream(int status, struct stream *stream, int outfd, const char *outfname)
    638 {
    639 	do {
    640 		if (writeall(outfd, stream->buf, stream->ptr)) {
    641 			if (outfname)
    642 				eprintf("write %s:", outfname);
    643 			return -1;
    644 		}
    645 		stream->ptr = 0;
    646 	} while (enread_stream(status, stream, SIZE_MAX));
    647 	return 0;
    648 }
    649 
    650 
    651 void
    652 nprocess_stream(int status, struct stream *stream, void (*process)(struct stream *stream, size_t n))
    653 {
    654 	size_t n;
    655 	do {
    656 		n = stream->ptr - (stream->ptr % stream->pixel_size);
    657 		process(stream, n);
    658 		memmove(stream->buf, stream->buf + n, stream->ptr -= n);
    659 	} while (enread_stream(status, stream, SIZE_MAX));
    660 }
    661 
    662 
    663 void
    664 nprocess_each_frame_segmented(int status, struct stream *stream, int output_fd, const char* output_fname,
    665 			      void (*process)(struct stream *stream, size_t n, size_t frame))
    666 {
    667 	size_t frame, r, n;
    668 	encheck_dimensions(status, stream, WIDTH | HEIGHT, NULL);
    669 	for (frame = 0; frame < stream->frames; frame++) {
    670 		for (n = stream->frame_size; n; n -= r) {
    671 			if (stream->ptr < n && !enread_stream(status, stream, SIZE_MAX))
    672 				enprintf(status, "%s: file is shorter than expected\n", stream->file);
    673 			r = stream->ptr - (stream->ptr % stream->pixel_size);
    674 			r = MIN(r, n);
    675 			process(stream, r, frame);
    676 			enwriteall(status, output_fd, stream->buf, r, output_fname);
    677 			memmove(stream->buf, stream->buf + r, stream->ptr -= r);
    678 		}
    679 	}
    680 }
    681 
    682 
    683 void
    684 nprocess_two_streams(int status, struct stream *left, struct stream *right, int output_fd, const char* output_fname,
    685 		     void (*process)(struct stream *left, struct stream *right, size_t n))
    686 {
    687 	size_t n;
    688 	int have_both = 1;
    689 
    690 	encheck_compat(status, left, right);
    691 
    692 	while (have_both) {
    693 		if (left->ptr < sizeof(left->buf) && !enread_stream(status, left, SIZE_MAX)) {
    694 			close(left->fd);
    695 			left->fd = -1;
    696 			have_both = 0;
    697 		}
    698 		if (right->ptr < sizeof(right->buf) && !enread_stream(status, right, SIZE_MAX)) {
    699 			close(right->fd);
    700 			right->fd = -1;
    701 			have_both = 0;
    702 		}
    703 
    704 		n = MIN(left->ptr, right->ptr);
    705 		n -= n % left->pixel_size;
    706 		left->ptr -= n;
    707 		right->ptr -= n;
    708 
    709 		process(left, right, n);
    710 
    711 		enwriteall(status, output_fd, left->buf, n, output_fname);
    712 		if ((n & 3) || left->ptr != right->ptr) {
    713 			memmove(left->buf,  left->buf  + n, left->ptr);
    714 			memmove(right->buf, right->buf + n, right->ptr);
    715 		}
    716 	}
    717 
    718 	if (right->fd >= 0)
    719 		close(right->fd);
    720 
    721 	enwriteall(status, output_fd, left->buf, left->ptr, output_fname);
    722 
    723 	if (left->fd >= 0) {
    724 		for (;;) {
    725 			left->ptr = 0;
    726 			if (!enread_stream(status, left, SIZE_MAX)) {
    727 				close(left->fd);
    728 				left->fd = -1;
    729 				break;
    730 			}
    731 			enwriteall(status, output_fd, left->buf, left->ptr, output_fname);
    732 		}
    733 	}
    734 }
    735 
    736 
    737 void
    738 nprocess_multiple_streams(int status, struct stream *streams, size_t n_streams, int output_fd, const char* output_fname,
    739 			  int shortest, void (*process)(struct stream *streams, size_t n_streams, size_t n))
    740 {
    741 	size_t closed, i, j, n;
    742 
    743 	for (i = 1; i < n_streams; i++)
    744 		encheck_compat(status, streams + i, streams);
    745 
    746 	while (n_streams) {
    747 		n = SIZE_MAX;
    748 		for (i = 0; i < n_streams; i++) {
    749 			if (streams[i].ptr < streams->pixel_size && !enread_stream(status, streams + i, SIZE_MAX)) {
    750 				close(streams[i].fd);
    751 				streams[i].fd = -1;
    752 				if (shortest)
    753 					return;
    754 			}
    755 			if (streams[i].ptr && streams[i].ptr < n)
    756 				n = streams[i].ptr;
    757 		}
    758 		if (n == SIZE_MAX)
    759 			break;
    760 		n -= n % streams->pixel_size;
    761 
    762 		process(streams, n_streams, n);
    763 		enwriteall(status, output_fd, streams->buf, n, output_fname);
    764 
    765 		closed = SIZE_MAX;
    766 		for (i = 0; i < n_streams; i++) {
    767 			if (streams[i].ptr)
    768 				memmove(streams[i].buf, streams[i].buf + n, streams[i].ptr -= n);
    769 			if (streams[i].ptr < streams->pixel_size && streams[i].fd < 0 && closed == SIZE_MAX)
    770 				closed = i;
    771 		}
    772 		if (closed != SIZE_MAX) {
    773 			for (i = (j = closed) + 1; i < n_streams; i++)
    774 				if (streams[i].ptr >= streams->pixel_size || streams[i].fd >= 0)
    775 					streams[j++] = streams[i];
    776 			n_streams = j;
    777 		}
    778 	}
    779 }
    780 
    781 
    782 void
    783 nprocess_each_frame_two_streams(int status, struct stream *left, struct stream *right, int output_fd, const char* output_fname,
    784 				void (*process)(char *restrict output, char *restrict lbuf, char *restrict rbuf,
    785 						struct stream *left, struct stream *right))
    786 {
    787 	char *lbuf, *rbuf, *image;
    788 
    789 	encheck_dimensions(status, left,  WIDTH | HEIGHT, NULL);
    790 	encheck_dimensions(status, right, WIDTH | HEIGHT, NULL);
    791 
    792 	if (left->frame_size > SIZE_MAX - left->frame_size ||
    793 	    2 * left->frame_size > SIZE_MAX - right->frame_size)
    794 		enprintf(status, "video frame is too large\n");
    795 
    796 	image = mmap(0, 2 * left->frame_size + right->frame_size,
    797 		     PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
    798 	if (image == MAP_FAILED)
    799 		enprintf(status, "mmap:");
    800 	lbuf = image + 1 * left->frame_size;
    801 	rbuf = image + 2 * left->frame_size;
    802 
    803 	for (;;) {
    804 		if (!enread_frame(status, left, lbuf)) {
    805 			close(left->fd);
    806 			left->fd = -1;
    807 			break;
    808 		}
    809 		if (!enread_frame(status, right, rbuf)) {
    810 			close(right->fd);
    811 			right->fd = -1;
    812 			break;
    813 		}
    814 
    815 		process(image, lbuf, rbuf, left, right);
    816 		enwriteall(status, output_fd, image, left->frame_size, output_fname);
    817 	}
    818 
    819 	if (right->fd >= 0)
    820 		close(right->fd);
    821 
    822 	if (left->fd >= 0) {
    823 		memcpy(image, lbuf, left->ptr);
    824 		while (enread_frame(status, left, lbuf))
    825 			enwriteall(status, output_fd, image, left->frame_size, output_fname);
    826 	}
    827 
    828 	munmap(image, 2 * left->frame_size + right->frame_size);
    829 }