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 uint64_t priv_data[];
50 };
51
52 extern const Muxer null_muxer;
53 extern const Muxer md5_muxer;
54 extern const Muxer xxh3_muxer;
55 extern const Muxer yuv_muxer;
56 extern const Muxer y4m2_muxer;
57 static const Muxer *muxers[] = {
58 &null_muxer,
59 &md5_muxer,
60 #if HAVE_XXHASH_H
61 &xxh3_muxer,
62 #endif
63 &yuv_muxer,
64 &y4m2_muxer,
65 NULL
66 };
67
find_extension(const char * const f)68 static const char *find_extension(const char *const f) {
69 const size_t l = strlen(f);
70
71 if (l == 0) return NULL;
72
73 const char *const end = &f[l - 1], *step = end;
74 while ((*step >= 'a' && *step <= 'z') ||
75 (*step >= 'A' && *step <= 'Z') ||
76 (*step >= '0' && *step <= '9'))
77 {
78 step--;
79 }
80
81 return (step < end && step > f && *step == '.' && step[-1] != '/') ?
82 &step[1] : NULL;
83 }
84
output_open(MuxerContext ** const c_out,const char * const name,const char * const filename,const Dav1dPictureParameters * const p,const unsigned fps[2])85 int output_open(MuxerContext **const c_out,
86 const char *const name, const char *const filename,
87 const Dav1dPictureParameters *const p, const unsigned fps[2])
88 {
89 const Muxer *impl;
90 MuxerContext *c;
91 unsigned i;
92 int res;
93 int name_offset = 0;
94
95 if (name) {
96 name_offset = 5 * !strncmp(name, "frame", 5);
97 for (i = 0; muxers[i]; i++) {
98 if (!strcmp(muxers[i]->name, &name[name_offset])) {
99 impl = muxers[i];
100 break;
101 }
102 }
103 if (!muxers[i]) {
104 fprintf(stderr, "Failed to find muxer named \"%s\"\n", name);
105 return DAV1D_ERR(ENOPROTOOPT);
106 }
107 } else if (!strcmp(filename, "/dev/null")) {
108 impl = muxers[0];
109 } else {
110 const char *const ext = find_extension(filename);
111 if (!ext) {
112 fprintf(stderr, "No extension found for file %s\n", filename);
113 return -1;
114 }
115 for (i = 0; muxers[i]; i++) {
116 if (!strcmp(muxers[i]->extension, ext)) {
117 impl = muxers[i];
118 break;
119 }
120 }
121 if (!muxers[i]) {
122 fprintf(stderr, "Failed to find muxer for extension \"%s\"\n", ext);
123 return DAV1D_ERR(ENOPROTOOPT);
124 }
125 }
126
127 if (!(c = malloc(offsetof(MuxerContext, priv_data) + impl->priv_data_size))) {
128 fprintf(stderr, "Failed to allocate memory\n");
129 return DAV1D_ERR(ENOMEM);
130 }
131 c->impl = impl;
132 c->data = (MuxerPriv *) c->priv_data;
133 int have_num_pattern = 0;
134 for (const char *ptr = filename ? strchr(filename, '%') : NULL;
135 !have_num_pattern && ptr; ptr = strchr(ptr, '%'))
136 {
137 ptr++; // skip '%'
138 while (*ptr >= '0' && *ptr <= '9')
139 ptr++; // skip length indicators
140 have_num_pattern = *ptr == 'n';
141 }
142 c->one_file_per_frame = name_offset || (!name && have_num_pattern);
143
144 if (c->one_file_per_frame) {
145 c->fps[0] = fps[0];
146 c->fps[1] = fps[1];
147 c->filename = filename;
148 c->framenum = 0;
149 } else if (impl->write_header &&
150 (res = impl->write_header(c->data, filename, p, fps)) < 0)
151 {
152 free(c);
153 return res;
154 }
155 *c_out = c;
156
157 return 0;
158 }
159
safe_strncat(char * const dst,const int dst_len,const char * const src,const int src_len)160 static void safe_strncat(char *const dst, const int dst_len,
161 const char *const src, const int src_len)
162 {
163 if (!src_len) return;
164 const int dst_fill = (int) strlen(dst);
165 assert(dst_fill < dst_len);
166 const int to_copy = imin(src_len, dst_len - dst_fill - 1);
167 if (!to_copy) return;
168 memcpy(dst + dst_fill, src, to_copy);
169 dst[dst_fill + to_copy] = 0;
170 }
171
assemble_field(char * const dst,const int dst_len,const char * const fmt,const int fmt_len,const int field)172 static void assemble_field(char *const dst, const int dst_len,
173 const char *const fmt, const int fmt_len,
174 const int field)
175 {
176 char fmt_copy[32];
177
178 assert(fmt[0] == '%');
179 fmt_copy[0] = '%';
180 if (fmt[1] >= '1' && fmt[1] <= '9') {
181 fmt_copy[1] = '0'; // pad with zeroes, not spaces
182 fmt_copy[2] = 0;
183 } else {
184 fmt_copy[1] = 0;
185 }
186 safe_strncat(fmt_copy, sizeof(fmt_copy), &fmt[1], fmt_len - 1);
187 safe_strncat(fmt_copy, sizeof(fmt_copy), "d", 1);
188
189 char tmp[32];
190 snprintf(tmp, sizeof(tmp), fmt_copy, field);
191
192 safe_strncat(dst, dst_len, tmp, (int) strlen(tmp));
193 }
194
assemble_filename(MuxerContext * const ctx,char * const filename,const int filename_size,const Dav1dPictureParameters * const p)195 static void assemble_filename(MuxerContext *const ctx, char *const filename,
196 const int filename_size,
197 const Dav1dPictureParameters *const p)
198 {
199 filename[0] = 0;
200 const int framenum = ctx->framenum++;
201 assert(ctx->filename);
202 const char *ptr = ctx->filename, *iptr;
203 while ((iptr = strchr(ptr, '%'))) {
204 safe_strncat(filename, filename_size, ptr, (int) (iptr - ptr));
205 ptr = iptr;
206
207 const char *iiptr = &iptr[1]; // skip '%'
208 while (*iiptr >= '0' && *iiptr <= '9')
209 iiptr++; // skip length indicators
210
211 switch (*iiptr) {
212 case 'w':
213 assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), p->w);
214 break;
215 case 'h':
216 assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), p->h);
217 break;
218 case 'n':
219 assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), framenum);
220 break;
221 default:
222 safe_strncat(filename, filename_size, "%", 1);
223 ptr = &iptr[1];
224 continue;
225 }
226
227 ptr = &iiptr[1];
228 }
229 safe_strncat(filename, filename_size, ptr, (int) strlen(ptr));
230 }
231
output_write(MuxerContext * const ctx,Dav1dPicture * const p)232 int output_write(MuxerContext *const ctx, Dav1dPicture *const p) {
233 int res;
234
235 if (ctx->one_file_per_frame && ctx->impl->write_header) {
236 char filename[1024];
237 assemble_filename(ctx, filename, sizeof(filename), &p->p);
238 res = ctx->impl->write_header(ctx->data, filename, &p->p, ctx->fps);
239 if (res < 0)
240 return res;
241 }
242 if ((res = ctx->impl->write_picture(ctx->data, p)) < 0)
243 return res;
244 if (ctx->one_file_per_frame && ctx->impl->write_trailer)
245 ctx->impl->write_trailer(ctx->data);
246
247 return 0;
248 }
249
output_close(MuxerContext * const ctx)250 void output_close(MuxerContext *const ctx) {
251 if (!ctx->one_file_per_frame && ctx->impl->write_trailer)
252 ctx->impl->write_trailer(ctx->data);
253 free(ctx);
254 }
255
output_verify(MuxerContext * const ctx,const char * const md5_str)256 int output_verify(MuxerContext *const ctx, const char *const md5_str) {
257 const int res = ctx->impl->verify ?
258 ctx->impl->verify(ctx->data, md5_str) : 0;
259 free(ctx);
260 return res;
261 }
262