/* See LICENSE file for copyright and license details. */ #include "common.h" void eninit_stream(int status, struct stream *stream) { int r = libblind_init_stream(stream); if (r) enprintf(status, "%s: %s\n", stream->file, libblind_strerror(r)); } void enopen_stream(int status, struct stream *stream, const char *file) { stream->file = file ? file : ""; stream->fd = file ? enopen(status, file, O_RDONLY) : STDIN_FILENO; eninit_stream(status, stream); } void enset_pixfmt(int status, struct stream *stream, const char *pixfmt) { int err; if ((err = set_pixfmt(stream, pixfmt))) { if (err != LIBBLIND_EPIXFMTNOSUPPORT) enprintf(status, "%s: %s\n", stream->file, libblind_strerror(err)); else if (pixfmt) enprintf(status, "pixel format %s is not supported, try xyza\n", pixfmt); else enprintf(status, "%s: unsupported pixel format: %s\n", stream->file, stream->pixfmt); } } void fprint_stream_head(FILE *fp, struct stream *stream) { FPRINTF_HEAD(fp, stream->frames, stream->width, stream->height, stream->pixfmt); } int dprint_stream_head(int fd, struct stream *stream) { return DPRINTF_HEAD(fd, stream->frames, stream->width, stream->height, stream->pixfmt); } size_t enread_stream(int status, struct stream *stream, size_t n) { ssize_t r = libblind_read_stream(stream, n); if (r < 0) enprintf(status, "read %s: %s\n", stream->file, libblind_strerror((int)r)); return (size_t)r; } void eninf_check_fd(int status, int fd, const char *file) { struct stat st; if (fstat(fd, &st)) enprintf(status, "fstat %s:", file); if (S_ISREG(st.st_mode)) enprintf(status, "%s is a regular file, refusing infinite write\n", file); } void encheck_dimensions(int status, const struct stream *stream, enum dimension dimensions, const char *prefix) { int r = libblind_check_dimensions(stream, dimensions); if (r < 0) enprintf(status, "%s: %s%s%s\n", stream->file, prefix ? prefix : "", (prefix && *prefix) ? ": " : "", libblind_strerror(r)); } void encheck_compat(int status, const struct stream *a, const struct stream *b) { if (a->width != b->width || a->height != b->height) enprintf(status, "videos do not have the same geometry\n"); if (strcmp(a->pixfmt, b->pixfmt)) enprintf(status, "videos use incompatible pixel formats\n"); } const char * nselect_print_format(int status, const char *format, enum encoding encoding, const char *fmt) { static char retbuf[512]; int with_plus = 0, inttyped = -1; const char *f = "", *orig = fmt; char *proto = alloca((fmt ? strlen(fmt) : 0) + sizeof("%+#.50llx")), *p; char *ret = retbuf; size_t n, len; if (!orig) goto check_done; for (; *fmt == '+'; fmt++) with_plus = 1; f = fmt + strspn(fmt, "0123456789"); if (f[0] && f[1]) enprintf(status, "invalid format: %s\n", orig); switch (*f) { case '\0': inttyped = -1; break; case 'd': case 'i': inttyped = 1; break; case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': inttyped = 0; break; default: enprintf(status, "invalid format: %s\n", orig); } switch (encoding) { case FLOAT: case DOUBLE: case LONG_DOUBLE: if (inttyped == 1) enprintf(status, "invalid format `%s' is incompatible with the video format\n", orig); inttyped = 0; break; case UINT8: case UINT16: case UINT32: case UINT64: if (*f != *fmt) enprintf(status, "invalid format: %s\n", orig); if (inttyped == 0) enprintf(status, "invalid format `%s' is incompatible with the video format\n", orig); inttyped = 1; break; default: abort(); } check_done: p = proto; *p++ = '%'; if (with_plus) *p++ = '+'; if (orig && *f != *fmt) { *p++ = '.'; p = stpncpy(p, fmt, (size_t)(f - fmt)); } else if (orig && inttyped && *f != 'a' && *f != 'A') { *p++ = '.'; *p++ = '2'; *p++ = '5'; } inttyped = 1; switch (encoding) { case FLOAT: inttyped = 0; break; case DOUBLE: *p++ = 'l'; inttyped = 0; break; case LONG_DOUBLE: *p++ = 'L'; inttyped = 0; break; case UINT8: fmt = PRIi8; break; case UINT16: fmt = PRIi16; break; case UINT32: fmt = PRIi32; break; case UINT64: fmt = PRIi64; break; default: abort(); } if (inttyped) while (*fmt == 'l' || *fmt == 'L') *p++ = *fmt++; switch (orig ? *f : '\0') { case '\0': *p++ = inttyped ? 'i' : 'f'; break; case 'd': case 'i': *p++ = 'i'; break; case 'a': case 'A': *p++ = 'a'; break; case 'e': case 'E': *p++ = 'e'; break; case 'f': case 'F': *p++ = 'f'; break; case 'g': case 'G': *p++ = 'g'; break; } *p = '\0'; len = strlen(proto); for (n = 1, f = format; *f; f++) { if (f[0] == '%' && f[1] == '!') { f++; n += len; } else { n++; } } if (n > sizeof(retbuf)) ret = enmalloc(status, n); for (p = ret, f = format; *f; f++) { if (f[0] == '%' && f[1] == '!') { f++; p = stpcpy(p, proto); } else { *p++ = *f; } } return ret; } int enread_segment(int status, struct stream *stream, void *buf, size_t n) { int r = libblind_read_segment(stream, buf, n); if (r < 0) enprintf(status, "%s: %s\n", stream->file, libblind_strerror(r)); return r; } size_t ensend_frames(int status, struct stream *stream, int outfd, size_t frames, const char *outfname) { int outfailed; int r = libblind_send_frames(stream, outfd, frames, &outfailed); if (r < 0) { if (!outfailed) enprintf(status, "%s:", stream->file); else if (outfname) enprintf(status, "%s:", outfname); } return r; } size_t ensend_rows(int status, struct stream *stream, int outfd, size_t rows, const char *outfname) { int outfailed; int r = libblind_send_rows(stream, outfd, rows, &outfailed); if (r < 0) { if (!outfailed) enprintf(status, "%s:", stream->file); else if (outfname) enprintf(status, "%s:", outfname); } return r; } size_t ensend_pixels(int status, struct stream *stream, int outfd, size_t pixels, const char *outfname) { int outfailed; int r = libblind_send_pixels(stream, outfd, pixels, &outfailed); if (r < 0) { if (!outfailed) enprintf(status, "%s:", stream->file); else if (outfname) enprintf(status, "%s:", outfname); } return r; } int ensend_stream(int status, struct stream *stream, int outfd, const char *outfname) { int outfailed; int r = libblind_send_stream(stream, outfd, &outfailed); if (r < 0) { if (!outfailed) enprintf(status, "%s:", stream->file); else if (outfname) enprintf(status, "%s:", outfname); } return r; } void nprocess_stream(int status, struct stream *stream, void (*process)(struct stream *stream, size_t n)) { size_t n; do { n = stream->ptr - (stream->ptr % stream->pixel_size); process(stream, n); memmove(stream->buf, stream->buf + n, stream->ptr -= n); } while (enread_stream(status, stream, SIZE_MAX)); } void nprocess_each_frame_segmented(int status, struct stream *stream, int output_fd, const char* output_fname, void (*process)(struct stream *stream, size_t n, size_t frame)) { size_t frame, r, n; encheck_dimensions(status, stream, WIDTH | HEIGHT, NULL); for (frame = 0; frame < stream->frames; frame++) { for (n = stream->frame_size; n; n -= r) { if (stream->ptr < n && !enread_stream(status, stream, SIZE_MAX)) enprintf(status, "%s: file is shorter than expected\n", stream->file); r = stream->ptr - (stream->ptr % stream->pixel_size); r = MIN(r, n); process(stream, r, frame); enwriteall(status, output_fd, stream->buf, r, output_fname); memmove(stream->buf, stream->buf + r, stream->ptr -= r); } } } void nprocess_two_streams(int status, struct stream *left, struct stream *right, int output_fd, const char* output_fname, void (*process)(struct stream *left, struct stream *right, size_t n)) { size_t n; int have_both = 1; encheck_compat(status, left, right); while (have_both) { if (left->ptr < sizeof(left->buf) && !enread_stream(status, left, SIZE_MAX)) { close(left->fd); left->fd = -1; have_both = 0; } if (right->ptr < sizeof(right->buf) && !enread_stream(status, right, SIZE_MAX)) { close(right->fd); right->fd = -1; have_both = 0; } n = MIN(left->ptr, right->ptr); n -= n % left->pixel_size; left->ptr -= n; right->ptr -= n; process(left, right, n); enwriteall(status, output_fd, left->buf, n, output_fname); if ((n & 3) || left->ptr != right->ptr) { memmove(left->buf, left->buf + n, left->ptr); memmove(right->buf, right->buf + n, right->ptr); } } if (right->fd >= 0) close(right->fd); enwriteall(status, output_fd, left->buf, left->ptr, output_fname); if (left->fd >= 0) { for (;;) { left->ptr = 0; if (!enread_stream(status, left, SIZE_MAX)) { close(left->fd); left->fd = -1; break; } enwriteall(status, output_fd, left->buf, left->ptr, output_fname); } } } void nprocess_multiple_streams(int status, struct stream *streams, size_t n_streams, int output_fd, const char* output_fname, int shortest, void (*process)(struct stream *streams, size_t n_streams, size_t n)) { size_t closed, i, j, n; for (i = 1; i < n_streams; i++) encheck_compat(status, streams + i, streams); while (n_streams) { n = SIZE_MAX; for (i = 0; i < n_streams; i++) { if (streams[i].ptr < streams->pixel_size && !enread_stream(status, streams + i, SIZE_MAX)) { close(streams[i].fd); streams[i].fd = -1; if (shortest) return; } if (streams[i].ptr && streams[i].ptr < n) n = streams[i].ptr; } if (n == SIZE_MAX) break; n -= n % streams->pixel_size; process(streams, n_streams, n); enwriteall(status, output_fd, streams->buf, n, output_fname); closed = SIZE_MAX; for (i = 0; i < n_streams; i++) { if (streams[i].ptr) memmove(streams[i].buf, streams[i].buf + n, streams[i].ptr -= n); if (streams[i].ptr < streams->pixel_size && streams[i].fd < 0 && closed == SIZE_MAX) closed = i; } if (closed != SIZE_MAX) { for (i = (j = closed) + 1; i < n_streams; i++) if (streams[i].ptr >= streams->pixel_size || streams[i].fd >= 0) streams[j++] = streams[i]; n_streams = j; } } } void nprocess_each_frame_two_streams(int status, struct stream *left, struct stream *right, int output_fd, const char* output_fname, void (*process)(char *restrict output, char *restrict lbuf, char *restrict rbuf, struct stream *left, struct stream *right)) { char *lbuf, *rbuf, *image; encheck_dimensions(status, left, WIDTH | HEIGHT, NULL); encheck_dimensions(status, right, WIDTH | HEIGHT, NULL); if (left->frame_size > SIZE_MAX - left->frame_size || 2 * left->frame_size > SIZE_MAX - right->frame_size) enprintf(status, "video frame is too large\n"); image = mmap(0, 2 * left->frame_size + right->frame_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); if (image == MAP_FAILED) enprintf(status, "mmap:"); lbuf = image + 1 * left->frame_size; rbuf = image + 2 * left->frame_size; for (;;) { if (!enread_frame(status, left, lbuf)) { close(left->fd); left->fd = -1; break; } if (!enread_frame(status, right, rbuf)) { close(right->fd); right->fd = -1; break; } process(image, lbuf, rbuf, left, right); enwriteall(status, output_fd, image, left->frame_size, output_fname); } if (right->fd >= 0) close(right->fd); if (left->fd >= 0) { memcpy(image, lbuf, left->ptr); while (enread_frame(status, left, lbuf)) enwriteall(status, output_fd, image, left->frame_size, output_fname); } munmap(image, 2 * left->frame_size + right->frame_size); }