stream.c - blind - suckless command-line video editing utility
  HTML git clone git://git.suckless.org/blind
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 }