Add blind-peek-head - blind - suckless command-line video editing utility
 (HTM) git clone git://git.suckless.org/blind
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 23100155b5d0ecbf95a8d1c4fa8d02557b69de94
 (DIR) parent 6ca85b3362761f24f8913314bf5cfdfa35241fef
 (HTM) Author: Mattias Andrée <maandree@kth.se>
       Date:   Wed, 23 Aug 2017 00:39:26 +0200
       
       Add blind-peek-head
       
       Signed-off-by: Mattias Andrée <maandree@kth.se>
       
       Diffstat:
         M Makefile                            |       3 ++-
         M README                              |       3 +++
         M config.mk                           |       8 ++++----
         A man/blind-peek-head.1               |      28 ++++++++++++++++++++++++++++
         M man/blind-read-head.1               |       1 +
         M man/blind.7                         |       3 +++
         A src/blind-peek-head.c               |     152 +++++++++++++++++++++++++++++++
       
       7 files changed, 193 insertions(+), 5 deletions(-)
       ---
 (DIR) diff --git a/Makefile b/Makefile
       @@ -60,8 +60,9 @@ BIN =\
                blind-multiply-matrices\
                blind-next-frame\
                blind-norm\
       -        blind-quaternion-product\
       +        blind-peek-head\
                blind-premultiply\
       +        blind-quaternion-product\
                blind-radial-gradient\
                blind-read-head\
                blind-rectangle-tessellation\
 (DIR) diff --git a/README b/README
       @@ -177,6 +177,9 @@ UTILITIES
               blind-norm(1)
                      Calculate the norm of colours in a video
        
       +       blind-peek-head
       +              Peeks the head from a video
       +
               blind-premultiply(1)
                      Premultiply the alpha channel of a video
        
 (DIR) diff --git a/config.mk b/config.mk
       @@ -13,9 +13,9 @@ KORN_SHELL = bash
        # Commands
        LN = ln -s
        
       -# You may want to remove -DHAVE_PRCTL, -DHAVE_EPOLL, and
       -# -DHAVE_SENDFILE from CPPFLAGS if you are not using Linux.
       +# You may want to remove -DHAVE_PRCTL, -DHAVE_EPOLL, -DHAVE_TEE,
       +# and -DHAVE_SENDFILE from CPPFLAGS if you are not using Linux.
        CFLAGS   = -std=c11 -Wall -pedantic -O2
       -CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_FILE_OFFSET_BITS=64\
       -           -DHAVE_PRCTL -DHAVE_EPOLL -DHAVE_SENDFILE
       +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE \
       +           -D_FILE_OFFSET_BITS=64 -DHAVE_PRCTL -DHAVE_EPOLL -DHAVE_TEE -DHAVE_SENDFILE
        LDFLAGS  = -lm -s
 (DIR) diff --git a/man/blind-peek-head.1 b/man/blind-peek-head.1
       @@ -0,0 +1,28 @@
       +.TH BLIND-PEEK-HEAD 1 blind
       +.SH NAME
       +blind-peek-head - Peeks the head from a video
       +.SH SYNOPSIS
       +.B blind-peek-head
       +.SH DESCRIPTION
       +.B blind-peek-head
       +peeks the head a video from stdin, and
       +prints it, without the magic number, to stdout.
       +The output will contain: the number of frames,
       +<space>, the width, <space>, the height, <space>,
       +and the pixel format. The output is text, and
       +thus ends with a <newline>. The state of stdin
       +will remain unchanged.
       +.SH NOTES
       +.B blind-peek-head
       +requires that stdin is a socket, a regular file,
       +or a (on Linux only) a pipe. Direct pipes and
       +datagram sockets are supported but require that
       +the entire head must be written in one write.
       +.SH SEE ALSO
       +.BR blind (7),
       +.BR blind-read-head (1),
       +.BR blind-write-head (1),
       +.BR blind-next-frame (1)
       +.SH AUTHORS
       +Mattias Andrée
       +.RI < maandree@kth.se >
 (DIR) diff --git a/man/blind-read-head.1 b/man/blind-read-head.1
       @@ -18,6 +18,7 @@ can be used to get the first, and
        any following, frame from stdin.
        .SH SEE ALSO
        .BR blind (7),
       +.BR blind-peek-head (1),
        .BR blind-write-head (1),
        .BR blind-next-frame (1)
        .SH AUTHORS
 (DIR) diff --git a/man/blind.7 b/man/blind.7
       @@ -195,6 +195,9 @@ Extracts the next frame from a video
        .BR blind-norm (1)
        Calculate the norm of colours in a video
        .TP
       +.BR blind-peek-head (1)
       +Peeks the head from a video
       +.TP
        .BR blind-premultiply (1)
        Premultiply the alpha channel of a video
        .TP
 (DIR) diff --git a/src/blind-peek-head.c b/src/blind-peek-head.c
       @@ -0,0 +1,152 @@
       +/* See LICENSE file for copyright and license details. */
       +#include "common.h"
       +
       +USAGE("")
       +
       +static ssize_t
       +peek_socket(char *buf, size_t n)
       +{
       +        ssize_t r = recv(STDIN_FILENO, buf, n, MSG_PEEK);
       +        if (r < 0 && errno != ENOTSOCK)
       +                eprintf("recv <stdin>:");
       +        return r;
       +}
       +
       +static ssize_t
       +peek_regular(char *buf, size_t n)
       +{
       +        ssize_t r;
       +        off_t pos = lseek(STDIN_FILENO, 0, SEEK_CUR);
       +        if (pos < 0) {
       +                if (errno != ESPIPE)
       +                        eprintf("lseek <stdin>:");
       +                return -1;
       +        }
       +        r = pread(STDIN_FILENO, buf, n, pos);
       +        if (r < 0 && errno != ESPIPE)
       +                eprintf("pread <stdin>:");
       +        return r;
       +}
       +
       +#if defined(HAVE_TEE)
       +static ssize_t
       +peek_pipe(char *buf, size_t n)
       +{
       +        int rw[2];
       +        ssize_t m;
       +        size_t p;
       +        if (pipe(rw))
       +                eprintf("pipe");
       +        m = tee(STDIN_FILENO, rw[1], n, 0);
       +        if (m < 0) {
       +                if (errno != EINVAL)
       +                        eprintf("tee <stdin>:");
       +                return -1;
       +        }
       +        close(rw[1]);
       +        p = ereadall(rw[0], buf, (size_t)m, "<pipe>");
       +        close(rw[0]);
       +        return (ssize_t)p;
       +}
       +#endif
       +
       +static size_t
       +peek(char *buf, size_t n)
       +{
       +        static int method = 0;
       +        ssize_t r;
       +        switch (method) {
       +        case 0:
       +                if ((r = peek_socket(buf, n)) >= 0)
       +                        return (size_t)r;
       +                method++;
       +                /* fall-through */
       +        case 1:
       +                if ((r = peek_regular(buf, n)) >= 0)
       +                        return (size_t)r;
       +                method++;
       +#if defined(HAVE_TEE)
       +                /* fall-through */
       +        default:
       +                if ((r = peek_pipe(buf, n)) >= 0)
       +                        return (size_t)r;
       +                eprintf("can only peek pipes, sockets, and regular files\n");
       +#else
       +                eprintf("can only peek sockets and regular files\n");
       +#endif
       +        }
       +}
       +
       +int
       +main(int argc, char *argv[])
       +{
       +        char buf[STREAM_HEAD_MAX], *p;
       +        char magic[] = {'\0', 'u', 'i', 'v', 'f'};
       +        size_t i, len = 0, last_len;
       +#if defined(HAVE_EPOLL)
       +        struct epoll_event ev;
       +        int epfd, epr = 0;
       +#endif
       +
       +        UNOFLAGS(argc);
       +
       +#if defined(HAVE_EPOLL)
       +        epfd = epoll_create1(0);
       +        if (epfd < 0)
       +                eprintf("epoll_create1:");
       +
       +        memset(&ev, 0, sizeof(ev));
       +        ev.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
       +        if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev))
       +                eprintf("epoll_ctl EPOLL_CTL_ADD:");
       +
       +        do {
       +                last_len = len;
       +                len = peek(buf, sizeof(buf));
       +                p = memchr(buf, '\n', len);
       +                if (p && len >= (size_t)(++p - buf) + ELEMENTSOF(magic))
       +                        goto ready;
       +        } while (len > last_len && (epr = epoll_wait(epfd, &ev, 1, -1)) >= 0);
       +        if (epr < 0)
       +                eprintf("epoll_wait:");
       +#else
       +        goto beginning;
       +        do {
       +                usleep(50000);
       +        beginning:
       +                last_len = len;
       +                len = peek(buf, n);
       +                p = memchr(buf, '\n', len);
       +                if (p && len >= (size_t)(++p - buf) + ELEMENTSOF(magic))
       +                        goto ready;
       +        } while (len > last_len);
       +#endif
       +        eprintf("could not read entire head\n");
       +
       +ready:
       +        len = (size_t)(p - buf);
       +        for (i = 0; i < ELEMENTSOF(magic); i++)
       +                if (p[i] != magic[i])
       +                        goto bad_format;
       +        p = buf;
       +        for (i = 0; i < 3; i++) {
       +                if (!isdigit(*p))
       +                        goto bad_format;
       +                while (isdigit(*p)) p++;
       +                if (*p++ != ' ')
       +                        goto bad_format;
       +        }
       +        while (isalnum(*p) || *p == ' ') {
       +                if (p[0] == ' ' && p[-1] == ' ')
       +                        goto bad_format;
       +                p++;
       +        }
       +        if (p[-1] == ' ' || p[0] != '\n')
       +                goto bad_format;
       +
       +        ewriteall(STDOUT_FILENO, buf, len, "<stdout>");
       +        return 0;
       +
       +bad_format:
       +        eprintf("<stdin>: file format not supported\n");
       +}