| /* |
| * Copyright © 2018, VideoLAN and dav1d authors |
| * Copyright © 2018, Two Orioles, LLC |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, this |
| * list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
| * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "cli_config.h" |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "common/attributes.h" |
| #include "common/intops.h" |
| |
| #include "output/output.h" |
| #include "output/muxer.h" |
| |
| struct MuxerContext { |
| MuxerPriv *data; |
| const Muxer *impl; |
| int one_file_per_frame; |
| unsigned fps[2]; |
| const char *filename; |
| int framenum; |
| uint64_t priv_data[]; |
| }; |
| |
| extern const Muxer null_muxer; |
| extern const Muxer md5_muxer; |
| extern const Muxer xxh3_muxer; |
| extern const Muxer yuv_muxer; |
| extern const Muxer y4m2_muxer; |
| static const Muxer *muxers[] = { |
| &null_muxer, |
| &md5_muxer, |
| #if HAVE_XXHASH_H |
| &xxh3_muxer, |
| #endif |
| &yuv_muxer, |
| &y4m2_muxer, |
| NULL |
| }; |
| |
| static const char *find_extension(const char *const f) { |
| const size_t l = strlen(f); |
| |
| if (l == 0) return NULL; |
| |
| const char *const end = &f[l - 1], *step = end; |
| while ((*step >= 'a' && *step <= 'z') || |
| (*step >= 'A' && *step <= 'Z') || |
| (*step >= '0' && *step <= '9')) |
| { |
| step--; |
| } |
| |
| return (step < end && step > f && *step == '.' && step[-1] != '/') ? |
| &step[1] : NULL; |
| } |
| |
| int output_open(MuxerContext **const c_out, |
| const char *const name, const char *const filename, |
| const Dav1dPictureParameters *const p, const unsigned fps[2]) |
| { |
| const Muxer *impl; |
| MuxerContext *c; |
| unsigned i; |
| int res; |
| int name_offset = 0; |
| |
| if (name) { |
| name_offset = 5 * !strncmp(name, "frame", 5); |
| for (i = 0; muxers[i]; i++) { |
| if (!strcmp(muxers[i]->name, &name[name_offset])) { |
| impl = muxers[i]; |
| break; |
| } |
| } |
| if (!muxers[i]) { |
| fprintf(stderr, "Failed to find muxer named \"%s\"\n", name); |
| return DAV1D_ERR(ENOPROTOOPT); |
| } |
| } else if (!strcmp(filename, "/dev/null")) { |
| impl = muxers[0]; |
| } else { |
| const char *const ext = find_extension(filename); |
| if (!ext) { |
| fprintf(stderr, "No extension found for file %s\n", filename); |
| return -1; |
| } |
| for (i = 0; muxers[i]; i++) { |
| if (!strcmp(muxers[i]->extension, ext)) { |
| impl = muxers[i]; |
| break; |
| } |
| } |
| if (!muxers[i]) { |
| fprintf(stderr, "Failed to find muxer for extension \"%s\"\n", ext); |
| return DAV1D_ERR(ENOPROTOOPT); |
| } |
| } |
| |
| if (!(c = malloc(offsetof(MuxerContext, priv_data) + impl->priv_data_size))) { |
| fprintf(stderr, "Failed to allocate memory\n"); |
| return DAV1D_ERR(ENOMEM); |
| } |
| c->impl = impl; |
| c->data = (MuxerPriv *) c->priv_data; |
| int have_num_pattern = 0; |
| for (const char *ptr = filename ? strchr(filename, '%') : NULL; |
| !have_num_pattern && ptr; ptr = strchr(ptr, '%')) |
| { |
| ptr++; // skip '%' |
| while (*ptr >= '0' && *ptr <= '9') |
| ptr++; // skip length indicators |
| have_num_pattern = *ptr == 'n'; |
| } |
| c->one_file_per_frame = name_offset || (!name && have_num_pattern); |
| |
| if (c->one_file_per_frame) { |
| c->fps[0] = fps[0]; |
| c->fps[1] = fps[1]; |
| c->filename = filename; |
| c->framenum = 0; |
| } else if (impl->write_header && |
| (res = impl->write_header(c->data, filename, p, fps)) < 0) |
| { |
| free(c); |
| return res; |
| } |
| *c_out = c; |
| |
| return 0; |
| } |
| |
| static void safe_strncat(char *const dst, const int dst_len, |
| const char *const src, const int src_len) |
| { |
| if (!src_len) return; |
| const int dst_fill = (int) strlen(dst); |
| assert(dst_fill < dst_len); |
| const int to_copy = imin(src_len, dst_len - dst_fill - 1); |
| if (!to_copy) return; |
| memcpy(dst + dst_fill, src, to_copy); |
| dst[dst_fill + to_copy] = 0; |
| } |
| |
| static void assemble_field(char *const dst, const int dst_len, |
| const char *const fmt, const int fmt_len, |
| const int field) |
| { |
| char fmt_copy[32]; |
| |
| assert(fmt[0] == '%'); |
| fmt_copy[0] = '%'; |
| if (fmt[1] >= '1' && fmt[1] <= '9') { |
| fmt_copy[1] = '0'; // pad with zeroes, not spaces |
| fmt_copy[2] = 0; |
| } else { |
| fmt_copy[1] = 0; |
| } |
| safe_strncat(fmt_copy, sizeof(fmt_copy), &fmt[1], fmt_len - 1); |
| safe_strncat(fmt_copy, sizeof(fmt_copy), "d", 1); |
| |
| char tmp[32]; |
| snprintf(tmp, sizeof(tmp), fmt_copy, field); |
| |
| safe_strncat(dst, dst_len, tmp, (int) strlen(tmp)); |
| } |
| |
| static void assemble_filename(MuxerContext *const ctx, char *const filename, |
| const int filename_size, |
| const Dav1dPictureParameters *const p) |
| { |
| filename[0] = 0; |
| const int framenum = ctx->framenum++; |
| assert(ctx->filename); |
| const char *ptr = ctx->filename, *iptr; |
| while ((iptr = strchr(ptr, '%'))) { |
| safe_strncat(filename, filename_size, ptr, (int) (iptr - ptr)); |
| ptr = iptr; |
| |
| const char *iiptr = &iptr[1]; // skip '%' |
| while (*iiptr >= '0' && *iiptr <= '9') |
| iiptr++; // skip length indicators |
| |
| switch (*iiptr) { |
| case 'w': |
| assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), p->w); |
| break; |
| case 'h': |
| assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), p->h); |
| break; |
| case 'n': |
| assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), framenum); |
| break; |
| default: |
| safe_strncat(filename, filename_size, "%", 1); |
| ptr = &iptr[1]; |
| continue; |
| } |
| |
| ptr = &iiptr[1]; |
| } |
| safe_strncat(filename, filename_size, ptr, (int) strlen(ptr)); |
| } |
| |
| int output_write(MuxerContext *const ctx, Dav1dPicture *const p) { |
| int res; |
| |
| if (ctx->one_file_per_frame && ctx->impl->write_header) { |
| char filename[1024]; |
| assemble_filename(ctx, filename, sizeof(filename), &p->p); |
| res = ctx->impl->write_header(ctx->data, filename, &p->p, ctx->fps); |
| if (res < 0) |
| return res; |
| } |
| if ((res = ctx->impl->write_picture(ctx->data, p)) < 0) |
| return res; |
| if (ctx->one_file_per_frame && ctx->impl->write_trailer) |
| ctx->impl->write_trailer(ctx->data); |
| |
| return 0; |
| } |
| |
| void output_close(MuxerContext *const ctx) { |
| if (!ctx->one_file_per_frame && ctx->impl->write_trailer) |
| ctx->impl->write_trailer(ctx->data); |
| free(ctx); |
| } |
| |
| int output_verify(MuxerContext *const ctx, const char *const md5_str) { |
| const int res = ctx->impl->verify ? |
| ctx->impl->verify(ctx->data, md5_str) : 0; |
| free(ctx); |
| return res; |
| } |