1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2006 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/grabbag.h"
24 #include "FLAC/assert.h"
29 /* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */
30 static char *local__strndup_(const char *s, size_t size)
32 char *x = (char*)malloc(size+1);
40 static FLAC__bool local__parse_type_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
45 picture->type = FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER;;
48 return true; /* empty string implies default to 'front cover' */
50 for(i = 0; i < len; i++) {
51 if(s[i] >= '0' && s[i] <= '9')
52 val = 10*val + (FLAC__uint32)(s[i] - '0');
65 static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture)
71 picture->width = picture->height = picture->depth = picture->colors = 0;
74 return true; /* empty string implies client wants to get info from the file itself */
76 for(i = 0; i < len; i++) {
81 picture->height = val;
87 else if(s[i] == '/') {
95 else if(s[i] >= '0' && s[i] <= '9')
96 val = 10*val + (FLAC__uint32)(s[i] - '0');
104 picture->depth = val;
106 picture->colors = val;
109 if(picture->depth < 32 && 1u<<picture->depth < picture->colors)
115 static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture)
117 const FLAC__byte *data = picture->data;
118 FLAC__uint32 len = picture->data_length;
120 if(0 == strcmp(picture->mime_type, "image/png")) {
121 /* c.f. http://www.w3.org/TR/PNG/ */
122 FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
123 if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8))
125 /* try to find IHDR chunk */
128 while(len > 12) { /* every PNG chunk must be at least 12 bytes long */
129 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3];
130 if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) {
131 unsigned color_type = data[17];
132 picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11];
133 picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15];
134 if(color_type == 3) {
135 /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
136 * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
137 * sample depth is always 8
139 picture->depth = 8 * 3u;
145 if(color_type == 0) /* greyscale, 1 sample per pixel */
146 picture->depth = (FLAC__uint32)data[16];
147 if(color_type == 2) /* truecolor, 3 samples per pixel */
148 picture->depth = (FLAC__uint32)data[16] * 3u;
149 if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */
150 picture->depth = (FLAC__uint32)data[16] * 2u;
151 if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */
152 picture->depth = (FLAC__uint32)data[16] * 4u;
157 else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) {
158 picture->colors = clen / 3u;
161 else if(clen + 12 > len)
169 else if(0 == strcmp(picture->mime_type, "image/jpeg")) {
170 /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
171 if(len < 2 || memcmp(data, "\xff\xd8", 2))
176 /* look for sync FF byte */
177 for( ; len > 0; data++, len--) {
183 /* eat any extra pad FF bytes before marker */
184 for( ; len > 0; data++, len--) {
190 /* if we hit SOS or EOI, bail */
191 if(*data == 0xda || *data == 0xd9)
193 /* looking for some SOFn */
194 else if(strchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data)) {
195 data++; len--; /* skip marker byte */
199 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
200 if(clen < 8 || len < clen)
202 picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6];
203 picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4];
204 picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7];
211 data++; len--; /* skip marker byte */
215 const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1];
216 if(clen < 2 || len < clen)
224 else if(0 == strcmp(picture->mime_type, "image/gif")) {
225 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
228 if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6))
231 /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
232 if(data[10] & 0x80 == 0)
235 picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8);
236 picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8);
238 /* this value doesn't seem to be reliable... */
239 picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u;
241 /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
242 picture->depth = 8u * 3u;
244 picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u);
250 FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message)
252 FLAC__StreamMetadata *obj;
256 static const char *error_messages[] = {
257 "memory allocation error",
258 "invalid picture specification",
259 "invalid picture specification: can't parse resolution/color part",
260 "unable to extract resolution and color info from URL, user must set explicitly",
261 "unable to extract resolution and color info from file, user must set explicitly",
262 "error opening picture file",
263 "error reading picture file",
264 "invalid picture type",
266 "type 1 icon must be a 32x32 pixel PNG"
269 FLAC__ASSERT(0 != spec);
270 FLAC__ASSERT(0 != error_message);
272 /* double protection */
275 if(0 == error_message)
280 if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE)))
281 *error_message = error_messages[0];
283 for(p = spec; *error_message==0 && *p; ) {
287 if(!local__parse_type_(spec, p-spec, &obj->data.picture))
288 *error_message = error_messages[7];
290 case 1: /* mime type */
292 *error_message = error_messages[8];
293 else if(0 == (q = local__strndup_(spec, p-spec)))
294 *error_message = error_messages[0];
295 else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false))
296 *error_message = error_messages[0];
298 case 2: /* description */
299 if(0 == (q = local__strndup_(spec, p-spec)))
300 *error_message = error_messages[0];
301 else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false))
302 *error_message = error_messages[0];
304 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
305 if(!local__parse_resolution_(spec, p-spec, &obj->data.picture))
306 *error_message = error_messages[2];
309 *error_message = error_messages[1];
319 /* parse filename, read file, try to extract resolution/color info if needed */
320 if(*error_message == 0) {
322 *error_message = error_messages[1];
323 else { /* 'spec' points to filename/URL */
324 if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */
325 if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true))
326 *error_message = error_messages[0];
327 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0)
328 *error_message = error_messages[3];
330 else { /* regular picture file */
331 const off_t size = grabbag__file_get_filesize(spec);
333 *error_message = error_messages[5];
335 FLAC__byte *buffer = (FLAC__byte*)malloc(size);
337 *error_message = error_messages[0];
339 FILE *f = fopen(spec, "rb");
341 *error_message = error_messages[5];
343 if(fread(buffer, 1, size, f) != size)
344 *error_message = error_messages[6];
346 if(0 == *error_message) {
347 if(!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false))
348 *error_message = error_messages[6];
349 /* try to extract resolution/color info if user left it blank */
350 else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) {
351 if(!local__extract_resolution_color_info_(&obj->data.picture))
352 *error_message = error_messages[4];
362 if(*error_message == 0) {
364 obj->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD &&
366 (strcmp(obj->data.picture.mime_type, "image/png") && strcmp(obj->data.picture.mime_type, "-->")) ||
367 obj->data.picture.width != 32 ||
368 obj->data.picture.height != 32
371 *error_message = error_messages[9];
374 if(*error_message && obj) {
375 FLAC__metadata_object_delete(obj);