1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2006-2009 Josh Coalson
3 * Copyright (C) 2011-2016 Xiph.Org Foundation
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "share/alloc.h"
25 #include "share/grabbag.h"
26 #include "FLAC/assert.h"
30 #include "share/compat.h"
31 #include "share/safe_str.h"
33 /* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */
34 static char *local__strndup_(const char *s, size_t size)
36 char *x = safe_malloc_add_2op_(size, /*+*/1);
44 static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
49 picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
52 return true; /* empty string implies default to 'front cover' */
54 for(i = 0; i < len; i++) {
55 if(s[i] >= '0' && s[i] <= '9')
56 val = 10*val + (FLAC__uint32)(s[i] - '0');
69 static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
75 picture->width = picture->height = picture->depth = picture->colors = 0;
78 return true; /* empty string implies client wants to get info from the file itself */
80 for(i = 0; i < len; i++) {
85 picture->height = val;
91 else if(s[i] == '/') {
99 else if(s[i] >= '0' && s[i] <= '9')
100 val = 10*val + (FLAC__uint32)(s[i] - '0');
108 picture->depth = val;
110 picture->colors = val;
113 if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
119 static FLAC__bool local__extract_mime_type_(FLAC__StreamMetadata *obj)
121 if(obj->data.picture.data_length >= 8 && 0 == memcmp(obj->data.picture.data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
122 return FLAC__metadata_object_picture_set_mime_type(obj, "image/png", /*copy=*/true);
123 else if(obj->data.picture.data_length >= 6 && (0 == memcmp(obj->data.picture.data, "GIF87a", 6) || 0 == memcmp(obj->data.picture.data, "GIF89a", 6)))
124 return FLAC__metadata_object_picture_set_mime_type(obj, "image/gif", /*copy=*/true);
125 else if(obj->data.picture.data_length >= 2 && 0 == memcmp(obj->data.picture.data, "\xff\xd8", 2))
126 return FLAC__metadata_object_picture_set_mime_type(obj, "image/jpeg", /*copy=*/true);
130 static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
132 const FLAC__byte *data = picture->data;
133 FLAC__uint32 len = picture->data_length;
135 if(0 == strcmp(picture->mime_type, "image/png")) {
136 /* c.f. http://www.w3.org/TR/PNG/ */
137 FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
138 if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
140 /* try to find IHDR chunk */
143 while(len > 12) { /* every PNG chunk must be at least 12 bytes long */
144 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3];
145 if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
146 unsigned color_type = data[17];
147 picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
148 picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
149 if(color_type == 3) {
150 /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
151 * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
152 * sample depth is always 8
154 picture->depth = 8 * 3u;
160 if(color_type == 0) /* greyscale, 1 sample per pixel */
161 picture->depth = (FLAC__uint32)data[16];
162 if(color_type == 2) /* truecolor, 3 samples per pixel */
163 picture->depth = (FLAC__uint32)data[16] * 3u;
164 if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
165 picture->depth = (FLAC__uint32)data[16] * 2u;
166 if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
167 picture->depth = (FLAC__uint32)data[16] * 4u;
172 else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
173 picture->colors = clen / 3u;
176 else if(clen + 12 > len)
184 else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
185 /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
186 if(len < 2 || memcmp(data, "\xff\xd8", 2))
191 /* look for sync FF byte */
192 for( ; len > 0; data++, len--) {
198 /* eat any extra pad FF bytes before marker */
199 for( ; len > 0; data++, len--) {
205 /* if we hit SOS or EOI, bail */
206 if(*data == 0xda || *data == 0xd9)
208 /* looking for some SOFn */
209 else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) {
210 data++; len--; /* skip marker byte */
214 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
215 if(clen < 8 || len < clen)
217 picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
218 picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
219 picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
226 data++; len--; /* skip marker byte */
230 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
231 if(clen < 2 || len < clen)
239 else if(0 == strcmp(picture->mime_type, "image/gif")) {
240 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
243 if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
246 /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
247 if(data[10] & 0x80 == 0)
250 picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
251 picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
253 /* this value doesn't seem to be reliable... */
254 picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u;
256 /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
257 picture->depth = 8u * 3u;
259 picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
265 static const char *error_messages[] = {
266 "memory allocation error",
267 "invalid picture specification",
268 "invalid picture specification: can't parse resolution/color part",
269 "unable to extract resolution and color info from URL, user must set explicitly",
270 "unable to extract resolution and color info from file, user must set explicitly",
271 "error opening picture file",
272 "error reading picture file",
273 "invalid picture type",
274 "unable to guess MIME type from file, user must set explicitly",
275 "type 1 icon must be a 32x32 pixel PNG",
276 "file not found", /* currently unused */
280 static const char * read_file (const char * filepath, FLAC__StreamMetadata * obj)
282 const FLAC__off_t size = grabbag__file_get_filesize(filepath);
285 const char *error_message=NULL;
288 return error_messages[5];
290 if (size >= (1u << FLAC__STREAM_METADATA_LENGTH_LEN)) /* actual limit is less because of other fields in the PICTURE metadata block */
291 return error_messages[11];
293 if ((buffer = safe_malloc_(size)) == NULL)
294 return error_messages[0];
296 if ((file = flac_fopen(filepath, "rb")) == NULL) {
298 return error_messages[5];
301 if (fread(buffer, 1, size, file) != (size_t) size) {
304 return error_messages[6];
308 if (!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false))
309 error_message = error_messages[6];
310 /* try to extract MIME type if user left it blank */
311 else if (*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
312 error_message = error_messages[8];
313 /* try to extract resolution/color info if user left it blank */
314 else if ((obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) && !local__extract_resolution_color_info_(&obj->data.picture))
315 error_message = error_messages[4];
316 /* check metadata block size */
317 else if (obj->length >= (1u << FLAC__STREAM_METADATA_LENGTH_LEN))
318 error_message = error_messages[11];
320 return error_message;
323 FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
325 FLAC__StreamMetadata *obj;
328 FLAC__ASSERT(0 != spec);
329 FLAC__ASSERT(0 != error_message);
331 /* double protection */
334 if(0 == error_message)
339 if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) {
340 *error_message = error_messages[0];
344 if(strchr(spec, '|')) { /* full format */
347 for(p = spec; *error_message==0 && *p; ) {
351 if(!local__parse_type_(spec, p-spec, &obj->data.picture))
352 *error_message = error_messages[7];
354 case 1: /* mime type */
355 if(p-spec) { /* if blank, we'll try to guess later from the picture data */
356 if(0 == (q = local__strndup_(spec, p-spec)))
357 *error_message = error_messages[0];
358 else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
359 *error_message = error_messages[0];
362 case 2: /* description */
363 if(0 == (q = local__strndup_(spec, p-spec)))
364 *error_message = error_messages[0];
365 else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
366 *error_message = error_messages[0];
368 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
369 if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
370 *error_message = error_messages[2];
373 *error_message = error_messages[1];
384 else { /* simple format, filename only, everything else guessed */
385 if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */
386 *error_message = error_messages[7];
387 /* leave MIME type to be filled in later */
388 /* leave description empty */
389 /* leave the rest to be filled in later: */
390 else if(!local__parse_resolution_("", 0, &obj->data.picture))
391 *error_message = error_messages[2];
396 /* parse filename, read file, try to extract resolution/color info if needed */
397 if(*error_message == 0) {
399 *error_message = error_messages[1];
400 else { /* 'spec' points to filename/URL */
401 if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
402 if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
403 *error_message = error_messages[0];
404 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
405 *error_message = error_messages[3];
407 else { /* regular picture file */
408 *error_message = read_file (spec, obj);
413 if(*error_message == 0) {
415 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
417 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
418 obj->data.picture.width != 32 ||
419 obj->data.picture.height != 32
422 *error_message = error_messages[9];
425 if(*error_message && obj) {
426 FLAC__metadata_object_delete(obj);
433 FLAC__StreamMetadata *grabbag__picture_from_specification(int type, const char *mime_type_in, const char * description,
434 const PictureResolution * res, const char * filepath, const char **error_message)
437 FLAC__StreamMetadata *obj;
438 char mime_type [64] ;
440 if (error_message == 0)
443 safe_strncpy(mime_type, mime_type_in, sizeof (mime_type));
447 if ((obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE)) == 0) {
448 *error_message = error_messages[0];
452 /* Picture type if known. */
453 obj->data.picture.type = type >= 0 ? type : FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
455 /* Mime type if known. */
456 if (mime_type_in && ! FLAC__metadata_object_picture_set_mime_type(obj, mime_type, /*copy=*/true)) {
457 *error_message = error_messages[0];
461 /* Description if present. */
462 if (description && ! FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*) description, /*copy=*/true)) {
463 *error_message = error_messages[0];
468 obj->data.picture.width = 0;
469 obj->data.picture.height = 0;
470 obj->data.picture.depth = 0;
471 obj->data.picture.colors = 0;
474 obj->data.picture.width = res->width;
475 obj->data.picture.height = res->height;
476 obj->data.picture.depth = res->depth;
477 obj->data.picture.colors = res->colors;
480 if (strcmp(obj->data.picture.mime_type, "-->") == 0) { /* magic MIME type means URL */
481 if (!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)filepath, strlen(filepath), /*copy=*/true))
482 *error_message = error_messages[0];
483 else if (obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
484 *error_message = error_messages[3];
487 *error_message = read_file (filepath, obj);
490 if (*error_message == NULL) {
492 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
494 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
495 obj->data.picture.width != 32 ||
496 obj->data.picture.height != 32
499 *error_message = error_messages[9];
502 if (*error_message && obj) {
503 FLAC__metadata_object_delete(obj);