1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2006,2007 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 = (char*)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];
293 if(strchr(spec, '|')) { /* full format */
296 for(p = spec; *error_message==0 && *p; ) {
300 if(!local__parse_type_(spec, p-spec, &obj->data.picture))
301 *error_message = error_messages[7];
303 case 1: /* mime type */
304 if(p-spec) { /* if blank, we'll try to guess later from the picture data */
305 if(0 == (q = local__strndup_(spec, p-spec)))
306 *error_message = error_messages[0];
307 else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
308 *error_message = error_messages[0];
311 case 2: /* description */
312 if(0 == (q = local__strndup_(spec, p-spec)))
313 *error_message = error_messages[0];
314 else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
315 *error_message = error_messages[0];
317 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
318 if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
319 *error_message = error_messages[2];
322 *error_message = error_messages[1];
333 else { /* simple format, filename only, everything else guessed */
334 if(!local__parse_type_("", 0, &obj->data.picture)) /* use default picture type */
335 *error_message = error_messages[7];
336 /* leave MIME type to be filled in later */
337 /* leave description empty */
338 /* leave the rest to be filled in later: */
339 else if(!local__parse_resolution_("", 0, &obj->data.picture))
340 *error_message = error_messages[2];
345 /* parse filename, read file, try to extract resolution/color info if needed */
346 if(*error_message == 0) {
348 *error_message = error_messages[1];
349 else { /* 'spec' points to filename/URL */
350 if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
351 if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
352 *error_message = error_messages[0];
353 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
354 *error_message = error_messages[3];
356 else { /* regular picture file */
357 const off_t size = grabbag__file_get_filesize(spec);
359 *error_message = error_messages[5];
361 FLAC__byte *buffer = (FLAC__byte*)safe_malloc_(size);
363 *error_message = error_messages[0];
365 FILE *f = fopen(spec, "rb");
367 *error_message = error_messages[5];
369 if(fread(buffer, 1, size, f) != (size_t)size)
370 *error_message = error_messages[6];
372 if(0 == *error_message) {
373 if(!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false))
374 *error_message = error_messages[6];
375 /* try to extract MIME type if user left it blank */
376 else if(*obj->data.picture.mime_type == '\0' && !local__extract_mime_type_(obj))
377 *error_message = error_messages[8];
378 /* try to extract resolution/color info if user left it blank */
379 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))
380 *error_message = error_messages[4];
389 if(*error_message == 0) {
391 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
393 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
394 obj->data.picture.width != 32 ||
395 obj->data.picture.height != 32
398 *error_message = error_messages[9];
401 if(*error_message && obj) {
402 FLAC__metadata_object_delete(obj);