1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2006,2007,2008,2009 Josh Coalson
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "share/alloc.h"
24 #include "share/grabbag.h"
25 #include "FLAC/assert.h"
30 /* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */
31 static char *local__strndup_(const char *s, size_t size)
33 char *x = safe_malloc_add_2op_(size, /*+*/1);
41 static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
46 picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;
49 return true; /* empty string implies default to 'front cover' */
51 for(i = 0; i < len; i++) {
52 if(s[i] >= '0' && s[i] <= '9')
53 val = 10*val + (FLAC__uint32)(s[i] - '0');
66 static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
72 picture->width = picture->height = picture->depth = picture->colors = 0;
75 return true; /* empty string implies client wants to get info from the file itself */
77 for(i = 0; i < len; i++) {
82 picture->height = val;
88 else if(s[i] == '/') {
96 else if(s[i] >= '0' && s[i] <= '9')
97 val = 10*val + (FLAC__uint32)(s[i] - '0');
105 picture->depth = val;
107 picture->colors = val;
110 if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
116 static FLAC__bool local__extract_mime_type_(FLAC__StreamMetadata *obj)
118 if(obj->data.picture.data_length >= 8 && 0 == memcmp(obj->data.picture.data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
119 return FLAC__metadata_object_picture_set_mime_type(obj, "image/png", /*copy=*/true);
120 else if(obj->data.picture.data_length >= 6 && (0 == memcmp(obj->data.picture.data, "GIF87a", 6) || 0 == memcmp(obj->data.picture.data, "GIF89a", 6)))
121 return FLAC__metadata_object_picture_set_mime_type(obj, "image/gif", /*copy=*/true);
122 else if(obj->data.picture.data_length >= 2 && 0 == memcmp(obj->data.picture.data, "\xff\xd8", 2))
123 return FLAC__metadata_object_picture_set_mime_type(obj, "image/jpeg", /*copy=*/true);
127 static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
129 const FLAC__byte *data = picture->data;
130 FLAC__uint32 len = picture->data_length;
132 if(0 == strcmp(picture->mime_type, "image/png")) {
133 /* c.f. http://www.w3.org/TR/PNG/ */
134 FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
135 if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
137 /* try to find IHDR chunk */
140 while(len > 12) { /* every PNG chunk must be at least 12 bytes long */
141 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3];
142 if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
143 unsigned color_type = data[17];
144 picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
145 picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
146 if(color_type == 3) {
147 /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
148 * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
149 * sample depth is always 8
151 picture->depth = 8 * 3u;
157 if(color_type == 0) /* greyscale, 1 sample per pixel */
158 picture->depth = (FLAC__uint32)data[16];
159 if(color_type == 2) /* truecolor, 3 samples per pixel */
160 picture->depth = (FLAC__uint32)data[16] * 3u;
161 if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
162 picture->depth = (FLAC__uint32)data[16] * 2u;
163 if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
164 picture->depth = (FLAC__uint32)data[16] * 4u;
169 else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
170 picture->colors = clen / 3u;
173 else if(clen + 12 > len)
181 else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
182 /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
183 if(len < 2 || memcmp(data, "\xff\xd8", 2))
188 /* look for sync FF byte */
189 for( ; len > 0; data++, len--) {
195 /* eat any extra pad FF bytes before marker */
196 for( ; len > 0; data++, len--) {
202 /* if we hit SOS or EOI, bail */
203 if(*data == 0xda || *data == 0xd9)
205 /* looking for some SOFn */
206 else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data, 13)) {
207 data++; len--; /* skip marker byte */
211 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
212 if(clen < 8 || len < clen)
214 picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
215 picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
216 picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
223 data++; len--; /* skip marker byte */
227 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
228 if(clen < 2 || len < clen)
236 else if(0 == strcmp(picture->mime_type, "image/gif")) {
237 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
240 if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
243 /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
244 if(data[10] & 0x80 == 0)
247 picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
248 picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
250 /* this value doesn't seem to be reliable... */
251 picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u;
253 /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
254 picture->depth = 8u * 3u;
256 picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
262 FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
264 FLAC__StreamMetadata *obj;
266 static const char *error_messages[] = {
267 "memory allocation error",
268 "invalid picture specification",
269 "invalid picture specification: can't parse resolution/color part",
270 "unable to extract resolution and color info from URL, user must set explicitly",
271 "unable to extract resolution and color info from file, user must set explicitly",
272 "error opening picture file",
273 "error reading picture file",
274 "invalid picture type",
275 "unable to guess MIME type from file, user must set explicitly",
276 "type 1 icon must be a 32x32 pixel PNG"
279 FLAC__ASSERT(0 != spec);
280 FLAC__ASSERT(0 != error_message);
282 /* double protection */
285 if(0 == error_message)
290 if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) {
291 *error_message = error_messages[0];
295 if(strchr(spec, '|')) { /* full format */
298 for(p = spec; *error_message==0 && *p; ) {
302 if(!local__parse_type_(spec, p-spec, &obj->data.picture))
303 *error_message = error_messages[7];
305 case 1: /* mime type */
306 if(p-spec) { /* if blank, we'll try to guess later from the picture data */
307 if(0 == (q = local__strndup_(spec, p-spec)))
308 *error_message = error_messages[0];
309 else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
310 *error_message = error_messages[0];
313 case 2: /* description */
314 if(0 == (q = local__strndup_(spec, p-spec)))
315 *error_message = error_messages[0];
316 else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
317 *error_message = error_messages[0];
319 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
320 if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
321 *error_message = error_messages[2];
324 *error_message = error_messages[1];
335 else { /* simple format, filename only, everything else guessed */
336 if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */
337 *error_message = error_messages[7];
338 /* leave MIME type to be filled in later */
339 /* leave description empty */
340 /* leave the rest to be filled in later: */
341 else if(!local__parse_resolution_("", 0, &obj->data.picture))
342 *error_message = error_messages[2];
347 /* parse filename, read file, try to extract resolution/color info if needed */
348 if(*error_message == 0) {
350 *error_message = error_messages[1];
351 else { /* 'spec' points to filename/URL */
352 if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
353 if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
354 *error_message = error_messages[0];
355 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
356 *error_message = error_messages[3];
358 else { /* regular picture file */
359 const off_t size = grabbag__file_get_filesize(spec);
361 *error_message = error_messages[5];
363 FLAC__byte *buffer = safe_malloc_(size);
365 *error_message = error_messages[0];
367 FILE *f = fopen(spec, "rb");
369 *error_message = error_messages[5];
373 if(fread(buffer, 1, size, f) != (size_t)size)
374 *error_message = error_messages[6];
376 if(0 == *error_message) {
377 if(!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false))
378 *error_message = error_messages[6];
379 /* try to extract MIME type if user left it blank */
380 else if(*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
381 *error_message = error_messages[8];
382 /* try to extract resolution/color info if user left it blank */
383 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))
384 *error_message = error_messages[4];
396 if(*error_message == 0) {
398 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
400 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
401 obj->data.picture.width != 32 ||
402 obj->data.picture.height != 32
405 *error_message = error_messages[9];
408 if(*error_message && obj) {
409 FLAC__metadata_object_delete(obj);