1 /*
2  * Copyright © 2018, VideoLAN and dav1d authors
3  * Copyright © 2018, Two Orioles, LLC
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright notice, this
10  *    list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "config.h"
29 #include "cli_config.h"
30 
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "common/attributes.h"
37 #include "common/intops.h"
38 
39 #include "output/output.h"
40 #include "output/muxer.h"
41 
42 struct MuxerContext {
43     MuxerPriv *data;
44     const Muxer *impl;
45     int one_file_per_frame;
46     unsigned fps[2];
47     const char *filename;
48     int framenum;
49 };
50 
51 extern const Muxer null_muxer;
52 extern const Muxer md5_muxer;
53 extern const Muxer xxh3_muxer;
54 extern const Muxer yuv_muxer;
55 extern const Muxer y4m2_muxer;
56 static const Muxer *muxers[] = {
57     &null_muxer,
58     &md5_muxer,
59 #if HAVE_XXHASH_H
60     &xxh3_muxer,
61 #endif
62     &yuv_muxer,
63     &y4m2_muxer,
64     NULL
65 };
66 
find_extension(const char * const f)67 static const char *find_extension(const char *const f) {
68     const size_t l = strlen(f);
69 
70     if (l == 0) return NULL;
71 
72     const char *const end = &f[l - 1], *step = end;
73     while ((*step >= 'a' && *step <= 'z') ||
74            (*step >= 'A' && *step <= 'Z') ||
75            (*step >= '0' && *step <= '9'))
76     {
77         step--;
78     }
79 
80     return (step < end && step > f && *step == '.' && step[-1] != '/') ?
81            &step[1] : NULL;
82 }
83 
output_open(MuxerContext ** const c_out,const char * const name,const char * const filename,const Dav1dPictureParameters * const p,const unsigned fps[2])84 int output_open(MuxerContext **const c_out,
85                 const char *const name, const char *const filename,
86                 const Dav1dPictureParameters *const p, const unsigned fps[2])
87 {
88     const Muxer *impl;
89     MuxerContext *c;
90     unsigned i;
91     int res;
92     int name_offset = 0;
93 
94     if (name) {
95         name_offset = 5 * !strncmp(name, "frame", 5);
96         for (i = 0; muxers[i]; i++) {
97             if (!strcmp(muxers[i]->name, &name[name_offset])) {
98                 impl = muxers[i];
99                 break;
100             }
101         }
102         if (!muxers[i]) {
103             fprintf(stderr, "Failed to find muxer named \"%s\"\n", name);
104             return DAV1D_ERR(ENOPROTOOPT);
105         }
106     } else if (!strcmp(filename, "/dev/null")) {
107         impl = muxers[0];
108     } else {
109         const char *const ext = find_extension(filename);
110         if (!ext) {
111             fprintf(stderr, "No extension found for file %s\n", filename);
112             return -1;
113         }
114         for (i = 0; muxers[i]; i++) {
115             if (!strcmp(muxers[i]->extension, ext)) {
116                 impl = muxers[i];
117                 break;
118             }
119         }
120         if (!muxers[i]) {
121             fprintf(stderr, "Failed to find muxer for extension \"%s\"\n", ext);
122             return DAV1D_ERR(ENOPROTOOPT);
123         }
124     }
125 
126     if (!(c = malloc(sizeof(MuxerContext) + impl->priv_data_size))) {
127         fprintf(stderr, "Failed to allocate memory\n");
128         return DAV1D_ERR(ENOMEM);
129     }
130     c->impl = impl;
131     c->data = (MuxerPriv *) &c[1];
132     int have_num_pattern = 0;
133     for (const char *ptr = filename ? strchr(filename, '%') : NULL;
134          !have_num_pattern && ptr; ptr = strchr(ptr, '%'))
135     {
136         ptr++; // skip '%'
137         while (*ptr >= '0' && *ptr <= '9')
138             ptr++; // skip length indicators
139         have_num_pattern = *ptr == 'n';
140     }
141     c->one_file_per_frame = name_offset || (!name && have_num_pattern);
142 
143     if (c->one_file_per_frame) {
144         c->fps[0] = fps[0];
145         c->fps[1] = fps[1];
146         c->filename = filename;
147         c->framenum = 0;
148     } else if (impl->write_header &&
149                (res = impl->write_header(c->data, filename, p, fps)) < 0)
150     {
151         free(c);
152         return res;
153     }
154     *c_out = c;
155 
156     return 0;
157 }
158 
safe_strncat(char * const dst,const int dst_len,const char * const src,const int src_len)159 static void safe_strncat(char *const dst, const int dst_len,
160                          const char *const src, const int src_len)
161 {
162     if (!src_len) return;
163     const int dst_fill = (int) strlen(dst);
164     assert(dst_fill < dst_len);
165     const int to_copy = imin(src_len, dst_len - dst_fill - 1);
166     if (!to_copy) return;
167     memcpy(dst + dst_fill, src, to_copy);
168     dst[dst_fill + to_copy] = 0;
169 }
170 
assemble_field(char * const dst,const int dst_len,const char * const fmt,const int fmt_len,const int field)171 static void assemble_field(char *const dst, const int dst_len,
172                            const char *const fmt, const int fmt_len,
173                            const int field)
174 {
175     char fmt_copy[32];
176 
177     assert(fmt[0] == '%');
178     fmt_copy[0] = '%';
179     if (fmt[1] >= '1' && fmt[1] <= '9') {
180         fmt_copy[1] = '0'; // pad with zeroes, not spaces
181         fmt_copy[2] = 0;
182     } else {
183         fmt_copy[1] = 0;
184     }
185     safe_strncat(fmt_copy, sizeof(fmt_copy), &fmt[1], fmt_len - 1);
186     safe_strncat(fmt_copy, sizeof(fmt_copy), "d", 1);
187 
188     char tmp[32];
189     snprintf(tmp, sizeof(tmp), fmt_copy, field);
190 
191     safe_strncat(dst, dst_len, tmp, (int) strlen(tmp));
192 }
193 
assemble_filename(MuxerContext * const ctx,char * const filename,const int filename_size,const Dav1dPictureParameters * const p)194 static void assemble_filename(MuxerContext *const ctx, char *const filename,
195                               const int filename_size,
196                               const Dav1dPictureParameters *const p)
197 {
198     filename[0] = 0;
199     const int framenum = ctx->framenum++;
200     assert(ctx->filename);
201     const char *ptr = ctx->filename, *iptr;
202     while ((iptr = strchr(ptr, '%'))) {
203         safe_strncat(filename, filename_size, ptr, (int) (iptr - ptr));
204         ptr = iptr;
205 
206         const char *iiptr = &iptr[1]; // skip '%'
207         while (*iiptr >= '0' && *iiptr <= '9')
208             iiptr++; // skip length indicators
209 
210         switch (*iiptr) {
211         case 'w':
212             assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), p->w);
213             break;
214         case 'h':
215             assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), p->h);
216             break;
217         case 'n':
218             assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), framenum);
219             break;
220         default:
221             safe_strncat(filename, filename_size, "%", 1);
222             ptr = &iptr[1];
223             continue;
224         }
225 
226         ptr = &iiptr[1];
227     }
228     safe_strncat(filename, filename_size, ptr, (int) strlen(ptr));
229 }
230 
output_write(MuxerContext * const ctx,Dav1dPicture * const p)231 int output_write(MuxerContext *const ctx, Dav1dPicture *const p) {
232     int res;
233 
234     if (ctx->one_file_per_frame && ctx->impl->write_header) {
235         char filename[1024];
236         assemble_filename(ctx, filename, sizeof(filename), &p->p);
237         res = ctx->impl->write_header(ctx->data, filename, &p->p, ctx->fps);
238         if (res < 0)
239             return res;
240     }
241     if ((res = ctx->impl->write_picture(ctx->data, p)) < 0)
242         return res;
243     if (ctx->one_file_per_frame && ctx->impl->write_trailer)
244         ctx->impl->write_trailer(ctx->data);
245 
246     return 0;
247 }
248 
output_close(MuxerContext * const ctx)249 void output_close(MuxerContext *const ctx) {
250     if (!ctx->one_file_per_frame && ctx->impl->write_trailer)
251         ctx->impl->write_trailer(ctx->data);
252     free(ctx);
253 }
254 
output_verify(MuxerContext * const ctx,const char * const md5_str)255 int output_verify(MuxerContext *const ctx, const char *const md5_str) {
256     const int res = ctx->impl->verify ?
257         ctx->impl->verify(ctx->data, md5_str) : 0;
258     free(ctx);
259     return res;
260 }
261