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