http://sourceforge.net/tracker/?func=detail&aid=3291468&group_id=129766&atid=715782 [PATCH] Add the cache feature of ffmpegthumbnailer. I want the cache feature of ffmpegthumbnailer because my machine is not powerful :-( So I created this patch for the cache feature. This patch adds a new option "cache-dir" in config.xml. If not specifying any string, the cache feature is disable. And specifying some directory, the cache feature is enable and the cache files will be created under the directory. Signed-off-by: Ken'ichi Ohmichi <ken1ohmichi@gmail.com> --- diff --git a/src/common.h b/src/common.h index d1998b3..358f4d1 100644 --- a/src/common.h +++ b/src/common.h @@ -367,6 +367,8 @@ #define DEFAULT_FFMPEGTHUMBNAILER_FILMSTRIP_OVERLAY YES #define DEFAULT_FFMPEGTHUMBNAILER_WORKAROUND_BUGS NO #define DEFAULT_FFMPEGTHUMBNAILER_IMAGE_QUALITY 8 + #define DEFAULT_FFMPEGTHUMBNAILER_CACHE_DIR_ENABLED YES + #define DEFAULT_FFMPEGTHUMBNAILER_CACHE_DIR "" #endif #if defined(HAVE_LASTFMLIB) diff --git a/src/config_manager.cc b/src/config_manager.cc index 8c975f8..2902090 100644 --- a/src/config_manager.cc +++ b/src/config_manager.cc @@ -1873,6 +1873,24 @@ void ConfigManager::validate(String serverhome) NEW_INT_OPTION(temp_int); SET_INT_OPTION(CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_IMAGE_QUALITY); + + temp = getOption("/server/extended-runtime-options/ffmpegthumbnailer/" + "cache-dir", DEFAULT_FFMPEGTHUMBNAILER_CACHE_DIR); + + NEW_OPTION(temp); + SET_OPTION(CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_CACHE_DIR); + + temp = getOption("/server/extended-runtime-options/ffmpegthumbnailer/" + "cache-dir/attribute::enabled", + DEFAULT_FFMPEGTHUMBNAILER_CACHE_DIR_ENABLED); + + if (!validateYesNo(temp)) + throw _Exception(_("Error in config file: " + "invalid \"enabled\" attribute value in " + "ffmpegthumbnailer <cache-dir> tag")); + + NEW_BOOL_OPTION(temp == YES ? true : false); + SET_BOOL_OPTION(CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_CACHE_DIR_ENABLED); } #endif diff --git a/src/config_manager.h b/src/config_manager.h index 52b9842..a447a60 100644 --- a/src/config_manager.h +++ b/src/config_manager.h @@ -110,6 +110,8 @@ typedef enum CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_FILMSTRIP_OVERLAY, CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_WORKAROUND_BUGS, CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_IMAGE_QUALITY, + CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_CACHE_DIR_ENABLED, + CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_CACHE_DIR, #endif CFG_SERVER_EXTOPTS_MARK_PLAYED_ITEMS_ENABLED, CFG_SERVER_EXTOPTS_MARK_PLAYED_ITEMS_STRING_MODE_PREPEND, diff --git a/src/metadata/ffmpeg_handler.cc b/src/metadata/ffmpeg_handler.cc index a637d9c..8e7ef23 100644 --- a/src/metadata/ffmpeg_handler.cc +++ b/src/metadata/ffmpeg_handler.cc @@ -50,6 +50,9 @@ // INT64_C is not defined in ffmpeg/avformat.h but is needed // macro defines included via autoconfig.h #include <stdint.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> //#ifdef FFMPEG_NEEDS_EXTERN_C extern "C" @@ -279,6 +282,122 @@ void FfmpegHandler::fillMetadata(Ref<CdsItem> item) av_close_input_file(pFormatCtx); } +#ifdef HAVE_FFMPEGTHUMBNAILER + +static int _mkdir(const char *path) +{ + int ret = mkdir(path, 0777); + + if (ret == 0) { + // Make sure we are +x in case of restrictive umask that strips +x. + struct stat st; + if (stat(path, &st)) { + log_warning("could not stat(%s): %s\n", path, strerror(errno)); + return -1; + } + mode_t xbits = S_IXUSR | S_IXGRP | S_IXOTH; + if (!(st.st_mode & xbits)) { + if (chmod(path, st.st_mode | xbits)) { + log_warning("could not chmod(%s, +x): %s\n", path, strerror(errno)); + return -1; + } + } + } + + return ret; +} + +static bool makeThumbnailCacheDir(String& path) +{ + char *path_temp = strdup(path.c_str()); + char *last_slash = strrchr(path_temp, '/'); + char *slash = last_slash; + bool ret = false; + + if (!last_slash) + return ret; + + // Assume most dirs exist, so scan backwards first. + // Avoid stat/access checks due to TOCTOU races. + errno = 0; + for (slash = last_slash; slash > path_temp; --slash) { + if (*slash != '/') + continue; + *slash = '\0'; + if (_mkdir(path_temp) == 0) { + // Now we can forward scan. + while (slash < last_slash) { + *slash = DIR_SEPARATOR; + if (_mkdir(path_temp) < 0) + // Allow EEXIST in case of someone else doing `mkdir`. + if (errno != EEXIST) + goto done; + slash += strlen(slash); + } + if (slash == last_slash) + ret = true; + break; + } else if (errno == EEXIST) { + ret = true; + break; + } else if (errno != ENOENT) { + break; + } + } + + done: + free(path_temp); + return ret; +} + +static String getThumbnailCacheFilePath(String& movie_filename, bool create) +{ + Ref<ConfigManager> cfg = ConfigManager::getInstance(); + String cache_dir = cfg->getOption(CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_CACHE_DIR); + + if (cache_dir.length() == 0) { + String home_dir = cfg->getOption(CFG_SERVER_HOME); + cache_dir = home_dir + "/cache-dir"; + } + + cache_dir = cache_dir + movie_filename + "-thumb.jpg"; + if (create && !makeThumbnailCacheDir(cache_dir)) + cache_dir = ""; + return cache_dir; +} + +static bool readThumbnailCacheFile(String movie_filename, uint8_t **ptr_img, size_t *size_img) +{ + String path = getThumbnailCacheFilePath(movie_filename, false); + FILE *fp = fopen(path.c_str(), "rb"); + if (!fp) + return false; + + size_t bytesRead; + uint8_t buffer[1024]; + *ptr_img = NULL; + *size_img = 0; + while ((bytesRead = fread(buffer, 1, sizeof(buffer), fp)) > 0) { + *ptr_img = (uint8_t *)realloc(*ptr_img, *size_img + bytesRead); + memcpy(*ptr_img + *size_img, buffer, bytesRead); + *size_img += bytesRead; + } + fclose(fp); + return true; +} + +static void writeThumbnailCacheFile(String movie_filename, uint8_t *ptr_img, int size_img) +{ + String path = getThumbnailCacheFilePath(movie_filename, true); + FILE *fp = fopen(path.c_str(), "wb"); + if (!fp) + return; + fwrite(ptr_img, sizeof(uint8_t), size_img, fp); + fclose(fp); +} + +#endif + Ref<IOHandler> FfmpegHandler::serveContent(Ref<CdsItem> item, int resNum, off_t *data_size) { *data_size = -1; @@ -288,6 +407,18 @@ Ref<IOHandler> FfmpegHandler::serveContent(Ref<CdsItem> item, int resNum, off_t if (!cfg->getBoolOption(CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_ENABLED)) return nil; + if (cfg->getBoolOption(CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_CACHE_DIR_ENABLED)) { + uint8_t *ptr_image; + size_t size_image; + if (readThumbnailCacheFile(item->getLocation(), + &ptr_image, &size_image)) { + *data_size = (off_t)size_image; + Ref<IOHandler> h(new MemIOHandler(ptr_image, size_image)); + free(ptr_image); + log_debug("Returning cached thumbnail for file: %s\n", item->getLocation().c_str()); + return h; + } + } #ifdef FFMPEGTHUMBNAILER_OLD_API video_thumbnailer *th = create_thumbnailer(); image_data *img = create_image_data(); @@ -318,6 +449,10 @@ Ref<IOHandler> FfmpegHandler::serveContent(Ref<CdsItem> item, int resNum, off_t #endif // old api throw _Exception(_("Could not generate thumbnail for ") + item->getLocation()); + if (cfg->getBoolOption(CFG_SERVER_EXTOPTS_FFMPEGTHUMBNAILER_CACHE_DIR_ENABLED)) { + writeThumbnailCacheFile(item->getLocation(), + img->image_data_ptr, img->image_data_size); + } *data_size = (off_t)img->image_data_size; Ref<IOHandler> h(new MemIOHandler((void *)img->image_data_ptr,