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 along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "share/alloc.h"
24 #include "share/grabbag.h"
25 #include "FLAC/assert.h"
29 #include "share/compat.h"
31 /* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */
32 static char *local__strndup_(const char *s
, size_t size
)
34 char *x
= safe_malloc_add_2op_(size
, /*+*/1);
42 static FLAC__bool
local__parse_type_(const char *s
, size_t len
, FLAC__StreamMetadata_Picture
*picture
)
47 picture
->type
= FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER
;
50 return true; /* empty string implies default to 'front cover' */
52 for(i
= 0; i
< len
; i
++) {
53 if(s
[i
] >= '0' && s
[i
] <= '9')
54 val
= 10*val
+ (FLAC__uint32
)(s
[i
] - '0');
67 static FLAC__bool
local__parse_resolution_(const char *s
, size_t len
, FLAC__StreamMetadata_Picture
*picture
)
73 picture
->width
= picture
->height
= picture
->depth
= picture
->colors
= 0;
76 return true; /* empty string implies client wants to get info from the file itself */
78 for(i
= 0; i
< len
; i
++) {
83 picture
->height
= val
;
89 else if(s
[i
] == '/') {
97 else if(s
[i
] >= '0' && s
[i
] <= '9')
98 val
= 10*val
+ (FLAC__uint32
)(s
[i
] - '0');
106 picture
->depth
= val
;
108 picture
->colors
= val
;
111 if(picture
->depth
< 32 && 1u<<picture
->depth
< picture
->colors
)
117 static FLAC__bool
local__extract_mime_type_(FLAC__StreamMetadata
*obj
)
119 if(obj
->data
.picture
.data_length
>= 8 && 0 == memcmp(obj
->data
.picture
.data
, "\x89PNG\x0d\x0a\x1a\x0a", 8))
120 return FLAC__metadata_object_picture_set_mime_type(obj
, "image/png", /*copy=*/true);
121 else if(obj
->data
.picture
.data_length
>= 6 && (0 == memcmp(obj
->data
.picture
.data
, "GIF87a", 6) || 0 == memcmp(obj
->data
.picture
.data
, "GIF89a", 6)))
122 return FLAC__metadata_object_picture_set_mime_type(obj
, "image/gif", /*copy=*/true);
123 else if(obj
->data
.picture
.data_length
>= 2 && 0 == memcmp(obj
->data
.picture
.data
, "\xff\xd8", 2))
124 return FLAC__metadata_object_picture_set_mime_type(obj
, "image/jpeg", /*copy=*/true);
128 static FLAC__bool
local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture
*picture
)
130 const FLAC__byte
*data
= picture
->data
;
131 FLAC__uint32 len
= picture
->data_length
;
133 if(0 == strcmp(picture
->mime_type
, "image/png")) {
134 /* c.f. http://www.w3.org/TR/PNG/ */
135 FLAC__bool need_palette
= false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */
136 if(len
< 8 || memcmp(data
, "\x89PNG\x0d\x0a\x1a\x0a", 8))
138 /* try to find IHDR chunk */
141 while(len
> 12) { /* every PNG chunk must be at least 12 bytes long */
142 const FLAC__uint32 clen
= (FLAC__uint32
)data
[0] << 24 | (FLAC__uint32
)data
[1] << 16 | (FLAC__uint32
)data
[2] << 8 | (FLAC__uint32
)data
[3];
143 if(0 == memcmp(data
+4, "IHDR", 4) && clen
== 13) {
144 unsigned color_type
= data
[17];
145 picture
->width
= (FLAC__uint32
)data
[8] << 24 | (FLAC__uint32
)data
[9] << 16 | (FLAC__uint32
)data
[10] << 8 | (FLAC__uint32
)data
[11];
146 picture
->height
= (FLAC__uint32
)data
[12] << 24 | (FLAC__uint32
)data
[13] << 16 | (FLAC__uint32
)data
[14] << 8 | (FLAC__uint32
)data
[15];
147 if(color_type
== 3) {
148 /* even though the bit depth for color_type==3 can be 1,2,4,or 8,
149 * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the
150 * sample depth is always 8
152 picture
->depth
= 8 * 3u;
158 if(color_type
== 0) /* greyscale, 1 sample per pixel */
159 picture
->depth
= (FLAC__uint32
)data
[16];
160 if(color_type
== 2) /* truecolor, 3 samples per pixel */
161 picture
->depth
= (FLAC__uint32
)data
[16] * 3u;
162 if(color_type
== 4) /* greyscale+alpha, 2 samples per pixel */
163 picture
->depth
= (FLAC__uint32
)data
[16] * 2u;
164 if(color_type
== 6) /* truecolor+alpha, 4 samples per pixel */
165 picture
->depth
= (FLAC__uint32
)data
[16] * 4u;
170 else if(need_palette
&& 0 == memcmp(data
+4, "PLTE", 4)) {
171 picture
->colors
= clen
/ 3u;
174 else if(clen
+ 12 > len
)
182 else if(0 == strcmp(picture
->mime_type
, "image/jpeg")) {
183 /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */
184 if(len
< 2 || memcmp(data
, "\xff\xd8", 2))
189 /* look for sync FF byte */
190 for( ; len
> 0; data
++, len
--) {
196 /* eat any extra pad FF bytes before marker */
197 for( ; len
> 0; data
++, len
--) {
203 /* if we hit SOS or EOI, bail */
204 if(*data
== 0xda || *data
== 0xd9)
206 /* looking for some SOFn */
207 else if(memchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data
, 13)) {
208 data
++; len
--; /* skip marker byte */
212 const FLAC__uint32 clen
= (FLAC__uint32
)data
[0] << 8 | (FLAC__uint32
)data
[1];
213 if(clen
< 8 || len
< clen
)
215 picture
->width
= (FLAC__uint32
)data
[5] << 8 | (FLAC__uint32
)data
[6];
216 picture
->height
= (FLAC__uint32
)data
[3] << 8 | (FLAC__uint32
)data
[4];
217 picture
->depth
= (FLAC__uint32
)data
[2] * (FLAC__uint32
)data
[7];
224 data
++; len
--; /* skip marker byte */
228 const FLAC__uint32 clen
= (FLAC__uint32
)data
[0] << 8 | (FLAC__uint32
)data
[1];
229 if(clen
< 2 || len
< clen
)
237 else if(0 == strcmp(picture
->mime_type
, "image/gif")) {
238 /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */
241 if(memcmp(data
, "GIF87a", 6) && memcmp(data
, "GIF89a", 6))
244 /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */
245 if(data
[10] & 0x80 == 0)
248 picture
->width
= (FLAC__uint32
)data
[6] | ((FLAC__uint32
)data
[7] << 8);
249 picture
->height
= (FLAC__uint32
)data
[8] | ((FLAC__uint32
)data
[9] << 8);
251 /* this value doesn't seem to be reliable... */
252 picture
->depth
= (((FLAC__uint32
)(data
[10] & 0x70) >> 4) + 1) * 3u;
254 /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */
255 picture
->depth
= 8u * 3u;
257 picture
->colors
= 1u << ((FLAC__uint32
)(data
[10] & 0x07) + 1u);
263 FLAC__StreamMetadata
*grabbag__picture_parse_specification(const char *spec
, const char **error_message
)
265 FLAC__StreamMetadata
*obj
;
267 static const char *error_messages
[] = {
268 "memory allocation error",
269 "invalid picture specification",
270 "invalid picture specification: can't parse resolution/color part",
271 "unable to extract resolution and color info from URL, user must set explicitly",
272 "unable to extract resolution and color info from file, user must set explicitly",
273 "error opening picture file",
274 "error reading picture file",
275 "invalid picture type",
276 "unable to guess MIME type from file, user must set explicitly",
277 "type 1 icon must be a 32x32 pixel PNG"
280 FLAC__ASSERT(0 != spec
);
281 FLAC__ASSERT(0 != error_message
);
283 /* double protection */
286 if(0 == error_message
)
291 if(0 == (obj
= FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE
))) {
292 *error_message
= error_messages
[0];
296 if(strchr(spec
, '|')) { /* full format */
299 for(p
= spec
; *error_message
==0 && *p
; ) {
303 if(!local__parse_type_(spec
, p
-spec
, &obj
->data
.picture
))
304 *error_message
= error_messages
[7];
306 case 1: /* mime type */
307 if(p
-spec
) { /* if blank, we'll try to guess later from the picture data */
308 if(0 == (q
= local__strndup_(spec
, p
-spec
)))
309 *error_message
= error_messages
[0];
310 else if(!FLAC__metadata_object_picture_set_mime_type(obj
, q
, /*copy=*/false))
311 *error_message
= error_messages
[0];
314 case 2: /* description */
315 if(0 == (q
= local__strndup_(spec
, p
-spec
)))
316 *error_message
= error_messages
[0];
317 else if(!FLAC__metadata_object_picture_set_description(obj
, (FLAC__byte
*)q
, /*copy=*/false))
318 *error_message
= error_messages
[0];
320 case 3: /* resolution/color (e.g. [300x300x16[/1234]] */
321 if(!local__parse_resolution_(spec
, p
-spec
, &obj
->data
.picture
))
322 *error_message
= error_messages
[2];
325 *error_message
= error_messages
[1];
336 else { /* simple format, filename only, everything else guessed */
337 if(!local__parse_type_("", 0, &obj
->data
.picture
)) /* use default picture type */
338 *error_message
= error_messages
[7];
339 /* leave MIME type to be filled in later */
340 /* leave description empty */
341 /* leave the rest to be filled in later: */
342 else if(!local__parse_resolution_("", 0, &obj
->data
.picture
))
343 *error_message
= error_messages
[2];
348 /* parse filename, read file, try to extract resolution/color info if needed */
349 if(*error_message
== 0) {
351 *error_message
= error_messages
[1];
352 else { /* 'spec' points to filename/URL */
353 if(0 == strcmp(obj
->data
.picture
.mime_type
, "-->")) { /* magic MIME type means URL */
354 if(!FLAC__metadata_object_picture_set_data(obj
, (FLAC__byte
*)spec
, strlen(spec
), /*copy=*/true))
355 *error_message
= error_messages
[0];
356 else if(obj
->data
.picture
.width
== 0 || obj
->data
.picture
.height
== 0 || obj
->data
.picture
.depth
== 0)
357 *error_message
= error_messages
[3];
359 else { /* regular picture file */
360 const FLAC__off_t size
= grabbag__file_get_filesize(spec
);
362 *error_message
= error_messages
[5];
364 FLAC__byte
*buffer
= safe_malloc_(size
);
366 *error_message
= error_messages
[0];
368 FILE *f
= fopen(spec
, "rb");
370 *error_message
= error_messages
[5];
374 if(fread(buffer
, 1, size
, f
) != (size_t)size
)
375 *error_message
= error_messages
[6];
377 if(0 == *error_message
) {
378 if(!FLAC__metadata_object_picture_set_data(obj
, buffer
, size
, /*copy=*/false))
379 *error_message
= error_messages
[6];
380 /* try to extract MIME type if user left it blank */
381 else if(*obj
->data
.picture
.mime_type
== '\0' && !local__extract_mime_type_(obj
))
382 *error_message
= error_messages
[8];
383 /* try to extract resolution/color info if user left it blank */
384 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
))
385 *error_message
= error_messages
[4];
397 if(*error_message
== 0) {
399 obj
->data
.picture
.type
== FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD
&&
401 (strcmp(obj
->data
.picture
.mime_type
, "image/png") && strcmp(obj
->data
.picture
.mime_type
, "-->")) ||
402 obj
->data
.picture
.width
!= 32 ||
403 obj
->data
.picture
.height
!= 32
406 *error_message
= error_messages
[9];
409 if(*error_message
&& obj
) {
410 FLAC__metadata_object_delete(obj
);