libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

commit c23496d863e051ca42fdda5e4719ef173518d3f0
parent 3f542fba4232cd50d198a658e72ac46961ebd374
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 16 Aug 2012 21:50:39 +0000

getting ffmpeg thumbnailer to compile with recent ffmpeg libs

Diffstat:
Msrc/plugins/Makefile.am | 23++++++++++++++++++++++-
Dsrc/plugins/old/thumbnailffmpeg_extractor.c | 655-------------------------------------------------------------------------------
Asrc/plugins/test_thumbnailffmpeg.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/thumbnailffmpeg_extractor.c | 729+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/plugins/thumbnailgtk_extractor.c | 6+++---
5 files changed, 807 insertions(+), 659 deletions(-)

diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am @@ -48,10 +48,16 @@ PLUGIN_MIME=libextractor_mime.la TEST_MIME=test_mime if HAVE_GTK -# Thumbnailer requires MAGIC and GTK +# Gtk-thumbnailer requires MAGIC and GTK PLUGIN_GTK=libextractor_thumbnailgtk.la TEST_GTK=test_thumbnailgtk endif + +if HAVE_FFMPEG +# FFmpeg-thumbnailer requires MAGIC and FFMPEG +PLUGIN_FFMPEG=libextractor_thumbnailffmpeg.la +TEST_FFMPEG=test_thumbnailffmpeg +endif endif if HAVE_GIF @@ -116,6 +122,7 @@ plugin_LTLIBRARIES = \ libextractor_wav.la \ libextractor_zip.la \ $(PLUGIN_GTK) \ + $(PLUGIN_FFMPEG) \ $(PLUGIN_ZLIB) \ $(PLUGIN_OGG) \ $(PLUGIN_MIME) \ @@ -145,6 +152,7 @@ check_PROGRAMS = \ test_nsfe \ $(TEST_ZLIB) \ $(TEST_GTK) \ + $(TEST_FFMPEG) \ $(TEST_OGG) \ $(TEST_MIME) \ $(TEST_TIFF) \ @@ -456,3 +464,16 @@ test_gstreamer_SOURCES = \ test_gstreamer.c test_gstreamer_LDADD = \ $(top_builddir)/src/plugins/libtest.la + + +libextractor_thumbnailffmpeg_la_SOURCES = \ + thumbnailffmpeg_extractor.c +libextractor_thumbnailffmpeg_la_LDFLAGS = \ + $(PLUGINFLAGS) +libextractor_thumbnailffmpeg_la_LIBADD = \ + -lavutil -lavformat -lavcodec -lswscale -lmagic + +test_thumbnailffmpeg_SOURCES = \ + test_thumbnailffmpeg.c +test_thumbnailffmpeg_LDADD = \ + $(top_builddir)/src/plugins/libtest.la diff --git a/src/plugins/old/thumbnailffmpeg_extractor.c b/src/plugins/old/thumbnailffmpeg_extractor.c @@ -1,655 +0,0 @@ -/* - This file is part of libextractor. - Copyright (C) 2008 Heikki Lindholm - - libextractor is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published - by the Free Software Foundation; either version 2, or (at your - option) any later version. - - libextractor is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with libextractor; see the file COPYING. If not, write to the - Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. - */ - -/** - * @file thumbnailffmpeg_extractor.c - * @author Heikki Lindholm - * @brief this extractor produces a binary encoded - * thumbnail of images and videos using the ffmpeg libs. - */ - -/* This is a thumbnail extractor using the ffmpeg libraries that will eventually - support extracting thumbnails from both image and video files. - - Note that ffmpeg has a few issues: - (1) there are no recent official releases of the ffmpeg libs - (2) ffmpeg has a history of having security issues (parser is not robust) - - So this plugin cannot be recommended for system with high security - requirements. -*/ - -#include "platform.h" -#include "extractor.h" -#if HAVE_LIBAVUTIL_AVUTIL_H -#include <libavutil/avutil.h> -#elif HAVE_FFMPEG_AVUTIL_H -#include <ffmpeg/avutil.h> -#endif -#if HAVE_LIBAVFORMAT_AVFORMAT_H -#include <libavformat/avformat.h> -#elif HAVE_FFMPEG_AVFORMAT_H -#include <ffmpeg/avformat.h> -#endif -#if HAVE_LIBAVCODEC_AVCODEC_H -#include <libavcodec/avcodec.h> -#elif HAVE_FFMPEG_AVCODEC_H -#include <ffmpeg/avcodec.h> -#endif -#if HAVE_LIBSWSCALE_SWSCALE_H -#include <libswscale/swscale.h> -#elif HAVE_FFMPEG_SWSCALE_H -#include <ffmpeg/swscale.h> -#endif - -#include "mime_extractor.c" /* TODO: do this cleaner */ - -#define DEBUG 0 - -static void thumbnailffmpeg_av_log_callback(void* ptr, - int level, - const char *format, - va_list ap) -{ -#if DEBUG - vfprintf(stderr, format, ap); -#endif -} - -void __attribute__ ((constructor)) ffmpeg_lib_init (void) -{ - av_log_set_callback (thumbnailffmpeg_av_log_callback); - av_register_all (); -} - -#define MAX_THUMB_DIMENSION 128 /* max dimension in pixels */ -#define MAX_THUMB_BYTES (100*1024) - -/* - * Rescale and encode a PNG thumbnail - * on success, fills in output_data and returns the number of bytes used - */ -static size_t create_thumbnail( - int src_width, int src_height, int src_stride[], - enum PixelFormat src_pixfmt, const uint8_t * const src_data[], - int dst_width, int dst_height, - uint8_t **output_data, size_t output_max_size) -{ - AVCodecContext *encoder_codec_ctx = NULL; - AVCodec *encoder_codec = NULL; - struct SwsContext *scaler_ctx = NULL; - int sws_flags = SWS_BILINEAR; - AVFrame *dst_frame = NULL; - uint8_t *dst_buffer = NULL; - uint8_t *encoder_output_buffer = NULL; - size_t encoder_output_buffer_size; - int err; - - encoder_codec = avcodec_find_encoder_by_name ("png"); - if (encoder_codec == NULL) - { -#if DEBUG - fprintf (stderr, - "Couldn't find a PNG encoder\n"); -#endif - return 0; - } - - /* NOTE: the scaler will be used even if the src and dst image dimensions - * match, because the scaler will also perform colour space conversion */ - scaler_ctx = - sws_getContext (src_width, src_height, src_pixfmt, - dst_width, dst_height, PIX_FMT_RGB24, - sws_flags, NULL, NULL, NULL); - if (scaler_ctx == NULL) - { -#if DEBUG - fprintf (stderr, - "Failed to get a scaler context\n"); -#endif - return 0; - } - - dst_frame = avcodec_alloc_frame (); - if (dst_frame == NULL) - { -#if DEBUG - fprintf (stderr, - "Failed to allocate the destination image frame\n"); -#endif - sws_freeContext(scaler_ctx); - return 0; - } - dst_buffer = - av_malloc (avpicture_get_size (PIX_FMT_RGB24, dst_width, dst_height)); - if (dst_buffer == NULL) - { -#if DEBUG - fprintf (stderr, - "Failed to allocate the destination image buffer\n"); -#endif - av_free (dst_frame); - sws_freeContext(scaler_ctx); - return 0; - } - avpicture_fill ((AVPicture *) dst_frame, dst_buffer, - PIX_FMT_RGB24, dst_width, dst_height); - - sws_scale (scaler_ctx, - src_data, - src_stride, - 0, src_height, - dst_frame->data, - dst_frame->linesize); - - encoder_output_buffer_size = output_max_size; - encoder_output_buffer = av_malloc (encoder_output_buffer_size); - if (encoder_output_buffer == NULL) - { -#if DEBUG - fprintf (stderr, - "Failed to allocate the encoder output buffer\n"); -#endif - av_free (dst_buffer); - av_free (dst_frame); - sws_freeContext(scaler_ctx); - return 0; - } - - encoder_codec_ctx = avcodec_alloc_context (); - if (encoder_codec_ctx == NULL) - { -#if DEBUG - fprintf (stderr, - "Failed to allocate the encoder codec context\n"); -#endif - av_free (encoder_output_buffer); - av_free (dst_buffer); - av_free (dst_frame); - sws_freeContext(scaler_ctx); - return 0; - } - encoder_codec_ctx->width = dst_width; - encoder_codec_ctx->height = dst_height; - encoder_codec_ctx->pix_fmt = PIX_FMT_RGB24; - - if (avcodec_open (encoder_codec_ctx, encoder_codec) < 0) - { -#if DEBUG - fprintf (stderr, - "Failed to open the encoder\n"); -#endif - av_free (encoder_codec_ctx); - av_free (encoder_output_buffer); - av_free (dst_buffer); - av_free (dst_frame); - sws_freeContext(scaler_ctx); - return 0; - } - - err = avcodec_encode_video (encoder_codec_ctx, - encoder_output_buffer, - encoder_output_buffer_size, dst_frame); - - avcodec_close (encoder_codec_ctx); - av_free (encoder_codec_ctx); - av_free (dst_buffer); - av_free (dst_frame); - sws_freeContext(scaler_ctx); - - *output_data = encoder_output_buffer; - - return err < 0 ? 0 : err; -} - -struct MIMEToDecoderMapping -{ - const char *mime_type; - enum CodecID codec_id; -}; - -/* map MIME image types to an ffmpeg decoder */ -static const struct MIMEToDecoderMapping m2d_map[] = { - {"image/x-bmp", CODEC_ID_BMP}, - {"image/gif", CODEC_ID_GIF}, - {"image/jpeg", CODEC_ID_MJPEG}, - {"image/png", CODEC_ID_PNG}, - {"image/x-png", CODEC_ID_PNG}, - {"image/x-portable-pixmap", CODEC_ID_PPM}, - {NULL, CODEC_ID_NONE} -}; - -static char *mime_type; - -static int -mime_processor (void *cls, - const char *plugin_name, - enum EXTRACTOR_MetaType type, - enum EXTRACTOR_MetaFormat format, - const char *data_mime_type, - const char *data, - size_t data_len) -{ - switch (format) - { - case EXTRACTOR_METAFORMAT_UTF8: - mime_type = strdup(data); - break; - default: - break; - } - return 0; -} - -/* calculate the thumbnail dimensions, taking pixel aspect into account */ -static void calculate_thumbnail_dimensions(int src_width, - int src_height, - int src_sar_num, - int src_sar_den, - int *dst_width, - int *dst_height) -{ - if (src_sar_num <= 0 || src_sar_den <= 0) - { - src_sar_num = 1; - src_sar_den = 1; - } - if ((src_width * src_sar_num) / src_sar_den > src_height) - { - *dst_width = MAX_THUMB_DIMENSION; - *dst_height = (*dst_width * src_height) / - ((src_width * src_sar_num) / src_sar_den); - } - else - { - *dst_height = MAX_THUMB_DIMENSION; - *dst_width = (*dst_height * - ((src_width * src_sar_num) / src_sar_den)) / - src_height; - } - if (*dst_width < 8) - *dst_width = 8; - if (*dst_height < 1) - *dst_height = 1; -#if DEBUG - fprintf (stderr, - "Thumbnail dimensions: %d %d\n", - *dst_width, *dst_height); -#endif -} - -static int -extract_image (enum CodecID image_codec_id, - const unsigned char *data, - size_t size, - EXTRACTOR_MetaDataProcessor proc, - void *proc_cls, - const char *options) -{ - AVCodecContext *codec_ctx; - AVCodec *codec = NULL; - AVFrame *frame = NULL; - uint8_t *encoded_thumbnail; - int thumb_width; - int thumb_height; - int err; - int frame_finished; - int ret = 0; - - codec_ctx = avcodec_alloc_context (); - if (codec_ctx == NULL) - { -#if DEBUG - fprintf (stderr, - "Failed to allocate codec context\n"); -#endif - return 0; - } - - codec = avcodec_find_decoder (image_codec_id); - if (codec != NULL) - { - if (avcodec_open (codec_ctx, codec) != 0) - { -#if DEBUG - fprintf (stderr, - "Failed to open image codec\n"); -#endif - av_free (codec_ctx); - return 0; - } - } - else - { -#if DEBUG - fprintf (stderr, - "No suitable codec found\n"); -#endif - av_free (codec_ctx); - return 0; - } - - frame = avcodec_alloc_frame (); - if (frame == NULL) - { -#if DEBUG - fprintf (stderr, - "Failed to allocate frame\n"); -#endif - avcodec_close (codec_ctx); - av_free (codec_ctx); - return 0; - } - - avcodec_decode_video (codec_ctx, frame, &frame_finished, data, size); - - if (!frame_finished) - { - fprintf (stderr, - "Failed to decode a complete frame\n"); - av_free (frame); - avcodec_close (codec_ctx); - av_free (codec_ctx); - return 0; - } - - calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height, - codec_ctx->sample_aspect_ratio.num, - codec_ctx->sample_aspect_ratio.den, - &thumb_width, &thumb_height); - - err = create_thumbnail (codec_ctx->width, codec_ctx->height, - frame->linesize, codec_ctx->pix_fmt, - (const uint8_t * const*) frame->data, - thumb_width, thumb_height, - &encoded_thumbnail, MAX_THUMB_BYTES); - - if (err > 0) - { - ret = proc (proc_cls, - "thumbnailffmpeg", - EXTRACTOR_METATYPE_THUMBNAIL, - EXTRACTOR_METAFORMAT_BINARY, - "image/png", - (const char*) encoded_thumbnail, - err); - av_free (encoded_thumbnail); - } - - av_free (frame); - avcodec_close (codec_ctx); - av_free (codec_ctx); - return ret; -} - -static int -extract_video (const unsigned char *data, - size_t size, - EXTRACTOR_MetaDataProcessor proc, - void *proc_cls, - const char *options) -{ - AVProbeData pd; - AVPacket packet; - AVInputFormat *input_format; - int input_format_nofileflag; - ByteIOContext *bio_ctx; - struct AVFormatContext *format_ctx; - AVCodecContext *codec_ctx; - AVCodec *codec = NULL; - AVFrame *frame = NULL; - uint8_t *encoded_thumbnail; - int video_stream_index = -1; - int thumb_width; - int thumb_height; - int i; - int err; - int frame_finished; - int ret = 0; - -#if DEBUG - fprintf (stderr, - "ffmpeg starting\n"); -#endif - /* probe format - * initial try with a smaller probe size for efficiency */ - pd.filename = ""; - pd.buf = (void *) data; - pd.buf_size = 128*1024 > size ? size : 128*1024; -RETRY_PROBE: - if (NULL == (input_format = av_probe_input_format(&pd, 1))) - { -#if DEBUG - fprintf (stderr, - "Failed to probe input format\n"); -#endif - if (pd.buf_size != size) /* retry probe once with full data size */ - { - pd.buf_size = size; - goto RETRY_PROBE; - } - return 0; - } - input_format_nofileflag = input_format->flags & AVFMT_NOFILE; - input_format->flags |= AVFMT_NOFILE; - bio_ctx = NULL; - pd.buf_size = size; - url_open_buf(&bio_ctx, pd.buf, pd.buf_size, URL_RDONLY); - bio_ctx->is_streamed = 1; - if ((av_open_input_stream(&format_ctx, bio_ctx, pd.filename, input_format, NULL)) < 0) - { - #if DEBUG - fprintf (stderr, - "Failed to open input stream\n"); -#endif - url_close_buf (bio_ctx); - if (!input_format_nofileflag) - input_format->flags ^= AVFMT_NOFILE; - return 0; - } - if (0 > av_find_stream_info (format_ctx)) - { - #if DEBUG - fprintf (stderr, - "Failed to read stream info\n"); -#endif - av_close_input_stream (format_ctx); - url_close_buf (bio_ctx); - if (!input_format_nofileflag) - input_format->flags ^= AVFMT_NOFILE; - return 0; - } - - codec_ctx = NULL; - for (i=0; i<format_ctx->nb_streams; i++) - { - codec_ctx = format_ctx->streams[i]->codec; - if (codec_ctx->codec_type != CODEC_TYPE_VIDEO) - continue; - codec = avcodec_find_decoder (codec_ctx->codec_id); - if (codec == NULL) - continue; - err = avcodec_open (codec_ctx, codec); - if (err != 0) - { - codec = NULL; - continue; - } - video_stream_index = i; - break; - } - - if ( (video_stream_index == -1) || - (codec_ctx->width == 0) || - (codec_ctx->height == 0) ) - { -#if DEBUG - fprintf (stderr, - "No video streams or no suitable codec found\n"); -#endif - if (codec != NULL) - avcodec_close (codec_ctx); - av_close_input_stream (format_ctx); - url_close_buf (bio_ctx); - if (!input_format_nofileflag) - input_format->flags ^= AVFMT_NOFILE; - return 0; - } - - frame = avcodec_alloc_frame (); - if (frame == NULL) - { -#if DEBUG - fprintf (stderr, - "Failed to allocate frame\n"); -#endif - avcodec_close (codec_ctx); - av_close_input_stream (format_ctx); - url_close_buf (bio_ctx); - if (!input_format_nofileflag) - input_format->flags ^= AVFMT_NOFILE; - return 0; - } -#if DEBUG - if (format_ctx->duration == AV_NOPTS_VALUE) - fprintf (stderr, - "Duration unknown\n"); - else - fprintf (stderr, - "Duration: %lld\n", - format_ctx->duration); -#endif - /* TODO: if duration is known, seek to some better place, - * but use 10 sec into stream for now */ - err = av_seek_frame (format_ctx, -1, 10 * AV_TIME_BASE, 0); - if (err >= 0) - avcodec_flush_buffers (codec_ctx); - frame_finished = 0; - - while (1) - { - err = av_read_frame (format_ctx, &packet); - if (err < 0) - break; - if (packet.stream_index == video_stream_index) - { - avcodec_decode_video (codec_ctx, - frame, - &frame_finished, - packet.data, packet.size); - if (frame_finished && frame->key_frame) - { - av_free_packet (&packet); - break; - } - } - av_free_packet (&packet); - } - if (!frame_finished) - { - fprintf (stderr, - "Failed to decode a complete frame\n"); - av_free (frame); - avcodec_close (codec_ctx); - av_close_input_stream (format_ctx); - url_close_buf (bio_ctx); - if (!input_format_nofileflag) - input_format->flags ^= AVFMT_NOFILE; - return 0; - } - - calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height, - codec_ctx->sample_aspect_ratio.num, - codec_ctx->sample_aspect_ratio.den, - &thumb_width, &thumb_height); - - err = create_thumbnail (codec_ctx->width, codec_ctx->height, - frame->linesize, codec_ctx->pix_fmt, - (const uint8_t* const *) frame->data, - thumb_width, thumb_height, - &encoded_thumbnail, MAX_THUMB_BYTES); - - if (err > 0) - { - ret = proc (proc_cls, - "thumbnailffmpeg", - EXTRACTOR_METATYPE_THUMBNAIL, - EXTRACTOR_METAFORMAT_BINARY, - "image/png", - (const char*) encoded_thumbnail, - err); - av_free (encoded_thumbnail); - } - - av_free (frame); - avcodec_close (codec_ctx); - av_close_input_stream (format_ctx); - url_close_buf (bio_ctx); - if (!input_format_nofileflag) - input_format->flags ^= AVFMT_NOFILE; - return ret; -} - -int -EXTRACTOR_thumbnailffmpeg_extract (const unsigned char *data, - size_t size, - EXTRACTOR_MetaDataProcessor proc, - void *proc_cls, - const char *options) -{ - enum CodecID image_codec_id; - int is_image = 0; - int i; - - mime_type = NULL; - EXTRACTOR_mime_extract((const char*) data, size, mime_processor, NULL, NULL); - if (mime_type != NULL) - { - i = 0; - while (m2d_map[i].mime_type != NULL) - { - if (!strcmp (m2d_map[i].mime_type, mime_type)) - { - is_image = 1; - image_codec_id = m2d_map[i].codec_id; - break; - } - i++; - } - free(mime_type); - } - - if (is_image) - return extract_image (image_codec_id, data, size, proc, proc_cls, options); - else - return extract_video (data, size, proc, proc_cls, options); -} - -int -EXTRACTOR_thumbnail_extract (const unsigned char *data, - size_t size, - EXTRACTOR_MetaDataProcessor proc, - void *proc_cls, - const char *options) -{ - return EXTRACTOR_thumbnailffmpeg_extract (data, size, proc, proc_cls, options); -} - -/* end of thumbnailffmpeg_extractor.c */ diff --git a/src/plugins/test_thumbnailffmpeg.c b/src/plugins/test_thumbnailffmpeg.c @@ -0,0 +1,53 @@ +/* + This file is part of libextractor. + (C) 2012 Vidyut Samanta and Christian Grothoff + + libextractor is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + libextractor is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libextractor; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ +/** + * @file plugins/test_thumbnailffmpeg.c + * @brief testcase for thumbnailffmpeg plugin + * @author Christian Grothoff + */ +#include "platform.h" +#include "test_lib.h" + + + +/** + * Main function for the THUMBNAILFFMPEG testcase. + * + * @param argc number of arguments (ignored) + * @param argv arguments (ignored) + * @return 0 on success + */ +int +main (int argc, char *argv[]) +{ + struct SolutionData thumbnailffmpeg_video_sol[] = + { + { 0, 0, NULL, NULL, 0, -1 } + }; + struct ProblemSet ps[] = + { + { "testdata/thumbnailffmpeg_video.mov", + thumbnailffmpeg_video_sol }, + { NULL, NULL } + }; + return ET_main ("thumbnailffmpeg", ps); +} + +/* end of test_thumbnailffmpeg.c */ diff --git a/src/plugins/thumbnailffmpeg_extractor.c b/src/plugins/thumbnailffmpeg_extractor.c @@ -0,0 +1,729 @@ +/* + This file is part of libextractor. + Copyright (C) 2008, 2012 Heikki Lindholm and Christian Grothoff + + libextractor is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + libextractor is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libextractor; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ +/** + * @file thumbnailffmpeg_extractor.c + * @author Heikki Lindholm + * @author Christian Grothoff + * @brief this extractor produces a binary encoded + * thumbnail of images and videos using the ffmpeg libs. + * + * This is a thumbnail extractor using the ffmpeg libraries that will eventually + * support extracting thumbnails from both image and video files. + * + * Note that ffmpeg has a few issues: + * (1) there are no recent official releases of the ffmpeg libs + * (2) ffmpeg has a history of having security issues (parser is not robust) + * + * So this plugin cannot be recommended for system with high security + *requirements. + */ +#include "platform.h" +#include "extractor.h" +#include <magic.h> + +#if HAVE_LIBAVUTIL_AVUTIL_H +#include <libavutil/avutil.h> +#elif HAVE_FFMPEG_AVUTIL_H +#include <ffmpeg/avutil.h> +#endif +#if HAVE_LIBAVFORMAT_AVFORMAT_H +#include <libavformat/avformat.h> +#elif HAVE_FFMPEG_AVFORMAT_H +#include <ffmpeg/avformat.h> +#endif +#if HAVE_LIBAVCODEC_AVCODEC_H +#include <libavcodec/avcodec.h> +#elif HAVE_FFMPEG_AVCODEC_H +#include <ffmpeg/avcodec.h> +#endif +#if HAVE_LIBSWSCALE_SWSCALE_H +#include <libswscale/swscale.h> +#elif HAVE_FFMPEG_SWSCALE_H +#include <ffmpeg/swscale.h> +#endif + +/** + * Set to 1 to enable debug output. + */ +#define DEBUG 1 + +/** + * max dimension in pixels for the thumbnail. + */ +#define MAX_THUMB_DIMENSION 128 + +/** + * Maximum size in bytes for the thumbnail. + */ +#define MAX_THUMB_BYTES (100*1024) + +/** + * Global handle to MAGIC data. + */ +static magic_t magic; + + +/** + * Read callback. + * + * @param opaque the 'struct EXTRACTOR_ExtractContext' + * @param buf where to write data + * @param buf_size how many bytes to read + * @return -1 on error (or for unknown file size) + */ +static int +read_cb (void *opaque, + uint8_t *buf, + int buf_size) +{ + struct EXTRACTOR_ExtractContext *ec = opaque; + void *data; + ssize_t ret; + + ret = ec->read (ec->cls, &data, buf_size); + if (ret <= 0) + return ret; + memcpy (buf, data, ret); + return ret; +} + + +/** + * Seek callback. + * + * @param opaque the 'struct EXTRACTOR_ExtractContext' + * @param offset where to seek + * @param whence how to seek; AVSEEK_SIZE to return file size without seeking + * @return -1 on error (or for unknown file size) + */ +static int64_t +seek_cb (void *opaque, + int64_t offset, + int whence) +{ + struct EXTRACTOR_ExtractContext *ec = opaque; + + if (AVSEEK_SIZE == whence) + return ec->get_size (ec->cls); + return ec->seek (ec->cls, offset, whence); +} + + +/** + * Rescale and encode a PNG thumbnail. + * + * @param src_width source image width + * @param src_height source image height + * @param src_stride + * @param src_pixfmt + * @param src_data source data + * @param dst_width desired thumbnail width + * @param dst_height desired thumbnail height + * @param output_data where to store the resulting PNG data + * @param output_max_size maximum size of result that is allowed + * @return the number of bytes used, 0 on error + */ +static size_t +create_thumbnail (int src_width, int src_height, + int src_stride[], + enum PixelFormat src_pixfmt, + const uint8_t * const src_data[], + int dst_width, int dst_height, + uint8_t **output_data, + size_t output_max_size) +{ + AVCodecContext *encoder_codec_ctx; + AVDictionary *opts; + AVCodec *encoder_codec; + struct SwsContext *scaler_ctx; + AVFrame *dst_frame; + uint8_t *dst_buffer; + uint8_t *encoder_output_buffer; + size_t encoder_output_buffer_size; + int err; + + if (NULL == (encoder_codec = avcodec_find_encoder_by_name ("png"))) + { +#if DEBUG + fprintf (stderr, + "Couldn't find a PNG encoder\n"); +#endif + return 0; + } + + /* NOTE: the scaler will be used even if the src and dst image dimensions + * match, because the scaler will also perform colour space conversion */ + if (NULL == + (scaler_ctx = + sws_getContext (src_width, src_height, src_pixfmt, + dst_width, dst_height, PIX_FMT_RGB24, + SWS_BILINEAR, NULL, NULL, NULL))) + { +#if DEBUG + fprintf (stderr, + "Failed to get a scaler context\n"); +#endif + return 0; + } + + if (NULL == (dst_frame = avcodec_alloc_frame ())) + { +#if DEBUG + fprintf (stderr, + "Failed to allocate the destination image frame\n"); +#endif + sws_freeContext (scaler_ctx); + return 0; + } + if (NULL == (dst_buffer = + av_malloc (avpicture_get_size (PIX_FMT_RGB24, dst_width, dst_height)))) + { +#if DEBUG + fprintf (stderr, + "Failed to allocate the destination image buffer\n"); +#endif + av_free (dst_frame); + sws_freeContext (scaler_ctx); + return 0; + } + avpicture_fill ((AVPicture *) dst_frame, dst_buffer, + PIX_FMT_RGB24, dst_width, dst_height); + sws_scale (scaler_ctx, + src_data, + src_stride, + 0, src_height, + dst_frame->data, + dst_frame->linesize); + + encoder_output_buffer_size = output_max_size; + if (NULL == (encoder_output_buffer = av_malloc (encoder_output_buffer_size))) + { +#if DEBUG + fprintf (stderr, + "Failed to allocate the encoder output buffer\n"); +#endif + av_free (dst_buffer); + av_free (dst_frame); + sws_freeContext (scaler_ctx); + return 0; + } + + if (NULL == (encoder_codec_ctx = avcodec_alloc_context3 (encoder_codec))) + { +#if DEBUG + fprintf (stderr, + "Failed to allocate the encoder codec context\n"); +#endif + av_free (encoder_output_buffer); + av_free (dst_buffer); + av_free (dst_frame); + sws_freeContext (scaler_ctx); + return 0; + } + encoder_codec_ctx->width = dst_width; + encoder_codec_ctx->height = dst_height; + encoder_codec_ctx->pix_fmt = PIX_FMT_RGB24; + opts = NULL; + if (avcodec_open2 (encoder_codec_ctx, encoder_codec, &opts) < 0) + { +#if DEBUG + fprintf (stderr, + "Failed to open the encoder\n"); +#endif + av_free (encoder_codec_ctx); + av_free (encoder_output_buffer); + av_free (dst_buffer); + av_free (dst_frame); + sws_freeContext (scaler_ctx); + return 0; + } + err = avcodec_encode_video (encoder_codec_ctx, + encoder_output_buffer, + encoder_output_buffer_size, dst_frame); + av_dict_free (&opts); + avcodec_close (encoder_codec_ctx); + av_free (encoder_codec_ctx); + av_free (dst_buffer); + av_free (dst_frame); + sws_freeContext (scaler_ctx); + *output_data = encoder_output_buffer; + + return err < 0 ? 0 : err; +} + + + +/** + * calculate the thumbnail dimensions, taking pixel aspect into account + * + * @param src_width source image width + * @param src_height source image height + * @param src_sar_num + * @param src_sar_den + * @param dst_width desired thumbnail width (set) + * @param dst_height desired thumbnail height (set) + */ +static void +calculate_thumbnail_dimensions (int src_width, + int src_height, + int src_sar_num, + int src_sar_den, + int *dst_width, + int *dst_height) +{ + if ( (src_sar_num <= 0) || (src_sar_den <= 0) ) + { + src_sar_num = 1; + src_sar_den = 1; + } + if ((src_width * src_sar_num) / src_sar_den > src_height) + { + *dst_width = MAX_THUMB_DIMENSION; + *dst_height = (*dst_width * src_height) / + ((src_width * src_sar_num) / src_sar_den); + } + else + { + *dst_height = MAX_THUMB_DIMENSION; + *dst_width = (*dst_height * + ((src_width * src_sar_num) / src_sar_den)) / + src_height; + } + if (*dst_width < 8) + *dst_width = 8; + if (*dst_height < 1) + *dst_height = 1; +#if DEBUG + fprintf (stderr, + "Thumbnail dimensions: %d %d\n", + *dst_width, *dst_height); +#endif +} + + +/** + * Perform thumbnailing when the input is an image. + * + * @param image_codec_id ffmpeg codec for the image format + * @param ec extraction context to use + */ +static void +extract_image (enum CodecID image_codec_id, + struct EXTRACTOR_ExtractContext *ec) +{ + AVDictionary *opts; + AVCodecContext *codec_ctx; + AVCodec *codec; + AVPacket avpkt; + AVFrame *frame; + uint8_t *encoded_thumbnail; + int thumb_width; + int thumb_height; + int err; + int frame_finished; + ssize_t iret; + void *data; + + if (NULL == (codec = avcodec_find_decoder (image_codec_id))) + { +#if DEBUG + fprintf (stderr, + "No suitable codec found\n"); +#endif + return; + } + if (NULL == (codec_ctx = avcodec_alloc_context3 (codec))) + { +#if DEBUG + fprintf (stderr, + "Failed to allocate codec context\n"); +#endif + return; + } + opts = NULL; + if (0 != avcodec_open2 (codec_ctx, codec, &opts)) + { +#if DEBUG + fprintf (stderr, + "Failed to open image codec\n"); +#endif + av_free (codec_ctx); + return; + } + av_dict_free (&opts); + if (NULL == (frame = avcodec_alloc_frame ())) + { +#if DEBUG + fprintf (stderr, + "Failed to allocate frame\n"); +#endif + avcodec_close (codec_ctx); + av_free (codec_ctx); + return; + } + + frame_finished = 0; + while (! frame_finished) + { + if (0 >= (iret = ec->read (ec->cls, + &data, + 32 * 1024))) + break; + av_init_packet (&avpkt); + avpkt.data = data; + avpkt.size = iret; + avcodec_decode_video2 (codec_ctx, frame, &frame_finished, &avpkt); + } + if (! frame_finished) + { +#if DEBUG + fprintf (stderr, + "Failed to decode a complete frame\n"); +#endif + av_free (frame); + avcodec_close (codec_ctx); + av_free (codec_ctx); + return; + } + calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height, + codec_ctx->sample_aspect_ratio.num, + codec_ctx->sample_aspect_ratio.den, + &thumb_width, &thumb_height); + + err = create_thumbnail (codec_ctx->width, codec_ctx->height, + frame->linesize, codec_ctx->pix_fmt, + (const uint8_t * const*) frame->data, + thumb_width, thumb_height, + &encoded_thumbnail, MAX_THUMB_BYTES); + if (err > 0) + { + ec->proc (ec->cls, + "thumbnailffmpeg", + EXTRACTOR_METATYPE_THUMBNAIL, + EXTRACTOR_METAFORMAT_BINARY, + "image/png", + (const char*) encoded_thumbnail, + err); + av_free (encoded_thumbnail); + } + av_free (frame); + avcodec_close (codec_ctx); + av_free (codec_ctx); +} + + +/** + * Perform thumbnailing when the input is a video + * + * @param ec extraction context to use + */ +static void +extract_video (struct EXTRACTOR_ExtractContext *ec) +{ + AVPacket packet; + AVIOContext *io_ctx; + struct AVFormatContext *format_ctx; + AVCodecContext *codec_ctx; + AVCodec *codec; + AVDictionary *options; + AVFrame *frame; + uint8_t *encoded_thumbnail; + int video_stream_index; + int thumb_width; + int thumb_height; + int i; + int err; + int frame_finished; + unsigned char *iob; + + if (NULL == (iob = av_malloc (16 * 1024))) + return; + if (NULL == (io_ctx = avio_alloc_context (iob, 16 * 1024, + 0, ec, + &read_cb, + NULL /* no writing */, + &seek_cb))) + { + av_free (iob); + return; + } + if (NULL == (format_ctx = avformat_alloc_context ())) + { + av_free (io_ctx); + return; + } + format_ctx->pb = io_ctx; + options = NULL; + if (0 != avformat_open_input (&format_ctx, "<no file>", NULL, &options)) + return; + av_dict_free (&options); + options = NULL; + if (0 > avformat_find_stream_info (format_ctx, &options)) + { + #if DEBUG + fprintf (stderr, + "Failed to read stream info\n"); +#endif + avformat_close_input (&format_ctx); + return; + } + av_dict_free (&options); + + codec = NULL; + codec_ctx = NULL; + video_stream_index = -1; + for (i=0; i<format_ctx->nb_streams; i++) + { + codec_ctx = format_ctx->streams[i]->codec; + if (AVMEDIA_TYPE_VIDEO != codec_ctx->codec_type) + continue; + if (NULL == (codec = avcodec_find_decoder (codec_ctx->codec_id))) + continue; + options = NULL; + if (0 != (err = avcodec_open2 (codec_ctx, codec, &options))) + { + codec = NULL; + continue; + } + av_dict_free (&options); + video_stream_index = i; + break; + } + if ( (-1 == video_stream_index) || + (0 == codec_ctx->width) || + (0 == codec_ctx->height) ) + { +#if DEBUG + fprintf (stderr, + "No video streams or no suitable codec found\n"); +#endif + if (NULL != codec) + avcodec_close (codec_ctx); + avformat_close_input (&format_ctx); + return; + } + + if (NULL == (frame = avcodec_alloc_frame ())) + { +#if DEBUG + fprintf (stderr, + "Failed to allocate frame\n"); +#endif + avcodec_close (codec_ctx); + avformat_close_input (&format_ctx); + return; + } +#if DEBUG + if (format_ctx->duration == AV_NOPTS_VALUE) + fprintf (stderr, + "Duration unknown\n"); + else + fprintf (stderr, + "Duration: %lld\n", + format_ctx->duration); +#endif + /* TODO: if duration is known, seek to some better place, + * but use 10 sec into stream for now */ + err = av_seek_frame (format_ctx, -1, 10 * AV_TIME_BASE, 0); + if (err >= 0) + avcodec_flush_buffers (codec_ctx); + frame_finished = 0; + + while (1) + { + err = av_read_frame (format_ctx, &packet); + if (err < 0) + break; + if (packet.stream_index == video_stream_index) + { + avcodec_decode_video2 (codec_ctx, + frame, + &frame_finished, + &packet); + if (frame_finished && frame->key_frame) + { + av_free_packet (&packet); + break; + } + } + av_free_packet (&packet); + } + + if (! frame_finished) + { + fprintf (stderr, + "Failed to decode a complete frame\n"); + av_free (frame); + avcodec_close (codec_ctx); + avformat_close_input (&format_ctx); + return; + } + calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height, + codec_ctx->sample_aspect_ratio.num, + codec_ctx->sample_aspect_ratio.den, + &thumb_width, &thumb_height); + err = create_thumbnail (codec_ctx->width, codec_ctx->height, + frame->linesize, codec_ctx->pix_fmt, + (const uint8_t* const *) frame->data, + thumb_width, thumb_height, + &encoded_thumbnail, MAX_THUMB_BYTES); + if (err > 0) + { + ec->proc (ec->cls, + "thumbnailffmpeg", + EXTRACTOR_METATYPE_THUMBNAIL, + EXTRACTOR_METAFORMAT_BINARY, + "image/png", + (const char*) encoded_thumbnail, + err); + av_free (encoded_thumbnail); + } + av_free (frame); + avcodec_close (codec_ctx); + avformat_close_input (&format_ctx); +} + + +/** + * Pair of mime type and ffmpeg decoder ID. + */ +struct MIMEToDecoderMapping +{ + /** + * String for a mime type. + */ + const char *mime_type; + + /** + * Corresponding ffmpeg decoder ID. + */ + enum CodecID codec_id; +}; + + +/** + * map MIME image types to an ffmpeg decoder + */ +static const struct MIMEToDecoderMapping m2d_map[] = + { + { "image/x-bmp", CODEC_ID_BMP }, + { "image/gif", CODEC_ID_GIF }, + { "image/jpeg", CODEC_ID_MJPEG }, + { "image/png", CODEC_ID_PNG }, + { "image/x-png", CODEC_ID_PNG }, + { "image/x-portable-pixmap", CODEC_ID_PPM }, + { NULL, CODEC_ID_NONE } + }; + + +/** + * Main method for the ffmpeg-thumbnailer plugin. + * + * @param ec extraction context + */ +void +EXTRACTOR_thumbnailffmpeg_extract_method (struct EXTRACTOR_ExtractContext *ec) +{ + unsigned int i; + ssize_t iret; + void *data; + const char *mime; + + if (-1 == (iret = ec->read (ec->cls, + &data, + 16 * 1024))) + return; + if (NULL == (mime = magic_buffer (magic, data, iret))) + return; + if (0 != ec->seek (ec->cls, 0, SEEK_SET)) + return; + for (i = 0; NULL != m2d_map[i].mime_type; i++) + if (0 == strcmp (m2d_map[i].mime_type, mime)) + { + extract_image (m2d_map[i].codec_id, ec); + return; + } + extract_video (ec); +} + + +/** + * This plugin sometimes is installed under the alias 'thumbnail'. + * So we need to provide a second entry method. + * + * @param ec extraction context + */ +void +EXTRACTOR_thumbnail_extract_method (struct EXTRACTOR_ExtractContext *ec) +{ + EXTRACTOR_thumbnailffmpeg_extract_method (ec); +} + + +/** + * Log callback. Does nothing. + * + * @param ptr NULL + * @param level log level + * @param format format string + * @param ap arguments for format + */ +static void +thumbnailffmpeg_av_log_callback (void* ptr, + int level, + const char *format, + va_list ap) +{ +#if DEBUG + vfprintf(stderr, format, ap); +#endif +} + + +/** + * Initialize av-libs and load magic file. + */ +void __attribute__ ((constructor)) +thumbnailffmpeg_lib_init (void) +{ + av_log_set_callback (&thumbnailffmpeg_av_log_callback); + av_register_all (); + magic = magic_open (MAGIC_MIME_TYPE); + if (0 != magic_load (magic, NULL)) + { + /* FIXME: how to deal with errors? */ + } +} + +/** + * Destructor for the library, cleans up. + */ +void __attribute__ ((destructor)) +thumbnailffmpeg_ltdl_fini () +{ + if (NULL != magic) + { + magic_close (magic); + magic = NULL; + } +} + + +/* end of thumbnailffmpeg_extractor.c */ diff --git a/src/plugins/thumbnailgtk_extractor.c b/src/plugins/thumbnailgtk_extractor.c @@ -52,7 +52,7 @@ static magic_t magic; /** - * Main method for the thumbnailer plugin. + * Main method for the gtk-thumbnailer plugin. * * @param ec extraction context */ @@ -209,7 +209,7 @@ EXTRACTOR_thumbnail_extract_method (struct EXTRACTOR_ExtractContext *ec) * Initialize glib and load magic file. */ void __attribute__ ((constructor)) -ole_gobject_init () +thumbnailgtk_gobject_init () { g_type_init (); magic = magic_open (MAGIC_MIME_TYPE); @@ -224,7 +224,7 @@ ole_gobject_init () * Destructor for the library, cleans up. */ void __attribute__ ((destructor)) -mime_ltdl_fini () +thumbnailgtk_ltdl_fini () { if (NULL != magic) {