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