diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/share/grabbag/Makefile.am | 1 | ||||
-rw-r--r-- | src/share/grabbag/Makefile.lite | 1 | ||||
-rw-r--r-- | src/share/grabbag/grabbag_static.dsp | 8 | ||||
-rw-r--r-- | src/share/grabbag/picture.c | 337 |
4 files changed, 347 insertions, 0 deletions
diff --git a/src/share/grabbag/Makefile.am b/src/share/grabbag/Makefile.am index edc8e2f7..f7b31c13 100644 --- a/src/share/grabbag/Makefile.am +++ b/src/share/grabbag/Makefile.am @@ -9,6 +9,7 @@ noinst_LTLIBRARIES = libgrabbag.la libgrabbag_la_SOURCES = \ cuesheet.c \ file.c \ + picture.c \ replaygain.c \ seektable.c diff --git a/src/share/grabbag/Makefile.lite b/src/share/grabbag/Makefile.lite index d81126f1..daf19751 100644 --- a/src/share/grabbag/Makefile.lite +++ b/src/share/grabbag/Makefile.lite @@ -10,6 +10,7 @@ INCLUDES = -I$(topdir)/include SRCS_C = \ cuesheet.c \ file.c \ + picture.c \ replaygain.c \ seektable.c diff --git a/src/share/grabbag/grabbag_static.dsp b/src/share/grabbag/grabbag_static.dsp index b138ef14..f864566c 100644 --- a/src/share/grabbag/grabbag_static.dsp +++ b/src/share/grabbag/grabbag_static.dsp @@ -93,6 +93,10 @@ SOURCE=.\file.c # End Source File
# Begin Source File
+SOURCE=.\picture.c
+# End Source File
+# Begin Source File
+
SOURCE=.\replaygain.c
# End Source File
# Begin Source File
@@ -113,6 +117,10 @@ SOURCE=..\..\..\include\share\grabbag\file.h # End Source File
# Begin Source File
+SOURCE=..\..\..\include\share\grabbag\picture.h
+# End Source File
+# Begin Source File
+
SOURCE=..\..\..\include\share\grabbag\replaygain.h
# End Source File
# Begin Source File
diff --git a/src/share/grabbag/picture.c b/src/share/grabbag/picture.c new file mode 100644 index 00000000..2e5779cb --- /dev/null +++ b/src/share/grabbag/picture.c @@ -0,0 +1,337 @@ +/* grabbag - Convenience lib for various routines common to several tools + * Copyright (C) 2006 Josh Coalson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#include "share/grabbag.h" +#include "FLAC/assert.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* slightly different that strndup(): this always copies 'size' bytes starting from s into a NUL-terminated string. */ +static char *local__strndup_(const char *s, size_t size) +{ + char *x = (char*)malloc(size+1); + if(x) { + memcpy(x, s, size); + x[size] = '\0'; + } + return x; +} + +static FLAC__bool local__parse_resolution_(const char *s, size_t len, FLAC__StreamMetadata_Picture *picture) +{ + int state = 0; + size_t i; + FLAC__uint32 val = 0; + + picture->width = picture->height = picture->depth = picture->colors = 0; + + if(len == 0) + return true; /* empty string implies client wants to get info from the file itself */ + + for(i = 0; i < len; i++) { + if(s[i] == 'x') { + if(state == 0) + picture->width = val; + else if(state == 1) + picture->height = val; + else + return false; + state++; + val = 0; + } + else if(s[i] == '/') { + if(state == 2) + picture->depth = val; + else + return false; + state++; + val = 0; + } + else if(s[i] >= '0' && s[i] <= '9') + val = 10*val + (FLAC__uint32)(s[i] - '0'); + else + return false; + } + + if(state < 2) + return false; + else if(state == 2) + picture->depth = val; + else if(state == 3) + picture->colors = val; + else + return false; + if(picture->depth < 32 && 1u<<picture->depth < picture->colors) + return false; + + return true; +} + +static FLAC__bool local__extract_resolution_color_info_(FLAC__StreamMetadata_Picture *picture) +{ + const FLAC__byte *data = picture->data; + FLAC__uint32 len = picture->data_length; + + if(0 == strcmp(picture->mime_type, "image/png")) { + /* c.f. http://www.w3.org/TR/PNG/ */ + FLAC__bool need_palette = false; /* if IHDR has color_type=3, we need to also read the PLTE chunk to get the #colors */ + if(len < 8 || memcmp(data, "\x89PNG\x0d\x0a\x1a\x0a", 8)) + return false; + /* try to find IHDR chunk */ + data += 8; + len -= 8; + while(len > 12) { /* every PNG chunk must be at least 12 bytes long */ + const FLAC__uint32 clen = (FLAC__uint32)data[0] << 24 | (FLAC__uint32)data[1] << 16 | (FLAC__uint32)data[2] << 8 | (FLAC__uint32)data[3]; + if(0 == memcmp(data+4, "IHDR", 4) && clen == 13) { + unsigned color_type = data[17]; + picture->width = (FLAC__uint32)data[8] << 24 | (FLAC__uint32)data[9] << 16 | (FLAC__uint32)data[10] << 8 | (FLAC__uint32)data[11]; + picture->height = (FLAC__uint32)data[12] << 24 | (FLAC__uint32)data[13] << 16 | (FLAC__uint32)data[14] << 8 | (FLAC__uint32)data[15]; + if(color_type == 3) { + /* even though the bit depth for color_type==3 can be 1,2,4,or 8, + * the spec in 11.2.2 of http://www.w3.org/TR/PNG/ says that the + * sample depth is always 8 + */ + picture->depth = 8 * 3u; + need_palette = true; + data += 12 + clen; + len -= 12 + clen; + } + else { + if(color_type == 0) /* greyscale, 1 sample per pixel */ + picture->depth = (FLAC__uint32)data[16]; + if(color_type == 2) /* truecolor, 3 samples per pixel */ + picture->depth = (FLAC__uint32)data[16] * 3u; + if(color_type == 4) /* greyscale+alpha, 2 samples per pixel */ + picture->depth = (FLAC__uint32)data[16] * 2u; + if(color_type == 6) /* truecolor+alpha, 4 samples per pixel */ + picture->depth = (FLAC__uint32)data[16] * 4u; + picture->colors = 0; + return true; + } + } + else if(need_palette && 0 == memcmp(data+4, "PLTE", 4)) { + picture->colors = clen / 3u; + return true; + } + else if(clen + 12 > len) + return false; + else { + data += 12 + clen; + len -= 12 + clen; + } + } + } + else if(0 == strcmp(picture->mime_type, "image/jpeg")) { + /* c.f. http://www.w3.org/Graphics/JPEG/itu-t81.pdf and Q22 of http://www.faqs.org/faqs/jpeg-faq/part1/ */ + if(len < 2 || memcmp(data, "\xff\xd8", 2)) + return false; + data += 2; + len -= 2; + while(1) { + /* look for sync FF byte */ + for( ; len > 0; data++, len--) { + if(*data == 0xff) + break; + } + if(len == 0) + return false; + /* eat any extra pad FF bytes before marker */ + for( ; len > 0; data++, len--) { + if(*data != 0xff) + break; + } + if(len == 0) + return false; + /* if we hit SOS or EOI, bail */ + if(*data == 0xda || *data == 0xd9) + return false; + /* looking for some SOFn */ + else if(strchr("\xc0\xc1\xc2\xc3\xc5\xc6\xc7\xc9\xca\xcb\xcd\xce\xcf", *data)) { + data++; len--; /* skip marker byte */ + if(len < 2) + return false; + else { + const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1]; + if(clen < 8 || len < clen) + return false; + picture->width = (FLAC__uint32)data[5] << 8 | (FLAC__uint32)data[6]; + picture->height = (FLAC__uint32)data[3] << 8 | (FLAC__uint32)data[4]; + picture->depth = (FLAC__uint32)data[2] * (FLAC__uint32)data[7]; + picture->colors = 0; + return true; + } + } + /* else skip it */ + else { + data++; len--; /* skip marker byte */ + if(len < 2) + return false; + else { + const FLAC__uint32 clen = (FLAC__uint32)data[0] << 8 | (FLAC__uint32)data[1]; + if(clen < 2 || len < clen) + return false; + data += clen; + len -= clen; + } + } + } + } + else if(0 == strcmp(picture->mime_type, "image/gif")) { + /* c.f. http://www.w3.org/Graphics/GIF/spec-gif89a.txt */ + if(len < 14) + return false; + if(memcmp(data, "GIF87a", 6) && memcmp(data, "GIF89a", 6)) + return false; +#if 0 + /* according to the GIF spec, even if the GCTF is 0, the low 3 bits should still tell the total # colors used */ + if(data[10] & 0x80 == 0) + return false; +#endif + picture->width = (FLAC__uint32)data[6] | ((FLAC__uint32)data[7] << 8); + picture->height = (FLAC__uint32)data[8] | ((FLAC__uint32)data[9] << 8); +#if 0 + /* this value doesn't seem to be reliable... */ + picture->depth = (((FLAC__uint32)(data[10] & 0x70) >> 4) + 1) * 3u; +#else + /* ...just pessimistically assume it's 24-bit color without scanning all the color tables */ + picture->depth = 8u * 3u; +#endif + picture->colors = 1u << ((FLAC__uint32)(data[10] & 0x07) + 1u); + return true; + } + return false; +} + +FLAC__StreamMetadata *grabbag__picture_parse_specification(const char *spec, const char **error_message) +{ + FLAC__StreamMetadata *obj; + int state = 0; + const char *p; + char *q; + static const char *error_messages[] = { + "memory allocation error", + "invalid picture specification", + "invalid picture specification: can't parse resolution/color part", + "unable to extract resolution and color info from URL, user must set explicitly", + "unable to extract resolution and color info from file, user must set explicitly", + "error opening picture file", + "error reading picture file", + "invalid MIME type" + }; + + FLAC__ASSERT(0 != spec); + FLAC__ASSERT(0 != error_message); + + /* double protection */ + if(0 == spec) + return 0; + if(0 == error_message) + return 0; + + *error_message = 0; + + if(0 == (obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE))) + *error_message = error_messages[0]; + + for(p = spec; *error_message==0 && *p; ) { + if(*p == '|') { + switch(state) { + case 0: /* mime type */ + if(p-spec == 0) + *error_message = error_messages[7]; + else if(0 == (q = local__strndup_(spec, p-spec))) + *error_message = error_messages[0]; + else if(!FLAC__metadata_object_picture_set_mime_type(obj, q, /*copy=*/false)) + *error_message = error_messages[0]; + break; + case 1: /* description */ + if(0 == (q = local__strndup_(spec, p-spec))) + *error_message = error_messages[0]; + else if(!FLAC__metadata_object_picture_set_description(obj, (FLAC__byte*)q, /*copy=*/false)) + *error_message = error_messages[0]; + break; + case 2: /* resolution/color (e.g. [300x300x16[/1234]] */ + if(!local__parse_resolution_(spec, p-spec, &obj->data.picture)) + *error_message = error_messages[2]; + break; + default: + *error_message = error_messages[1]; + break; + } + p++; + spec = p; + state++; + } + else + p++; + } + /* parse filename, read file, try to extract resolution/color info if needed */ + if(*error_message == 0) { + if(state != 3) + *error_message = error_messages[1]; + else { /* 'spec' points to filename/URL */ + if(0 == strcmp(obj->data.picture.mime_type, "-->")) { /* magic MIME type means URL */ + if(!FLAC__metadata_object_picture_set_data(obj, (FLAC__byte*)spec, strlen(spec), /*copy=*/true)) + *error_message = error_messages[0]; + else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) + *error_message = error_messages[3]; + } + else { /* regular picture file */ + const off_t size = grabbag__file_get_filesize(spec); + if(size < 0) + *error_message = error_messages[5]; + else { + FLAC__byte *buffer = (FLAC__byte*)malloc(size); + if(0 == buffer) + *error_message = error_messages[0]; + else { + FILE *f = fopen(spec, "rb"); + if(0 == f) + *error_message = error_messages[5]; + else { + if(fread(buffer, 1, size, f) != size) + *error_message = error_messages[6]; + fclose(f); + if(0 == *error_message) { + if(!FLAC__metadata_object_picture_set_data(obj, buffer, size, /*copy=*/false)) + *error_message = error_messages[6]; + /* try to extract resolution/color info if user left it blank */ + else if(obj->data.picture.width == 0 || obj->data.picture.height == 0 || obj->data.picture.depth == 0) { + if(!local__extract_resolution_color_info_(&obj->data.picture)) + *error_message = error_messages[4]; + } + } + } + } + } + } + } + } + + if(*error_message && obj) { + FLAC__metadata_object_delete(obj); + obj = 0; + } + + return obj; +} |