File 15-thumbnails.patch of Package minidlna

From: "Barak A. Pearlmutter" <barak+git@pearlmutter.net>
Date: Mon, 21 Aug 2023 12:00:00 +0000
Subject: thumbnails

This patch is from
https://sourceforge.net/p/minidlna/patches/_discuss/thread/28a9f79d/9aca/attachment/minidlna-1.3.3-thumb.patch
by https://sourceforge.net/u/wilderman/profile/
with changes to derived and cache files (configure and such) removed.
---
 Makefile.am       |  4 ++--
 albumart.c        | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 configure.ac      | 15 ++++++++++++++
 minidlna.c        | 29 ++++++++++++++++++++++++++
 minidlna.conf     | 12 +++++++++++
 minidlnatypes.h   |  4 ++++
 monitor.c         |  8 +++++++
 monitor_inotify.c | 25 ++++++++++++++++++++++
 options.c         |  6 ++++++
 options.h         |  6 ++++++
 upnpglobalvars.h  |  5 +++++
 utils.c           | 14 +++++++++++++
 utils.h           |  3 +++
 13 files changed, 189 insertions(+), 4 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index dcfc625..603ee03 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -62,7 +62,7 @@ minidlnad_LDADD = \
 	@LIBEXIF_LIBS@ \
 	@LIBINTL@ \
 	@LIBICONV@ \
-	-lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs)
+	-lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs)  @LIBFFMPEGTHUMBNAILER_LIBS@
 
 testupnpdescgen_SOURCES = testupnpdescgen.c upnpdescgen.c
 testupnpdescgen_LDADD = \
@@ -71,7 +71,7 @@ testupnpdescgen_LDADD = \
 	@LIBSQLITE3_LIBS@ \
 	@LIBAVFORMAT_LIBS@ \
 	@LIBEXIF_LIBS@ \
-	-lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs)
+	-lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs) @LIBFFMPEGTHUMBNAILER_LIBS@
 
 SUFFIXES = .tmpl .
 
diff --git a/albumart.c b/albumart.c
index de026ec..28f174b 100644
--- a/albumart.c
+++ b/albumart.c
@@ -32,6 +32,10 @@
 
 #include <jpeglib.h>
 
+#ifdef THUMBNAIL_CREATION
+#include <libffmpegthumbnailer/videothumbnailerc.h>
+#endif
+
 #include "upnpglobalvars.h"
 #include "albumart.h"
 #include "sql.h"
@@ -348,14 +352,68 @@ found_file:
 	return NULL;
 }
 
+#ifdef THUMBNAIL_CREATION
+char *
+generate_thumbnail(const char * path)
+{
+	char *tfile = NULL;
+	video_thumbnailer *vt = NULL;
+	char cache_dir[MAXPATHLEN];
+
+	if( art_cache_exists(path, &tfile) )
+		return tfile;
+
+	if ( is_video(path) )
+	{
+
+		vt = video_thumbnailer_create();
+		if ( !vt )
+		{
+			free(tfile);
+			return 0;
+		}
+		vt->thumbnail_image_type = Jpeg;
+		vt->thumbnail_image_quality = runtime_vars.thumb_quality;
+		vt->thumbnail_size = runtime_vars.thumb_width;
+		vt->seek_percentage = 20;
+		vt->overlay_film_strip = (GETFLAG(THUMB_FILMSTRIP))?1:0;
+
+		DPRINTF(E_DEBUG, L_METADATA, "generating thumbnail: %s\n", path);
+
+		strncpyt(cache_dir, tfile, sizeof(cache_dir));
+		if ( !make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) &&
+			!video_thumbnailer_generate_thumbnail_to_file(vt, path, tfile) )
+		{
+			video_thumbnailer_destroy(vt);
+			return tfile;
+		}
+
+		video_thumbnailer_destroy(vt);
+	}
+	free(tfile);
+
+	return 0;
+}
+#endif
+
 int64_t
 find_album_art(const char *path, uint8_t *image_data, int image_size)
 {
 	char *album_art = NULL;
 	int64_t ret = 0;
 
-	if( (image_size && (album_art = check_embedded_art(path, image_data, image_size))) ||
-	    (album_art = check_for_album_file(path)) )
+	if(image_size)
+		album_art = check_embedded_art(path, image_data, image_size);
+
+	if(!album_art)
+		album_art = check_for_album_file(path);
+
+#ifdef THUMBNAIL_CREATION
+	if(!album_art && GETFLAG(THUMB_MASK))
+		album_art = generate_thumbnail(path);
+#endif
+
+	if(album_art)
 	{
 		ret = sql_get_int_field(db, "SELECT ID from ALBUM_ART where PATH = '%q'", album_art);
 		if( !ret )
diff --git a/configure.ac b/configure.ac
index 11421a5..2c3278b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -642,6 +642,21 @@ AC_ARG_ENABLE(lto,
         ]
 )
 
+AC_ARG_ENABLE(thumbnail,
+	[  --enable-thumbnail       enable video thumbnail generation using libffmpegthumbnailer],[
+	if test "$enableval" = "yes"; then
+		AC_DEFINE([THUMBNAIL_CREATION],[1],[Define to 1 if you want to enable video thumbnail generation])
+		PKG_CHECK_MODULES([LIBFFMPEGTHUMBNAILER], libffmpegthumbnailer, ,
+			AC_MSG_ERROR([Unable to find libffmpegthumbnailer]))
+		AC_SUBST([LIBFFMPEGTHUMBNAILER_CFLAGS])
+		AC_SUBST([LIBFFMPEGTHUMBNAILER_LIBS])
+        else
+                AC_MSG_RESULT([no])
+        fi
+        ],[
+                AC_MSG_RESULT([no])
+        ]
+)
 
 case "$target_os" in
 	darwin*)
diff --git a/minidlna.c b/minidlna.c
index 6d2d06b..74e1fac 100644
--- a/minidlna.c
+++ b/minidlna.c
@@ -589,6 +589,11 @@ init(int argc, char **argv)
 	runtime_vars.root_container = NULL;
 	runtime_vars.ifaces[0] = NULL;
 
+#ifdef THUMBNAIL_CREATION
+	runtime_vars.thumb_width = 160;
+	runtime_vars.thumb_quality = 8;
+#endif
+
 	/* read options file first since
 	 * command line arguments have final say */
 	if (readoptionsfile(optionsfile) < 0)
@@ -812,6 +817,30 @@ init(int argc, char **argv)
 			if (!strtobool(ary_options[i].value))
 				CLEARFLAG(SUBTITLES_MASK);
 			break;
+#ifdef THUMBNAIL_CREATION
+		case ENABLE_THUMB:
+			if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) )
+				SETFLAG(THUMB_MASK);
+		break;
+		case THUMB_WIDTH:
+			runtime_vars.thumb_width = atoi(ary_options[i].value);
+			if (runtime_vars.thumb_width < 120)
+				runtime_vars.thumb_width = 120;
+			if (runtime_vars.thumb_width > 480)
+				runtime_vars.thumb_width = 480;
+			break;
+		case THUMB_QUALITY:
+			runtime_vars.thumb_quality = atoi(ary_options[i].value);
+			if (runtime_vars.thumb_quality < 5)
+				runtime_vars.thumb_quality = 5;
+			if (runtime_vars.thumb_quality > 30)
+				runtime_vars.thumb_quality = 30;
+		break;
+		case ENABLE_THUMB_FILMSTRIP:
+			if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) )
+				SETFLAG(THUMB_FILMSTRIP);
+		break;
+#endif
 		default:
 			DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n",
 				optionsfile);
diff --git a/minidlna.conf b/minidlna.conf
index 82db5f1..f6ededf 100644
--- a/minidlna.conf
+++ b/minidlna.conf
@@ -95,3 +95,15 @@ model_number=1
 # enable subtitle support by default on unknown clients.
 # note: the default is yes
 #enable_subtitles=yes
+
+# Suport to Movie Thumbnail generation. To use this option, thumbnail generation must be enable at compile time.
+#enable_thumbnail=no
+
+# The width of the thumbnail image. Large images takes more time to generate.  To use this option, thumbnail generation must be enable at compile time.
+#thumbnail_width=160
+
+# Thumbnail Image quality. To use this option, thumbnail generation must be enable at compile time.
+#thumbnail_quality=8
+
+# Should the thumbnail have a film strip? To use this option, thumbnail generation must be enable at compile time.
+#enable_thumbnail_filmstrip=no
diff --git a/minidlnatypes.h b/minidlnatypes.h
index a92cc7d..98f91b1 100644
--- a/minidlnatypes.h
+++ b/minidlnatypes.h
@@ -51,6 +51,10 @@ struct runtime_vars_s {
 	int max_connections;	/* max number of simultaneous conenctions */
 	const char *root_container;	/* root ObjectID (instead of "0") */
 	const char *ifaces[MAX_LAN_ADDR];	/* list of configured network interfaces */
+#ifdef THUMBNAIL_CREATION
+	int thumb_width;
+	int thumb_quality;
+#endif
 };
 
 struct string_s {
diff --git a/monitor.c b/monitor.c
index b5757d9..f064ee5 100644
--- a/monitor.c
+++ b/monitor.c
@@ -117,6 +117,14 @@ monitor_remove_file(const char * path)
 		sql_exec(db, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID);
 	}
 	snprintf(art_cache, sizeof(art_cache), "%s/art_cache%s", db_path, path);
+#ifdef THUMBNAIL_CREATION
+	/* Remove video thumbnails */
+	if ( is_video(path) )
+	{
+		char *vthumb = art_cache;
+		strcpy(strchr(vthumb, '\0')-4, ".jpg");
+	}
+#endif
 	remove(art_cache);
 
 	return 0;
diff --git a/monitor_inotify.c b/monitor_inotify.c
index 365b3fe..d051dd0 100644
--- a/monitor_inotify.c
+++ b/monitor_inotify.c
@@ -265,6 +265,10 @@ inotify_thread(void *arg)
 	char * esc_name = NULL;
 	struct stat st;
 	sigset_t set;
+#ifdef THUMBNAIL_CREATION
+	char renpath_buf[PATH_MAX];
+	int cookie = 0;
+#endif
 
 	sigfillset(&set);
 	sigdelset(&set, SIGCHLD);
@@ -332,6 +336,16 @@ inotify_thread(void *arg)
 				{
 					DPRINTF(E_DEBUG, L_INOTIFY,  "The directory %s was %s.\n",
 						path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created"));
+#ifdef THUMBNAIL_CREATION
+					/* We do not want to regenerate the thumbnails if e rename a directory.
+					   We should keep at least four cookies/olddir since IN_MOVED_FROM/IN_MOVED_TO may
+					   not arrive in sequence, but one should cover most cases */
+					if (event->cookie == cookie && event->mask & IN_MOVED_TO)
+					{
+						DPRINTF(E_DEBUG, L_INOTIFY, "Directory rename: %s -> %s \n", renpath_buf, path_buf);
+						rename_artcache_dir(renpath_buf, path_buf);
+					}
+#endif
 					monitor_insert_directory(pollfds[0].fd, esc_name, path_buf);
 				}
 				else if ( (event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE)) &&
@@ -364,7 +378,18 @@ inotify_thread(void *arg)
 						(event->mask & IN_ISDIR ? "directory" : "file"),
 						path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted"));
 					if ( event->mask & IN_ISDIR )
+#ifdef THUMBNAIL_CREATION
+					{
+						if ( event->mask & IN_MOVED_FROM )
+						{
+							strncpy(renpath_buf, path_buf, sizeof(renpath_buf));
+							cookie = event->cookie;
+						}
+#endif
 						monitor_remove_directory(pollfds[0].fd, path_buf);
+#ifdef THUMBNAIL_CREATION
+					}	
+#endif
 					else
 					{
 						monitor_remove_file(path_buf);
diff --git a/options.c b/options.c
index cee3dff..f161d60 100644
--- a/options.c
+++ b/options.c
@@ -68,6 +68,12 @@ static const struct {
 	{ WIDE_LINKS, "wide_links" },
 	{ TIVO_DISCOVERY, "tivo_discovery" },
 	{ ENABLE_SUBTITLES, "enable_subtitles" },
+#ifdef THUMBNAIL_CREATION
+	{ ENABLE_THUMB, "enable_thumbnail" },
+	{ THUMB_WIDTH, "thumbnail_width" },
+	{ THUMB_QUALITY, "thumbnail_quality" },
+	{ ENABLE_THUMB_FILMSTRIP, "enable_thumbnail_filmstrip" },
+#endif
 };
 
 int
diff --git a/options.h b/options.h
index 1ff5b22..0e8c53c 100644
--- a/options.h
+++ b/options.h
@@ -61,6 +61,12 @@ enum upnpconfigoptions {
 	WIDE_LINKS,			/* allow following symlinks outside the defined media_dirs */
 	TIVO_DISCOVERY,			/* TiVo discovery protocol: bonjour or beacon. Defaults to bonjour if supported */
 	ENABLE_SUBTITLES,		/* Enable generic subtitle support for all clients by default */
+#ifdef THUMBNAIL_CREATION
+	ENABLE_THUMB,                   /* enable thumbnail generation */
+	THUMB_WIDTH,                    /* thunbnail image with */
+	THUMB_QUALITY,                  /* thumnail image quality */
+	ENABLE_THUMB_FILMSTRIP          /* film strip overlay */
+#endif
 };
 
 /* readoptionsfile()
diff --git a/upnpglobalvars.h b/upnpglobalvars.h
index 8a35555..66be7ba 100644
--- a/upnpglobalvars.h
+++ b/upnpglobalvars.h
@@ -208,6 +208,11 @@ extern uint32_t runtime_flags;
 #define SUBTITLES_MASK        0x0400
 #define FORCE_ALPHASORT_MASK  0x0800
 
+#ifdef THUMBNAIL_CREATION
+#define THUMB_MASK            0x1000
+#define THUMB_FILMSTRIP       0x2000
+#endif
+
 #define SETFLAG(mask)	runtime_flags |= mask
 #define GETFLAG(mask)	(runtime_flags & mask)
 #define CLEARFLAG(mask)	runtime_flags &= ~mask
diff --git a/utils.c b/utils.c
index 03ba850..eb809b8 100644
--- a/utils.c
+++ b/utils.c
@@ -576,3 +576,17 @@ timevalfix(struct timeval *t1)
 		t1->tv_usec -= 1000000;
 	}
 }
+
+#ifdef THUMBNAIL_CREATION
+int
+rename_artcache_dir(const char * oldpath, const char * newpath)
+{
+	char old_artcache[PATH_MAX];
+	char new_artcache[PATH_MAX];
+
+	snprintf(old_artcache, sizeof(old_artcache), "%s/art_cache%s", db_path, oldpath);
+	snprintf(new_artcache, sizeof(old_artcache), "%s/art_cache%s", db_path, newpath);
+
+	return rename(old_artcache, new_artcache);
+}
+#endif
diff --git a/utils.h b/utils.h
index 84b9923..641b57b 100644
--- a/utils.h
+++ b/utils.h
@@ -100,6 +100,9 @@ const char *mime_to_ext(const char * mime);
 /* Others */
 int make_dir(char * path, mode_t mode);
 unsigned int DJBHash(uint8_t *data, int len);
+#ifdef THUMBNAIL_CREATION
+int rename_artcache_dir(const char * oldpath, const char * newpath);
+#endif
 
 /* Timeval manipulations */
 void	timevaladd(struct timeval *t1, const struct timeval *t2);
openSUSE Build Service is sponsored by