aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/thumbnailffmpeg_extractor.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/thumbnailffmpeg_extractor.c')
-rw-r--r--src/plugins/thumbnailffmpeg_extractor.c729
1 files changed, 729 insertions, 0 deletions
diff --git a/src/plugins/thumbnailffmpeg_extractor.c b/src/plugins/thumbnailffmpeg_extractor.c
new file mode 100644
index 0000000..0bb10bf
--- /dev/null
+++ b/src/plugins/thumbnailffmpeg_extractor.c
@@ -0,0 +1,729 @@
1/*
2 This file is part of libextractor.
3 Copyright (C) 2008, 2012 Heikki Lindholm and Christian Grothoff
4
5 libextractor is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
9
10 libextractor is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with libextractor; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
19 */
20/**
21 * @file thumbnailffmpeg_extractor.c
22 * @author Heikki Lindholm
23 * @author Christian Grothoff
24 * @brief this extractor produces a binary encoded
25 * thumbnail of images and videos using the ffmpeg libs.
26 *
27 * This is a thumbnail extractor using the ffmpeg libraries that will eventually
28 * support extracting thumbnails from both image and video files.
29 *
30 * Note that ffmpeg has a few issues:
31 * (1) there are no recent official releases of the ffmpeg libs
32 * (2) ffmpeg has a history of having security issues (parser is not robust)
33 *
34 * So this plugin cannot be recommended for system with high security
35 *requirements.
36 */
37#include "platform.h"
38#include "extractor.h"
39#include <magic.h>
40
41#if HAVE_LIBAVUTIL_AVUTIL_H
42#include <libavutil/avutil.h>
43#elif HAVE_FFMPEG_AVUTIL_H
44#include <ffmpeg/avutil.h>
45#endif
46#if HAVE_LIBAVFORMAT_AVFORMAT_H
47#include <libavformat/avformat.h>
48#elif HAVE_FFMPEG_AVFORMAT_H
49#include <ffmpeg/avformat.h>
50#endif
51#if HAVE_LIBAVCODEC_AVCODEC_H
52#include <libavcodec/avcodec.h>
53#elif HAVE_FFMPEG_AVCODEC_H
54#include <ffmpeg/avcodec.h>
55#endif
56#if HAVE_LIBSWSCALE_SWSCALE_H
57#include <libswscale/swscale.h>
58#elif HAVE_FFMPEG_SWSCALE_H
59#include <ffmpeg/swscale.h>
60#endif
61
62/**
63 * Set to 1 to enable debug output.
64 */
65#define DEBUG 1
66
67/**
68 * max dimension in pixels for the thumbnail.
69 */
70#define MAX_THUMB_DIMENSION 128
71
72/**
73 * Maximum size in bytes for the thumbnail.
74 */
75#define MAX_THUMB_BYTES (100*1024)
76
77/**
78 * Global handle to MAGIC data.
79 */
80static magic_t magic;
81
82
83/**
84 * Read callback.
85 *
86 * @param opaque the 'struct EXTRACTOR_ExtractContext'
87 * @param buf where to write data
88 * @param buf_size how many bytes to read
89 * @return -1 on error (or for unknown file size)
90 */
91static int
92read_cb (void *opaque,
93 uint8_t *buf,
94 int buf_size)
95{
96 struct EXTRACTOR_ExtractContext *ec = opaque;
97 void *data;
98 ssize_t ret;
99
100 ret = ec->read (ec->cls, &data, buf_size);
101 if (ret <= 0)
102 return ret;
103 memcpy (buf, data, ret);
104 return ret;
105}
106
107
108/**
109 * Seek callback.
110 *
111 * @param opaque the 'struct EXTRACTOR_ExtractContext'
112 * @param offset where to seek
113 * @param whence how to seek; AVSEEK_SIZE to return file size without seeking
114 * @return -1 on error (or for unknown file size)
115 */
116static int64_t
117seek_cb (void *opaque,
118 int64_t offset,
119 int whence)
120{
121 struct EXTRACTOR_ExtractContext *ec = opaque;
122
123 if (AVSEEK_SIZE == whence)
124 return ec->get_size (ec->cls);
125 return ec->seek (ec->cls, offset, whence);
126}
127
128
129/**
130 * Rescale and encode a PNG thumbnail.
131 *
132 * @param src_width source image width
133 * @param src_height source image height
134 * @param src_stride
135 * @param src_pixfmt
136 * @param src_data source data
137 * @param dst_width desired thumbnail width
138 * @param dst_height desired thumbnail height
139 * @param output_data where to store the resulting PNG data
140 * @param output_max_size maximum size of result that is allowed
141 * @return the number of bytes used, 0 on error
142 */
143static size_t
144create_thumbnail (int src_width, int src_height,
145 int src_stride[],
146 enum PixelFormat src_pixfmt,
147 const uint8_t * const src_data[],
148 int dst_width, int dst_height,
149 uint8_t **output_data,
150 size_t output_max_size)
151{
152 AVCodecContext *encoder_codec_ctx;
153 AVDictionary *opts;
154 AVCodec *encoder_codec;
155 struct SwsContext *scaler_ctx;
156 AVFrame *dst_frame;
157 uint8_t *dst_buffer;
158 uint8_t *encoder_output_buffer;
159 size_t encoder_output_buffer_size;
160 int err;
161
162 if (NULL == (encoder_codec = avcodec_find_encoder_by_name ("png")))
163 {
164#if DEBUG
165 fprintf (stderr,
166 "Couldn't find a PNG encoder\n");
167#endif
168 return 0;
169 }
170
171 /* NOTE: the scaler will be used even if the src and dst image dimensions
172 * match, because the scaler will also perform colour space conversion */
173 if (NULL ==
174 (scaler_ctx =
175 sws_getContext (src_width, src_height, src_pixfmt,
176 dst_width, dst_height, PIX_FMT_RGB24,
177 SWS_BILINEAR, NULL, NULL, NULL)))
178 {
179#if DEBUG
180 fprintf (stderr,
181 "Failed to get a scaler context\n");
182#endif
183 return 0;
184 }
185
186 if (NULL == (dst_frame = avcodec_alloc_frame ()))
187 {
188#if DEBUG
189 fprintf (stderr,
190 "Failed to allocate the destination image frame\n");
191#endif
192 sws_freeContext (scaler_ctx);
193 return 0;
194 }
195 if (NULL == (dst_buffer =
196 av_malloc (avpicture_get_size (PIX_FMT_RGB24, dst_width, dst_height))))
197 {
198#if DEBUG
199 fprintf (stderr,
200 "Failed to allocate the destination image buffer\n");
201#endif
202 av_free (dst_frame);
203 sws_freeContext (scaler_ctx);
204 return 0;
205 }
206 avpicture_fill ((AVPicture *) dst_frame, dst_buffer,
207 PIX_FMT_RGB24, dst_width, dst_height);
208 sws_scale (scaler_ctx,
209 src_data,
210 src_stride,
211 0, src_height,
212 dst_frame->data,
213 dst_frame->linesize);
214
215 encoder_output_buffer_size = output_max_size;
216 if (NULL == (encoder_output_buffer = av_malloc (encoder_output_buffer_size)))
217 {
218#if DEBUG
219 fprintf (stderr,
220 "Failed to allocate the encoder output buffer\n");
221#endif
222 av_free (dst_buffer);
223 av_free (dst_frame);
224 sws_freeContext (scaler_ctx);
225 return 0;
226 }
227
228 if (NULL == (encoder_codec_ctx = avcodec_alloc_context3 (encoder_codec)))
229 {
230#if DEBUG
231 fprintf (stderr,
232 "Failed to allocate the encoder codec context\n");
233#endif
234 av_free (encoder_output_buffer);
235 av_free (dst_buffer);
236 av_free (dst_frame);
237 sws_freeContext (scaler_ctx);
238 return 0;
239 }
240 encoder_codec_ctx->width = dst_width;
241 encoder_codec_ctx->height = dst_height;
242 encoder_codec_ctx->pix_fmt = PIX_FMT_RGB24;
243 opts = NULL;
244 if (avcodec_open2 (encoder_codec_ctx, encoder_codec, &opts) < 0)
245 {
246#if DEBUG
247 fprintf (stderr,
248 "Failed to open the encoder\n");
249#endif
250 av_free (encoder_codec_ctx);
251 av_free (encoder_output_buffer);
252 av_free (dst_buffer);
253 av_free (dst_frame);
254 sws_freeContext (scaler_ctx);
255 return 0;
256 }
257 err = avcodec_encode_video (encoder_codec_ctx,
258 encoder_output_buffer,
259 encoder_output_buffer_size, dst_frame);
260 av_dict_free (&opts);
261 avcodec_close (encoder_codec_ctx);
262 av_free (encoder_codec_ctx);
263 av_free (dst_buffer);
264 av_free (dst_frame);
265 sws_freeContext (scaler_ctx);
266 *output_data = encoder_output_buffer;
267
268 return err < 0 ? 0 : err;
269}
270
271
272
273/**
274 * calculate the thumbnail dimensions, taking pixel aspect into account
275 *
276 * @param src_width source image width
277 * @param src_height source image height
278 * @param src_sar_num
279 * @param src_sar_den
280 * @param dst_width desired thumbnail width (set)
281 * @param dst_height desired thumbnail height (set)
282 */
283static void
284calculate_thumbnail_dimensions (int src_width,
285 int src_height,
286 int src_sar_num,
287 int src_sar_den,
288 int *dst_width,
289 int *dst_height)
290{
291 if ( (src_sar_num <= 0) || (src_sar_den <= 0) )
292 {
293 src_sar_num = 1;
294 src_sar_den = 1;
295 }
296 if ((src_width * src_sar_num) / src_sar_den > src_height)
297 {
298 *dst_width = MAX_THUMB_DIMENSION;
299 *dst_height = (*dst_width * src_height) /
300 ((src_width * src_sar_num) / src_sar_den);
301 }
302 else
303 {
304 *dst_height = MAX_THUMB_DIMENSION;
305 *dst_width = (*dst_height *
306 ((src_width * src_sar_num) / src_sar_den)) /
307 src_height;
308 }
309 if (*dst_width < 8)
310 *dst_width = 8;
311 if (*dst_height < 1)
312 *dst_height = 1;
313#if DEBUG
314 fprintf (stderr,
315 "Thumbnail dimensions: %d %d\n",
316 *dst_width, *dst_height);
317#endif
318}
319
320
321/**
322 * Perform thumbnailing when the input is an image.
323 *
324 * @param image_codec_id ffmpeg codec for the image format
325 * @param ec extraction context to use
326 */
327static void
328extract_image (enum CodecID image_codec_id,
329 struct EXTRACTOR_ExtractContext *ec)
330{
331 AVDictionary *opts;
332 AVCodecContext *codec_ctx;
333 AVCodec *codec;
334 AVPacket avpkt;
335 AVFrame *frame;
336 uint8_t *encoded_thumbnail;
337 int thumb_width;
338 int thumb_height;
339 int err;
340 int frame_finished;
341 ssize_t iret;
342 void *data;
343
344 if (NULL == (codec = avcodec_find_decoder (image_codec_id)))
345 {
346#if DEBUG
347 fprintf (stderr,
348 "No suitable codec found\n");
349#endif
350 return;
351 }
352 if (NULL == (codec_ctx = avcodec_alloc_context3 (codec)))
353 {
354#if DEBUG
355 fprintf (stderr,
356 "Failed to allocate codec context\n");
357#endif
358 return;
359 }
360 opts = NULL;
361 if (0 != avcodec_open2 (codec_ctx, codec, &opts))
362 {
363#if DEBUG
364 fprintf (stderr,
365 "Failed to open image codec\n");
366#endif
367 av_free (codec_ctx);
368 return;
369 }
370 av_dict_free (&opts);
371 if (NULL == (frame = avcodec_alloc_frame ()))
372 {
373#if DEBUG
374 fprintf (stderr,
375 "Failed to allocate frame\n");
376#endif
377 avcodec_close (codec_ctx);
378 av_free (codec_ctx);
379 return;
380 }
381
382 frame_finished = 0;
383 while (! frame_finished)
384 {
385 if (0 >= (iret = ec->read (ec->cls,
386 &data,
387 32 * 1024)))
388 break;
389 av_init_packet (&avpkt);
390 avpkt.data = data;
391 avpkt.size = iret;
392 avcodec_decode_video2 (codec_ctx, frame, &frame_finished, &avpkt);
393 }
394 if (! frame_finished)
395 {
396#if DEBUG
397 fprintf (stderr,
398 "Failed to decode a complete frame\n");
399#endif
400 av_free (frame);
401 avcodec_close (codec_ctx);
402 av_free (codec_ctx);
403 return;
404 }
405 calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height,
406 codec_ctx->sample_aspect_ratio.num,
407 codec_ctx->sample_aspect_ratio.den,
408 &thumb_width, &thumb_height);
409
410 err = create_thumbnail (codec_ctx->width, codec_ctx->height,
411 frame->linesize, codec_ctx->pix_fmt,
412 (const uint8_t * const*) frame->data,
413 thumb_width, thumb_height,
414 &encoded_thumbnail, MAX_THUMB_BYTES);
415 if (err > 0)
416 {
417 ec->proc (ec->cls,
418 "thumbnailffmpeg",
419 EXTRACTOR_METATYPE_THUMBNAIL,
420 EXTRACTOR_METAFORMAT_BINARY,
421 "image/png",
422 (const char*) encoded_thumbnail,
423 err);
424 av_free (encoded_thumbnail);
425 }
426 av_free (frame);
427 avcodec_close (codec_ctx);
428 av_free (codec_ctx);
429}
430
431
432/**
433 * Perform thumbnailing when the input is a video
434 *
435 * @param ec extraction context to use
436 */
437static void
438extract_video (struct EXTRACTOR_ExtractContext *ec)
439{
440 AVPacket packet;
441 AVIOContext *io_ctx;
442 struct AVFormatContext *format_ctx;
443 AVCodecContext *codec_ctx;
444 AVCodec *codec;
445 AVDictionary *options;
446 AVFrame *frame;
447 uint8_t *encoded_thumbnail;
448 int video_stream_index;
449 int thumb_width;
450 int thumb_height;
451 int i;
452 int err;
453 int frame_finished;
454 unsigned char *iob;
455
456 if (NULL == (iob = av_malloc (16 * 1024)))
457 return;
458 if (NULL == (io_ctx = avio_alloc_context (iob, 16 * 1024,
459 0, ec,
460 &read_cb,
461 NULL /* no writing */,
462 &seek_cb)))
463 {
464 av_free (iob);
465 return;
466 }
467 if (NULL == (format_ctx = avformat_alloc_context ()))
468 {
469 av_free (io_ctx);
470 return;
471 }
472 format_ctx->pb = io_ctx;
473 options = NULL;
474 if (0 != avformat_open_input (&format_ctx, "<no file>", NULL, &options))
475 return;
476 av_dict_free (&options);
477 options = NULL;
478 if (0 > avformat_find_stream_info (format_ctx, &options))
479 {
480 #if DEBUG
481 fprintf (stderr,
482 "Failed to read stream info\n");
483#endif
484 avformat_close_input (&format_ctx);
485 return;
486 }
487 av_dict_free (&options);
488
489 codec = NULL;
490 codec_ctx = NULL;
491 video_stream_index = -1;
492 for (i=0; i<format_ctx->nb_streams; i++)
493 {
494 codec_ctx = format_ctx->streams[i]->codec;
495 if (AVMEDIA_TYPE_VIDEO != codec_ctx->codec_type)
496 continue;
497 if (NULL == (codec = avcodec_find_decoder (codec_ctx->codec_id)))
498 continue;
499 options = NULL;
500 if (0 != (err = avcodec_open2 (codec_ctx, codec, &options)))
501 {
502 codec = NULL;
503 continue;
504 }
505 av_dict_free (&options);
506 video_stream_index = i;
507 break;
508 }
509 if ( (-1 == video_stream_index) ||
510 (0 == codec_ctx->width) ||
511 (0 == codec_ctx->height) )
512 {
513#if DEBUG
514 fprintf (stderr,
515 "No video streams or no suitable codec found\n");
516#endif
517 if (NULL != codec)
518 avcodec_close (codec_ctx);
519 avformat_close_input (&format_ctx);
520 return;
521 }
522
523 if (NULL == (frame = avcodec_alloc_frame ()))
524 {
525#if DEBUG
526 fprintf (stderr,
527 "Failed to allocate frame\n");
528#endif
529 avcodec_close (codec_ctx);
530 avformat_close_input (&format_ctx);
531 return;
532 }
533#if DEBUG
534 if (format_ctx->duration == AV_NOPTS_VALUE)
535 fprintf (stderr,
536 "Duration unknown\n");
537 else
538 fprintf (stderr,
539 "Duration: %lld\n",
540 format_ctx->duration);
541#endif
542 /* TODO: if duration is known, seek to some better place,
543 * but use 10 sec into stream for now */
544 err = av_seek_frame (format_ctx, -1, 10 * AV_TIME_BASE, 0);
545 if (err >= 0)
546 avcodec_flush_buffers (codec_ctx);
547 frame_finished = 0;
548
549 while (1)
550 {
551 err = av_read_frame (format_ctx, &packet);
552 if (err < 0)
553 break;
554 if (packet.stream_index == video_stream_index)
555 {
556 avcodec_decode_video2 (codec_ctx,
557 frame,
558 &frame_finished,
559 &packet);
560 if (frame_finished && frame->key_frame)
561 {
562 av_free_packet (&packet);
563 break;
564 }
565 }
566 av_free_packet (&packet);
567 }
568
569 if (! frame_finished)
570 {
571 fprintf (stderr,
572 "Failed to decode a complete frame\n");
573 av_free (frame);
574 avcodec_close (codec_ctx);
575 avformat_close_input (&format_ctx);
576 return;
577 }
578 calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height,
579 codec_ctx->sample_aspect_ratio.num,
580 codec_ctx->sample_aspect_ratio.den,
581 &thumb_width, &thumb_height);
582 err = create_thumbnail (codec_ctx->width, codec_ctx->height,
583 frame->linesize, codec_ctx->pix_fmt,
584 (const uint8_t* const *) frame->data,
585 thumb_width, thumb_height,
586 &encoded_thumbnail, MAX_THUMB_BYTES);
587 if (err > 0)
588 {
589 ec->proc (ec->cls,
590 "thumbnailffmpeg",
591 EXTRACTOR_METATYPE_THUMBNAIL,
592 EXTRACTOR_METAFORMAT_BINARY,
593 "image/png",
594 (const char*) encoded_thumbnail,
595 err);
596 av_free (encoded_thumbnail);
597 }
598 av_free (frame);
599 avcodec_close (codec_ctx);
600 avformat_close_input (&format_ctx);
601}
602
603
604/**
605 * Pair of mime type and ffmpeg decoder ID.
606 */
607struct MIMEToDecoderMapping
608{
609 /**
610 * String for a mime type.
611 */
612 const char *mime_type;
613
614 /**
615 * Corresponding ffmpeg decoder ID.
616 */
617 enum CodecID codec_id;
618};
619
620
621/**
622 * map MIME image types to an ffmpeg decoder
623 */
624static const struct MIMEToDecoderMapping m2d_map[] =
625 {
626 { "image/x-bmp", CODEC_ID_BMP },
627 { "image/gif", CODEC_ID_GIF },
628 { "image/jpeg", CODEC_ID_MJPEG },
629 { "image/png", CODEC_ID_PNG },
630 { "image/x-png", CODEC_ID_PNG },
631 { "image/x-portable-pixmap", CODEC_ID_PPM },
632 { NULL, CODEC_ID_NONE }
633 };
634
635
636/**
637 * Main method for the ffmpeg-thumbnailer plugin.
638 *
639 * @param ec extraction context
640 */
641void
642EXTRACTOR_thumbnailffmpeg_extract_method (struct EXTRACTOR_ExtractContext *ec)
643{
644 unsigned int i;
645 ssize_t iret;
646 void *data;
647 const char *mime;
648
649 if (-1 == (iret = ec->read (ec->cls,
650 &data,
651 16 * 1024)))
652 return;
653 if (NULL == (mime = magic_buffer (magic, data, iret)))
654 return;
655 if (0 != ec->seek (ec->cls, 0, SEEK_SET))
656 return;
657 for (i = 0; NULL != m2d_map[i].mime_type; i++)
658 if (0 == strcmp (m2d_map[i].mime_type, mime))
659 {
660 extract_image (m2d_map[i].codec_id, ec);
661 return;
662 }
663 extract_video (ec);
664}
665
666
667/**
668 * This plugin sometimes is installed under the alias 'thumbnail'.
669 * So we need to provide a second entry method.
670 *
671 * @param ec extraction context
672 */
673void
674EXTRACTOR_thumbnail_extract_method (struct EXTRACTOR_ExtractContext *ec)
675{
676 EXTRACTOR_thumbnailffmpeg_extract_method (ec);
677}
678
679
680/**
681 * Log callback. Does nothing.
682 *
683 * @param ptr NULL
684 * @param level log level
685 * @param format format string
686 * @param ap arguments for format
687 */
688static void
689thumbnailffmpeg_av_log_callback (void* ptr,
690 int level,
691 const char *format,
692 va_list ap)
693{
694#if DEBUG
695 vfprintf(stderr, format, ap);
696#endif
697}
698
699
700/**
701 * Initialize av-libs and load magic file.
702 */
703void __attribute__ ((constructor))
704thumbnailffmpeg_lib_init (void)
705{
706 av_log_set_callback (&thumbnailffmpeg_av_log_callback);
707 av_register_all ();
708 magic = magic_open (MAGIC_MIME_TYPE);
709 if (0 != magic_load (magic, NULL))
710 {
711 /* FIXME: how to deal with errors? */
712 }
713}
714
715/**
716 * Destructor for the library, cleans up.
717 */
718void __attribute__ ((destructor))
719thumbnailffmpeg_ltdl_fini ()
720{
721 if (NULL != magic)
722 {
723 magic_close (magic);
724 magic = NULL;
725 }
726}
727
728
729/* end of thumbnailffmpeg_extractor.c */