diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/Makefile.am | 23 | ||||
-rw-r--r-- | src/plugins/old/thumbnailffmpeg_extractor.c | 655 | ||||
-rw-r--r-- | src/plugins/test_thumbnailffmpeg.c | 53 | ||||
-rw-r--r-- | src/plugins/thumbnailffmpeg_extractor.c | 729 | ||||
-rw-r--r-- | src/plugins/thumbnailgtk_extractor.c | 6 |
5 files changed, 807 insertions, 659 deletions
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 6e53b2f..11cb2b5 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am | |||
@@ -48,10 +48,16 @@ PLUGIN_MIME=libextractor_mime.la | |||
48 | TEST_MIME=test_mime | 48 | TEST_MIME=test_mime |
49 | 49 | ||
50 | if HAVE_GTK | 50 | if HAVE_GTK |
51 | # Thumbnailer requires MAGIC and GTK | 51 | # Gtk-thumbnailer requires MAGIC and GTK |
52 | PLUGIN_GTK=libextractor_thumbnailgtk.la | 52 | PLUGIN_GTK=libextractor_thumbnailgtk.la |
53 | TEST_GTK=test_thumbnailgtk | 53 | TEST_GTK=test_thumbnailgtk |
54 | endif | 54 | endif |
55 | |||
56 | if HAVE_FFMPEG | ||
57 | # FFmpeg-thumbnailer requires MAGIC and FFMPEG | ||
58 | PLUGIN_FFMPEG=libextractor_thumbnailffmpeg.la | ||
59 | TEST_FFMPEG=test_thumbnailffmpeg | ||
60 | endif | ||
55 | endif | 61 | endif |
56 | 62 | ||
57 | if HAVE_GIF | 63 | if HAVE_GIF |
@@ -116,6 +122,7 @@ plugin_LTLIBRARIES = \ | |||
116 | libextractor_wav.la \ | 122 | libextractor_wav.la \ |
117 | libextractor_zip.la \ | 123 | libextractor_zip.la \ |
118 | $(PLUGIN_GTK) \ | 124 | $(PLUGIN_GTK) \ |
125 | $(PLUGIN_FFMPEG) \ | ||
119 | $(PLUGIN_ZLIB) \ | 126 | $(PLUGIN_ZLIB) \ |
120 | $(PLUGIN_OGG) \ | 127 | $(PLUGIN_OGG) \ |
121 | $(PLUGIN_MIME) \ | 128 | $(PLUGIN_MIME) \ |
@@ -145,6 +152,7 @@ check_PROGRAMS = \ | |||
145 | test_nsfe \ | 152 | test_nsfe \ |
146 | $(TEST_ZLIB) \ | 153 | $(TEST_ZLIB) \ |
147 | $(TEST_GTK) \ | 154 | $(TEST_GTK) \ |
155 | $(TEST_FFMPEG) \ | ||
148 | $(TEST_OGG) \ | 156 | $(TEST_OGG) \ |
149 | $(TEST_MIME) \ | 157 | $(TEST_MIME) \ |
150 | $(TEST_TIFF) \ | 158 | $(TEST_TIFF) \ |
@@ -456,3 +464,16 @@ test_gstreamer_SOURCES = \ | |||
456 | test_gstreamer.c | 464 | test_gstreamer.c |
457 | test_gstreamer_LDADD = \ | 465 | test_gstreamer_LDADD = \ |
458 | $(top_builddir)/src/plugins/libtest.la | 466 | $(top_builddir)/src/plugins/libtest.la |
467 | |||
468 | |||
469 | libextractor_thumbnailffmpeg_la_SOURCES = \ | ||
470 | thumbnailffmpeg_extractor.c | ||
471 | libextractor_thumbnailffmpeg_la_LDFLAGS = \ | ||
472 | $(PLUGINFLAGS) | ||
473 | libextractor_thumbnailffmpeg_la_LIBADD = \ | ||
474 | -lavutil -lavformat -lavcodec -lswscale -lmagic | ||
475 | |||
476 | test_thumbnailffmpeg_SOURCES = \ | ||
477 | test_thumbnailffmpeg.c | ||
478 | test_thumbnailffmpeg_LDADD = \ | ||
479 | $(top_builddir)/src/plugins/libtest.la | ||
diff --git a/src/plugins/old/thumbnailffmpeg_extractor.c b/src/plugins/old/thumbnailffmpeg_extractor.c deleted file mode 100644 index e93e0b2..0000000 --- a/src/plugins/old/thumbnailffmpeg_extractor.c +++ /dev/null | |||
@@ -1,655 +0,0 @@ | |||
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 | #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 | #include "mime_extractor.c" /* TODO: do this cleaner */ | ||
63 | |||
64 | #define DEBUG 0 | ||
65 | |||
66 | static void thumbnailffmpeg_av_log_callback(void* ptr, | ||
67 | int level, | ||
68 | const char *format, | ||
69 | va_list ap) | ||
70 | { | ||
71 | #if DEBUG | ||
72 | vfprintf(stderr, format, ap); | ||
73 | #endif | ||
74 | } | ||
75 | |||
76 | void __attribute__ ((constructor)) ffmpeg_lib_init (void) | ||
77 | { | ||
78 | av_log_set_callback (thumbnailffmpeg_av_log_callback); | ||
79 | av_register_all (); | ||
80 | } | ||
81 | |||
82 | #define MAX_THUMB_DIMENSION 128 /* max dimension in pixels */ | ||
83 | #define MAX_THUMB_BYTES (100*1024) | ||
84 | |||
85 | /* | ||
86 | * Rescale and encode a PNG thumbnail | ||
87 | * on success, fills in output_data and returns the number of bytes used | ||
88 | */ | ||
89 | static size_t create_thumbnail( | ||
90 | int src_width, int src_height, int src_stride[], | ||
91 | enum PixelFormat src_pixfmt, const uint8_t * const src_data[], | ||
92 | int dst_width, int dst_height, | ||
93 | uint8_t **output_data, size_t output_max_size) | ||
94 | { | ||
95 | AVCodecContext *encoder_codec_ctx = NULL; | ||
96 | AVCodec *encoder_codec = NULL; | ||
97 | struct SwsContext *scaler_ctx = NULL; | ||
98 | int sws_flags = SWS_BILINEAR; | ||
99 | AVFrame *dst_frame = NULL; | ||
100 | uint8_t *dst_buffer = NULL; | ||
101 | uint8_t *encoder_output_buffer = NULL; | ||
102 | size_t encoder_output_buffer_size; | ||
103 | int err; | ||
104 | |||
105 | encoder_codec = avcodec_find_encoder_by_name ("png"); | ||
106 | if (encoder_codec == NULL) | ||
107 | { | ||
108 | #if DEBUG | ||
109 | fprintf (stderr, | ||
110 | "Couldn't find a PNG encoder\n"); | ||
111 | #endif | ||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | /* NOTE: the scaler will be used even if the src and dst image dimensions | ||
116 | * match, because the scaler will also perform colour space conversion */ | ||
117 | scaler_ctx = | ||
118 | sws_getContext (src_width, src_height, src_pixfmt, | ||
119 | dst_width, dst_height, PIX_FMT_RGB24, | ||
120 | sws_flags, NULL, NULL, NULL); | ||
121 | if (scaler_ctx == NULL) | ||
122 | { | ||
123 | #if DEBUG | ||
124 | fprintf (stderr, | ||
125 | "Failed to get a scaler context\n"); | ||
126 | #endif | ||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | dst_frame = avcodec_alloc_frame (); | ||
131 | if (dst_frame == NULL) | ||
132 | { | ||
133 | #if DEBUG | ||
134 | fprintf (stderr, | ||
135 | "Failed to allocate the destination image frame\n"); | ||
136 | #endif | ||
137 | sws_freeContext(scaler_ctx); | ||
138 | return 0; | ||
139 | } | ||
140 | dst_buffer = | ||
141 | av_malloc (avpicture_get_size (PIX_FMT_RGB24, dst_width, dst_height)); | ||
142 | if (dst_buffer == NULL) | ||
143 | { | ||
144 | #if DEBUG | ||
145 | fprintf (stderr, | ||
146 | "Failed to allocate the destination image buffer\n"); | ||
147 | #endif | ||
148 | av_free (dst_frame); | ||
149 | sws_freeContext(scaler_ctx); | ||
150 | return 0; | ||
151 | } | ||
152 | avpicture_fill ((AVPicture *) dst_frame, dst_buffer, | ||
153 | PIX_FMT_RGB24, dst_width, dst_height); | ||
154 | |||
155 | sws_scale (scaler_ctx, | ||
156 | src_data, | ||
157 | src_stride, | ||
158 | 0, src_height, | ||
159 | dst_frame->data, | ||
160 | dst_frame->linesize); | ||
161 | |||
162 | encoder_output_buffer_size = output_max_size; | ||
163 | encoder_output_buffer = av_malloc (encoder_output_buffer_size); | ||
164 | if (encoder_output_buffer == NULL) | ||
165 | { | ||
166 | #if DEBUG | ||
167 | fprintf (stderr, | ||
168 | "Failed to allocate the encoder output buffer\n"); | ||
169 | #endif | ||
170 | av_free (dst_buffer); | ||
171 | av_free (dst_frame); | ||
172 | sws_freeContext(scaler_ctx); | ||
173 | return 0; | ||
174 | } | ||
175 | |||
176 | encoder_codec_ctx = avcodec_alloc_context (); | ||
177 | if (encoder_codec_ctx == NULL) | ||
178 | { | ||
179 | #if DEBUG | ||
180 | fprintf (stderr, | ||
181 | "Failed to allocate the encoder codec context\n"); | ||
182 | #endif | ||
183 | av_free (encoder_output_buffer); | ||
184 | av_free (dst_buffer); | ||
185 | av_free (dst_frame); | ||
186 | sws_freeContext(scaler_ctx); | ||
187 | return 0; | ||
188 | } | ||
189 | encoder_codec_ctx->width = dst_width; | ||
190 | encoder_codec_ctx->height = dst_height; | ||
191 | encoder_codec_ctx->pix_fmt = PIX_FMT_RGB24; | ||
192 | |||
193 | if (avcodec_open (encoder_codec_ctx, encoder_codec) < 0) | ||
194 | { | ||
195 | #if DEBUG | ||
196 | fprintf (stderr, | ||
197 | "Failed to open the encoder\n"); | ||
198 | #endif | ||
199 | av_free (encoder_codec_ctx); | ||
200 | av_free (encoder_output_buffer); | ||
201 | av_free (dst_buffer); | ||
202 | av_free (dst_frame); | ||
203 | sws_freeContext(scaler_ctx); | ||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | err = avcodec_encode_video (encoder_codec_ctx, | ||
208 | encoder_output_buffer, | ||
209 | encoder_output_buffer_size, dst_frame); | ||
210 | |||
211 | avcodec_close (encoder_codec_ctx); | ||
212 | av_free (encoder_codec_ctx); | ||
213 | av_free (dst_buffer); | ||
214 | av_free (dst_frame); | ||
215 | sws_freeContext(scaler_ctx); | ||
216 | |||
217 | *output_data = encoder_output_buffer; | ||
218 | |||
219 | return err < 0 ? 0 : err; | ||
220 | } | ||
221 | |||
222 | struct MIMEToDecoderMapping | ||
223 | { | ||
224 | const char *mime_type; | ||
225 | enum CodecID codec_id; | ||
226 | }; | ||
227 | |||
228 | /* map MIME image types to an ffmpeg decoder */ | ||
229 | static const struct MIMEToDecoderMapping m2d_map[] = { | ||
230 | {"image/x-bmp", CODEC_ID_BMP}, | ||
231 | {"image/gif", CODEC_ID_GIF}, | ||
232 | {"image/jpeg", CODEC_ID_MJPEG}, | ||
233 | {"image/png", CODEC_ID_PNG}, | ||
234 | {"image/x-png", CODEC_ID_PNG}, | ||
235 | {"image/x-portable-pixmap", CODEC_ID_PPM}, | ||
236 | {NULL, CODEC_ID_NONE} | ||
237 | }; | ||
238 | |||
239 | static char *mime_type; | ||
240 | |||
241 | static int | ||
242 | mime_processor (void *cls, | ||
243 | const char *plugin_name, | ||
244 | enum EXTRACTOR_MetaType type, | ||
245 | enum EXTRACTOR_MetaFormat format, | ||
246 | const char *data_mime_type, | ||
247 | const char *data, | ||
248 | size_t data_len) | ||
249 | { | ||
250 | switch (format) | ||
251 | { | ||
252 | case EXTRACTOR_METAFORMAT_UTF8: | ||
253 | mime_type = strdup(data); | ||
254 | break; | ||
255 | default: | ||
256 | break; | ||
257 | } | ||
258 | return 0; | ||
259 | } | ||
260 | |||
261 | /* calculate the thumbnail dimensions, taking pixel aspect into account */ | ||
262 | static void calculate_thumbnail_dimensions(int src_width, | ||
263 | int src_height, | ||
264 | int src_sar_num, | ||
265 | int src_sar_den, | ||
266 | int *dst_width, | ||
267 | int *dst_height) | ||
268 | { | ||
269 | if (src_sar_num <= 0 || src_sar_den <= 0) | ||
270 | { | ||
271 | src_sar_num = 1; | ||
272 | src_sar_den = 1; | ||
273 | } | ||
274 | if ((src_width * src_sar_num) / src_sar_den > src_height) | ||
275 | { | ||
276 | *dst_width = MAX_THUMB_DIMENSION; | ||
277 | *dst_height = (*dst_width * src_height) / | ||
278 | ((src_width * src_sar_num) / src_sar_den); | ||
279 | } | ||
280 | else | ||
281 | { | ||
282 | *dst_height = MAX_THUMB_DIMENSION; | ||
283 | *dst_width = (*dst_height * | ||
284 | ((src_width * src_sar_num) / src_sar_den)) / | ||
285 | src_height; | ||
286 | } | ||
287 | if (*dst_width < 8) | ||
288 | *dst_width = 8; | ||
289 | if (*dst_height < 1) | ||
290 | *dst_height = 1; | ||
291 | #if DEBUG | ||
292 | fprintf (stderr, | ||
293 | "Thumbnail dimensions: %d %d\n", | ||
294 | *dst_width, *dst_height); | ||
295 | #endif | ||
296 | } | ||
297 | |||
298 | static int | ||
299 | extract_image (enum CodecID image_codec_id, | ||
300 | const unsigned char *data, | ||
301 | size_t size, | ||
302 | EXTRACTOR_MetaDataProcessor proc, | ||
303 | void *proc_cls, | ||
304 | const char *options) | ||
305 | { | ||
306 | AVCodecContext *codec_ctx; | ||
307 | AVCodec *codec = NULL; | ||
308 | AVFrame *frame = NULL; | ||
309 | uint8_t *encoded_thumbnail; | ||
310 | int thumb_width; | ||
311 | int thumb_height; | ||
312 | int err; | ||
313 | int frame_finished; | ||
314 | int ret = 0; | ||
315 | |||
316 | codec_ctx = avcodec_alloc_context (); | ||
317 | if (codec_ctx == NULL) | ||
318 | { | ||
319 | #if DEBUG | ||
320 | fprintf (stderr, | ||
321 | "Failed to allocate codec context\n"); | ||
322 | #endif | ||
323 | return 0; | ||
324 | } | ||
325 | |||
326 | codec = avcodec_find_decoder (image_codec_id); | ||
327 | if (codec != NULL) | ||
328 | { | ||
329 | if (avcodec_open (codec_ctx, codec) != 0) | ||
330 | { | ||
331 | #if DEBUG | ||
332 | fprintf (stderr, | ||
333 | "Failed to open image codec\n"); | ||
334 | #endif | ||
335 | av_free (codec_ctx); | ||
336 | return 0; | ||
337 | } | ||
338 | } | ||
339 | else | ||
340 | { | ||
341 | #if DEBUG | ||
342 | fprintf (stderr, | ||
343 | "No suitable codec found\n"); | ||
344 | #endif | ||
345 | av_free (codec_ctx); | ||
346 | return 0; | ||
347 | } | ||
348 | |||
349 | frame = avcodec_alloc_frame (); | ||
350 | if (frame == NULL) | ||
351 | { | ||
352 | #if DEBUG | ||
353 | fprintf (stderr, | ||
354 | "Failed to allocate frame\n"); | ||
355 | #endif | ||
356 | avcodec_close (codec_ctx); | ||
357 | av_free (codec_ctx); | ||
358 | return 0; | ||
359 | } | ||
360 | |||
361 | avcodec_decode_video (codec_ctx, frame, &frame_finished, data, size); | ||
362 | |||
363 | if (!frame_finished) | ||
364 | { | ||
365 | fprintf (stderr, | ||
366 | "Failed to decode a complete frame\n"); | ||
367 | av_free (frame); | ||
368 | avcodec_close (codec_ctx); | ||
369 | av_free (codec_ctx); | ||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height, | ||
374 | codec_ctx->sample_aspect_ratio.num, | ||
375 | codec_ctx->sample_aspect_ratio.den, | ||
376 | &thumb_width, &thumb_height); | ||
377 | |||
378 | err = create_thumbnail (codec_ctx->width, codec_ctx->height, | ||
379 | frame->linesize, codec_ctx->pix_fmt, | ||
380 | (const uint8_t * const*) frame->data, | ||
381 | thumb_width, thumb_height, | ||
382 | &encoded_thumbnail, MAX_THUMB_BYTES); | ||
383 | |||
384 | if (err > 0) | ||
385 | { | ||
386 | ret = proc (proc_cls, | ||
387 | "thumbnailffmpeg", | ||
388 | EXTRACTOR_METATYPE_THUMBNAIL, | ||
389 | EXTRACTOR_METAFORMAT_BINARY, | ||
390 | "image/png", | ||
391 | (const char*) encoded_thumbnail, | ||
392 | err); | ||
393 | av_free (encoded_thumbnail); | ||
394 | } | ||
395 | |||
396 | av_free (frame); | ||
397 | avcodec_close (codec_ctx); | ||
398 | av_free (codec_ctx); | ||
399 | return ret; | ||
400 | } | ||
401 | |||
402 | static int | ||
403 | extract_video (const unsigned char *data, | ||
404 | size_t size, | ||
405 | EXTRACTOR_MetaDataProcessor proc, | ||
406 | void *proc_cls, | ||
407 | const char *options) | ||
408 | { | ||
409 | AVProbeData pd; | ||
410 | AVPacket packet; | ||
411 | AVInputFormat *input_format; | ||
412 | int input_format_nofileflag; | ||
413 | ByteIOContext *bio_ctx; | ||
414 | struct AVFormatContext *format_ctx; | ||
415 | AVCodecContext *codec_ctx; | ||
416 | AVCodec *codec = NULL; | ||
417 | AVFrame *frame = NULL; | ||
418 | uint8_t *encoded_thumbnail; | ||
419 | int video_stream_index = -1; | ||
420 | int thumb_width; | ||
421 | int thumb_height; | ||
422 | int i; | ||
423 | int err; | ||
424 | int frame_finished; | ||
425 | int ret = 0; | ||
426 | |||
427 | #if DEBUG | ||
428 | fprintf (stderr, | ||
429 | "ffmpeg starting\n"); | ||
430 | #endif | ||
431 | /* probe format | ||
432 | * initial try with a smaller probe size for efficiency */ | ||
433 | pd.filename = ""; | ||
434 | pd.buf = (void *) data; | ||
435 | pd.buf_size = 128*1024 > size ? size : 128*1024; | ||
436 | RETRY_PROBE: | ||
437 | if (NULL == (input_format = av_probe_input_format(&pd, 1))) | ||
438 | { | ||
439 | #if DEBUG | ||
440 | fprintf (stderr, | ||
441 | "Failed to probe input format\n"); | ||
442 | #endif | ||
443 | if (pd.buf_size != size) /* retry probe once with full data size */ | ||
444 | { | ||
445 | pd.buf_size = size; | ||
446 | goto RETRY_PROBE; | ||
447 | } | ||
448 | return 0; | ||
449 | } | ||
450 | input_format_nofileflag = input_format->flags & AVFMT_NOFILE; | ||
451 | input_format->flags |= AVFMT_NOFILE; | ||
452 | bio_ctx = NULL; | ||
453 | pd.buf_size = size; | ||
454 | url_open_buf(&bio_ctx, pd.buf, pd.buf_size, URL_RDONLY); | ||
455 | bio_ctx->is_streamed = 1; | ||
456 | if ((av_open_input_stream(&format_ctx, bio_ctx, pd.filename, input_format, NULL)) < 0) | ||
457 | { | ||
458 | #if DEBUG | ||
459 | fprintf (stderr, | ||
460 | "Failed to open input stream\n"); | ||
461 | #endif | ||
462 | url_close_buf (bio_ctx); | ||
463 | if (!input_format_nofileflag) | ||
464 | input_format->flags ^= AVFMT_NOFILE; | ||
465 | return 0; | ||
466 | } | ||
467 | if (0 > av_find_stream_info (format_ctx)) | ||
468 | { | ||
469 | #if DEBUG | ||
470 | fprintf (stderr, | ||
471 | "Failed to read stream info\n"); | ||
472 | #endif | ||
473 | av_close_input_stream (format_ctx); | ||
474 | url_close_buf (bio_ctx); | ||
475 | if (!input_format_nofileflag) | ||
476 | input_format->flags ^= AVFMT_NOFILE; | ||
477 | return 0; | ||
478 | } | ||
479 | |||
480 | codec_ctx = NULL; | ||
481 | for (i=0; i<format_ctx->nb_streams; i++) | ||
482 | { | ||
483 | codec_ctx = format_ctx->streams[i]->codec; | ||
484 | if (codec_ctx->codec_type != CODEC_TYPE_VIDEO) | ||
485 | continue; | ||
486 | codec = avcodec_find_decoder (codec_ctx->codec_id); | ||
487 | if (codec == NULL) | ||
488 | continue; | ||
489 | err = avcodec_open (codec_ctx, codec); | ||
490 | if (err != 0) | ||
491 | { | ||
492 | codec = NULL; | ||
493 | continue; | ||
494 | } | ||
495 | video_stream_index = i; | ||
496 | break; | ||
497 | } | ||
498 | |||
499 | if ( (video_stream_index == -1) || | ||
500 | (codec_ctx->width == 0) || | ||
501 | (codec_ctx->height == 0) ) | ||
502 | { | ||
503 | #if DEBUG | ||
504 | fprintf (stderr, | ||
505 | "No video streams or no suitable codec found\n"); | ||
506 | #endif | ||
507 | if (codec != NULL) | ||
508 | avcodec_close (codec_ctx); | ||
509 | av_close_input_stream (format_ctx); | ||
510 | url_close_buf (bio_ctx); | ||
511 | if (!input_format_nofileflag) | ||
512 | input_format->flags ^= AVFMT_NOFILE; | ||
513 | return 0; | ||
514 | } | ||
515 | |||
516 | frame = avcodec_alloc_frame (); | ||
517 | if (frame == NULL) | ||
518 | { | ||
519 | #if DEBUG | ||
520 | fprintf (stderr, | ||
521 | "Failed to allocate frame\n"); | ||
522 | #endif | ||
523 | avcodec_close (codec_ctx); | ||
524 | av_close_input_stream (format_ctx); | ||
525 | url_close_buf (bio_ctx); | ||
526 | if (!input_format_nofileflag) | ||
527 | input_format->flags ^= AVFMT_NOFILE; | ||
528 | return 0; | ||
529 | } | ||
530 | #if DEBUG | ||
531 | if (format_ctx->duration == AV_NOPTS_VALUE) | ||
532 | fprintf (stderr, | ||
533 | "Duration unknown\n"); | ||
534 | else | ||
535 | fprintf (stderr, | ||
536 | "Duration: %lld\n", | ||
537 | format_ctx->duration); | ||
538 | #endif | ||
539 | /* TODO: if duration is known, seek to some better place, | ||
540 | * but use 10 sec into stream for now */ | ||
541 | err = av_seek_frame (format_ctx, -1, 10 * AV_TIME_BASE, 0); | ||
542 | if (err >= 0) | ||
543 | avcodec_flush_buffers (codec_ctx); | ||
544 | frame_finished = 0; | ||
545 | |||
546 | while (1) | ||
547 | { | ||
548 | err = av_read_frame (format_ctx, &packet); | ||
549 | if (err < 0) | ||
550 | break; | ||
551 | if (packet.stream_index == video_stream_index) | ||
552 | { | ||
553 | avcodec_decode_video (codec_ctx, | ||
554 | frame, | ||
555 | &frame_finished, | ||
556 | packet.data, packet.size); | ||
557 | if (frame_finished && frame->key_frame) | ||
558 | { | ||
559 | av_free_packet (&packet); | ||
560 | break; | ||
561 | } | ||
562 | } | ||
563 | av_free_packet (&packet); | ||
564 | } | ||
565 | if (!frame_finished) | ||
566 | { | ||
567 | fprintf (stderr, | ||
568 | "Failed to decode a complete frame\n"); | ||
569 | av_free (frame); | ||
570 | avcodec_close (codec_ctx); | ||
571 | av_close_input_stream (format_ctx); | ||
572 | url_close_buf (bio_ctx); | ||
573 | if (!input_format_nofileflag) | ||
574 | input_format->flags ^= AVFMT_NOFILE; | ||
575 | return 0; | ||
576 | } | ||
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 | |||
583 | err = create_thumbnail (codec_ctx->width, codec_ctx->height, | ||
584 | frame->linesize, codec_ctx->pix_fmt, | ||
585 | (const uint8_t* const *) frame->data, | ||
586 | thumb_width, thumb_height, | ||
587 | &encoded_thumbnail, MAX_THUMB_BYTES); | ||
588 | |||
589 | if (err > 0) | ||
590 | { | ||
591 | ret = proc (proc_cls, | ||
592 | "thumbnailffmpeg", | ||
593 | EXTRACTOR_METATYPE_THUMBNAIL, | ||
594 | EXTRACTOR_METAFORMAT_BINARY, | ||
595 | "image/png", | ||
596 | (const char*) encoded_thumbnail, | ||
597 | err); | ||
598 | av_free (encoded_thumbnail); | ||
599 | } | ||
600 | |||
601 | av_free (frame); | ||
602 | avcodec_close (codec_ctx); | ||
603 | av_close_input_stream (format_ctx); | ||
604 | url_close_buf (bio_ctx); | ||
605 | if (!input_format_nofileflag) | ||
606 | input_format->flags ^= AVFMT_NOFILE; | ||
607 | return ret; | ||
608 | } | ||
609 | |||
610 | int | ||
611 | EXTRACTOR_thumbnailffmpeg_extract (const unsigned char *data, | ||
612 | size_t size, | ||
613 | EXTRACTOR_MetaDataProcessor proc, | ||
614 | void *proc_cls, | ||
615 | const char *options) | ||
616 | { | ||
617 | enum CodecID image_codec_id; | ||
618 | int is_image = 0; | ||
619 | int i; | ||
620 | |||
621 | mime_type = NULL; | ||
622 | EXTRACTOR_mime_extract((const char*) data, size, mime_processor, NULL, NULL); | ||
623 | if (mime_type != NULL) | ||
624 | { | ||
625 | i = 0; | ||
626 | while (m2d_map[i].mime_type != NULL) | ||
627 | { | ||
628 | if (!strcmp (m2d_map[i].mime_type, mime_type)) | ||
629 | { | ||
630 | is_image = 1; | ||
631 | image_codec_id = m2d_map[i].codec_id; | ||
632 | break; | ||
633 | } | ||
634 | i++; | ||
635 | } | ||
636 | free(mime_type); | ||
637 | } | ||
638 | |||
639 | if (is_image) | ||
640 | return extract_image (image_codec_id, data, size, proc, proc_cls, options); | ||
641 | else | ||
642 | return extract_video (data, size, proc, proc_cls, options); | ||
643 | } | ||
644 | |||
645 | int | ||
646 | EXTRACTOR_thumbnail_extract (const unsigned char *data, | ||
647 | size_t size, | ||
648 | EXTRACTOR_MetaDataProcessor proc, | ||
649 | void *proc_cls, | ||
650 | const char *options) | ||
651 | { | ||
652 | return EXTRACTOR_thumbnailffmpeg_extract (data, size, proc, proc_cls, options); | ||
653 | } | ||
654 | |||
655 | /* end of thumbnailffmpeg_extractor.c */ | ||
diff --git a/src/plugins/test_thumbnailffmpeg.c b/src/plugins/test_thumbnailffmpeg.c new file mode 100644 index 0000000..6c6b70e --- /dev/null +++ b/src/plugins/test_thumbnailffmpeg.c | |||
@@ -0,0 +1,53 @@ | |||
1 | /* | ||
2 | This file is part of libextractor. | ||
3 | (C) 2012 Vidyut Samanta 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 plugins/test_thumbnailffmpeg.c | ||
22 | * @brief testcase for thumbnailffmpeg plugin | ||
23 | * @author Christian Grothoff | ||
24 | */ | ||
25 | #include "platform.h" | ||
26 | #include "test_lib.h" | ||
27 | |||
28 | |||
29 | |||
30 | /** | ||
31 | * Main function for the THUMBNAILFFMPEG testcase. | ||
32 | * | ||
33 | * @param argc number of arguments (ignored) | ||
34 | * @param argv arguments (ignored) | ||
35 | * @return 0 on success | ||
36 | */ | ||
37 | int | ||
38 | main (int argc, char *argv[]) | ||
39 | { | ||
40 | struct SolutionData thumbnailffmpeg_video_sol[] = | ||
41 | { | ||
42 | { 0, 0, NULL, NULL, 0, -1 } | ||
43 | }; | ||
44 | struct ProblemSet ps[] = | ||
45 | { | ||
46 | { "testdata/thumbnailffmpeg_video.mov", | ||
47 | thumbnailffmpeg_video_sol }, | ||
48 | { NULL, NULL } | ||
49 | }; | ||
50 | return ET_main ("thumbnailffmpeg", ps); | ||
51 | } | ||
52 | |||
53 | /* end of test_thumbnailffmpeg.c */ | ||
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 */ | ||
diff --git a/src/plugins/thumbnailgtk_extractor.c b/src/plugins/thumbnailgtk_extractor.c index 63ac359..a68df7b 100644 --- a/src/plugins/thumbnailgtk_extractor.c +++ b/src/plugins/thumbnailgtk_extractor.c | |||
@@ -52,7 +52,7 @@ static magic_t magic; | |||
52 | 52 | ||
53 | 53 | ||
54 | /** | 54 | /** |
55 | * Main method for the thumbnailer plugin. | 55 | * Main method for the gtk-thumbnailer plugin. |
56 | * | 56 | * |
57 | * @param ec extraction context | 57 | * @param ec extraction context |
58 | */ | 58 | */ |
@@ -209,7 +209,7 @@ EXTRACTOR_thumbnail_extract_method (struct EXTRACTOR_ExtractContext *ec) | |||
209 | * Initialize glib and load magic file. | 209 | * Initialize glib and load magic file. |
210 | */ | 210 | */ |
211 | void __attribute__ ((constructor)) | 211 | void __attribute__ ((constructor)) |
212 | ole_gobject_init () | 212 | thumbnailgtk_gobject_init () |
213 | { | 213 | { |
214 | g_type_init (); | 214 | g_type_init (); |
215 | magic = magic_open (MAGIC_MIME_TYPE); | 215 | magic = magic_open (MAGIC_MIME_TYPE); |
@@ -224,7 +224,7 @@ ole_gobject_init () | |||
224 | * Destructor for the library, cleans up. | 224 | * Destructor for the library, cleans up. |
225 | */ | 225 | */ |
226 | void __attribute__ ((destructor)) | 226 | void __attribute__ ((destructor)) |
227 | mime_ltdl_fini () | 227 | thumbnailgtk_ltdl_fini () |
228 | { | 228 | { |
229 | if (NULL != magic) | 229 | if (NULL != magic) |
230 | { | 230 | { |