/src/vlc/modules/demux/mp4/attachments.c
Line | Count | Source (jump to first uncovered line) |
1 | | /***************************************************************************** |
2 | | * attachments.c : MP4 attachments handling |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 2001-2015 VLC authors and VideoLAN |
5 | | * 2019 VideoLabs |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify it |
8 | | * under the terms of the GNU Lesser General Public License as published by |
9 | | * the Free Software Foundation; either version 2.1 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * This program is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with this program; if not, write to the Free Software Foundation, |
19 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
20 | | *****************************************************************************/ |
21 | | #ifdef HAVE_CONFIG_H |
22 | | # include "config.h" |
23 | | #endif |
24 | | |
25 | | #include <vlc_common.h> |
26 | | #include <vlc_input.h> |
27 | | |
28 | | #include "libmp4.h" |
29 | | #include "attachments.h" |
30 | | #include <limits.h> |
31 | | |
32 | | static const char *psz_meta_roots[] = { "/moov/udta/meta/ilst", |
33 | | "/moov/meta/ilst", |
34 | | "/moov/udta/meta", |
35 | | "/moov/udta", |
36 | | "/meta/ilst", |
37 | | "/udta" }; |
38 | | |
39 | | const MP4_Box_t *MP4_GetMetaRoot( const MP4_Box_t *p_root, const char **ppsz_path ) |
40 | 7 | { |
41 | 40 | for( size_t i = 0; i < ARRAY_SIZE(psz_meta_roots); i++ ) |
42 | 36 | { |
43 | 36 | MP4_Box_t *p_udta = MP4_BoxGet( p_root, psz_meta_roots[i] ); |
44 | 36 | if ( p_udta ) |
45 | 3 | { |
46 | 3 | *ppsz_path = psz_meta_roots[i]; |
47 | 3 | return p_udta; |
48 | 3 | } |
49 | 36 | } |
50 | 4 | return NULL; |
51 | 7 | } |
52 | | |
53 | | enum detectedImageType |
54 | | { |
55 | | IMAGE_TYPE_UNKNOWN = DATA_WKT_RESERVED, |
56 | | IMAGE_TYPE_JPEG = DATA_WKT_JPEG, |
57 | | IMAGE_TYPE_PNG = DATA_WKT_PNG, |
58 | | IMAGE_TYPE_BMP = DATA_WKT_BMP, |
59 | | }; |
60 | | |
61 | | static const struct |
62 | | { |
63 | | enum detectedImageType type; |
64 | | const char *mime; |
65 | | } rg_imageTypeToMime[] = { |
66 | | { IMAGE_TYPE_JPEG, "image/jpeg" }, |
67 | | { IMAGE_TYPE_PNG, "image/png" }, |
68 | | { IMAGE_TYPE_BMP, "image/bmp" }, |
69 | | }; |
70 | | |
71 | | static const char * getMimeType( enum detectedImageType type ) |
72 | 0 | { |
73 | 0 | for( size_t i=0; i<ARRAY_SIZE(rg_imageTypeToMime); i++ ) |
74 | 0 | { |
75 | 0 | if( rg_imageTypeToMime[i].type == type ) |
76 | 0 | return rg_imageTypeToMime[i].mime; |
77 | 0 | } |
78 | 0 | return NULL; |
79 | 0 | } |
80 | | |
81 | | static enum detectedImageType wellKnownToimageType( int e_wellknowntype ) |
82 | 0 | { |
83 | 0 | switch( e_wellknowntype ) |
84 | 0 | { |
85 | 0 | case DATA_WKT_PNG: |
86 | 0 | case DATA_WKT_JPEG: |
87 | 0 | case DATA_WKT_BMP: |
88 | 0 | return e_wellknowntype; |
89 | 0 | default: |
90 | 0 | return IMAGE_TYPE_UNKNOWN; |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | | static enum detectedImageType probeImageType( const uint8_t *p_data, size_t i_data ) |
95 | 0 | { |
96 | 0 | if( i_data > 4 ) |
97 | 0 | { |
98 | 0 | if( !memcmp( p_data, "\xFF\xD8\xFF", 3 ) ) |
99 | 0 | return IMAGE_TYPE_JPEG; |
100 | 0 | else if( !memcmp( p_data, ".PNG", 4 ) ) |
101 | 0 | return IMAGE_TYPE_PNG; |
102 | 0 | } |
103 | 0 | return IMAGE_TYPE_UNKNOWN; |
104 | 0 | } |
105 | | |
106 | | static const MP4_Box_t * GetValidCovrMeta( const MP4_Box_t *p_data, |
107 | | unsigned *pi_index, |
108 | | const void **ctx ) |
109 | 3 | { |
110 | 3 | for( ; p_data; p_data = p_data->p_next ) |
111 | 0 | { |
112 | 0 | if( p_data->i_type != ATOM_data || p_data == *ctx ) |
113 | 0 | continue; |
114 | 0 | (*pi_index)++; |
115 | 0 | if ( !BOXDATA(p_data) || |
116 | 0 | wellKnownToimageType( |
117 | 0 | BOXDATA(p_data)->e_wellknowntype ) == IMAGE_TYPE_UNKNOWN ) |
118 | 0 | continue; |
119 | 0 | *ctx = p_data; |
120 | 0 | return p_data; |
121 | 0 | } |
122 | 3 | return NULL; |
123 | 3 | } |
124 | | |
125 | | static const MP4_Box_t * GetValidPnotMeta( const MP4_Box_t *p_pnot, |
126 | | unsigned *pi_index, |
127 | | const void **ctx ) |
128 | 0 | { |
129 | 0 | for( ; p_pnot; p_pnot = p_pnot->p_next ) |
130 | 0 | { |
131 | 0 | if( p_pnot->i_type != ATOM_pnot || p_pnot == *ctx ) |
132 | 0 | continue; |
133 | 0 | (*pi_index)++; |
134 | 0 | if( BOXDATA(p_pnot)->i_type != ATOM_PICT && |
135 | 0 | BOXDATA(p_pnot)->i_type != ATOM_pict ) |
136 | 0 | continue; |
137 | 0 | *ctx = p_pnot; |
138 | 0 | return p_pnot; |
139 | 0 | } |
140 | 0 | return NULL; |
141 | 0 | } |
142 | | |
143 | | static const MP4_Box_t * GetValidThumMeta( const MP4_Box_t *p_thum, |
144 | | unsigned *pi_index, |
145 | | const void **ctx ) |
146 | 0 | { |
147 | 0 | for( ; p_thum; p_thum = p_thum->p_next ) |
148 | 0 | { |
149 | 0 | if( p_thum->i_type != ATOM_thum || p_thum == *ctx ) |
150 | 0 | continue; |
151 | 0 | (*pi_index)++; |
152 | 0 | if ( !p_thum->data.p_binary || |
153 | 0 | probeImageType( p_thum->data.p_binary->p_blob, |
154 | 0 | p_thum->data.p_binary->i_blob ) |
155 | 0 | == IMAGE_TYPE_UNKNOWN ) |
156 | 0 | continue; |
157 | 0 | *ctx = p_thum; |
158 | 0 | return p_thum; |
159 | 0 | } |
160 | 0 | return NULL; |
161 | 0 | } |
162 | | |
163 | | int MP4_GetCoverMetaURI( const MP4_Box_t *p_root, |
164 | | const MP4_Box_t *p_metaroot, |
165 | | const char *psz_metapath, |
166 | | vlc_meta_t *p_meta ) |
167 | 7 | { |
168 | 7 | bool b_attachment_set = false; |
169 | | |
170 | 7 | if( !p_meta ) |
171 | 0 | return VLC_EGENERIC; |
172 | | |
173 | 7 | if ( p_metaroot ) |
174 | 3 | { |
175 | 3 | const MP4_Box_t *p_data = MP4_BoxGet( p_metaroot, "covr/data" ); |
176 | 3 | unsigned i_index = 0; |
177 | 3 | const void *ctx = NULL; |
178 | 3 | if( (p_data = GetValidCovrMeta( p_data, &i_index, &ctx )) ) |
179 | 0 | { |
180 | 0 | char *psz_attachment; |
181 | 0 | if ( -1 != asprintf( &psz_attachment, |
182 | 0 | "attachment://%s/covr/data[%u]", |
183 | 0 | psz_metapath, i_index - 1 ) ) |
184 | 0 | { |
185 | 0 | vlc_meta_SetArtURL( p_meta, psz_attachment ); |
186 | 0 | b_attachment_set = true; |
187 | 0 | free( psz_attachment ); |
188 | 0 | } |
189 | 0 | } |
190 | 3 | } |
191 | | |
192 | 7 | const MP4_Box_t *p_pnot; |
193 | 7 | if ( !b_attachment_set && (p_pnot = MP4_BoxGet( p_root, "pnot" )) ) |
194 | 0 | { |
195 | 0 | unsigned i_index = 0; |
196 | 0 | const void *ctx = NULL; |
197 | 0 | if( (p_pnot = GetValidPnotMeta( p_pnot, &i_index, &ctx )) ) |
198 | 0 | { |
199 | 0 | char *psz_attachment; |
200 | 0 | if ( -1 != asprintf( &psz_attachment, |
201 | 0 | "attachment://pnot[%u]", i_index - 1 ) ) |
202 | 0 | { |
203 | 0 | vlc_meta_SetArtURL( p_meta, psz_attachment ); |
204 | 0 | b_attachment_set = true; |
205 | 0 | free( psz_attachment ); |
206 | 0 | } |
207 | 0 | } |
208 | 0 | } |
209 | | |
210 | | |
211 | 7 | const MP4_Box_t *p_thum; |
212 | 7 | if( !b_attachment_set && (p_thum = MP4_BoxGet( p_root, "thum" )) ) |
213 | 0 | { |
214 | 0 | unsigned i_index = 0; |
215 | 0 | const void *ctx = NULL; |
216 | 0 | if( (p_thum = GetValidThumMeta( p_thum, &i_index, &ctx )) ) |
217 | 0 | { |
218 | 0 | char *psz_attachment; |
219 | 0 | if ( -1 != asprintf( &psz_attachment, |
220 | 0 | "attachment://thum[%u]", |
221 | 0 | i_index - 1 ) ) |
222 | 0 | { |
223 | 0 | vlc_meta_SetArtURL( p_meta, psz_attachment ); |
224 | 0 | b_attachment_set = true; |
225 | 0 | free( psz_attachment ); |
226 | 0 | } |
227 | 0 | } |
228 | 0 | } |
229 | | |
230 | 7 | if( !b_attachment_set ) |
231 | 7 | return VLC_EGENERIC; |
232 | | |
233 | 0 | return VLC_SUCCESS; |
234 | 7 | } |
235 | | |
236 | | size_t MP4_GetAttachments( const MP4_Box_t *p_root, input_attachment_t ***ppp_attach ) |
237 | 0 | { |
238 | 0 | const MP4_Box_t *p_metaroot = NULL; |
239 | 0 | const char *psz_metarootpath; |
240 | 0 | size_t i_count = 0; |
241 | 0 | input_attachment_t **pp_attach = NULL; |
242 | 0 | *ppp_attach = NULL; |
243 | | |
244 | | /* Count MAX number of total attachments */ |
245 | 0 | p_metaroot = MP4_GetMetaRoot( p_root, &psz_metarootpath ); |
246 | 0 | if( p_metaroot ) |
247 | 0 | { |
248 | 0 | unsigned i_covercount = MP4_BoxCount( p_metaroot, "covr/data" ); |
249 | 0 | if( unlikely(i_covercount > INT_MAX - i_count) ) |
250 | 0 | return 0; |
251 | 0 | i_count += i_covercount; |
252 | 0 | } |
253 | | |
254 | 0 | unsigned i_pictcount = MP4_BoxCount( p_root, "pnot" ); |
255 | 0 | if( unlikely(i_pictcount > INT_MAX - i_count) ) |
256 | 0 | return 0; |
257 | 0 | i_count += i_pictcount; |
258 | |
|
259 | 0 | unsigned i_thmb_count = MP4_BoxCount( p_root, "thum" ); |
260 | 0 | if( unlikely(i_thmb_count > INT_MAX - i_count) ) |
261 | 0 | return 0; |
262 | 0 | i_count += i_thmb_count; |
263 | |
|
264 | 0 | if ( i_count == 0 ) |
265 | 0 | return 0; |
266 | | |
267 | 0 | pp_attach = vlc_alloc( i_count, sizeof(input_attachment_t*) ); |
268 | 0 | if( !(pp_attach) ) |
269 | 0 | return 0; |
270 | | |
271 | | /* Create and add valid attachments */ |
272 | 0 | i_count = 0; |
273 | | |
274 | | /* First add cover attachments */ |
275 | 0 | if ( p_metaroot ) |
276 | 0 | { |
277 | 0 | const MP4_Box_t *p_data = MP4_BoxGet( p_metaroot, "covr/data" ); |
278 | 0 | unsigned i_index = 0; |
279 | 0 | const void *ctx = NULL; |
280 | 0 | while( (p_data = GetValidCovrMeta( p_data, &i_index, &ctx )) ) |
281 | 0 | { |
282 | 0 | char *psz_filename; |
283 | |
|
284 | 0 | enum detectedImageType type = |
285 | 0 | wellKnownToimageType( BOXDATA(p_data)->e_wellknowntype ); |
286 | 0 | const char *psz_mime = getMimeType( type ); |
287 | |
|
288 | 0 | if ( asprintf( &psz_filename, "%s/covr/data[%u]", |
289 | 0 | psz_metarootpath, |
290 | 0 | i_index - 1 ) > -1 ) |
291 | 0 | { |
292 | 0 | input_attachment_t *p_attach = |
293 | 0 | vlc_input_attachment_New( |
294 | 0 | psz_filename, |
295 | 0 | psz_mime, |
296 | 0 | "Cover picture", |
297 | 0 | BOXDATA(p_data)->p_blob, |
298 | 0 | BOXDATA(p_data)->i_blob ); |
299 | 0 | free( psz_filename ); |
300 | 0 | if( p_attach ) |
301 | 0 | pp_attach[i_count++] = p_attach; |
302 | 0 | } |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | | /* Then quickdraw pict ones */ |
307 | 0 | const MP4_Box_t *p_pnot = MP4_BoxGet( p_root, "pnot" ); |
308 | 0 | if( p_pnot ) |
309 | 0 | { |
310 | 0 | unsigned i_index = 0; |
311 | 0 | const void *ctx = NULL; |
312 | 0 | while( (p_pnot = GetValidPnotMeta( p_pnot, &i_index, &ctx )) ) |
313 | 0 | { |
314 | 0 | char *psz_location; |
315 | 0 | if ( asprintf( &psz_location, "pnot[%u]", i_index - 1 ) > -1 ) |
316 | 0 | { |
317 | 0 | char rgz_path[14]; |
318 | 0 | snprintf( rgz_path, 14, |
319 | 0 | "/%4.4s[%u]", |
320 | 0 | (const char *) &p_pnot->data.p_pnot->i_type, |
321 | 0 | p_pnot->data.p_pnot->i_index - 1 ); |
322 | 0 | const MP4_Box_t *p_pict = MP4_BoxGet( p_root, rgz_path ); |
323 | 0 | if( p_pict ) |
324 | 0 | { |
325 | 0 | input_attachment_t *p_attach = |
326 | 0 | vlc_input_attachment_New( |
327 | 0 | psz_location, |
328 | 0 | "image/x-pict", |
329 | 0 | "Quickdraw image", |
330 | 0 | p_pict->data.p_binary->p_blob, |
331 | 0 | p_pict->data.p_binary->i_blob ); |
332 | 0 | free( psz_location ); |
333 | 0 | if( p_attach ) |
334 | 0 | pp_attach[i_count++] = p_attach; |
335 | 0 | } |
336 | 0 | } |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | |
341 | | /* Then other thumbnails */ |
342 | 0 | const MP4_Box_t *p_thum = MP4_BoxGet( p_root, "thum" ); |
343 | 0 | if( p_thum ) |
344 | 0 | { |
345 | 0 | unsigned i_index = 0; |
346 | 0 | const void *ctx = NULL; |
347 | 0 | while( (p_thum = GetValidThumMeta( p_thum, &i_index, &ctx )) ) |
348 | 0 | { |
349 | 0 | char *psz_location; |
350 | 0 | enum detectedImageType type = |
351 | 0 | probeImageType( p_thum->data.p_binary->p_blob, |
352 | 0 | p_thum->data.p_binary->i_blob ); |
353 | 0 | const char *psz_mime = getMimeType( type ); |
354 | 0 | if ( asprintf( &psz_location, "thum[%u]", i_index - 1 ) > -1 ) |
355 | 0 | { |
356 | 0 | input_attachment_t *p_attach = |
357 | 0 | vlc_input_attachment_New( |
358 | 0 | psz_location, |
359 | 0 | psz_mime, |
360 | 0 | "Cover picture", |
361 | 0 | p_thum->data.p_binary->p_blob, |
362 | 0 | p_thum->data.p_binary->i_blob ); |
363 | 0 | free( psz_location ); |
364 | 0 | if( p_attach ) |
365 | 0 | pp_attach[i_count++] = p_attach; |
366 | 0 | } |
367 | 0 | } |
368 | 0 | } |
369 | | |
370 | | /* errors in adding attachments */ |
371 | 0 | if ( i_count == 0 ) |
372 | 0 | { |
373 | 0 | free( pp_attach ); |
374 | 0 | return 0; |
375 | 0 | } |
376 | | |
377 | 0 | *ppp_attach = pp_attach; |
378 | |
|
379 | 0 | return i_count; |
380 | 0 | } |