1/* MiniDLNA media server 2 * Copyright (C) 2008 Justin Maggard 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 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. 13 * 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 17 */ 18#include <stdio.h> 19#include <stdlib.h> 20#include <string.h> 21#include <unistd.h> 22#include <dirent.h> 23#include <sys/stat.h> 24#include <libgen.h> 25#include <setjmp.h> 26#include <errno.h> 27 28#include <jpeglib.h> 29 30#include "upnpglobalvars.h" 31#include "albumart.h" 32#include "sql.h" 33#include "utils.h" 34#include "image_utils.h" 35#include "log.h" 36 37int 38art_cache_exists(const char * orig_path, char ** cache_file) 39{ 40 asprintf(cache_file, "%s/art_cache%s", db_path, orig_path); 41 strcpy(strchr(*cache_file, '\0')-4, ".jpg"); 42 43 return (!access(*cache_file, F_OK)); 44} 45 46char * 47save_resized_album_art(image * imsrc, const char * path) 48{ 49 int dstw, dsth; 50 image * imdst; 51 char * cache_file; 52 char * cache_dir; 53 54 if( !imsrc ) 55 return NULL; 56 57 if( art_cache_exists(path, &cache_file) ) 58 return cache_file; 59 60 cache_dir = strdup(cache_file); 61 make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); 62 free(cache_dir); 63 64 if( imsrc->width > imsrc->height ) 65 { 66 dstw = 160; 67 dsth = (imsrc->height<<8) / ((imsrc->width<<8)/160); 68 } 69 else 70 { 71 dstw = (imsrc->width<<8) / ((imsrc->height<<8)/160); 72 dsth = 160; 73 } 74 imdst = image_resize(imsrc, dstw, dsth); 75 if( !imdst ) 76 goto error; 77 78 if( image_save_to_jpeg_file(imdst, cache_file) == 0 ) 79 { 80 image_free(imdst); 81 return cache_file; 82 } 83error: 84 free(cache_file); 85 return NULL; 86} 87 88/* Simple, efficient hash function from Daniel J. Bernstein */ 89unsigned int DJBHash(const char * str, int len) 90{ 91 unsigned int hash = 5381; 92 unsigned int i = 0; 93 94 for(i = 0; i < len; str++, i++) 95 { 96 hash = ((hash << 5) + hash) + (*str); 97 } 98 99 return hash; 100} 101 102/* And our main album art functions */ 103void 104update_if_album_art(const char * path) 105{ 106 char * dir; 107 char * match = NULL; 108 char * file = NULL; 109 int ncmp = 0; 110 struct album_art_name_s * album_art_name; 111 DIR * dh; 112 struct dirent *dp; 113 enum file_types type = TYPE_UNKNOWN; 114 sqlite_int64 art_id = 0; 115 116 match = strdup(basename((char *)path)); 117 /* Check if this file name matches a specific audio or video file */ 118 if( ends_with(match, ".cover.jpg") ) 119 { 120 ncmp = strlen(match)-10; 121 } 122 else 123 { 124 ncmp = strrchr(match, '.')-match; 125 } 126 /* Check if this file name matches one of the default album art names */ 127 for( album_art_name = album_art_names; album_art_name; album_art_name = album_art_name->next ) 128 { 129 if( strcmp(album_art_name->name, match) == 0 ) 130 break; 131 } 132 133 dir = dirname(strdup(path)); 134 dh = opendir(dir); 135 if( !dh ) 136 return; 137 while ((dp = readdir(dh)) != NULL) 138 { 139 switch( dp->d_type ) 140 { 141 case DT_REG: 142 type = TYPE_FILE; 143 break; 144 case DT_LNK: 145 case DT_UNKNOWN: 146 asprintf(&file, "%s/%s", dir, dp->d_name); 147 type = resolve_unknown_type(file, ALL_MEDIA); 148 free(file); 149 break; 150 default: 151 type = TYPE_UNKNOWN; 152 break; 153 } 154 if( type != TYPE_FILE ) 155 continue; 156 if( (*(dp->d_name) != '.') && 157 (is_video(dp->d_name) || is_audio(dp->d_name)) && 158 (album_art_name || strncmp(dp->d_name, match, ncmp) == 0) ) 159 { 160 DPRINTF(E_DEBUG, L_METADATA, "New file %s looks like cover art for %s\n", path, dp->d_name); 161 asprintf(&file, "%s/%s", dir, dp->d_name); 162 art_id = find_album_art(file, NULL, 0); 163 if( sql_exec(db, "UPDATE DETAILS set ALBUM_ART = %lld where PATH = '%q'", art_id, file) != SQLITE_OK ) 164 DPRINTF(E_WARN, L_METADATA, "Error setting %s as cover art for %s\n", match, dp->d_name); 165 free(file); 166 } 167 } 168 closedir(dh); 169 170 free(dir); 171 free(match); 172} 173 174char * 175check_embedded_art(const char * path, const char * image_data, int image_size) 176{ 177 int width = 0, height = 0; 178 char * art_path = NULL; 179 char * cache_dir; 180 FILE * dstfile; 181 image * imsrc; 182 size_t nwritten; 183 static char last_path[PATH_MAX]; 184 static unsigned int last_hash = 0; 185 static int last_success = 0; 186 unsigned int hash; 187 188 if( !image_data || !image_size || !path ) 189 { 190 return NULL; 191 } 192 /* If the embedded image matches the embedded image from the last file we 193 * checked, just make a hard link. Better than storing it on the disk twice. */ 194 hash = DJBHash(image_data, image_size); 195 if( hash == last_hash ) 196 { 197 if( !last_success ) 198 return NULL; 199 art_cache_exists(path, &art_path); 200 if( link(last_path, art_path) == 0 ) 201 { 202 return(art_path); 203 } 204 else 205 { 206 if( errno == ENOENT ) 207 { 208 cache_dir = strdup(art_path); 209 make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); 210 free(cache_dir); 211 if( link(last_path, art_path) == 0 ) 212 return(art_path); 213 } 214 DPRINTF(E_WARN, L_METADATA, "Linking %s to %s failed [%s]\n", art_path, last_path, strerror(errno)); 215 free(art_path); 216 art_path = NULL; 217 } 218 } 219 last_hash = hash; 220 221 imsrc = image_new_from_jpeg(NULL, 0, image_data, image_size, 1); 222 if( !imsrc ) 223 { 224 last_success = 0; 225 return NULL; 226 } 227 width = imsrc->width; 228 height = imsrc->height; 229 230 if( width > 160 || height > 160 ) 231 { 232 art_path = save_resized_album_art(imsrc, path); 233 } 234 else if( width > 0 && height > 0 ) 235 { 236 if( art_cache_exists(path, &art_path) ) 237 goto end_art; 238 cache_dir = strdup(art_path); 239 make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); 240 free(cache_dir); 241 dstfile = fopen(art_path, "w"); 242 if( !dstfile ) 243 { 244 free(art_path); 245 art_path = NULL; 246 goto end_art; 247 } 248 nwritten = fwrite((void *)image_data, 1, image_size, dstfile); 249 fclose(dstfile); 250 if( nwritten != image_size ) 251 { 252 remove(art_path); 253 free(art_path); 254 art_path = NULL; 255 goto end_art; 256 } 257 } 258end_art: 259 image_free(imsrc); 260 if( !art_path ) 261 { 262 DPRINTF(E_WARN, L_METADATA, "Invalid embedded album art in %s\n", basename((char *)path)); 263 last_success = 0; 264 return NULL; 265 } 266 DPRINTF(E_DEBUG, L_METADATA, "Found new embedded album art in %s\n", basename((char *)path)); 267 last_success = 1; 268 strcpy(last_path, art_path); 269 270 return(art_path); 271} 272 273char * 274check_for_album_file(char * dir, const char * path) 275{ 276 char * file = malloc(PATH_MAX); 277 struct album_art_name_s * album_art_name; 278 image * imsrc = NULL; 279 int width=0, height=0; 280 char * art_file; 281 282 /* First look for file-specific cover art */ 283 sprintf(file, "%s.cover.jpg", path); 284 if( access(file, R_OK) == 0 ) 285 { 286 if( art_cache_exists(file, &art_file) ) 287 goto existing_file; 288 free(art_file); 289 imsrc = image_new_from_jpeg(file, 1, NULL, 0, 1); 290 if( imsrc ) 291 goto found_file; 292 } 293 sprintf(file, "%s", path); 294 strip_ext(file); 295 strcat(file, ".jpg"); 296 if( access(file, R_OK) == 0 ) 297 { 298 if( art_cache_exists(file, &art_file) ) 299 goto existing_file; 300 free(art_file); 301 imsrc = image_new_from_jpeg(file, 1, NULL, 0, 1); 302 if( imsrc ) 303 goto found_file; 304 } 305 306 /* Then fall back to possible generic cover art file names */ 307 for( album_art_name = album_art_names; album_art_name; album_art_name = album_art_name->next ) 308 { 309 sprintf(file, "%s/%s", dir, album_art_name->name); 310 if( access(file, R_OK) == 0 ) 311 { 312 if( art_cache_exists(file, &art_file) ) 313 { 314existing_file: 315 free(file); 316 return art_file; 317 } 318 free(art_file); 319 imsrc = image_new_from_jpeg(file, 1, NULL, 0, 1); 320 if( !imsrc ) 321 continue; 322found_file: 323 width = imsrc->width; 324 height = imsrc->height; 325 if( width > 160 || height > 160 ) 326 { 327 art_file = file; 328 file = save_resized_album_art(imsrc, art_file); 329 free(art_file); 330 } 331 image_free(imsrc); 332 return(file); 333 } 334 } 335 free(file); 336 return NULL; 337} 338 339sqlite_int64 340find_album_art(const char * path, const char * image_data, int image_size) 341{ 342 char * album_art = NULL; 343 char * sql; 344 char ** result; 345 int cols, rows; 346 sqlite_int64 ret = 0; 347 char * mypath = strdup(path); 348 349 if( (image_size && (album_art = check_embedded_art(path, image_data, image_size))) || 350 (album_art = check_for_album_file(dirname(mypath), path)) ) 351 { 352 sql = sqlite3_mprintf("SELECT ID from ALBUM_ART where PATH = '%q'", album_art ? album_art : path); 353 if( (sql_get_table(db, sql, &result, &rows, &cols) == SQLITE_OK) && rows ) 354 { 355 ret = strtoll(result[1], NULL, 10); 356 } 357 else 358 { 359 if( sql_exec(db, "INSERT into ALBUM_ART (PATH) VALUES ('%q')", album_art) == SQLITE_OK ) 360 ret = sqlite3_last_insert_rowid(db); 361 } 362 sqlite3_free_table(result); 363 sqlite3_free(sql); 364 } 365 if( album_art ) 366 free(album_art); 367 free(mypath); 368 369 return ret; 370} 371