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