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