Skip to content
Browse files

Initial version of nginx module to extract thumbs from a video file

  • Loading branch information...
0 parents commit fe9f810d7c8ac19e3ecba71aac63f02578554b8c @wandenberg committed Nov 22, 2011
2 .gitignore
@@ -0,0 +1,2 @@
+.cproject
+.project
21 README
@@ -0,0 +1,21 @@
+nginx-video-thumbextractor-module
+
+=============
+Requirements:
+=============
+
+lib avformat
+lib avcodec
+lib swscale
+lib jpeg
+GCC, make, the usual guys
+
+
+================
+Developer Guide:
+================
+
+Install the above requirements.
+Download nginx server.
+Configure nginx using
+ ./configure --add-module=<PATH_TO_MODULE_CODE>/nginx-video-thumbextractor-module
9 config
@@ -0,0 +1,9 @@
+ngx_addon_name=ngx_http_video_thumbextractor_module
+ngx_feature_libs="-lavformat -lavcodec -lswscale -ljpeg"
+HTTP_MODULES="$HTTP_MODULES ngx_http_video_thumbextractor_module"
+CORE_INCS="$CORE_INCS \
+ $ngx_addon_dir/src \
+ $ngx_addon_dir/include"
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
+ ${ngx_addon_dir}/src/ngx_http_video_thumbextractor_module.c"
+CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
35 include/ngx_http_video_thumbextractor_module.h
@@ -0,0 +1,35 @@
+#ifndef NGX_HTTP_VIDEO_THUMBEXTRACTOR_MODULE_H_
+#define NGX_HTTP_VIDEO_THUMBEXTRACTOR_MODULE_H_
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <video_hash.h>
+
+typedef struct {
+ ngx_int_t index_video_filename;
+ ngx_int_t index_video_second;
+ ngx_int_t index_image_width;
+ ngx_int_t index_image_height;
+
+ ngx_uint_t jpeg_baseline;
+ ngx_uint_t jpeg_progressive_mode;
+ ngx_uint_t jpeg_optimize;
+ ngx_uint_t jpeg_smooth;
+ ngx_uint_t jpeg_quality;
+ ngx_uint_t jpeg_dpi;
+
+ ngx_flag_t enabled;
+} ngx_http_video_thumbextractor_loc_conf_t;
+
+static ngx_int_t ngx_http_video_thumbextractor_handler(ngx_http_request_t *r);
+
+static ngx_str_t NGX_HTTP_VIDEO_THUMBEXTRACTOR_CONTENT_TYPE = ngx_string("image/jpeg");
+
+#define NGX_HTTP_VIDEO_THUMBEXTRACTOR_VARIABLE_REQUIRED(variable, log, msg) \
+ if ((variable == NULL) || variable->not_found || (variable->len == 0)) { \
+ ngx_log_error(NGX_LOG_ERR, log, 0, "video thumb extractor module: %s", msg); \
+ return NGX_HTTP_BAD_REQUEST; \
+ }
+
+#endif /* NGX_HTTP_VIDEO_THUMBEXTRACTOR_MODULE_H_ */
12 include/ngx_http_video_thumbextractor_module_utils.h
@@ -0,0 +1,12 @@
+#ifndef NGX_HTTP_VIDEO_THUMBEXTRACTOR_MODULE_UTILS_H_
+#define NGX_HTTP_VIDEO_THUMBEXTRACTOR_MODULE_UTILS_H_
+
+static ngx_str_t *ngx_http_video_thumbextractor_create_str(ngx_pool_t *pool, uint len);
+
+static int ngx_http_video_thumbextractor_get_thumb(ngx_http_video_thumbextractor_loc_conf_t *cf, const char *filename, int64_t second, ngx_uint_t width, ngx_uint_t height, caddr_t *out_buffer, size_t *out_len, ngx_pool_t *temp_pool, ngx_log_t *log);
+static void ngx_http_video_thumbextractor_init_libraries(void);
+
+#define NGX_HTTP_VIDEO_THUMBEXTRACTOR_FILE_NOT_FOUND 1
+#define NGX_HTTP_VIDEO_THUMBEXTRACTOR_SECOND_NOT_FOUND 2
+
+#endif /* NGX_HTTP_VIDEO_THUMBEXTRACTOR_MODULE_UTILS_H_ */
33 nginx.conf
@@ -0,0 +1,33 @@
+pid logs/nginx.pid;
+error_log logs/nginx-main_error.log debug;
+
+# Development Mode
+master_process off;
+daemon off;
+worker_processes 2;
+
+events {
+ worker_connections 1024;
+ #use kqueue; # MacOS
+ use epoll; # Linux
+}
+
+http {
+ default_type application/octet-stream;
+
+ access_log logs/nginx-http_access.log;
+ error_log logs/nginx-http_error.log debug;
+
+ server {
+ listen 8080;
+ server_name localhost;
+
+ location / {
+ video_thumbextractor;
+ set $video_thumbextractor_video_filename $uri;
+ set $video_thumbextractor_video_second $arg_second;
+ set $video_thumbextractor_image_width $arg_width;
+ set $video_thumbextractor_image_height $arg_height;
+ }
+ }
+}
82 src/ngx_http_video_thumbextractor_module.c
@@ -0,0 +1,82 @@
+#include <ngx_http_video_thumbextractor_module.h>
+#include <ngx_http_video_thumbextractor_module_setup.c>
+#include <ngx_http_video_thumbextractor_module_utils.c>
+
+static ngx_int_t
+ngx_http_video_thumbextractor_handler(ngx_http_request_t *r)
+{
+ ngx_http_video_thumbextractor_loc_conf_t *vtlcf;
+ ngx_http_variable_value_t *vv_filename, *vv_second, *vv_width, *vv_height;
+ ngx_str_t *filename;
+ ngx_uint_t second = 0, width = 0, height = 0;
+ ngx_int_t rc;
+ caddr_t out_buffer = 0;
+ size_t out_len = 0;
+ ngx_buf_t *b;
+ ngx_chain_t *out;
+
+ vtlcf = ngx_http_get_module_loc_conf(r, ngx_http_video_thumbextractor_module);
+
+ ngx_http_core_loc_conf_t *clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ // check if received a filename
+ vv_filename = ngx_http_get_indexed_variable(r, vtlcf->index_video_filename);
+ NGX_HTTP_VIDEO_THUMBEXTRACTOR_VARIABLE_REQUIRED(vv_filename, r->connection->log, "filename variable is empty");
+
+ vv_second = ngx_http_get_indexed_variable(r, vtlcf->index_video_second);
+ NGX_HTTP_VIDEO_THUMBEXTRACTOR_VARIABLE_REQUIRED(vv_second, r->connection->log, "second variable is empty");
+
+ vv_width = ngx_http_get_indexed_variable(r, vtlcf->index_image_width);
+ if ((vv_width != NULL) && !vv_width->not_found && (vv_width->len > 0)) {
+ width = ngx_atoi(vv_width->data, vv_width->len);
+ }
+
+ vv_height = ngx_http_get_indexed_variable(r, vtlcf->index_image_height);
+ if ((vv_height != NULL) && !vv_height->not_found && (vv_height->len > 0)) {
+ height = ngx_atoi(vv_height->data, vv_height->len);
+ }
+
+ if ((filename = ngx_http_video_thumbextractor_create_str(r->pool, clcf->root.len + vv_filename->len)) == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "video thumb extractor module: unable to allocate memory to store full filename");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ ngx_memcpy(ngx_copy(filename->data, clcf->root.data, clcf->root.len), vv_filename->data, vv_filename->len);
+
+ second = ngx_atoi(vv_second->data, vv_second->len);
+
+
+ if ((rc = ngx_http_video_thumbextractor_get_thumb(vtlcf, (char *)filename->data, second, width, height, &out_buffer, &out_len, r->pool, r->connection->log)) != NGX_OK) {
+ if (rc == NGX_ERROR) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return NGX_HTTP_NOT_FOUND;
+ }
+
+ /* write response */
+ r->headers_out.content_type = NGX_HTTP_VIDEO_THUMBEXTRACTOR_CONTENT_TYPE;
+ r->headers_out.status = NGX_HTTP_OK;
+ r->headers_out.content_length_n = out_len;
+
+ ngx_http_send_header(r);
+
+ out = (ngx_chain_t *) ngx_pcalloc(r->pool, sizeof(ngx_chain_t));
+ b = ngx_calloc_buf(r->pool);
+ if ((out == NULL) || (b == NULL)) {
+ return NGX_ERROR;
+ }
+
+ b->last_buf = 1;
+ b->flush = 1;
+ b->memory = 1;
+ b->pos = (u_char *) out_buffer;
+ b->start = b->pos;
+ b->end = b->pos + out_len;
+ b->last = b->end;
+
+ out->buf = b;
+ out->next = NULL;
+
+ ngx_http_output_filter(r, out);
+
+ return NGX_DONE;
+}
190 src/ngx_http_video_thumbextractor_module_setup.c
@@ -0,0 +1,190 @@
+#include <ngx_http_video_thumbextractor_module_utils.h>
+#include <ngx_http_video_thumbextractor_module.h>
+
+static void *ngx_http_video_thumbextractor_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_video_thumbextractor_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
+
+static ngx_int_t ngx_http_video_thumbextractor_init_worker(ngx_cycle_t *cycle);
+
+static char *ngx_http_video_thumbextractor(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static ngx_str_t ngx_http_video_thumbextractor_video_filename = ngx_string("video_thumbextractor_video_filename");
+static ngx_str_t ngx_http_video_thumbextractor_video_second = ngx_string("video_thumbextractor_video_second");
+static ngx_str_t ngx_http_video_thumbextractor_image_width = ngx_string("video_thumbextractor_image_width");
+static ngx_str_t ngx_http_video_thumbextractor_image_height = ngx_string("video_thumbextractor_image_height");
+
+static ngx_command_t ngx_http_video_thumbextractor_commands[] = {
+ { ngx_string("video_thumbextractor"),
+ NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
+ ngx_http_video_thumbextractor,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+ { ngx_string("video_thumbextractor_jpeg_baseline"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_video_thumbextractor_loc_conf_t, jpeg_baseline),
+ NULL },
+ { ngx_string("video_thumbextractor_jpeg_progressive_mode"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_video_thumbextractor_loc_conf_t, jpeg_progressive_mode),
+ NULL },
+ { ngx_string("video_thumbextractor_jpeg_optimize"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_video_thumbextractor_loc_conf_t, jpeg_optimize),
+ NULL },
+ { ngx_string("video_thumbextractor_jpeg_optimize"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_video_thumbextractor_loc_conf_t, jpeg_optimize),
+ NULL },
+ { ngx_string("video_thumbextractor_jpeg_smooth"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_video_thumbextractor_loc_conf_t, jpeg_smooth),
+ NULL },
+ { ngx_string("video_thumbextractor_jpeg_quality"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_video_thumbextractor_loc_conf_t, jpeg_quality),
+ NULL },
+ { ngx_string("video_thumbextractor_jpeg_dpi"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_video_thumbextractor_loc_conf_t, jpeg_dpi),
+ NULL },
+ ngx_null_command
+};
+
+static ngx_http_module_t ngx_http_video_thumbextractor_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_video_thumbextractor_create_loc_conf, /* create location configration */
+ ngx_http_video_thumbextractor_merge_loc_conf /* merge location configration */
+};
+
+
+ngx_module_t ngx_http_video_thumbextractor_module = {
+ NGX_MODULE_V1,
+ &ngx_http_video_thumbextractor_module_ctx, /* module context */
+ ngx_http_video_thumbextractor_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ ngx_http_video_thumbextractor_init_worker, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_http_video_thumbextractor_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_video_thumbextractor_loc_conf_t *conf;
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_video_thumbextractor_loc_conf_t));
+ if (conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->enabled = NGX_CONF_UNSET;
+ conf->jpeg_baseline = NGX_CONF_UNSET_UINT;
+ conf->jpeg_progressive_mode = NGX_CONF_UNSET_UINT;
+ conf->jpeg_optimize = NGX_CONF_UNSET_UINT;
+ conf->jpeg_smooth = NGX_CONF_UNSET_UINT;
+ conf->jpeg_quality = NGX_CONF_UNSET_UINT;
+ conf->jpeg_dpi = NGX_CONF_UNSET_UINT;
+
+ return conf;
+}
+
+
+static char *
+ngx_http_video_thumbextractor_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_video_thumbextractor_loc_conf_t *prev = parent;
+ ngx_http_video_thumbextractor_loc_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enabled, prev->enabled, 0);
+
+ // if video thumb extractor is disable the other configurations don't have to be checked
+ if (!conf->enabled) {
+ return NGX_CONF_OK;
+ }
+
+ ngx_conf_merge_uint_value(conf->jpeg_baseline, prev->jpeg_baseline, 1);
+ ngx_conf_merge_uint_value(conf->jpeg_progressive_mode, prev->jpeg_progressive_mode, 0);
+ ngx_conf_merge_uint_value(conf->jpeg_optimize, prev->jpeg_optimize, 100);
+ ngx_conf_merge_uint_value(conf->jpeg_smooth, prev->jpeg_smooth, 0);
+ ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75);
+ ngx_conf_merge_uint_value(conf->jpeg_dpi, prev->jpeg_dpi, 72); /** Screen resolution = 72 dpi */
+
+ // sanity checks
+
+ return NGX_CONF_OK;
+}
+
+static ngx_int_t
+ngx_http_video_thumbextractor_init_worker(ngx_cycle_t *cycle)
+{
+ ngx_http_video_thumbextractor_init_libraries();
+ return NGX_OK;
+}
+
+
+static char *
+ngx_http_video_thumbextractor(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ ngx_http_video_thumbextractor_loc_conf_t *vtlcf = conf;
+
+ clcf->handler = ngx_http_video_thumbextractor_handler;
+
+ vtlcf->enabled = 1;
+
+ vtlcf->index_video_filename = ngx_http_get_variable_index(cf, &ngx_http_video_thumbextractor_video_filename);
+ if (vtlcf->index_video_filename == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "video thumbextractor module: video_thumbextractor_video_filename variable could not be defined");
+ return NGX_CONF_ERROR;
+ }
+
+ vtlcf->index_video_second = ngx_http_get_variable_index(cf, &ngx_http_video_thumbextractor_video_second);
+ if (vtlcf->index_video_second == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "video thumbextractor module: ngx_http_video_thumbextractor_video_second variable could not be defined");
+ return NGX_CONF_ERROR;
+ }
+
+ vtlcf->index_image_width = ngx_http_get_variable_index(cf, &ngx_http_video_thumbextractor_image_width);
+ if (vtlcf->index_image_width == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "video thumbextractor module: ngx_http_video_thumbextractor_image_width variable could not be defined");
+ return NGX_CONF_ERROR;
+ }
+
+ vtlcf->index_image_height = ngx_http_get_variable_index(cf, &ngx_http_video_thumbextractor_image_height);
+ if (vtlcf->index_image_height == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "video thumbextractor module: ngx_http_video_thumbextractor_image_height variable could not be defined");
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
308 src/ngx_http_video_thumbextractor_module_utils.c
@@ -0,0 +1,308 @@
+#include <ngx_http_video_thumbextractor_module_utils.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+#include <jpeglib.h>
+
+#define NGX_HTTP_VIDEO_THUMBEXTRACTOR_MEMORY_STEP 1024
+
+static uint32_t ngx_http_video_thumbextractor_jpeg_compress(ngx_http_video_thumbextractor_loc_conf_t *cf, uint8_t * buffer, int in_width, int in_height, int out_width, int out_height, caddr_t *out_buffer, size_t *out_len, size_t uncompressed_size, ngx_pool_t *temp_pool);
+static void ngx_http_video_thumbextractor_jpeg_memory_dest (j_compress_ptr cinfo, caddr_t *out_buf, size_t *out_size, size_t uncompressed_size, ngx_pool_t *temp_pool);
+
+static ngx_str_t *
+ngx_http_video_thumbextractor_create_str(ngx_pool_t *pool, uint len)
+{
+ ngx_str_t *aux = (ngx_str_t *) ngx_pcalloc(pool, sizeof(ngx_str_t) + len + 1);
+ if (aux != NULL) {
+ aux->data = (u_char *) (aux + 1);
+ aux->len = len;
+ ngx_memset(aux->data, '\0', len + 1);
+ }
+ return aux;
+}
+
+
+static int
+ngx_http_video_thumbextractor_get_thumb(ngx_http_video_thumbextractor_loc_conf_t *cf, const char *filename, int64_t second, ngx_uint_t width, ngx_uint_t height, caddr_t *out_buffer, size_t *out_len, ngx_pool_t *temp_pool, ngx_log_t *log)
+{
+ int rc, videoStream, frameFinished;
+ unsigned int i;
+ AVFormatContext *pFormatCtx = NULL;
+ AVCodecContext *pCodecCtx = NULL;
+ AVCodec *pCodec = NULL;
+ AVFrame *pFrame = NULL, *pFrameRGB = NULL;
+ uint8_t *buffer = NULL;
+ AVPacket packet;
+ size_t uncompressed_size;
+
+ // Open video file
+ if ((rc = av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)) != 0) {
+ ngx_log_error(NGX_LOG_ERR, log, 0, "video thumb extractor module: Couldn't open file %s, error: %d", filename, rc);
+ rc = (rc == AVERROR_NOENT) ? NGX_HTTP_VIDEO_THUMBEXTRACTOR_FILE_NOT_FOUND : NGX_ERROR;
+ goto exit;
+ }
+
+ // Retrieve stream information
+ if (av_find_stream_info(pFormatCtx) < 0) {
+ ngx_log_error(NGX_LOG_ERR, log, 0, "video thumb extractor module: Couldn't find stream information");
+ rc = NGX_ERROR;
+ goto exit;
+ }
+
+ if ((pFormatCtx->duration > 0) && (second > (pFormatCtx->duration / AV_TIME_BASE))) {
+ ngx_log_error(NGX_LOG_WARN, log, 0, "video thumb extractor module: seconds greater than duration");
+ rc = NGX_HTTP_VIDEO_THUMBEXTRACTOR_SECOND_NOT_FOUND;
+ goto exit;
+ }
+
+ // Find the first video stream
+ videoStream = -1;
+ for (i = 0; i < pFormatCtx->nb_streams; i++) {
+ if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+ videoStream = i;
+ break;
+ }
+ }
+
+ if (videoStream == -1) {
+ ngx_log_error(NGX_LOG_ERR, log, 0, "video thumb extractor module: Didn't find a video stream");
+ rc = NGX_ERROR;
+ goto exit;
+ }
+
+ // Get a pointer to the codec context for the video stream
+ pCodecCtx = pFormatCtx->streams[videoStream]->codec;
+
+ // Find the decoder for the video stream
+ if ((pCodec = avcodec_find_decoder(pCodecCtx->codec_id)) == NULL) {
+ ngx_log_error(NGX_LOG_ERR, log, 0, "video thumb extractor module: Codec %d not found", pCodecCtx->codec_id);
+ rc = NGX_ERROR;
+ goto exit;
+ }
+
+ // Open codec
+ if ((rc = avcodec_open(pCodecCtx, pCodec)) < 0) {
+ ngx_log_error(NGX_LOG_ERR, log, 0, "video thumb extractor module: Could not open codec, error %d", rc);
+ rc = NGX_ERROR;
+ goto exit;
+ }
+
+ if ((width == 0) && (height == 0)) {
+ // keep original format
+ width = pCodecCtx->width;
+ height = pCodecCtx->height;
+ } else if (width == 0) {
+ // calculate width related with original aspect
+ width = height * pCodecCtx->width / pCodecCtx->height;
+ width = ((width + 8) / 16) * 16;
+ }
+
+ // Allocate video frame
+ pFrame = avcodec_alloc_frame();
+
+ // Allocate an AVFrame structure
+ pFrameRGB = avcodec_alloc_frame();
+ if ((pFrameRGB == NULL) || (pFrameRGB == NULL)) {
+ ngx_log_error(NGX_LOG_ERR, log, 0, "video thumb extractor module: Could not alloc frame memory");
+ rc = NGX_ERROR;
+ goto exit;
+ }
+
+ // Determine required buffer size and allocate buffer
+ uncompressed_size = avpicture_get_size(PIX_FMT_RGB24, width, height) * sizeof(uint8_t);
+ buffer = (uint8_t *) av_malloc(uncompressed_size);
+
+ // Assign appropriate parts of buffer to image planes in pFrameRGB
+ // Note that pFrameRGB is an AVFrame, but AVFrame is a superset of AVPicture
+ avpicture_fill((AVPicture *) pFrameRGB, buffer, PIX_FMT_RGB24, width, height);
+
+ if ((rc = av_seek_frame(pFormatCtx, -1, second * AV_TIME_BASE, 0)) < 0) {
+ ngx_log_error(NGX_LOG_ERR, log, 0, "video thumb extractor module: Seek to an invalid time, error: %d", rc);
+ rc = NGX_HTTP_VIDEO_THUMBEXTRACTOR_SECOND_NOT_FOUND;
+ goto exit;
+ }
+
+ rc = NGX_ERROR;
+ while (av_read_frame(pFormatCtx, &packet) >= 0) {
+ // Is this a packet from the video stream?
+ if (packet.stream_index == videoStream) {
+ // Decode video frame
+ avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
+ // Did we get a video frame?
+ if (frameFinished) {
+ // Convert the image from its native format to RGB
+ struct SwsContext *img_resample_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
+ width, height, PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
+
+ sws_scale(img_resample_ctx, (const uint8_t * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
+ sws_freeContext(img_resample_ctx);
+
+ // Compress to jpeg
+ if (ngx_http_video_thumbextractor_jpeg_compress(cf, pFrameRGB->data[0], pCodecCtx->width, pCodecCtx->height, width, height, out_buffer, out_len, uncompressed_size, temp_pool) == 0) {
+ rc = NGX_OK;
+ }
+ break;
+ }
+ }
+
+ // Free the packet that was allocated by av_read_frame
+ av_free_packet(&packet);
+ }
+ av_free_packet(&packet);
+
+exit:
+ /* destroy unneeded objects */
+
+ // Free the RGB image
+ if (buffer != NULL) av_free(buffer);
+ if (pFrameRGB != NULL) av_free(pFrameRGB);
+
+ // Free the YUV frame
+ if (pFrame != NULL) av_free(pFrame);
+
+ // Close the codec
+ if (pCodecCtx != NULL) avcodec_close(pCodecCtx);
+
+ // Close the video file
+ if (pFormatCtx != NULL) av_close_input_file(pFormatCtx);
+
+ return rc;
+}
+
+
+static void
+ngx_http_video_thumbextractor_init_libraries(void)
+{
+ // Register all formats and codecs
+ av_register_all();
+}
+
+
+static uint32_t
+ngx_http_video_thumbextractor_jpeg_compress(ngx_http_video_thumbextractor_loc_conf_t *cf, uint8_t * buffer, int in_width, int in_height, int out_width, int out_height, caddr_t *out_buffer, size_t *out_len, size_t uncompressed_size, ngx_pool_t *temp_pool)
+{
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ JSAMPROW row_pointer[1];
+ int row_stride;
+ int image_d_width = in_width;
+ int image_d_height = in_height;
+
+ if ( !buffer ) return 1;
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+ ngx_http_video_thumbextractor_jpeg_memory_dest(&cinfo, out_buffer, out_len, uncompressed_size, temp_pool);
+
+ cinfo.image_width = out_width;
+ cinfo.image_height = out_height;
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+
+ jpeg_set_defaults(&cinfo);
+ /* Important: Header info must be set AFTER jpeg_set_defaults() */
+ cinfo.write_JFIF_header = TRUE;
+ cinfo.JFIF_major_version = 1;
+ cinfo.JFIF_minor_version = 2;
+ cinfo.density_unit = 1; /* 0=unknown, 1=dpi, 2=dpcm */
+ /* Image DPI is determined by Y_density, so we leave that at
+ jpeg_dpi if possible and crunch X_density instead (PAR > 1) */
+
+ if (out_height * image_d_width > out_width * image_d_height) {
+ image_d_width = out_height * image_d_width / image_d_height;
+ image_d_height = out_height;
+ } else {
+ image_d_height = out_width * image_d_height / image_d_width;
+ image_d_width = out_width;
+ }
+
+ cinfo.X_density = cf->jpeg_dpi * out_width / image_d_width;
+ cinfo.Y_density = cf->jpeg_dpi * out_height / image_d_height;
+ cinfo.write_Adobe_marker = TRUE;
+
+ jpeg_set_quality(&cinfo, cf->jpeg_quality, cf->jpeg_baseline);
+ cinfo.optimize_coding = cf->jpeg_optimize;
+ cinfo.smoothing_factor = cf->jpeg_smooth;
+
+ if ( cf->jpeg_progressive_mode ) {
+ jpeg_simple_progression(&cinfo);
+ }
+
+ jpeg_start_compress(&cinfo, TRUE);
+
+ row_stride = out_width * 3;
+ while (cinfo.next_scanline < cinfo.image_height) {
+ row_pointer[0] = &buffer[cinfo.next_scanline * row_stride];
+ (void)jpeg_write_scanlines(&cinfo, row_pointer,1);
+ }
+
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ return 0;
+}
+
+
+typedef struct {
+ struct jpeg_destination_mgr pub; /* public fields */
+
+ unsigned char **buf;
+ size_t *size;
+ size_t uncompressed_size;
+ ngx_pool_t *pool;
+} ngx_http_video_thumbextractor_jpeg_destination_mgr;
+
+
+static void ngx_http_video_thumbextractor_init_destination (j_compress_ptr cinfo)
+{
+ ngx_http_video_thumbextractor_jpeg_destination_mgr * dest = (ngx_http_video_thumbextractor_jpeg_destination_mgr *) cinfo->dest;
+
+ *(dest->buf) = ngx_palloc(dest->pool, dest->uncompressed_size);
+ *(dest->size) = dest->uncompressed_size;
+ dest->pub.next_output_byte = *(dest->buf);
+ dest->pub.free_in_buffer = dest->uncompressed_size;
+}
+
+
+static boolean ngx_http_video_thumbextractor_empty_output_buffer (j_compress_ptr cinfo)
+{
+ ngx_http_video_thumbextractor_jpeg_destination_mgr *dest = (ngx_http_video_thumbextractor_jpeg_destination_mgr *) cinfo->dest;
+ unsigned char *ret;
+
+ ret = ngx_palloc(dest->pool, *(dest->size) + NGX_HTTP_VIDEO_THUMBEXTRACTOR_MEMORY_STEP);
+ ngx_memcpy(ret, *(dest->buf), *(dest->size));
+
+ *(dest->buf) = ret;
+ (*dest->size) += NGX_HTTP_VIDEO_THUMBEXTRACTOR_MEMORY_STEP;
+
+ dest->pub.next_output_byte = *(dest->buf) + *(dest->size) - NGX_HTTP_VIDEO_THUMBEXTRACTOR_MEMORY_STEP ;
+ dest->pub.free_in_buffer = NGX_HTTP_VIDEO_THUMBEXTRACTOR_MEMORY_STEP;
+
+ return TRUE;
+}
+
+
+static void ngx_http_video_thumbextractor_term_destination (j_compress_ptr cinfo)
+{
+ ngx_http_video_thumbextractor_jpeg_destination_mgr *dest = (ngx_http_video_thumbextractor_jpeg_destination_mgr *) cinfo->dest;
+ *(dest->size)-=dest->pub.free_in_buffer;
+}
+
+
+static void
+ngx_http_video_thumbextractor_jpeg_memory_dest (j_compress_ptr cinfo, caddr_t *out_buf, size_t *out_size, size_t uncompressed_size, ngx_pool_t *temp_pool)
+{
+ ngx_http_video_thumbextractor_jpeg_destination_mgr *dest;
+
+ if (cinfo->dest == NULL) {
+ cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(ngx_http_video_thumbextractor_jpeg_destination_mgr));
+ }
+
+ dest = (ngx_http_video_thumbextractor_jpeg_destination_mgr *) cinfo->dest;
+ dest->pub.init_destination = ngx_http_video_thumbextractor_init_destination;
+ dest->pub.empty_output_buffer = ngx_http_video_thumbextractor_empty_output_buffer;
+ dest->pub.term_destination = ngx_http_video_thumbextractor_term_destination;
+ dest->buf = (unsigned char **)out_buf;
+ dest->size = out_size;
+ dest->uncompressed_size = uncompressed_size;
+ dest->pool = temp_pool;
+}

0 comments on commit fe9f810

Please sign in to comment.
Something went wrong with that request. Please try again.