diff options
Diffstat (limited to 'src/plugins/thumbnailffmpeg_extractor.c')
-rw-r--r-- | src/plugins/thumbnailffmpeg_extractor.c | 890 |
1 files changed, 0 insertions, 890 deletions
diff --git a/src/plugins/thumbnailffmpeg_extractor.c b/src/plugins/thumbnailffmpeg_extractor.c deleted file mode 100644 index ef7a05f..0000000 --- a/src/plugins/thumbnailffmpeg_extractor.c +++ /dev/null | |||
@@ -1,890 +0,0 @@ | |||
1 | /* | ||
2 | This file is part of libextractor. | ||
3 | Copyright 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., 51 Franklin Street, Fifth Floor, | ||
18 | Boston, MA 02110-1301, 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 | #include <libavutil/avutil.h> | ||
42 | #include <libavutil/imgutils.h> | ||
43 | #include <libavformat/avformat.h> | ||
44 | #include <libavcodec/avcodec.h> | ||
45 | #include <libswscale/swscale.h> | ||
46 | |||
47 | #if USE_JPEG | ||
48 | #ifdef PIX_FMT_YUVJ420P | ||
49 | #define PIX_OUTPUT_FORMAT PIX_FMT_YUVJ420P | ||
50 | #else | ||
51 | #define PIX_OUTPUT_FORMAT AV_PIX_FMT_YUVJ420P | ||
52 | #endif | ||
53 | #else | ||
54 | #ifdef PIX_FMT_RGB24 | ||
55 | #define PIX_OUTPUT_FORMAT PIX_FMT_RGB24 | ||
56 | #else | ||
57 | #define PIX_OUTPUT_FORMAT AV_PIX_FMT_RGB24 | ||
58 | #endif | ||
59 | #endif | ||
60 | |||
61 | /** | ||
62 | * Set to 1 to use JPEG, PNG otherwise | ||
63 | */ | ||
64 | #define USE_JPEG 1 | ||
65 | |||
66 | /** | ||
67 | * Set to 1 to enable a output file for testing. | ||
68 | */ | ||
69 | #define OUTPUT_FILE 0 | ||
70 | |||
71 | |||
72 | /** | ||
73 | * Set to 1 to use jpeg. | ||
74 | */ | ||
75 | #define DEBUG 0 | ||
76 | |||
77 | /** | ||
78 | * max dimension in pixels for the thumbnail. | ||
79 | */ | ||
80 | #define MAX_THUMB_DIMENSION 128 | ||
81 | |||
82 | /** | ||
83 | * Maximum size in bytes for the thumbnail. | ||
84 | */ | ||
85 | #define MAX_THUMB_BYTES (100 * 1024) | ||
86 | |||
87 | /** | ||
88 | * Number of bytes to feed to libav in one go. | ||
89 | */ | ||
90 | #define BUFFER_SIZE (32 * 1024) | ||
91 | |||
92 | /** | ||
93 | * Number of bytes to feed to libav in one go, with padding (padding is zeroed). | ||
94 | */ | ||
95 | #ifdef AV_INPUT_BUFFER_PADDING_SIZE | ||
96 | #define PADDED_BUFFER_SIZE (BUFFER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE) | ||
97 | #else | ||
98 | /* legacy */ | ||
99 | #define PADDED_BUFFER_SIZE (BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE) | ||
100 | #endif | ||
101 | |||
102 | #ifndef AV_CODEC_FLAG_QSCALE | ||
103 | #define AV_CODEC_FLAG_QSCALE CODEC_FLAG_QSCALE | ||
104 | #endif | ||
105 | |||
106 | |||
107 | /** | ||
108 | * Global handle to MAGIC data. | ||
109 | */ | ||
110 | static magic_t magic; | ||
111 | |||
112 | |||
113 | /** | ||
114 | * Read callback. | ||
115 | * | ||
116 | * @param opaque the 'struct EXTRACTOR_ExtractContext' | ||
117 | * @param buf where to write data | ||
118 | * @param buf_size how many bytes to read | ||
119 | * @return -1 on error (or for unknown file size) | ||
120 | */ | ||
121 | static int | ||
122 | read_cb (void *opaque, | ||
123 | uint8_t *buf, | ||
124 | int buf_size) | ||
125 | { | ||
126 | struct EXTRACTOR_ExtractContext *ec = opaque; | ||
127 | void *data; | ||
128 | ssize_t ret; | ||
129 | |||
130 | ret = ec->read (ec->cls, &data, buf_size); | ||
131 | if (ret <= 0) | ||
132 | return ret; | ||
133 | memcpy (buf, data, ret); | ||
134 | return ret; | ||
135 | } | ||
136 | |||
137 | |||
138 | /** | ||
139 | * Seek callback. | ||
140 | * | ||
141 | * @param opaque the 'struct EXTRACTOR_ExtractContext' | ||
142 | * @param offset where to seek | ||
143 | * @param whence how to seek; AVSEEK_SIZE to return file size without seeking | ||
144 | * @return -1 on error (or for unknown file size) | ||
145 | */ | ||
146 | static int64_t | ||
147 | seek_cb (void *opaque, | ||
148 | int64_t offset, | ||
149 | int whence) | ||
150 | { | ||
151 | struct EXTRACTOR_ExtractContext *ec = opaque; | ||
152 | |||
153 | if (AVSEEK_SIZE == whence) | ||
154 | return ec->get_size (ec->cls); | ||
155 | return ec->seek (ec->cls, offset, whence); | ||
156 | } | ||
157 | |||
158 | |||
159 | /** | ||
160 | * Rescale and encode a PNG thumbnail. | ||
161 | * | ||
162 | * @param src_width source image width | ||
163 | * @param src_height source image height | ||
164 | * @param src_stride | ||
165 | * @param src_pixfmt | ||
166 | * @param src_data source data | ||
167 | * @param dst_width desired thumbnail width | ||
168 | * @param dst_height desired thumbnail height | ||
169 | * @param output_data where to store the resulting PNG data | ||
170 | * @param output_max_size maximum size of result that is allowed | ||
171 | * @return the number of bytes used, 0 on error | ||
172 | */ | ||
173 | static size_t | ||
174 | create_thumbnail (AVCodecContext *pCodecCtx, int src_width, int src_height, | ||
175 | int src_stride[], | ||
176 | enum AVPixelFormat src_pixfmt, | ||
177 | const uint8_t *const src_data[], | ||
178 | int dst_width, int dst_height, | ||
179 | uint8_t **output_data, | ||
180 | size_t output_max_size) | ||
181 | { | ||
182 | AVCodecContext *encoder_codec_ctx; | ||
183 | AVDictionary *opts; | ||
184 | AVCodec *encoder_codec; | ||
185 | struct SwsContext *scaler_ctx; | ||
186 | AVFrame *dst_frame; | ||
187 | uint8_t *dst_buffer; | ||
188 | uint8_t *encoder_output_buffer; | ||
189 | size_t encoder_output_buffer_size; | ||
190 | int err; | ||
191 | |||
192 | AVPacket pkt; | ||
193 | av_init_packet (&pkt); | ||
194 | pkt.data = NULL; | ||
195 | pkt.size = 0; | ||
196 | int gotPacket; | ||
197 | #if USE_JPEG | ||
198 | if (NULL == (encoder_codec = avcodec_find_encoder (AV_CODEC_ID_MJPEG))) | ||
199 | #else | ||
200 | if (NULL == (encoder_codec = avcodec_find_encoder_by_name ("png"))) | ||
201 | #endif | ||
202 | { | ||
203 | #if DEBUG | ||
204 | fprintf (stderr, | ||
205 | "Couldn't find a encoder\n"); | ||
206 | #endif | ||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | /* NOTE: the scaler will be used even if the src and dst image dimensions | ||
211 | * match, because the scaler will also perform colour space conversion */ | ||
212 | if (NULL == | ||
213 | (scaler_ctx = | ||
214 | sws_getContext (src_width, src_height, src_pixfmt, | ||
215 | dst_width, dst_height, | ||
216 | PIX_OUTPUT_FORMAT, | ||
217 | SWS_BILINEAR, NULL, NULL, NULL))) | ||
218 | { | ||
219 | #if DEBUG | ||
220 | fprintf (stderr, | ||
221 | "Failed to get a scaler context\n"); | ||
222 | #endif | ||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | dst_frame = av_frame_alloc (); | ||
227 | if (NULL == dst_frame) | ||
228 | { | ||
229 | #if DEBUG | ||
230 | fprintf (stderr, | ||
231 | "Failed to allocate the destination image frame\n"); | ||
232 | #endif | ||
233 | sws_freeContext (scaler_ctx); | ||
234 | return 0; | ||
235 | } | ||
236 | if (NULL == (dst_buffer = | ||
237 | av_malloc (av_image_get_buffer_size (PIX_OUTPUT_FORMAT, | ||
238 | dst_width, | ||
239 | dst_height, | ||
240 | 1)))) | ||
241 | { | ||
242 | #if DEBUG | ||
243 | fprintf (stderr, | ||
244 | "Failed to allocate the destination image buffer\n"); | ||
245 | #endif | ||
246 | av_frame_free (&dst_frame); | ||
247 | sws_freeContext (scaler_ctx); | ||
248 | return 0; | ||
249 | } | ||
250 | av_image_fill_arrays (dst_frame->data, | ||
251 | dst_frame->linesize, | ||
252 | dst_buffer, | ||
253 | PIX_OUTPUT_FORMAT, | ||
254 | dst_width, | ||
255 | dst_height, | ||
256 | 1); | ||
257 | sws_scale (scaler_ctx, | ||
258 | src_data, | ||
259 | src_stride, | ||
260 | 0, src_height, | ||
261 | dst_frame->data, | ||
262 | dst_frame->linesize); | ||
263 | |||
264 | encoder_output_buffer_size = output_max_size; | ||
265 | if (NULL == (encoder_output_buffer = av_malloc (encoder_output_buffer_size))) | ||
266 | { | ||
267 | #if DEBUG | ||
268 | fprintf (stderr, | ||
269 | "Failed to allocate the encoder output buffer\n"); | ||
270 | #endif | ||
271 | av_free (dst_buffer); | ||
272 | av_frame_free (&dst_frame); | ||
273 | sws_freeContext (scaler_ctx); | ||
274 | return 0; | ||
275 | } | ||
276 | |||
277 | if (NULL == (encoder_codec_ctx = avcodec_alloc_context3 (encoder_codec))) | ||
278 | { | ||
279 | #if DEBUG | ||
280 | fprintf (stderr, | ||
281 | "Failed to allocate the encoder codec context\n"); | ||
282 | #endif | ||
283 | av_free (encoder_output_buffer); | ||
284 | av_free (dst_buffer); | ||
285 | av_frame_free (&dst_frame); | ||
286 | sws_freeContext (scaler_ctx); | ||
287 | return 0; | ||
288 | } | ||
289 | encoder_codec_ctx->width = dst_width; | ||
290 | encoder_codec_ctx->height = dst_height; | ||
291 | #if USE_JPEG | ||
292 | encoder_codec_ctx->bit_rate = pCodecCtx->bit_rate; | ||
293 | encoder_codec_ctx->codec_id = AV_CODEC_ID_MJPEG; | ||
294 | encoder_codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO; | ||
295 | encoder_codec_ctx->time_base.num = pCodecCtx->time_base.num; | ||
296 | encoder_codec_ctx->time_base.den = pCodecCtx->time_base.den; | ||
297 | encoder_codec_ctx->pix_fmt = PIX_OUTPUT_FORMAT; | ||
298 | #else | ||
299 | encoder_codec_ctx->pix_fmt = PIX_OUTPUT_FORMAT; | ||
300 | #endif | ||
301 | |||
302 | opts = NULL; | ||
303 | if (avcodec_open2 (encoder_codec_ctx, encoder_codec, &opts) < 0) | ||
304 | { | ||
305 | #if DEBUG | ||
306 | fprintf (stderr, | ||
307 | "Failed to open the encoder\n"); | ||
308 | #endif | ||
309 | avcodec_free_context (&encoder_codec_ctx); | ||
310 | av_free (encoder_output_buffer); | ||
311 | av_free (dst_buffer); | ||
312 | av_frame_free (&dst_frame); | ||
313 | sws_freeContext (scaler_ctx); | ||
314 | return 0; | ||
315 | } | ||
316 | |||
317 | |||
318 | #ifdef USE_JPEG | ||
319 | #if FF_API_MPV_OPT | ||
320 | encoder_codec_ctx->mb_lmin = encoder_codec_ctx->lmin = | ||
321 | encoder_codec_ctx->qmin * FF_QP2LAMBDA; | ||
322 | encoder_codec_ctx->mb_lmax = encoder_codec_ctx->lmax = | ||
323 | encoder_codec_ctx->qmax * FF_QP2LAMBDA; | ||
324 | #else | ||
325 | encoder_codec_ctx->mb_lmin = encoder_codec_ctx->qmin * FF_QP2LAMBDA; | ||
326 | encoder_codec_ctx->mb_lmax = encoder_codec_ctx->qmax * FF_QP2LAMBDA; | ||
327 | #endif | ||
328 | encoder_codec_ctx->flags = AV_CODEC_FLAG_QSCALE; | ||
329 | encoder_codec_ctx->global_quality = encoder_codec_ctx->qmin * FF_QP2LAMBDA; | ||
330 | |||
331 | dst_frame->pts = 1; | ||
332 | dst_frame->quality = encoder_codec_ctx->global_quality; | ||
333 | #endif | ||
334 | |||
335 | err = avcodec_encode_video2 (encoder_codec_ctx, | ||
336 | &pkt, | ||
337 | dst_frame, &gotPacket); | ||
338 | |||
339 | if (err < 0) | ||
340 | goto cleanup; | ||
341 | err = pkt.size; | ||
342 | memcpy (encoder_output_buffer,pkt.data, pkt.size); | ||
343 | |||
344 | av_packet_unref (&pkt); | ||
345 | cleanup: | ||
346 | av_dict_free (&opts); | ||
347 | avcodec_close (encoder_codec_ctx); | ||
348 | avcodec_free_context (&encoder_codec_ctx); | ||
349 | av_free (dst_buffer); | ||
350 | av_frame_free (&dst_frame); | ||
351 | sws_freeContext (scaler_ctx); | ||
352 | *output_data = encoder_output_buffer; | ||
353 | |||
354 | return err < 0 ? 0 : err; | ||
355 | } | ||
356 | |||
357 | |||
358 | /** | ||
359 | * calculate the thumbnail dimensions, taking pixel aspect into account | ||
360 | * | ||
361 | * @param src_width source image width | ||
362 | * @param src_height source image height | ||
363 | * @param src_sar_num | ||
364 | * @param src_sar_den | ||
365 | * @param dst_width desired thumbnail width (set) | ||
366 | * @param dst_height desired thumbnail height (set) | ||
367 | */ | ||
368 | static void | ||
369 | calculate_thumbnail_dimensions (int src_width, | ||
370 | int src_height, | ||
371 | int src_sar_num, | ||
372 | int src_sar_den, | ||
373 | int *dst_width, | ||
374 | int *dst_height) | ||
375 | { | ||
376 | if ( (src_sar_num <= 0) || (src_sar_den <= 0) ) | ||
377 | { | ||
378 | src_sar_num = 1; | ||
379 | src_sar_den = 1; | ||
380 | } | ||
381 | if ((src_width * src_sar_num) / src_sar_den > src_height) | ||
382 | { | ||
383 | *dst_width = MAX_THUMB_DIMENSION; | ||
384 | *dst_height = (*dst_width * src_height) | ||
385 | / ((src_width * src_sar_num) / src_sar_den); | ||
386 | } | ||
387 | else | ||
388 | { | ||
389 | *dst_height = MAX_THUMB_DIMENSION; | ||
390 | *dst_width = (*dst_height | ||
391 | * ((src_width * src_sar_num) / src_sar_den)) | ||
392 | / src_height; | ||
393 | } | ||
394 | if (*dst_width < 8) | ||
395 | *dst_width = 8; | ||
396 | if (*dst_height < 1) | ||
397 | *dst_height = 1; | ||
398 | #if DEBUG | ||
399 | fprintf (stderr, | ||
400 | "Thumbnail dimensions: %d %d\n", | ||
401 | *dst_width, *dst_height); | ||
402 | #endif | ||
403 | } | ||
404 | |||
405 | |||
406 | #define ENUM_CODEC_ID enum AVCodecID | ||
407 | |||
408 | /** | ||
409 | * Perform thumbnailing when the input is an image. | ||
410 | * | ||
411 | * @param image_codec_id ffmpeg codec for the image format | ||
412 | * @param ec extraction context to use | ||
413 | */ | ||
414 | static void | ||
415 | extract_image (ENUM_CODEC_ID image_codec_id, | ||
416 | struct EXTRACTOR_ExtractContext *ec) | ||
417 | { | ||
418 | AVDictionary *opts; | ||
419 | AVCodecContext *codec_ctx; | ||
420 | AVCodec *codec; | ||
421 | AVPacket avpkt; | ||
422 | AVFrame *frame; | ||
423 | uint8_t *encoded_thumbnail; | ||
424 | int thumb_width; | ||
425 | int thumb_height; | ||
426 | int err; | ||
427 | int frame_finished; | ||
428 | ssize_t iret; | ||
429 | void *data; | ||
430 | unsigned char padded_data[PADDED_BUFFER_SIZE]; | ||
431 | |||
432 | if (NULL == (codec = avcodec_find_decoder (image_codec_id))) | ||
433 | { | ||
434 | #if DEBUG | ||
435 | fprintf (stderr, | ||
436 | "No suitable codec found\n"); | ||
437 | #endif | ||
438 | return; | ||
439 | } | ||
440 | if (NULL == (codec_ctx = avcodec_alloc_context3 (codec))) | ||
441 | { | ||
442 | #if DEBUG | ||
443 | fprintf (stderr, | ||
444 | "Failed to allocate codec context\n"); | ||
445 | #endif | ||
446 | return; | ||
447 | } | ||
448 | opts = NULL; | ||
449 | if (0 != avcodec_open2 (codec_ctx, codec, &opts)) | ||
450 | { | ||
451 | #if DEBUG | ||
452 | fprintf (stderr, | ||
453 | "Failed to open image codec\n"); | ||
454 | #endif | ||
455 | avcodec_free_context (&codec_ctx); | ||
456 | return; | ||
457 | } | ||
458 | av_dict_free (&opts); | ||
459 | frame = av_frame_alloc (); | ||
460 | if (NULL == frame) | ||
461 | { | ||
462 | #if DEBUG | ||
463 | fprintf (stderr, | ||
464 | "Failed to allocate frame\n"); | ||
465 | #endif | ||
466 | avcodec_close (codec_ctx); | ||
467 | avcodec_free_context (&codec_ctx); | ||
468 | return; | ||
469 | } | ||
470 | |||
471 | frame_finished = 0; | ||
472 | while (! frame_finished) | ||
473 | { | ||
474 | if (0 >= (iret = ec->read (ec->cls, | ||
475 | &data, | ||
476 | BUFFER_SIZE))) | ||
477 | break; | ||
478 | memcpy (padded_data, data, iret); | ||
479 | memset (&padded_data[iret], 0, PADDED_BUFFER_SIZE - iret); | ||
480 | av_init_packet (&avpkt); | ||
481 | avpkt.data = padded_data; | ||
482 | avpkt.size = iret; | ||
483 | avcodec_decode_video2 (codec_ctx, frame, &frame_finished, &avpkt); | ||
484 | } | ||
485 | if (! frame_finished) | ||
486 | { | ||
487 | #if DEBUG | ||
488 | fprintf (stderr, | ||
489 | "Failed to decode a complete frame\n"); | ||
490 | #endif | ||
491 | av_frame_free (&frame); | ||
492 | avcodec_close (codec_ctx); | ||
493 | avcodec_free_context (&codec_ctx); | ||
494 | return; | ||
495 | } | ||
496 | calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height, | ||
497 | codec_ctx->sample_aspect_ratio.num, | ||
498 | codec_ctx->sample_aspect_ratio.den, | ||
499 | &thumb_width, &thumb_height); | ||
500 | |||
501 | err = create_thumbnail (codec_ctx, codec_ctx->width, codec_ctx->height, | ||
502 | frame->linesize, codec_ctx->pix_fmt, | ||
503 | (const uint8_t *const*) frame->data, | ||
504 | thumb_width, thumb_height, | ||
505 | &encoded_thumbnail, MAX_THUMB_BYTES); | ||
506 | if (err > 0) | ||
507 | { | ||
508 | ec->proc (ec->cls, | ||
509 | "thumbnailffmpeg", | ||
510 | EXTRACTOR_METATYPE_THUMBNAIL, | ||
511 | EXTRACTOR_METAFORMAT_BINARY, | ||
512 | "image/png", | ||
513 | (const char*) encoded_thumbnail, | ||
514 | err); | ||
515 | |||
516 | #if OUTPUT_FILE | ||
517 | FILE *f; | ||
518 | #ifdef USE_JPEG | ||
519 | f = fopen ("thumb.jpg", "wb"); | ||
520 | #else | ||
521 | f = fopen ("thumb.png", "wb"); | ||
522 | #endif | ||
523 | if (! f) | ||
524 | { | ||
525 | fprintf (stderr, "Could not open %s\n", "file"); | ||
526 | exit (1); | ||
527 | } | ||
528 | |||
529 | fwrite (encoded_thumbnail, 1, err, f); | ||
530 | fclose (f); | ||
531 | |||
532 | #endif | ||
533 | |||
534 | |||
535 | av_free (encoded_thumbnail); | ||
536 | } | ||
537 | av_frame_free (&frame); | ||
538 | avcodec_close (codec_ctx); | ||
539 | avcodec_free_context (&codec_ctx); | ||
540 | } | ||
541 | |||
542 | |||
543 | /** | ||
544 | * Perform thumbnailing when the input is a video | ||
545 | * | ||
546 | * @param ec extraction context to use | ||
547 | */ | ||
548 | static void | ||
549 | extract_video (struct EXTRACTOR_ExtractContext *ec) | ||
550 | { | ||
551 | AVPacket packet; | ||
552 | AVIOContext *io_ctx; | ||
553 | struct AVFormatContext *format_ctx; | ||
554 | AVCodecContext *codec_ctx; | ||
555 | AVCodecParameters *codecpar; | ||
556 | AVCodec *codec; | ||
557 | AVDictionary *options; | ||
558 | AVFrame *frame; | ||
559 | uint8_t *encoded_thumbnail; | ||
560 | int video_stream_index; | ||
561 | int thumb_width; | ||
562 | int thumb_height; | ||
563 | int i; | ||
564 | int err; | ||
565 | int frame_finished; | ||
566 | unsigned char *iob; | ||
567 | int duration; | ||
568 | |||
569 | if (NULL == (iob = av_malloc (16 * 1024))) | ||
570 | return; | ||
571 | if (NULL == (io_ctx = avio_alloc_context (iob, | ||
572 | 16 * 1024, | ||
573 | 0, ec, | ||
574 | &read_cb, | ||
575 | NULL /* no writing */, | ||
576 | &seek_cb))) | ||
577 | { | ||
578 | av_free (iob); | ||
579 | return; | ||
580 | } | ||
581 | if (NULL == (format_ctx = avformat_alloc_context ())) | ||
582 | { | ||
583 | av_free (io_ctx); | ||
584 | return; | ||
585 | } | ||
586 | format_ctx->pb = io_ctx; | ||
587 | options = NULL; | ||
588 | if (0 != avformat_open_input (&format_ctx, "<no file>", NULL, &options)) | ||
589 | { | ||
590 | av_free (io_ctx); | ||
591 | return; | ||
592 | } | ||
593 | av_dict_free (&options); | ||
594 | if (0 > avformat_find_stream_info (format_ctx, NULL)) | ||
595 | { | ||
596 | #if DEBUG | ||
597 | fprintf (stderr, | ||
598 | "Failed to read stream info\n"); | ||
599 | #endif | ||
600 | avformat_close_input (&format_ctx); | ||
601 | av_free (io_ctx); | ||
602 | return; | ||
603 | } | ||
604 | codec = NULL; | ||
605 | codec_ctx = NULL; | ||
606 | video_stream_index = -1; | ||
607 | for (i = 0; i<format_ctx->nb_streams; i++) | ||
608 | { | ||
609 | codecpar = format_ctx->streams[i]->codecpar; | ||
610 | codec_ctx = format_ctx->streams[i]->codec; | ||
611 | if (AVMEDIA_TYPE_VIDEO != codec_ctx->codec_type) | ||
612 | continue; | ||
613 | if (NULL == (codec = avcodec_find_decoder (codecpar->codec_id))) | ||
614 | continue; | ||
615 | options = NULL; | ||
616 | if (0 != (err = avcodec_open2 (codec_ctx, codec, &options))) | ||
617 | { | ||
618 | codec = NULL; | ||
619 | continue; | ||
620 | } | ||
621 | av_dict_free (&options); | ||
622 | video_stream_index = i; | ||
623 | break; | ||
624 | } | ||
625 | if ( (-1 == video_stream_index) || | ||
626 | (0 == codec_ctx->width) || | ||
627 | (0 == codec_ctx->height) ) | ||
628 | { | ||
629 | #if DEBUG | ||
630 | fprintf (stderr, | ||
631 | "No video streams or no suitable codec found\n"); | ||
632 | #endif | ||
633 | if (NULL != codec) | ||
634 | avcodec_close (codec_ctx); | ||
635 | avformat_close_input (&format_ctx); | ||
636 | av_free (io_ctx); | ||
637 | return; | ||
638 | } | ||
639 | |||
640 | frame = av_frame_alloc (); | ||
641 | if (NULL == frame) | ||
642 | { | ||
643 | #if DEBUG | ||
644 | fprintf (stderr, | ||
645 | "Failed to allocate frame\n"); | ||
646 | #endif | ||
647 | avcodec_close (codec_ctx); | ||
648 | avformat_close_input (&format_ctx); | ||
649 | av_free (io_ctx); | ||
650 | return; | ||
651 | } | ||
652 | |||
653 | if (format_ctx->duration == AV_NOPTS_VALUE) | ||
654 | { | ||
655 | duration = -1; | ||
656 | #if DEBUG | ||
657 | fprintf (stderr, | ||
658 | "Duration unknown\n"); | ||
659 | #endif | ||
660 | } | ||
661 | else | ||
662 | { | ||
663 | duration = format_ctx->duration; | ||
664 | } | ||
665 | |||
666 | /* if duration is known, seek to first tried, | ||
667 | * else use 10 sec into stream */ | ||
668 | |||
669 | if (-1 != duration) | ||
670 | err = av_seek_frame (format_ctx, -1, (duration / 3), 0); | ||
671 | else | ||
672 | err = av_seek_frame (format_ctx, -1, 10 * AV_TIME_BASE, 0); | ||
673 | |||
674 | if (err >= 0) | ||
675 | avcodec_flush_buffers (codec_ctx); | ||
676 | frame_finished = 0; | ||
677 | |||
678 | while (1) | ||
679 | { | ||
680 | err = av_read_frame (format_ctx, &packet); | ||
681 | if (err < 0) | ||
682 | break; | ||
683 | if (packet.stream_index == video_stream_index) | ||
684 | { | ||
685 | avcodec_decode_video2 (codec_ctx, | ||
686 | frame, | ||
687 | &frame_finished, | ||
688 | &packet); | ||
689 | if (frame_finished && frame->key_frame) | ||
690 | { | ||
691 | av_packet_unref (&packet); | ||
692 | break; | ||
693 | } | ||
694 | } | ||
695 | av_packet_unref (&packet); | ||
696 | } | ||
697 | |||
698 | if (! frame_finished) | ||
699 | { | ||
700 | #if DEBUG | ||
701 | fprintf (stderr, | ||
702 | "Failed to decode a complete frame\n"); | ||
703 | #endif | ||
704 | av_frame_free (&frame); | ||
705 | avcodec_close (codec_ctx); | ||
706 | avformat_close_input (&format_ctx); | ||
707 | av_free (io_ctx); | ||
708 | return; | ||
709 | } | ||
710 | calculate_thumbnail_dimensions (codec_ctx->width, codec_ctx->height, | ||
711 | codec_ctx->sample_aspect_ratio.num, | ||
712 | codec_ctx->sample_aspect_ratio.den, | ||
713 | &thumb_width, &thumb_height); | ||
714 | |||
715 | err = create_thumbnail (codec_ctx, codec_ctx->width, codec_ctx->height, | ||
716 | frame->linesize, codec_ctx->pix_fmt, | ||
717 | (const uint8_t*const *) frame->data, | ||
718 | thumb_width, thumb_height, | ||
719 | &encoded_thumbnail, MAX_THUMB_BYTES); | ||
720 | if (err > 0) | ||
721 | { | ||
722 | ec->proc (ec->cls, | ||
723 | "thumbnailffmpeg", | ||
724 | EXTRACTOR_METATYPE_THUMBNAIL, | ||
725 | EXTRACTOR_METAFORMAT_BINARY, | ||
726 | "image/png", | ||
727 | (const char*) encoded_thumbnail, | ||
728 | err); | ||
729 | #if OUTPUT_FILE | ||
730 | FILE *f; | ||
731 | #ifdef USE_JPEG | ||
732 | f = fopen ("thumb.jpg", "wb"); | ||
733 | #else | ||
734 | f = fopen ("thumb.png", "wb"); | ||
735 | #endif | ||
736 | if (! f) | ||
737 | { | ||
738 | fprintf (stderr, "Could not open %s\n", "file"); | ||
739 | exit (1); | ||
740 | } | ||
741 | |||
742 | fwrite (encoded_thumbnail, 1, err, f); | ||
743 | fclose (f); | ||
744 | #endif | ||
745 | av_free (encoded_thumbnail); | ||
746 | } | ||
747 | av_frame_free (&frame); | ||
748 | avcodec_close (codec_ctx); | ||
749 | avformat_close_input (&format_ctx); | ||
750 | av_free (io_ctx); | ||
751 | } | ||
752 | |||
753 | |||
754 | /** | ||
755 | * Pair of mime type and ffmpeg decoder ID. | ||
756 | */ | ||
757 | struct MIMEToDecoderMapping | ||
758 | { | ||
759 | /** | ||
760 | * String for a mime type. | ||
761 | */ | ||
762 | const char *mime_type; | ||
763 | |||
764 | /** | ||
765 | * Corresponding ffmpeg decoder ID. | ||
766 | */ | ||
767 | ENUM_CODEC_ID codec_id; | ||
768 | }; | ||
769 | |||
770 | |||
771 | /** | ||
772 | * map MIME image types to an ffmpeg decoder | ||
773 | */ | ||
774 | static const struct MIMEToDecoderMapping m2d_map[] = { | ||
775 | |||
776 | #if LIBAVCODEC_BUILD >= AV_VERSION_INT (54,25,0) | ||
777 | { "image/x-bmp", AV_CODEC_ID_BMP }, | ||
778 | { "image/gif", AV_CODEC_ID_GIF }, | ||
779 | { "image/jpeg", AV_CODEC_ID_MJPEG }, | ||
780 | { "image/png", AV_CODEC_ID_PNG }, | ||
781 | { "image/x-png", AV_CODEC_ID_PNG }, | ||
782 | { "image/x-portable-pixmap", AV_CODEC_ID_PPM }, | ||
783 | { NULL, AV_CODEC_ID_NONE } | ||
784 | #else | ||
785 | { "image/x-bmp", CODEC_ID_BMP }, | ||
786 | { "image/gif", CODEC_ID_GIF }, | ||
787 | { "image/jpeg", CODEC_ID_MJPEG }, | ||
788 | { "image/png", CODEC_ID_PNG }, | ||
789 | { "image/x-png", CODEC_ID_PNG }, | ||
790 | { "image/x-portable-pixmap", CODEC_ID_PPM }, | ||
791 | { NULL, CODEC_ID_NONE } | ||
792 | #endif | ||
793 | |||
794 | }; | ||
795 | |||
796 | |||
797 | /** | ||
798 | * Main method for the ffmpeg-thumbnailer plugin. | ||
799 | * | ||
800 | * @param ec extraction context | ||
801 | */ | ||
802 | void | ||
803 | EXTRACTOR_thumbnailffmpeg_extract_method (struct EXTRACTOR_ExtractContext *ec) | ||
804 | { | ||
805 | unsigned int i; | ||
806 | ssize_t iret; | ||
807 | void *data; | ||
808 | const char *mime; | ||
809 | |||
810 | if (-1 == (iret = ec->read (ec->cls, | ||
811 | &data, | ||
812 | 16 * 1024))) | ||
813 | return; | ||
814 | if (NULL == (mime = magic_buffer (magic, data, iret))) | ||
815 | return; | ||
816 | if (0 != ec->seek (ec->cls, 0, SEEK_SET)) | ||
817 | return; | ||
818 | for (i = 0; NULL != m2d_map[i].mime_type; i++) | ||
819 | if (0 == strcmp (m2d_map[i].mime_type, mime)) | ||
820 | { | ||
821 | extract_image (m2d_map[i].codec_id, ec); | ||
822 | return; | ||
823 | } | ||
824 | extract_video (ec); | ||
825 | } | ||
826 | |||
827 | |||
828 | /** | ||
829 | * This plugin sometimes is installed under the alias 'thumbnail'. | ||
830 | * So we need to provide a second entry method. | ||
831 | * | ||
832 | * @param ec extraction context | ||
833 | */ | ||
834 | void | ||
835 | EXTRACTOR_thumbnail_extract_method (struct EXTRACTOR_ExtractContext *ec) | ||
836 | { | ||
837 | EXTRACTOR_thumbnailffmpeg_extract_method (ec); | ||
838 | } | ||
839 | |||
840 | |||
841 | /** | ||
842 | * Log callback. Does nothing. | ||
843 | * | ||
844 | * @param ptr NULL | ||
845 | * @param level log level | ||
846 | * @param format format string | ||
847 | * @param ap arguments for format | ||
848 | */ | ||
849 | static void | ||
850 | thumbnailffmpeg_av_log_callback (void*ptr, | ||
851 | int level, | ||
852 | const char *format, | ||
853 | va_list ap) | ||
854 | { | ||
855 | #if DEBUG | ||
856 | vfprintf (stderr, format, ap); | ||
857 | #endif | ||
858 | } | ||
859 | |||
860 | |||
861 | /** | ||
862 | * Initialize av-libs and load magic file. | ||
863 | */ | ||
864 | void __attribute__ ((constructor)) | ||
865 | thumbnailffmpeg_lib_init (void) | ||
866 | { | ||
867 | av_log_set_callback (&thumbnailffmpeg_av_log_callback); | ||
868 | magic = magic_open (MAGIC_MIME_TYPE); | ||
869 | if (0 != magic_load (magic, NULL)) | ||
870 | { | ||
871 | /* FIXME: how to deal with errors? */ | ||
872 | } | ||
873 | } | ||
874 | |||
875 | |||
876 | /** | ||
877 | * Destructor for the library, cleans up. | ||
878 | */ | ||
879 | void __attribute__ ((destructor)) | ||
880 | thumbnailffmpeg_ltdl_fini () | ||
881 | { | ||
882 | if (NULL != magic) | ||
883 | { | ||
884 | magic_close (magic); | ||
885 | magic = NULL; | ||
886 | } | ||
887 | } | ||
888 | |||
889 | |||
890 | /* end of thumbnailffmpeg_extractor.c */ | ||