diff options
Diffstat (limited to 'src/plugins/thumbnailffmpeg_extractor.c')
-rw-r--r-- | src/plugins/thumbnailffmpeg_extractor.c | 729 |
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 | */ | ||
80 | static 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 | */ | ||
91 | static int | ||
92 | read_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 | */ | ||
116 | static int64_t | ||
117 | seek_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 | */ | ||
143 | static size_t | ||
144 | create_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 | */ | ||
283 | static void | ||
284 | calculate_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 | */ | ||
327 | static void | ||
328 | extract_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 | */ | ||
437 | static void | ||
438 | extract_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 | */ | ||
607 | struct 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 | */ | ||
624 | static 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 | */ | ||
641 | void | ||
642 | EXTRACTOR_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 | */ | ||
673 | void | ||
674 | EXTRACTOR_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 | */ | ||
688 | static void | ||
689 | thumbnailffmpeg_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 | */ | ||
703 | void __attribute__ ((constructor)) | ||
704 | thumbnailffmpeg_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 | */ | ||
718 | void __attribute__ ((destructor)) | ||
719 | thumbnailffmpeg_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 */ | ||