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