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.c536
1 files changed, 536 insertions, 0 deletions
diff --git a/src/plugins/thumbnailffmpeg_extractor.c b/src/plugins/thumbnailffmpeg_extractor.c
new file mode 100644
index 0000000..7fbde2d
--- /dev/null
+++ b/src/plugins/thumbnailffmpeg_extractor.c
@@ -0,0 +1,536 @@
1/*
2 This file is part of libextractor.
3 Copyright (C) 2008 Heikki Lindholm
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 2, 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/**
22 * @file thumbnailffmpeg_extractor.c
23 * @author Heikki Lindholm
24 * @brief this extractor produces a binary encoded
25 * thumbnail of images and videos using the ffmpeg libs.
26 */
27
28/* This is a thumbnail extractor using the ffmpeg libraries that will eventually
29 support extracting thumbnails from both image and video files.
30
31 Note that ffmpeg has a few issues:
32 (1) there are no recent official releases of the ffmpeg libs
33 (2) ffmpeg has a history of having security issues (parser is not robust)
34
35 So this plugin cannot be recommended for system with high security
36 requirements.
37*/
38
39#include "platform.h"
40#include "extractor.h"
41#include <libavformat/avformat.h>
42#include <libavcodec/avcodec.h>
43#include <libswscale/swscale.h>
44
45#define DEBUG 0
46
47struct StreamDescriptor
48{
49 const uint8_t *data;
50 size_t offset;
51 size_t size;
52};
53
54
55void __attribute__ ((constructor)) ffmpeg_lib_init (void)
56{
57#if DEBUG
58 printf ("av_register_all()\n");
59#endif
60 av_register_all ();
61}
62
63static int
64stream_read (void *opaque, uint8_t * buf, int buf_size)
65{
66 struct StreamDescriptor *rs = (struct StreamDescriptor *) opaque;
67 size_t len;
68#if DEBUG
69 printf ("read_packet: %zu\n", buf_size);
70#endif
71 if (rs)
72 {
73 if (rs->data == NULL)
74 return -1;
75 if (rs->offset >= rs->size)
76 return 0;
77 len = buf_size;
78 if (rs->offset + len > rs->size)
79 len = rs->size - rs->offset;
80
81 memcpy (buf, rs->data + rs->offset, len);
82 rs->offset += len;
83#if DEBUG
84 printf ("read_packet: len: %zu\n", len);
85#endif
86 return len;
87 }
88 return -1;
89}
90
91static offset_t
92stream_seek (void *opaque, offset_t offset, int whence)
93{
94 struct StreamDescriptor *rs = (struct StreamDescriptor *) opaque;
95 offset_t off_abs;
96#if DEBUG
97 printf ("my_seek: %lld %d\n", offset, whence);
98#endif
99 if (rs)
100 {
101 if (whence == AVSEEK_SIZE)
102 return (offset_t) rs->size;
103 else if (whence == SEEK_CUR)
104 off_abs = (offset_t) rs->offset + offset;
105 else if (whence == SEEK_SET)
106 off_abs = offset;
107 else if (whence == SEEK_END)
108 off_abs = (offset_t) rs->size + offset;
109 else
110 {
111 printf ("whence error %d\n", whence);
112 abort ();
113 return AVERROR (EINVAL);
114 }
115 if (off_abs >= 0 && off_abs < (offset_t) rs->size)
116 rs->offset = (size_t) off_abs;
117 else
118 off_abs = AVERROR (EINVAL);
119 return off_abs;
120 }
121 return -1;
122}
123
124static EXTRACTOR_KeywordList *
125addKeyword (EXTRACTOR_KeywordType type,
126 char *keyword, EXTRACTOR_KeywordList * next)
127{
128 EXTRACTOR_KeywordList *result;
129
130 if (keyword == NULL)
131 return next;
132 result = malloc (sizeof (EXTRACTOR_KeywordList));
133 result->next = next;
134 result->keyword = keyword;
135 result->keywordType = type;
136 return result;
137}
138
139
140struct MimeToDecoderMapping
141{
142 const char *mime_type;
143 enum CodecID codec_id;
144};
145
146/* map mime image types to a decoder */
147static const struct MimeToDecoderMapping m2d_map[] = {
148 {"image/x-bmp", CODEC_ID_BMP},
149 {"image/gif", CODEC_ID_GIF},
150 {"image/jpeg", CODEC_ID_MJPEG},
151 {"image/png", CODEC_ID_PNG},
152 {"image/x-portable-pixmap", CODEC_ID_PPM},
153 {NULL, CODEC_ID_NONE}
154};
155
156#define PROBE_MAX (1<<20)
157#define BIOBUF_SIZE (64*1024)
158#define THUMBSIZE 128 /* max dimension in pixels */
159#define MAX_THUMB_SIZE (100*1024) /* in bytes */
160
161int
162EXTRACTOR_thumbnailffmpeg_extract (const unsigned char *data,
163 size_t size,
164 EXTRACTOR_MetaDataProcessor proc,
165 void *proc_cls,
166 const char *options)
167{
168 int score;
169 AVInputFormat *fmt;
170 AVProbeData pdat;
171 ByteIOContext *bio_ctx = NULL;
172 uint8_t *bio_buffer;
173 struct StreamDescriptor reader_state;
174 AVFormatContext *format_ctx = NULL;
175 AVCodecContext *codec_ctx = NULL;
176 AVPacket packet;
177 int video_stream_index;
178 AVCodec *codec;
179 AVFrame *frame = NULL;
180 AVFrame *thumb_frame = NULL;
181 int64_t ts;
182
183 struct SwsContext *scaler_ctx;
184 int sws_flags = SWS_BILINEAR;
185 uint8_t *thumb_buffer;
186 int thumb_width, thumb_height;
187 int sar_num, sar_den;
188
189 uint8_t *encoder_output_buffer;
190 size_t encoder_output_buffer_size;
191 AVCodecContext *enc_codec_ctx;
192 AVCodec *enc_codec;
193
194 int i;
195 int err;
196 int frame_finished;
197
198 char *binary;
199 const char *mime;
200 int is_image;
201 enum CodecID image_codec_id;
202
203 bio_ctx = NULL;
204 bio_buffer = NULL;
205 format_ctx = NULL;
206 codec = NULL;
207 frame = NULL;
208 thumb_frame = NULL;
209 thumb_buffer = NULL;
210 scaler_ctx = NULL;
211 encoder_output_buffer = NULL;
212 enc_codec = NULL;
213 enc_codec_ctx = NULL;
214
215 is_image = 0;
216
217 mime = EXTRACTOR_extractLast (EXTRACTOR_MIMETYPE, prev);
218 if (mime != NULL)
219 {
220 i = 0;
221 while (m2d_map[i].mime_type != NULL)
222 {
223 if (!strcmp (m2d_map[i].mime_type, mime))
224 {
225 is_image = 1;
226 image_codec_id = m2d_map[i].codec_id;
227 break;
228 }
229 i++;
230 }
231 }
232
233#if DEBUG
234 printf ("is_image: %d codec:%d\n", is_image, image_codec_id);
235#endif
236 if (!is_image)
237 {
238 pdat.filename = filename;
239 pdat.buf = (unsigned char *) data;
240 pdat.buf_size = (size > PROBE_MAX) ? PROBE_MAX : size;
241
242 fmt = av_probe_input_format (&pdat, 1);
243 if (fmt == NULL)
244 return prev;
245#if DEBUG
246 printf ("format %p [%s] [%s]\n", fmt, fmt->name, fmt->long_name);
247#endif
248 pdat.buf = (unsigned char *) data;
249 pdat.buf_size = size > PROBE_MAX ? PROBE_MAX : size;
250 score = fmt->read_probe (&pdat);
251#if DEBUG
252 printf ("score: %d\n", score);
253#endif
254 /*if (score < 50) return prev; */
255 }
256
257 if (is_image)
258 {
259 codec_ctx = avcodec_alloc_context ();
260 codec = avcodec_find_decoder (image_codec_id);
261 if (codec != NULL)
262 {
263 if (avcodec_open (codec_ctx, codec) != 0)
264 {
265#if DEBUG
266 printf ("open codec failed\n");
267#endif
268 codec = NULL;
269 }
270 }
271 }
272 else
273 {
274 bio_ctx = malloc (sizeof (ByteIOContext));
275 bio_buffer = malloc (BIOBUF_SIZE);
276
277 reader_state.data = data;
278 reader_state.offset = 0;
279 reader_state.size = size;
280
281 init_put_byte (bio_ctx, bio_buffer,
282 BIOBUF_SIZE, 0, &reader_state,
283 stream_read, NULL, stream_seek);
284
285 fmt->flags |= AVFMT_NOFILE;
286 err = av_open_input_stream (&format_ctx, bio_ctx, "", fmt, NULL);
287 if (err < 0)
288 {
289#if DEBUG
290 printf ("couldn't open input stream\n");
291#endif
292 goto out;
293 }
294
295 err = av_find_stream_info (format_ctx);
296 if (err < 0)
297 {
298#if DEBUG
299 printf ("couldn't find codec params\n");
300#endif
301 goto out;
302 }
303
304 for (i = 0; i < format_ctx->nb_streams; i++)
305 {
306 codec_ctx = format_ctx->streams[i]->codec;
307 if (codec_ctx->codec_type == CODEC_TYPE_VIDEO)
308 {
309 video_stream_index = i;
310 codec = avcodec_find_decoder (codec_ctx->codec_id);
311 if (codec == NULL)
312 {
313#if DEBUG
314 printf ("find_decoder failed\n");
315#endif
316 break;
317 }
318 err = avcodec_open (codec_ctx, codec);
319 if (err != 0)
320 {
321#if DEBUG
322 printf ("failed to open codec\n");
323#endif
324 codec = NULL;
325 }
326 break;
327 }
328 }
329 }
330
331 if (codec_ctx == NULL || codec == NULL)
332 {
333#if DEBUG
334 printf ("failed to open codec");
335#endif
336 goto out;
337 }
338 frame = avcodec_alloc_frame ();
339 if (frame == NULL)
340 {
341#if DEBUG
342 printf ("failed to alloc frame");
343#endif
344 goto out;
345 }
346
347 if (!is_image)
348 {
349#if DEBUG
350 printf ("duration: %lld\n", format_ctx->duration);
351 if (format_ctx->duration == AV_NOPTS_VALUE)
352 printf ("duration unknown\n");
353#endif
354 /* TODO: if duration is known seek to to some better place(?) */
355 ts = 10; // s
356 ts = ts * AV_TIME_BASE;
357 err = av_seek_frame (format_ctx, -1, ts, 0);
358 if (err >= 0)
359 {
360 avcodec_flush_buffers (codec_ctx);
361 }
362#if DEBUG
363 else
364 printf ("seeking failed %d\n", err);
365#endif
366 }
367
368 frame_finished = 0;
369 if (is_image)
370 {
371 avcodec_decode_video (codec_ctx, frame, &frame_finished, data, size);
372 }
373 else
374 {
375 while (1)
376 {
377 err = av_read_frame (format_ctx, &packet);
378 if (err < 0)
379 break;
380 if (packet.stream_index == video_stream_index)
381 {
382 avcodec_decode_video (codec_ctx,
383 frame,
384 &frame_finished,
385 packet.data, packet.size);
386 if (frame_finished && frame->key_frame)
387 {
388 av_free_packet (&packet);
389 break;
390 }
391 }
392 av_free_packet (&packet);
393 }
394 }
395
396 if (!frame_finished || codec_ctx->width == 0 || codec_ctx->height == 0)
397 goto out;
398
399 sar_num = codec_ctx->sample_aspect_ratio.num;
400 sar_den = codec_ctx->sample_aspect_ratio.den;
401 if (sar_num <= 0 || sar_den <= 0)
402 {
403 sar_num = 1;
404 sar_den = 1;
405 }
406 if ((codec_ctx->width * sar_num) / sar_den > codec_ctx->height)
407 {
408 thumb_width = THUMBSIZE;
409 thumb_height = (thumb_width * codec_ctx->height) /
410 ((codec_ctx->width * sar_num) / sar_den);
411 }
412 else
413 {
414 thumb_height = THUMBSIZE;
415 thumb_width = (thumb_height *
416 ((codec_ctx->width * sar_num) / sar_den)) /
417 codec_ctx->height;
418 }
419 if (thumb_width < 8)
420 thumb_width = 8;
421 if (thumb_height < 1)
422 thumb_height = 1;
423#if DEBUG
424 printf ("thumb dim: %d %d\n", thumb_width, thumb_height);
425#endif
426
427 scaler_ctx =
428 sws_getContext (codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
429 thumb_width, thumb_height, PIX_FMT_RGB24, sws_flags, NULL,
430 NULL, NULL);
431 if (scaler_ctx == NULL)
432 {
433#if DEBUG
434 printf ("failed to alloc scaler\n");
435#endif
436 goto out;
437 }
438 thumb_frame = avcodec_alloc_frame ();
439 thumb_buffer =
440 av_malloc (avpicture_get_size (PIX_FMT_RGB24, thumb_width, thumb_height));
441 if (thumb_frame == NULL || thumb_buffer == NULL)
442 {
443#if DEBUG
444 printf ("failed to alloc thumb frame\n");
445#endif
446 goto out;
447 }
448 avpicture_fill ((AVPicture *) thumb_frame, thumb_buffer,
449 PIX_FMT_RGB24, thumb_width, thumb_height);
450
451 sws_scale (scaler_ctx,
452 frame->data, frame->linesize,
453 0, codec_ctx->height, thumb_frame->data, thumb_frame->linesize);
454
455 encoder_output_buffer_size = MAX_THUMB_SIZE;
456 encoder_output_buffer = av_malloc (encoder_output_buffer_size);
457 if (encoder_output_buffer == NULL)
458 {
459#if DEBUG
460 printf ("couldn't alloc encoder output buf\n");
461#endif
462 goto out;
463 }
464
465 enc_codec = avcodec_find_encoder_by_name ("png");
466 if (enc_codec == NULL)
467 {
468#if DEBUG
469 printf ("couldn't find encoder\n");
470#endif
471 goto out;
472 }
473 enc_codec_ctx = avcodec_alloc_context ();
474 enc_codec_ctx->width = thumb_width;
475 enc_codec_ctx->height = thumb_height;
476 enc_codec_ctx->pix_fmt = PIX_FMT_RGB24;
477
478 if (avcodec_open (enc_codec_ctx, enc_codec) < 0)
479 {
480#if DEBUG
481 printf ("couldn't open encoder\n");
482#endif
483 enc_codec = NULL;
484 goto out;
485 }
486
487 err = avcodec_encode_video (enc_codec_ctx,
488 encoder_output_buffer,
489 encoder_output_buffer_size, thumb_frame);
490 if (err <= 0)
491 goto out;
492
493 binary =
494 EXTRACTOR_binaryEncode ((const unsigned char *) encoder_output_buffer,
495 err);
496 if (binary != NULL)
497 prev = addKeyword (EXTRACTOR_THUMBNAIL_DATA, binary, prev);
498
499out:
500 if (enc_codec != NULL)
501 avcodec_close (enc_codec_ctx);
502 if (enc_codec_ctx != NULL)
503 av_free (enc_codec_ctx);
504 if (encoder_output_buffer != NULL)
505 av_free (encoder_output_buffer);
506 if (scaler_ctx != NULL)
507 sws_freeContext(scaler_ctx);
508 if (codec != NULL)
509 avcodec_close (codec_ctx);
510 if (format_ctx != NULL)
511 av_close_input_file (format_ctx);
512 if (frame != NULL)
513 av_free (frame);
514 if (thumb_buffer != NULL)
515 av_free (thumb_buffer);
516 if (thumb_frame != NULL)
517 av_free (thumb_frame);
518 if (bio_ctx != NULL)
519 free (bio_ctx);
520 if (bio_buffer != NULL)
521 free (bio_buffer);
522
523 return prev;
524}
525
526int
527EXTRACTOR_thumbnail_extract (const unsigned char *data,
528 size_t size,
529 EXTRACTOR_MetaDataProcessor proc,
530 void *proc_cls,
531 const char *options)
532{
533 return EXTRACTOR_thumbnailffmpeg_extract (data, size, proc, proc_cls, options);
534}
535
536/* end of thumbnailffmpeg_extractor.c */