diff options
author | Justin Maggard <jmaggard@users.sourceforge.net> | 2009-02-26 00:16:51 +0300 |
---|---|---|
committer | Justin Maggard <jmaggard@users.sourceforge.net> | 2009-02-26 00:16:51 +0300 |
commit | e5f3e012485a696175181ac53d5bd9f85ca4be75 (patch) | |
tree | 364d6fa38b92dfbb24c6a3a2c5cc8df4903b1d7b /albumart.c | |
parent | 0212b7ced1220936dc7c14f4358bed5dc65dea17 (diff) |
* Use internal music metadata functions intead of taglib.
1) Taglib does not support MP4 or WMA/ASF without hacking it in there.
2) Taglib is C++, so it's nice to remove that dependency.
* Use embedded album art where available.
Diffstat (limited to 'albumart.c')
-rw-r--r-- | albumart.c | 263 |
1 files changed, 206 insertions, 57 deletions
@@ -15,7 +15,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#undef HAVE_LIBID3TAG #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -31,6 +30,7 @@ #include "upnpglobalvars.h" #include "sql.h" #include "utils.h" +#include "log.h" /* For libjpeg error handling */ jmp_buf setjmp_buffer; @@ -41,6 +41,7 @@ static void libjpeg_error_handler(j_common_ptr cinfo) return; } +#if 0 // Not needed currently int check_res(int width, int height, char * dlna_pn) { @@ -58,6 +59,131 @@ check_res(int width, int height, char * dlna_pn) return 0; return 1; } +#endif + +/* Use our own boxfilter resizer, because gdCopyImageResampled is slow, + * and gdCopyImageResized looks horrible when you downscale this much. */ +#define N_FRAC 8 +#define MASK_FRAC ((1 << N_FRAC) - 1) +#define ROUND2(v) (((v) + (1 << (N_FRAC - 1))) >> N_FRAC) +#define DIV(x, y) ( ((x) << (N_FRAC - 3)) / ((y) >> 3) ) +static void +boxfilter_resize(gdImagePtr dst, gdImagePtr src, + int dstX, int dstY, int srcX, int srcY, + int dstW, int dstH, int srcW, int srcH) +{ + int x, y; + int sy1, sy2, sx1, sx2; + + if(!dst->trueColor) + { + gdImageCopyResized(dst, src, dstX, dstY, srcX, srcY, dstW, dstH, + srcW, srcH); + return; + } + for(y = dstY; y < (dstY + dstH); y++) + { + sy1 = (((y - dstY) * srcH) << N_FRAC) / dstH; + sy2 = (((y - dstY + 1) * srcH) << N_FRAC) / dstH; + for(x = dstX; x < (dstX + dstW); x++) + { + int sx, sy; + int spixels = 0; + int red = 0, green = 0, blue = 0, alpha = 0; + sx1 = (((x - dstX) * srcW) << N_FRAC) / dstW; + sx2 = (((x - dstX + 1) * srcW) << N_FRAC) / dstW; + sy = sy1; + do { + int yportion; + if((sy >> N_FRAC) == (sy1 >> N_FRAC)) + { + yportion = (1 << N_FRAC) - (sy & MASK_FRAC); + if(yportion > sy2 - sy1) + { + yportion = sy2 - sy1; + } + sy = sy & ~MASK_FRAC; + } + else if(sy == (sy2 & ~MASK_FRAC)) + { + yportion = sy2 & MASK_FRAC; + } + else + { + yportion = (1 << N_FRAC); + } + sx = sx1; + do { + int xportion; + int pcontribution; + int p; + if((sx >> N_FRAC) == (sx1 >> N_FRAC)) + { + xportion = (1 << N_FRAC) - (sx & MASK_FRAC); + if(xportion > sx2 - sx1) + { + xportion = sx2 - sx1; + } + sx = sx & ~MASK_FRAC; + } + else if(sx == (sx2 & ~MASK_FRAC)) + { + xportion = sx2 & MASK_FRAC; + } + else + { + xportion = (1 << N_FRAC); + } + + if(xportion && yportion) + { + pcontribution = (xportion * yportion) >> N_FRAC; + p = gdImageGetTrueColorPixel(src, ROUND2(sx) + srcX, ROUND2(sy) + srcY); + if(pcontribution == (1 << N_FRAC)) + { + // optimization for down-scaler, which many pixel has pcontribution=1 + red += gdTrueColorGetRed(p) << N_FRAC; + green += gdTrueColorGetGreen(p) << N_FRAC; + blue += gdTrueColorGetBlue(p) << N_FRAC; + alpha += gdTrueColorGetAlpha(p) << N_FRAC; + spixels += (1 << N_FRAC); + } + else + { + red += gdTrueColorGetRed(p) * pcontribution; + green += gdTrueColorGetGreen(p) * pcontribution; + blue += gdTrueColorGetBlue(p) * pcontribution; + alpha += gdTrueColorGetAlpha(p) * pcontribution; + spixels += pcontribution; + } + } + sx += (1 << N_FRAC); + } + while(sx < sx2); + sy += (1 << N_FRAC); + } + while(sy < sy2); + if(spixels != 0) + { + red = DIV(red, spixels); + green = DIV(green, spixels); + blue = DIV(blue, spixels); + alpha = DIV(alpha, spixels); + } + /* Clamping to allow for rounding errors above */ + if(red > (255 << N_FRAC)) + red = (255 << N_FRAC); + if(green > (255 << N_FRAC)) + green = (255 << N_FRAC); + if(blue > (255 << N_FRAC)) + blue = (255 << N_FRAC); + if(alpha > (gdAlphaMax << N_FRAC)) + alpha = (gdAlphaMax << N_FRAC); + gdImageSetPixel(dst, x, y, + gdTrueColorAlpha(ROUND2(red), ROUND2(green), ROUND2(blue), ROUND2(alpha))); + } + } +} char * save_resized_album_art(void * ptr, const char * path, int srcw, int srch, int file, int size) @@ -104,11 +230,15 @@ save_resized_album_art(void * ptr, const char * path, int srcw, int srch, int fi fclose(dstfile); goto error; } + #if 0 // Try our box filter resizer for now #ifdef __sparc__ gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy); #else gdImageCopyResampled(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy); #endif + #else + boxfilter_resize(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy); + #endif gdImageJpeg(imdst, dstfile, 96); fclose(dstfile); gdImageDestroy(imsrc); @@ -120,10 +250,6 @@ error: return NULL; } - -#ifdef HAVE_LIBID3TAG -#include <id3tag.h> - /* These next few functions are to allow loading JPEG data directly from memory for libjpeg. * The standard functions only allow you to read from a file. * This code comes from the JpgAlleg library, at http://wiki.allegro.cc/index.php?title=Libjpeg */ @@ -184,68 +310,99 @@ jpeg_memory_src(j_decompress_ptr cinfo, unsigned char const *buffer, size_t bufs src->pub.bytes_in_buffer = bufsize; } +/* Simple, efficient hash function from Daniel J. Bernstein */ +unsigned int DJBHash(const char* str, int len) +{ + unsigned int hash = 5381; + unsigned int i = 0; + + for(i = 0; i < len; str++, i++) + { + hash = ((hash << 5) + hash) + (*str); + } + + return hash; +} + /* And our main album art functions */ char * -check_embedded_art(const char * path, char * dlna_pn) +check_embedded_art(const char * path, const char * image_data, int image_size) { - struct id3_file *file; - struct id3_tag *pid3tag; - struct id3_frame *pid3frame; - id3_byte_t const *image; - id3_latin1_t const *mime; - id3_length_t length; - int index; struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; int width = 0, height = 0; char * art_path = NULL; + char * cache_dir; + FILE * dstfile; + size_t nwritten; + static char last_path[PATH_MAX]; + static unsigned int last_hash = 0; + unsigned int hash; - file = id3_file_open(path, ID3_FILE_MODE_READONLY); - if( !file ) - return 0; - - pid3tag = id3_file_tag(file); - - for( index=0; (pid3frame = id3_tag_findframe(pid3tag, "", index)); index++ ) + /* If the embedded image matches the embedded image from the last file we + * checked, just make a hard link. No use in storing it on the disk twice. */ + hash = DJBHash(image_data, image_size); + if( hash == last_hash ) { - if( strcmp(pid3frame->id, "APIC") == 0 ) + asprintf(&art_path, DB_PATH "/art_cache%s", path); + if( link(last_path, art_path) == 0 ) { - mime = id3_field_getlatin1(&pid3frame->fields[1]); - if( strcmp((char*)mime, "image/jpeg") && strcmp((char*)mime, "jpeg") ) - continue; - image = id3_field_getbinarydata(&pid3frame->fields[4], &length); - - cinfo.err = jpeg_std_error(&jerr); - jerr.error_exit = libjpeg_error_handler; - jpeg_create_decompress(&cinfo); - if( setjmp(setjmp_buffer) ) - goto error; - jpeg_memory_src(&cinfo, image, length); - jpeg_read_header(&cinfo, TRUE); - jpeg_start_decompress(&cinfo); - width = cinfo.output_width; - height = cinfo.output_height; - error: - jpeg_destroy_decompress(&cinfo); - break; + return(art_path); + } + else + { + printf("link failed\n"); + free(art_path); + art_path = NULL; } } + + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = libjpeg_error_handler; + jpeg_create_decompress(&cinfo); + if( setjmp(setjmp_buffer) ) + goto error; + jpeg_memory_src(&cinfo, (unsigned char *)image_data, image_size); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + width = cinfo.output_width; + height = cinfo.output_height; + error: + jpeg_destroy_decompress(&cinfo); + if( width > 160 || height > 160 ) { - art_path = save_resized_album_art((void *)image, path, width, height, 0, length); + art_path = save_resized_album_art((void *)image_data, path, width, height, 0, image_size); } else if( width > 0 && height > 0 ) { - art_path = path; + asprintf(&art_path, DB_PATH "/art_cache%s", path); + if( access(art_path, F_OK) == 0 ) + return art_path; + cache_dir = strdup(art_path); + make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + free(cache_dir); + dstfile = fopen(art_path, "w"); + if( !dstfile ) + return NULL; + nwritten = fwrite((void *)image_data, image_size, 1, dstfile); + fclose(dstfile); + if( nwritten != image_size ) + { + free(art_path); + remove(art_path); + return NULL; + } } - id3_file_close(file); + DPRINTF(E_DEBUG, L_METADATA, "Found new embedded album art in %s\n", basename((char *)path)); + last_hash = hash; + strcpy(last_path, art_path); - return(art_file); + return(art_path); } -#endif // HAVE_LIBID3TAG char * -check_for_album_file(char * dir, char * dlna_pn) +check_for_album_file(char * dir) { char * file = malloc(PATH_MAX); struct album_art_name_s * album_art_name; @@ -290,7 +447,7 @@ check_for_album_file(char * dir, char * dlna_pn) } sqlite_int64 -find_album_art(const char * path, char * dlna_pn) +find_album_art(const char * path, char * dlna_pn, const char * image_data, int image_size) { char * album_art = NULL; char * sql; @@ -299,11 +456,8 @@ find_album_art(const char * path, char * dlna_pn) sqlite_int64 ret = 0; char * mypath = strdup(path); - #ifdef HAVE_LIBID3TAG - if( check_embedded_art(path, dlna_pn) || (album_art = check_for_album_file(dirname(mypath), dlna_pn)) ) - #else - if( (album_art = check_for_album_file(dirname(mypath), dlna_pn)) ) - #endif + if( (image_size && (album_art = check_embedded_art(path, image_data, image_size))) || + (album_art = check_for_album_file(dirname(mypath))) ) { strcpy(dlna_pn, "JPEG_TN"); sql = sqlite3_mprintf("SELECT ID from ALBUM_ART where PATH = '%q'", album_art ? album_art : path); @@ -314,12 +468,7 @@ find_album_art(const char * path, char * dlna_pn) else { sqlite3_free(sql); - sql = sqlite3_mprintf( "INSERT into ALBUM_ART" - " (PATH, EMBEDDED) " - "VALUES" - " ('%s', %d);", - (album_art ? album_art : path), - (album_art ? 0 : 1) ); + sql = sqlite3_mprintf("INSERT into ALBUM_ART (PATH) VALUES ('%q')", album_art); if( sql_exec(db, sql) == SQLITE_OK ) ret = sqlite3_last_insert_rowid(db); } |