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