blind

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

commit ae811c3ce7b71902b508c65278050f0358471e98
parent 521dc0c9100cd167be8525ca00ccaed4e0d7fa33
Author: Mattias Andrée <maandree@kth.se>
Date:   Thu, 12 Jan 2017 07:53:06 +0100

m + Add vu-gauss-blur

Signed-off-by: Mattias Andrée <maandree@kth.se>

Diffstat:
MMakefile | 1+
MTODO | 3++-
Mconfig.mk | 4+++-
Msrc/stream.c | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/stream.h | 15++++++++++++---
Msrc/util.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/util.h | 1+
Asrc/util/jobs.h | 7+++++++
Msrc/vu-concat.c | 4+++-
Asrc/vu-gauss-blur.c | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/vu-next-frame.c | 17+++++++----------
Msrc/vu-rewrite-head.c | 2++
12 files changed, 353 insertions(+), 38 deletions(-)

diff --git a/Makefile b/Makefile @@ -12,6 +12,7 @@ BIN =\ vu-flip\ vu-flop\ vu-from-image\ + vu-gauss-blur\ vu-invert-luma\ vu-next-frame\ vu-read-head\ diff --git a/TODO b/TODO @@ -1,10 +1,11 @@ -vu-gauss-blur gaussian blur, -c to only blur chroma vu-transform transformation by matrix multiplication, -t for tiling, -s for improve quality on downscaling (pixels' neighbours must not change) vu-chroma-key replace a chroma with transparency vu-primary-key replace a primary with transparency, -g for greyscaled images vu-from-video use ffmpeg to convert from another format vu-to-video use ffmpeg to convert to another format +vu-to-text convert each pixel to text +vu-from-text convert each pixel from text vu-primaries given three selectable primaries split the video into three side-by-side which only one primary active vu-apply-map remap pixels (distortion) using the X and Y values, -t for tiling, -s for diff --git a/config.mk b/config.mk @@ -1,3 +1,5 @@ +# You may want to remove -DHAVE_PRCTL from CPPFLAGS if you are not using Linux. + CFLAGS = -std=c99 -Wall -pedantic -O2 -CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_FILE_OFFSET_BITS=64 +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_FILE_OFFSET_BITS=64 -DHAVE_PRCTL LDFLAGS = -lm -s diff --git a/src/stream.c b/src/stream.c @@ -2,6 +2,7 @@ #include "stream.h" #include "util.h" +#include <sys/mman.h> #include <sys/stat.h> #include <errno.h> #include <inttypes.h> @@ -189,27 +190,27 @@ enread_frame(int status, struct stream *stream, void *buf, size_t n) } if (!stream->ptr) return 0; - stream->ptr = 0; + stream->ptr -= n; return 1; } void -process_each_frame_segmented(struct stream *stream, int output_fd, const char* output_fname, - void (*process)(struct stream *stream, size_t n, size_t frame)) +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_size, frame, r, n; - echeck_frame_size(stream->width, stream->height, stream->pixel_size, 0, stream->file); + encheck_frame_size(status, stream->width, stream->height, stream->pixel_size, 0, stream->file); frame_size = stream->height * stream->width * stream->pixel_size; for (frame = 0; frame < stream->frames; frame++) { for (n = frame_size; n; n -= r) { - if (!eread_stream(stream, n)) - eprintf("%s: file is shorter than expected\n", stream->file); + if (!enread_stream(status, stream, n)) + enprintf(status, "%s: file is shorter than expected\n", stream->file); r = stream->ptr - (stream->ptr % stream->pixel_size); (process)(stream, r, frame); - ewriteall(output_fd, stream->buf, r, output_fname); + enwriteall(status, output_fd, stream->buf, r, output_fname); memmove(stream->buf, stream->buf + r, stream->ptr -= r); } } @@ -217,20 +218,20 @@ process_each_frame_segmented(struct stream *stream, int output_fd, const char* o void -process_two_streams(struct stream *left, struct stream *right, int output_fd, const char* output_fname, - void (*process)(struct stream *left, struct stream *right, size_t n)) +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; - echeck_compat(left, right); + encheck_compat(status, left, right); for (;;) { - if (left->ptr < sizeof(left->buf) && !eread_stream(left, SIZE_MAX)) { + if (left->ptr < sizeof(left->buf) && !enread_stream(status, left, SIZE_MAX)) { close(left->fd); left->fd = -1; break; } - if (right->ptr < sizeof(right->buf) && !eread_stream(right, SIZE_MAX)) { + if (right->ptr < sizeof(right->buf) && !enread_stream(status, right, SIZE_MAX)) { close(right->fd); right->fd = -1; break; @@ -243,40 +244,40 @@ process_two_streams(struct stream *left, struct stream *right, int output_fd, co process(left, right, n); - ewriteall(output_fd, left->buf, n, output_fname); + 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); + memmove(left->buf, left->buf + n, left->ptr); + memmove(right->buf, right->buf + n, right->ptr); } } if (right->fd >= 0) close(right->fd); - ewriteall(output_fd, left->buf, left->ptr, output_fname); + enwriteall(status, output_fd, left->buf, left->ptr, output_fname); if (left->fd >= 0) { for (;;) { left->ptr = 0; - if (!eread_stream(left, SIZE_MAX)) { + if (!enread_stream(status, left, SIZE_MAX)) { close(left->fd); left->fd = -1; break; } - ewriteall(output_fd, left->buf, left->ptr, output_fname); + enwriteall(status, output_fd, left->buf, left->ptr, output_fname); } } } void -process_multiple_streams(struct stream *streams, size_t n_streams, int output_fd, const char* output_fname, - void (*process)(struct stream *streams, size_t n_streams, size_t n)) +nprocess_multiple_streams(int status, struct stream *streams, size_t n_streams, int output_fd, const char* output_fname, + 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++) - echeck_compat(streams + i, streams); + encheck_compat(status, streams + i, streams); while (n_streams) { n = SIZE_MAX; @@ -291,7 +292,7 @@ process_multiple_streams(struct stream *streams, size_t n_streams, int output_fd n -= n % streams->pixel_size; process(streams, n_streams, n); - ewriteall(output_fd, streams->buf, n, output_fname); + enwriteall(status, output_fd, streams->buf, n, output_fname); closed = SIZE_MAX; for (i = 0; i < n_streams; i++) { @@ -307,3 +308,56 @@ process_multiple_streams(struct stream *streams, size_t n_streams, int output_fd } } } + + +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, size_t ln, size_t rn)) +{ + size_t lframe_size, rframe_size; + char *lbuf, *rbuf, *image; + + encheck_frame_size(status, left->width, left->height, left->pixel_size, 0, left->file); + encheck_frame_size(status, right->width, right->height, right->pixel_size, 0, right->file); + lframe_size = left->height * left->width * left->pixel_size; + rframe_size = right->height * right->width * right->pixel_size; + + if (lframe_size > SIZE_MAX - lframe_size || 2 * lframe_size > SIZE_MAX - rframe_size) + enprintf(status, "video frame is too large\n"); + + image = mmap(0, 2 * lframe_size + lframe_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (image == MAP_FAILED) + enprintf(status, "mmap:"); + lbuf = image + 1 * lframe_size; + rbuf = image + 2 * lframe_size; + + for (;;) { + if (!enread_frame(status, left, lbuf, lframe_size)) { + close(left->fd); + left->fd = -1; + break; + } + if (!enread_frame(status, right, rbuf, rframe_size)) { + close(right->fd); + right->fd = -1; + break; + } + + process(image, lbuf, rbuf, left, right, lframe_size, rframe_size); + enwriteall(status, output_fd, image, lframe_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, lframe_size)) + enwriteall(status, output_fd, image, lframe_size, output_fname); + } + + free(lbuf); + free(rbuf); + munmap(image, 2 * lframe_size + rframe_size); +} diff --git a/src/stream.h b/src/stream.h @@ -13,6 +13,11 @@ #define enread_row(...) enread_frame(__VA_ARGS__) #define eread_row(...) eread_frame(__VA_ARGS__) +#define process_each_frame_segmented(...) nprocess_each_frame_segmented(1, __VA_ARGS__) +#define process_two_streams(...) nprocess_two_streams(1, __VA_ARGS__) +#define process_multiple_streams(...) nprocess_multiple_streams(1, __VA_ARGS__) +#define process_each_frame_two_streams(...) nprocess_each_frame_two_streams(1, __VA_ARGS__) + struct stream { size_t frames; @@ -38,11 +43,15 @@ void encheck_frame_size(int status, size_t width, size_t height, size_t pixel_si void encheck_compat(int status, const struct stream *a, const struct stream *b); int enread_frame(int status, struct stream *stream, void *buf, size_t n); -void process_each_frame_segmented(struct stream *stream, int output_fd, const char* output_fname, +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)); -void process_two_streams(struct stream *left, struct stream *right, int output_fd, const char* output_fname, +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)); -void process_multiple_streams(struct stream *streams, size_t n_streams, int output_fd, const char* output_fname, +void nprocess_multiple_streams(int status, struct stream *streams, size_t n_streams, int output_fd, const char* output_fname, void (*process)(struct stream *streams, size_t n_streams, size_t n)); + +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, size_t ln, size_t rn)); diff --git a/src/util.c b/src/util.c @@ -1,9 +1,14 @@ /* See LICENSE file for copyright and license details. */ #include "util.h" +#if defined(HAVE_PRCTL) +# include <sys/prctl.h> +#endif +#include <sys/wait.h> #include <ctype.h> #include <errno.h> #include <limits.h> +#include <signal.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -118,3 +123,48 @@ writeall(int fd, void *buf, size_t n) } return 0; } + + +static inline pid_t +enfork(int status) +{ + pid_t pid = fork(); + if (pid == -1) + enprintf(status, "fork:"); + return pid; +} + + +int +enfork_jobs(int status, size_t *start, size_t *end, size_t jobs) +{ + size_t j, s = *start, n = *end - *start; + if (jobs < 2) + return 1; + *end = n / jobs + s; + for (j = 1; j < jobs; j++) { + if (!enfork(status)) { +#if defined(HAVE_PRCTL) && defined(PR_SET_PDEATHSIG) + prctl(PR_SET_PDEATHSIG, SIGKILL); +#endif + *start = n * (j + 0) / jobs + s; + *end = n * (j + 1) / jobs + s; + return 0; + } + } + return 1; +} + + +void +enjoin_jobs(int status, int is_master) +{ + int stat; + if (!is_master) + exit(0); + while (wait(&stat) != -1) + if (!stat) + exit(status); + if (errno != ECHILD) + enprintf(status, "wait:"); +} diff --git a/src/util.h b/src/util.h @@ -15,3 +15,4 @@ #include "util/to.h" #include "util/colour.h" #include "util/io.h" +#include "util/jobs.h" diff --git a/src/util/jobs.h b/src/util/jobs.h @@ -0,0 +1,7 @@ +/* See LICENSE file for copyright and license details. */ + +#define efork_jobs(...) enfork_jobs(1, __VA_ARGS__) +#define ejoin_jobs(...) enjoin_jobs(1, __VA_ARGS__) + +int enfork_jobs(int status, size_t *start, size_t *end, size_t jobs); +void enjoin_jobs(int status, int is_master); diff --git a/src/vu-concat.c b/src/vu-concat.c @@ -77,9 +77,11 @@ concat_to_file(int argc, char *argv[], char *output_file) sprintf(head, "%zu %zu %zu %s\n%cuivf%zn", stream.frames, stream.width, stream.height, stream.pixfmt, 0, &headlen); - ewriteall(STDOUT_FILENO, head, (size_t)headlen, "<stdout>"); + ewriteall(fd, head, (size_t)headlen, output_file); data = mmap(0, size + (size_t)headlen, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) + eprintf("mmap %s:", output_file); memmove(data + headlen, data, size); memcpy(data, head, (size_t)headlen); munmap(data, size + (size_t)headlen); diff --git a/src/vu-gauss-blur.c b/src/vu-gauss-blur.c @@ -0,0 +1,189 @@ +/* See LICENSE file for copyright and license details. */ +#include "stream.h" +#include "util.h" + +#include <fcntl.h> +#include <limits.h> +#include <math.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +USAGE("[-j jobs] [-ac] sd-stream") + +static int chroma = 0; +static int noalpha = 0; +static size_t jobs = 1; + +static void +process_xyza(char *restrict output, char *restrict cbuf, char *restrict sbuf, + struct stream *colour, struct stream *sigma, size_t n, size_t unused) +{ + typedef double pixel_t[4]; + + pixel_t *restrict clr = (pixel_t *)cbuf; + pixel_t *restrict sig = (pixel_t *)sbuf; + pixel_t *img = (pixel_t *)output; + pixel_t c, k; + double *p; + size_t x1, y1, i1, x2, y2, i2; + double dy, d, m, dblurred; + int i, blurred, blur[3] = {0, 0, 0}; + size_t start = 0, end = colour->height; + int is_master; + + /* premultiply alpha channel */ + if (!noalpha) { + for (y1 = i1 = 0; y1 < colour->height; y1++) { + for (x1 = 0; x1 < colour->width; x1++, i1++) { + clr[i1][0] *= clr[i1][3]; + clr[i1][1] *= clr[i1][3]; + clr[i1][2] *= clr[i1][3]; + } + } + } + + is_master = efork_jobs(&start, &end, jobs); + + /* blur */ + i1 = start * colour->width; + for (y1 = i1 = start; y1 < end; y1++) { + for (x1 = 0; x1 < colour->width; x1++, i1++) { + if (sig[i1][3] == 0) + goto no_blur; + if (!chroma) { + for (i = 0; i < 3; i++) { + k[i] = sig[i1][i] * sig[i1][3], c[i] = k[i] *= k[i] * 2, c[i] *= M_PI; + k[i] = 1 / k[i], c[i] = -1 / c[i]; + blur[i] = !sig[i1][1]; + } + } else { + k[1] = sig[i1][1] * sig[i1][3], c[1] = k[1] *= k[1] * 2, c[1] *= M_PI; + k[1] = 1 / -k[1], c[1] = 1 / c[1]; + blur[1] = !sig[i1][1]; + } + if (blur[0] + blur[1] + blur[2] == 0) + goto no_blur; + + p = img[i1]; + p[0] = p[1] = p[2] = 0; + p[3] = noalpha; + if (k[0] == k[1] && k[1] == k[2]) { + for (y2 = i2 = 0; y2 < colour->height; y2++) { + dy = (ssize_t)y1 - (ssize_t)y2; + dy *= dy; + for (x2 = 0; x2 < colour->width; x2++, i2++) { + d = (ssize_t)x1 - (ssize_t)x2; + d = d * d + dy; + m = c[i1] * exp(d * k[i1]); + for (i = noalpha ? 3 : 4; i--;) + p[i] += clr[i2][i] * m; + } + } + } else { + blurred = 0; + for (i = 0; i < n; i++) { + if (!blur[i]) { + p[i] = clr[i1][i]; + continue; + } + for (y2 = i2 = 0; y2 < colour->height; y2++) { + dy = (ssize_t)y1 - (ssize_t)y2; + dy *= dy; + if (!noalpha) { + for (x2 = 0; x2 < colour->width; x2++, i2++) { + d = (ssize_t)x1 - (ssize_t)x2; + d = d * d + dy; + m = c[i1] * exp(d * k[i1]); + p[i] += clr[i2][i] * m; + p[3] += clr[i2][3] * m; + } + } else { + for (x2 = 0; x2 < colour->width; x2++, i2++) { + d = (ssize_t)x1 - (ssize_t)x2; + d = d * d + dy; + m = c[i1] * exp(d * k[i1]); + p[i] += clr[i2][i] * m; + } + } + } + blurred += 1; + } + if (!noalpha) { + dblurred = blurred; + for (y2 = i2 = 0; y2 < colour->height; y2++) + for (x2 = 0; x2 < colour->width; x2++, i2++) + p[3] /= dblurred; + } + } + + continue; + no_blur: + img[i1][0] = clr[i1][0]; + img[i1][1] = clr[i1][1]; + img[i1][2] = clr[i1][2]; + img[i1][3] = clr[i1][3]; + } + } + + ejoin_jobs(is_master); + + /* unpremultiply alpha channel */ + if (!noalpha) { + for (y1 = i1 = 0; y1 < colour->height; y1++) { + for (x1 = 0; x1 < colour->width; x1++, i1++) { + if (!img[i1][3]) + continue; + img[i1][0] /= img[i1][3]; + img[i1][1] /= img[i1][3]; + img[i1][2] /= img[i1][3]; + } + } + } + + (void) unused; +} + +int +main(int argc, char *argv[]) +{ + struct stream colour, sigma; + void (*process)(char *restrict output, char *restrict cbuf, char *restrict sbuf, + struct stream *colour, struct stream *sigma, size_t cn, size_t sn); + + ARGBEGIN { + case 'a': + noalpha = 1; + break; + case 'c': + chroma = 1; + break; + case 'j': + jobs = etozu_flag('j', EARG(), 1, SHRT_MAX); + break; + default: + usage(); + } ARGEND; + + if (argc != 1) + usage(); + + colour.file = "<stdin>"; + colour.fd = STDIN_FILENO; + einit_stream(&colour); + + sigma.file = argv[0]; + sigma.fd = eopen(sigma.file, O_RDONLY); + einit_stream(&sigma); + + if (!strcmp(colour.pixfmt, "xyza")) + process = process_xyza; + else + eprintf("pixel format %s is not supported, try xyza\n", colour.pixfmt); + + echeck_compat(&colour, &sigma); + + process_each_frame_two_streams(&colour, &sigma, STDOUT_FILENO, "<stdout>", process); + + return 0; +} diff --git a/src/vu-next-frame.c b/src/vu-next-frame.c @@ -6,9 +6,6 @@ #include <string.h> #include <unistd.h> -#undef eprintf -#define eprintf(...) enprintf(2, __VA_ARGS__) - USAGE("width height pixel-format ...") int @@ -26,8 +23,8 @@ main(int argc, char *argv[]) stream.file = "<stdin>"; stream.pixfmt[0] = '\0'; - stream.width = etozu_arg("the width", argv[0], 1, SIZE_MAX); - stream.height = etozu_arg("the height", argv[1], 1, SIZE_MAX); + stream.width = entozu_arg(2, "the width", argv[0], 1, SIZE_MAX); + stream.height = entozu_arg(2, "the height", argv[1], 1, SIZE_MAX); argv += 2, argc -= 2; n = (size_t)argc - 1; @@ -41,26 +38,26 @@ main(int argc, char *argv[]) } } - eset_pixel_size(&stream); + enset_pixel_size(2, &stream); fprint_stream_head(stdout, &stream); - efflush(stdout, "<stdout>"); + enfflush(2, stdout, "<stdout>"); w = stream.width * stream.pixel_size; while (stream.height) { stream.height--; for (n = w; n; n -= stream.ptr) { stream.ptr = 0; - if (!eread_stream(&stream, n)) + if (!enread_stream(2, &stream, n)) goto done; anything = 1; - ewriteall(STDOUT_FILENO, stream.buf, stream.ptr, "<stdout>"); + enwriteall(2, STDOUT_FILENO, stream.buf, stream.ptr, "<stdout>"); } } done: if (stream.height || n) - eprintf("incomplete frame\n"); + enprintf(2, "incomplete frame\n"); return !anything; } diff --git a/src/vu-rewrite-head.c b/src/vu-rewrite-head.c @@ -50,6 +50,8 @@ rewrite(struct stream *stream, int frames_auto) eprintf("ftruncate %s:", stream->file); data = mmap(0, length + (size_t)headlen, PROT_READ | PROT_WRITE, MAP_PRIVATE, stream->fd, 0); + if (data == MAP_FAILED) + eprintf("mmap %s:", stream->file); if (headlen != stream->headlen) memmove(data + headlen, data + stream->headlen, length); memcpy(data, head, (size_t)headlen);