thumbnailgst_extractor.c (7817B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2026 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., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file plugins/thumbnailgst_extractor.c 22 * @author Christian Grothoff 23 * @brief this extractor produces a binary encoded thumbnail of a frame 24 * taken from a video, using GStreamer 25 * 26 * This is the GStreamer counterpart of thumbnailffmpeg: it seeks about 27 * 30s into a video and encodes the frame it finds there as a small JPEG 28 * (or PNG). Which of the two is built depends on what was found at 29 * configure time; if both are installed, both will report a thumbnail and 30 * it is up to the application to disable one of them in its plugin 31 * configuration string. 32 * 33 * The decoders and the image encoder are themselves GStreamer plugins, 34 * resolved at run time, so a missing "jpeg" or "playback" plugin cannot 35 * be detected when libextractor is built. We simply report nothing. 36 */ 37 #include "platform.h" 38 #include "extractor.h" 39 #include "mediautil.h" 40 #include "mediagst.h" 41 42 #include <gst/video/video.h> 43 44 45 /** 46 * Scale and encode the raw video @a sample. 47 * 48 * @param sample raw frame to convert 49 * @param opt plugin options (target size and format) 50 * @param timeout how long the conversion may take, in nanoseconds 51 * @return the encoded image, NULL on error 52 */ 53 static GstSample * 54 encode_thumbnail (GstSample *sample, 55 const struct EXTRACTOR_MediaOptions *opt, 56 GstClockTime timeout) 57 { 58 GstCaps *caps; 59 GstCaps *to_caps; 60 GstStructure *s; 61 GstSample *out; 62 gint width; 63 gint height; 64 unsigned int tw; 65 unsigned int th; 66 67 if (NULL == (caps = gst_sample_get_caps (sample))) 68 return NULL; 69 if (NULL == (s = gst_caps_get_structure (caps, 0))) 70 return NULL; 71 if ( (! gst_structure_get_int (s, "width", &width)) || 72 (! gst_structure_get_int (s, "height", &height)) || 73 (0 >= width) || 74 (0 >= height) ) 75 return NULL; 76 if (! EXTRACTOR_media_thumbnail_size_ ((unsigned int) width, 77 (unsigned int) height, 78 opt->size, 79 &tw, 80 &th)) 81 return NULL; 82 to_caps = gst_caps_new_simple (opt->png ? "image/png" : "image/jpeg", 83 "width", G_TYPE_INT, (gint) tw, 84 "height", G_TYPE_INT, (gint) th, 85 NULL); 86 out = gst_video_convert_sample (sample, 87 to_caps, 88 timeout, 89 NULL); 90 gst_caps_unref (to_caps); 91 return out; 92 } 93 94 95 /** 96 * Main method for the gstreamer-thumbnailer plugin. 97 * 98 * @param ec extraction context 99 */ 100 void 101 EXTRACTOR_thumbnailgst_extract_method (struct EXTRACTOR_ExtractContext *ec); 102 103 void 104 EXTRACTOR_thumbnailgst_extract_method (struct EXTRACTOR_ExtractContext *ec) 105 { 106 struct EXTRACTOR_MediaOptions opt; 107 struct EXTRACTOR_MediaDeadline deadline; 108 struct EXTRACTOR_GstIO io; 109 GstElement *pipeline; 110 GstElement *sink; 111 GstSample *sample = NULL; 112 GstSample *thumbnail = NULL; 113 GstBuffer *buffer; 114 GstMapInfo mi; 115 GstClockTime timeout; 116 gint64 duration; 117 gint64 target; 118 119 EXTRACTOR_media_options_parse_ (ec->config, 120 &opt); 121 EXTRACTOR_media_deadline_start_ (&deadline, 122 opt.deadline); 123 if (! EXTRACTOR_media_is_media_ (ec, 0)) 124 return; 125 if (! gst_is_initialized ()) 126 gst_init (NULL, NULL); 127 sink = gst_element_factory_make ("fakesink", NULL); 128 if (NULL != sink) 129 g_object_set (sink, 130 "sync", FALSE, 131 NULL); 132 if (NULL == (pipeline = 133 EXTRACTOR_gst_playbin_ (&io, 134 ec, 135 EXTRACTOR_GST_PLAY_FLAG_VIDEO, 136 sink, 137 gst_element_factory_make ("fakesink", 138 NULL)))) 139 return; 140 if (GST_STATE_CHANGE_FAILURE == 141 gst_element_set_state (pipeline, 142 GST_STATE_PAUSED)) 143 goto cleanup; 144 if (! EXTRACTOR_gst_wait_async_ (pipeline, 145 EXTRACTOR_media_deadline_remaining_ ( 146 &deadline) * GST_MSECOND)) 147 goto cleanup; 148 target = EXTRACTOR_gst_sample_position_ (pipeline, 149 opt.offset, 150 &duration); 151 if (target >= GST_SECOND) 152 { 153 /* a key frame is good enough for a thumbnail and much cheaper than 154 decoding forward to an exact position */ 155 if (gst_element_seek_simple (pipeline, 156 GST_FORMAT_TIME, 157 GST_SEEK_FLAG_FLUSH 158 | GST_SEEK_FLAG_KEY_UNIT, 159 target)) 160 (void) EXTRACTOR_gst_wait_async_ (pipeline, 161 EXTRACTOR_media_deadline_remaining_ ( 162 &deadline) * GST_MSECOND); 163 } 164 g_object_get (sink, 165 "last-sample", 166 &sample, 167 NULL); 168 if (NULL == sample) 169 goto cleanup; 170 timeout = EXTRACTOR_media_deadline_remaining_ (&deadline) * GST_MSECOND; 171 if (0 == timeout) 172 goto cleanup; 173 if (NULL == (thumbnail = encode_thumbnail (sample, 174 &opt, 175 timeout))) 176 goto cleanup; 177 if (NULL == (buffer = gst_sample_get_buffer (thumbnail))) 178 goto cleanup; 179 if (! gst_buffer_map (buffer, &mi, GST_MAP_READ)) 180 goto cleanup; 181 if ( (0 < mi.size) && 182 (EXTRACTOR_MEDIA_MAX_THUMBNAIL >= mi.size) ) 183 ec->proc (ec->cls, 184 "thumbnailgst", 185 EXTRACTOR_METATYPE_THUMBNAIL, 186 EXTRACTOR_METAFORMAT_BINARY, 187 opt.png ? "image/png" : "image/jpeg", 188 (const char *) mi.data, 189 mi.size); 190 gst_buffer_unmap (buffer, &mi); 191 cleanup: 192 if (NULL != thumbnail) 193 gst_sample_unref (thumbnail); 194 if (NULL != sample) 195 gst_sample_unref (sample); 196 gst_element_set_state (pipeline, 197 GST_STATE_NULL); 198 gst_object_unref (pipeline); 199 } 200 201 202 /** 203 * Keep the diagnostics of the media framework out of the output of the 204 * calling application; a decoder complaining about a broken file is not 205 * something the application asked to see. 206 * 207 * ("force-kill" would be tempting here, to get a fresh process for every 208 * file, but the host cannot know about that special without loading the 209 * plugin in-process: it only finds out that the child is gone once it has 210 * already handed it the next file, which is then silently lost.) 211 * 212 * @return special options for this plugin 213 */ 214 const char * 215 EXTRACTOR_thumbnailgst_options (void); 216 217 const char * 218 EXTRACTOR_thumbnailgst_options () 219 { 220 return "close-stderr"; 221 } 222 223 224 /* end of thumbnailgst_extractor.c */