1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2006-2009 Josh Coalson
3 * Copyright (C) 2011-2013 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"
32 /* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */
33 static char *local__strndup_(const char *s, size_t size)
35 char *x = safe_malloc_add_2op_(size, /*+*/1);
43 static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
48 picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
51 return true; /* empty string implies default to 'front cover' */
53 for(i = 0; i < len; i++) {
54 if(s[i] >= '0' && s[i] <= '9')
55 val = 10*val + (FLAC__uint32)(s[i] - '0');
68 static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
74 picture->width = picture->height = picture->depth = picture->colors = 0;
77 return true; /* empty string implies client wants to get info from the file itself */
79 for(i = 0; i < len; i++) {
84 picture->height = val;
90 else if(s[i] == '/') {
98 else if(s[i] >= '0' && s[i] <= '9')
99 val = 10*val + (FLAC__uint32)(s[i] - '0');
107 picture->depth = val;
109 picture->colors = val;
112 if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
118 static FLAC__bool local__extract_mime_type_(FLAC__StreamMetadata *obj)
120 if(obj->data.picture.data_length >= 8 && 0 == memcmp(obj->data.picture.data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
121 return FLAC__metadata_object_picture_set_mime_type(obj, "image/png", /*copy=*/true);
122 else if(obj->data.picture.data_length >= 6 && (0 == memcmp(obj->data.picture.data, "GIF87a", 6) || 0 == memcmp(obj->data.picture.data, "GIF89a", 6)))
123 return FLAC__metadata_object_picture_set_mime_type(obj, "image/gif", /*copy=*/true);
124 else if(obj->data.picture.data_length >= 2 && 0 == memcmp(obj->data.picture.data, "\xff\xd8", 2))
125 return FLAC__metadata_object_picture_set_mime_type(obj, "image/jpeg", /*copy=*/true);
129 static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
131 const FLAC__byte *data = picture->data;
132 FLAC__uint32 len = picture->data_length;
134 if(0 == strcmp(picture->mime_type, "image/png")) {
135 /* c.f. http://www.w3.org/TR/PNG/ */
136 FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
137 if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
139 /* try to find IHDR chunk */
142 while(len > 12) { /* every PNG chunk must be at least 12 bytes long */
143 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3];
144 if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
145 unsigned color_type = data[17];
146 picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
147 picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
148 if(color_type == 3) {
149 /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
150 * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
151 * sample depth is always 8
153 picture->depth = 8 * 3u;
159 if(color_type == 0) /* greyscale, 1 sample per pixel */
160 picture->depth = (FLAC__uint32)data[16];
161 if(color_type == 2) /* truecolor, 3 samples per pixel */
162 picture->depth = (FLAC__uint32)data[16] * 3u;
163 if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
164 picture->depth = (FLAC__uint32)data[16] * 2u;
165 if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
166 picture->depth = (FLAC__uint32)data[16] * 4u;
171 else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
172 picture->colors = clen / 3u;
175 else if(clen + 12 > len)
183 else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
184 /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
185 if(len < 2 || memcmp(data, "\xff\xd8", 2))
190 /* look for sync FF byte */
191 for( ; len > 0; data++, len--) {
197 /* eat any extra pad FF bytes before marker */
198 for( ; len > 0; data++, len--) {
204 /* if we hit SOS or EOI, bail */
205 if(*data == 0xda || *data == 0xd9)
207 /* looking for some SOFn */
208 else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) {
209 data++; len--; /* skip marker byte */
213 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
214 if(clen < 8 || len < clen)
216 picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
217 picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
218 picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
225 data++; len--; /* skip marker byte */
229 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
230 if(clen < 2 || len < clen)
238 else if(0 == strcmp(picture->mime_type, "image/gif")) {
239 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
242 if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
245 /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
246 if(data[10] & 0x80 == 0)
249 picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
250 picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
252 /* this value doesn't seem to be reliable... */
253 picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u;
255 /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
256 picture->depth = 8u * 3u;
258 picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
264 FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
266 FLAC__StreamMetadata *obj;
268 static const char *error_messages[] = {
269 "memory allocation error",
270 "invalid picture specification",
271 "invalid picture specification: can't parse resolution/color part",
272 "unable to extract resolution and color info from URL, user must set explicitly",
273 "unable to extract resolution and color info from file, user must set explicitly",
274 "error opening picture file",
275 "error reading picture file",
276 "invalid picture type",
277 "unable to guess MIME type from file, user must set explicitly",
278 "type 1 icon must be a 32x32 pixel PNG"
281 FLAC__ASSERT(0 != spec);
282 FLAC__ASSERT(0 != error_message);
284 /* double protection */
287 if(0 == error_message)
292 if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) {
293 *error_message = error_messages[0];
297 if(strchr(spec, '|')) { /* full format */
300 for(p = spec; *error_message==0 && *p; ) {
304 if(!local__parse_type_(spec, p-spec, &obj->data.picture))
305 *error_message = error_messages[7];
307 case 1: /* mime type */
308 if(p-spec) { /* if blank, we'll try to guess later from the picture data */
309 if(0 == (q = local__strndup_(spec, p-spec)))
310 *error_message = error_messages[0];
311 else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
312 *error_message = error_messages[0];
315 case 2: /* description */
316 if(0 == (q = local__strndup_(spec, p-spec)))
317 *error_message = error_messages[0];
318 else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
319 *error_message = error_messages[0];
321 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
322 if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
323 *error_message = error_messages[2];
326 *error_message = error_messages[1];
337 else { /* simple format, filename only, everything else guessed */
338 if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */
339 *error_message = error_messages[7];
340 /* leave MIME type to be filled in later */
341 /* leave description empty */
342 /* leave the rest to be filled in later: */
343 else if(!local__parse_resolution_("", 0, &obj->data.picture))
344 *error_message = error_messages[2];
349 /* parse filename, read file, try to extract resolution/color info if needed */
350 if(*error_message == 0) {
352 *error_message = error_messages[1];
353 else { /* 'spec' points to filename/URL */
354 if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
355 if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
356 *error_message = error_messages[0];
357 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
358 *error_message = error_messages[3];
360 else { /* regular picture file */
361 const FLAC__off_t size = grabbag__file_get_filesize(spec);
363 *error_message = error_messages[5];
365 FLAC__byte *buffer = safe_malloc_(size);
367 *error_message = error_messages[0];
369 FILE *f = flac_fopen(spec, "rb");
371 *error_message = error_messages[5];
375 if(fread(buffer, 1, size, f) != (size_t)size)
376 *error_message = error_messages[6];
378 if(0 == *error_message) {
379 if(!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false))
380 *error_message = error_messages[6];
381 /* try to extract MIME type if user left it blank */
382 else if(*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
383 *error_message = error_messages[8];
384 /* try to extract resolution/color info if user left it blank */
385 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))
386 *error_message = error_messages[4];
398 if(*error_message == 0) {
400 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
402 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
403 obj->data.picture.width != 32 ||
404 obj->data.picture.height != 32
407 *error_message = error_messages[9];
410 if(*error_message && obj) {
411 FLAC__metadata_object_delete(obj);