• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2006 David A. Schleef ds@schleef.org
3  * Copyright (C) 2019 Cesar Fabian Orccon Chipana
4  * Copyright (C) 2020 Thibault Saunier <tsaunier@igalia.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
19  * Boston, MA 02110-1335, USA.
20  */
21 
22 /**
23  * SECTION:element-imagesequencesrc
24  *
25  * Stream image sequences from image files.
26  *
27  * ```
28  * gst-launch-1.0 imagesequencesrc location=image-%05d.jpg start-index=1 stop-index=50 framerate=24/1 ! decodebin ! videoconvert ! autovideosink
29  * ```
30  *
31  * This elements implements the #GstURIHandler interface meaning that you can use it with playbin,
32  * (make sure to quote the URI for the filename pattern, like: `%2505d` instead of the `%05d` you would use
33  * when dealing with the location).
34  *
35  * Note that you can pass the #imagesequencesrc:framerate, #imagesequencesrc:start-index and #imagesequencesrc:stop-index
36  * properties directly in the URI using its 'query' component, for example:
37  *
38  * ```
39  * gst-launch-1.0 playbin uri="imagesequence://path/to/image-%2505d.jpeg?start-index=0&framerate=30/1"
40  * ```
41  */
42 
43 #ifdef HAVE_CONFIG_H
44 #  include "config.h"
45 #endif
46 
47 #include <gst/gst.h>
48 #include <gst/base/gsttypefindhelper.h>
49 
50 #include "gstimagesequencesrc.h"
51 
52 #define LOCK(self) (g_rec_mutex_lock (&self->fields_lock))
53 #define UNLOCK(self) (g_rec_mutex_unlock (&self->fields_lock))
54 
55 static GstFlowReturn gst_image_sequence_src_create (GstPushSrc * src,
56     GstBuffer ** buffer);
57 
58 
59 static void gst_image_sequence_src_set_property (GObject * object,
60     guint prop_id, const GValue * value, GParamSpec * pspec);
61 static void gst_image_sequence_src_get_property (GObject * object,
62     guint prop_id, GValue * value, GParamSpec * pspec);
63 static GstCaps *gst_image_sequence_src_getcaps (GstBaseSrc * src,
64     GstCaps * filter);
65 static gboolean gst_image_sequence_src_query (GstBaseSrc * src,
66     GstQuery * query);
67 static void gst_image_sequence_src_set_caps (GstImageSequenceSrc * self,
68     GstCaps * caps);
69 static void gst_image_sequence_src_set_duration (GstImageSequenceSrc * self);
70 static gint gst_image_sequence_src_count_frames (GstImageSequenceSrc * self,
71     gboolean can_read);
72 
73 
74 static GstStaticPadTemplate gst_image_sequence_src_pad_template =
75 GST_STATIC_PAD_TEMPLATE ("src",
76     GST_PAD_SRC,
77     GST_PAD_ALWAYS,
78     GST_STATIC_CAPS_ANY);
79 
80 GST_DEBUG_CATEGORY_STATIC (gst_image_sequence_src_debug);
81 #define GST_CAT_DEFAULT gst_image_sequence_src_debug
82 
83 enum
84 {
85   PROP_0,
86   PROP_LOCATION,
87   PROP_START_INDEX,
88   PROP_STOP_INDEX,
89   PROP_FRAMERATE
90 };
91 
92 #define DEFAULT_LOCATION "%05d"
93 #define DEFAULT_START_INDEX 0
94 #define DEFAULT_STOP_INDEX -1
95 #define DEFAULT_FRAMERATE 30
96 
97 /* Call with LOCK taken */
98 static gboolean
gst_image_sequence_src_set_location(GstImageSequenceSrc * self,const gchar * location)99 gst_image_sequence_src_set_location (GstImageSequenceSrc * self,
100     const gchar * location)
101 {
102   g_free (self->path);
103   if (location != NULL)
104     self->path = g_strdup (location);
105   else
106     self->path = NULL;
107 
108   return TRUE;
109 }
110 
111 /*** GSTURIHANDLER INTERFACE *************************************************/
112 
113 static GstURIType
gst_image_sequence_src_uri_get_type(GType type)114 gst_image_sequence_src_uri_get_type (GType type)
115 {
116   return GST_URI_SRC;
117 }
118 
119 static const gchar *const *
gst_image_sequence_src_uri_get_protocols(GType type)120 gst_image_sequence_src_uri_get_protocols (GType type)
121 {
122   static const gchar *protocols[] = { "imagesequence", NULL };
123 
124   return protocols;
125 }
126 
127 static gchar *
gst_image_sequence_src_uri_get_uri(GstURIHandler * handler)128 gst_image_sequence_src_uri_get_uri (GstURIHandler * handler)
129 {
130   GstImageSequenceSrc *self = GST_IMAGE_SEQUENCE_SRC (handler);
131   gchar *uri = NULL;
132 
133   LOCK (self);
134   if (self->uri)
135     uri = gst_uri_to_string (self->uri);
136   else if (self->path)
137     uri = gst_uri_construct ("imagesequence", self->path);
138   UNLOCK (self);
139 
140   return uri;
141 }
142 
143 static gboolean
gst_image_sequence_src_uri_set_uri(GstURIHandler * handler,const gchar * uri,GError ** err)144 gst_image_sequence_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
145     GError ** err)
146 {
147   gchar *hostname = NULL, *location = NULL, *tmp;
148   gboolean ret = FALSE;
149   GstImageSequenceSrc *self = GST_IMAGE_SEQUENCE_SRC (handler);
150   GstUri *ruri = gst_uri_from_string (uri);
151   GHashTable *query = NULL;
152 
153   if (!ruri) {
154     g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
155         "imagesequencesrc URI is invalid: '%s'", uri);
156     goto beach;
157   }
158 
159 
160   LOCK (self);
161   g_clear_pointer (&self->uri, gst_uri_unref);
162   self->uri = ruri;
163   tmp = gst_filename_to_uri (gst_uri_get_path (ruri), err);
164   location = g_filename_from_uri (tmp, &hostname, err);
165   g_free (tmp);
166   query = gst_uri_get_query_table (ruri);
167   if (!location || (err != NULL && *err != NULL)) {
168     GST_WARNING_OBJECT (self, "Invalid URI '%s' for imagesequencesrc: %s", uri,
169         (err != NULL && *err != NULL) ? (*err)->message : "unknown error");
170     goto beach;
171   }
172 
173   if (hostname && strcmp (hostname, "localhost")) {
174     /* Only 'localhost' is permitted */
175     GST_WARNING_OBJECT (self, "Invalid hostname '%s' for filesrc", hostname);
176     g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
177         "File URI with invalid hostname '%s'", hostname);
178     goto beach;
179   }
180 #ifdef G_OS_WIN32
181   /* Unfortunately, g_filename_from_uri() doesn't handle some UNC paths
182    * correctly on windows, it leaves them with an extra backslash
183    * at the start if they're of the mozilla-style file://///host/path/file
184    * form. Correct this.
185    */
186   if (location[0] == '\\' && location[1] == '\\' && location[2] == '\\')
187     memmove (location, location + 1, strlen (location + 1) + 1);
188 #endif
189 
190   ret = gst_image_sequence_src_set_location (self, location);
191 
192   if (query) {
193     GHashTableIter iter;
194     gpointer key, value;
195 
196     g_hash_table_iter_init (&iter, query);
197     while (g_hash_table_iter_next (&iter, &key, &value)) {
198       GST_INFO_OBJECT (self, "Setting property from URI: %s=%s", (gchar *) key,
199           (gchar *) value);
200       gst_util_set_object_arg (G_OBJECT (self), key, value);
201     }
202   }
203 
204 beach:
205   UNLOCK (self);
206 
207   g_free (location);
208   g_free (hostname);
209   g_clear_pointer (&query, g_hash_table_unref);
210 
211   return ret;
212 }
213 
214 static void
gst_image_sequence_src_uri_handler_init(gpointer g_iface,gpointer iface_data)215 gst_image_sequence_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
216 {
217   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
218 
219   iface->get_type = gst_image_sequence_src_uri_get_type;
220   iface->get_protocols = gst_image_sequence_src_uri_get_protocols;
221   iface->get_uri = gst_image_sequence_src_uri_get_uri;
222   iface->set_uri = gst_image_sequence_src_uri_set_uri;
223 }
224 
225 #define gst_image_sequence_src_parent_class parent_class
226 #define _do_init \
227   G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_image_sequence_src_uri_handler_init); \
228   GST_DEBUG_CATEGORY_INIT (gst_image_sequence_src_debug, "imagesequencesrc", \
229       0, "imagesequencesrc element");
230 G_DEFINE_TYPE_WITH_CODE (GstImageSequenceSrc, gst_image_sequence_src,
231     GST_TYPE_PUSH_SRC, _do_init);
232 GST_ELEMENT_REGISTER_DEFINE (imagesequencesrc, "imagesequencesrc",
233     GST_RANK_NONE, gst_image_sequence_src_get_type ());
234 
235 static gboolean
is_seekable(GstBaseSrc * src)236 is_seekable (GstBaseSrc * src)
237 {
238   GstImageSequenceSrc *self = GST_IMAGE_SEQUENCE_SRC (src);
239 
240   if ((self->n_frames != 0) && (self->fps_n) && (self->fps_d))
241     return TRUE;
242   return FALSE;
243 }
244 
245 
246 static gboolean
do_seek(GstBaseSrc * bsrc,GstSegment * segment)247 do_seek (GstBaseSrc * bsrc, GstSegment * segment)
248 {
249   GstImageSequenceSrc *self;
250 
251   self = GST_IMAGE_SEQUENCE_SRC (bsrc);
252 
253   self->reverse = segment->rate < 0;
254   if (self->reverse) {
255     segment->time = segment->start;
256   }
257 
258   self->index =
259       self->start_index +
260       segment->position * self->fps_n / (self->fps_d * GST_SECOND);
261 
262   return TRUE;
263 }
264 
265 static void
gst_image_sequence_src_finalize(GObject * object)266 gst_image_sequence_src_finalize (GObject * object)
267 {
268   GstImageSequenceSrc *self = GST_IMAGE_SEQUENCE_SRC (object);
269 
270   g_clear_pointer (&self->path, g_free);
271   g_rec_mutex_clear (&self->fields_lock);
272 
273   G_OBJECT_CLASS (parent_class)->finalize (object);
274 }
275 
276 static void
gst_image_sequence_src_dispose(GObject * object)277 gst_image_sequence_src_dispose (GObject * object)
278 {
279   GstImageSequenceSrc *self = GST_IMAGE_SEQUENCE_SRC (object);
280 
281   gst_clear_caps (&self->caps);
282   g_clear_pointer (&self->uri, gst_uri_unref);
283 
284   G_OBJECT_CLASS (parent_class)->dispose (object);
285 }
286 
287 static void
gst_image_sequence_src_class_init(GstImageSequenceSrcClass * klass)288 gst_image_sequence_src_class_init (GstImageSequenceSrcClass * klass)
289 {
290   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
291   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
292   GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
293   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
294 
295   gobject_class->set_property = gst_image_sequence_src_set_property;
296   gobject_class->get_property = gst_image_sequence_src_get_property;
297 
298 
299   g_object_class_install_property (gobject_class, PROP_LOCATION,
300       g_param_spec_string ("location", "File Location",
301           "Pattern to create file names of input files.  File names are "
302           "created by calling sprintf() with the pattern and the current "
303           "index.", DEFAULT_LOCATION,
304           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
305   g_object_class_install_property (gobject_class, PROP_START_INDEX,
306       g_param_spec_int ("start-index", "Start Index",
307           "Start value of index.  The initial value of index can be set "
308           "either by setting index or start-index.  When the end of the loop "
309           "is reached, the index will be set to the value start-index.",
310           0, INT_MAX, DEFAULT_START_INDEX,
311           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
312   g_object_class_install_property (gobject_class, PROP_STOP_INDEX,
313       g_param_spec_int ("stop-index", "Stop Index",
314           "Stop value of index.  The special value -1 means no stop.",
315           -1, INT_MAX, DEFAULT_STOP_INDEX,
316           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
317   g_object_class_install_property (gobject_class, PROP_FRAMERATE,
318       gst_param_spec_fraction ("framerate", "Framerate",
319           "The output framerate.",
320           1, 1, G_MAXINT, 1, DEFAULT_FRAMERATE, 1,
321           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
322 
323   gobject_class->finalize = gst_image_sequence_src_finalize;
324   gobject_class->dispose = gst_image_sequence_src_dispose;
325 
326   gstbasesrc_class->get_caps = gst_image_sequence_src_getcaps;
327   gstbasesrc_class->query = gst_image_sequence_src_query;
328   gstbasesrc_class->is_seekable = is_seekable;
329   gstbasesrc_class->do_seek = do_seek;
330 
331   gstpushsrc_class->create = gst_image_sequence_src_create;
332 
333   gst_element_class_add_static_pad_template (gstelement_class,
334       &gst_image_sequence_src_pad_template);
335   gst_element_class_set_static_metadata (gstelement_class,
336       "Image Sequence Source", "Source/File/Video",
337       "Create a video stream from a sequence of image files",
338       "Cesar Fabian Orccon Chipana <cfoch.fabian@gmail.com>\n"
339       "Thibault Saunier <tsaunier@igalia.com>");
340 }
341 
342 static void
gst_image_sequence_src_init(GstImageSequenceSrc * self)343 gst_image_sequence_src_init (GstImageSequenceSrc * self)
344 {
345   GstBaseSrc *bsrc;
346 
347   GST_DEBUG_CATEGORY_INIT (gst_image_sequence_src_debug, "imagesequencesrc", 0,
348       "imagesequencesrc element");
349 
350   bsrc = GST_BASE_SRC (self);
351   gst_base_src_set_format (bsrc, GST_FORMAT_TIME);
352 
353   g_rec_mutex_init (&self->fields_lock);
354   self->start_index = DEFAULT_START_INDEX;
355   self->index = 0;
356   self->stop_index = DEFAULT_STOP_INDEX;
357   self->path = NULL;
358   self->caps = NULL;
359   self->n_frames = 0;
360   self->fps_n = 30;
361   self->fps_d = 1;
362 }
363 
364 static GstCaps *
gst_image_sequence_src_getcaps(GstBaseSrc * src,GstCaps * filter)365 gst_image_sequence_src_getcaps (GstBaseSrc * src, GstCaps * filter)
366 {
367   GstImageSequenceSrc *self = GST_IMAGE_SEQUENCE_SRC (src);
368 
369   GST_DEBUG_OBJECT (self, "returning %" GST_PTR_FORMAT, self->caps);
370 
371   if (filter) {
372     if (self->caps)
373       return gst_caps_intersect_full (filter, self->caps,
374           GST_CAPS_INTERSECT_FIRST);
375     else
376       return gst_caps_ref (filter);
377   }
378 
379   return gst_caps_new_any ();
380 }
381 
382 static gboolean
gst_image_sequence_src_query(GstBaseSrc * bsrc,GstQuery * query)383 gst_image_sequence_src_query (GstBaseSrc * bsrc, GstQuery * query)
384 {
385   gboolean ret;
386   GstImageSequenceSrc *self;
387 
388   self = GST_IMAGE_SEQUENCE_SRC (bsrc);
389 
390   switch (GST_QUERY_TYPE (query)) {
391     case GST_QUERY_DURATION:
392     {
393       GstFormat format;
394 
395       gst_query_parse_duration (query, &format, NULL);
396 
397       switch (format) {
398         case GST_FORMAT_TIME:
399           LOCK (self);
400           if (self->n_frames <= 0) {
401             gst_image_sequence_src_count_frames (self, FALSE);
402             gst_image_sequence_src_set_duration (self);
403           }
404 
405           if (self->n_frames > 0)
406             gst_query_set_duration (query, format, self->duration);
407           UNLOCK (self);
408 
409           ret = TRUE;
410           break;
411         default:
412           ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
413       }
414       break;
415     }
416     default:
417       ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
418       break;
419   }
420 
421   return ret;
422 }
423 
424 static void
gst_image_sequence_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)425 gst_image_sequence_src_set_property (GObject * object, guint prop_id,
426     const GValue * value, GParamSpec * pspec)
427 {
428   GstImageSequenceSrc *self = GST_IMAGE_SEQUENCE_SRC (object);
429 
430   LOCK (self);
431   switch (prop_id) {
432     case PROP_LOCATION:
433       gst_image_sequence_src_set_location (self, g_value_get_string (value));
434       break;
435     case PROP_START_INDEX:
436       self->start_index = g_value_get_int (value);
437       gst_image_sequence_src_count_frames (self, FALSE);
438       break;
439     case PROP_STOP_INDEX:
440       self->stop_index = g_value_get_int (value);
441       gst_image_sequence_src_count_frames (self, FALSE);
442       break;
443     case PROP_FRAMERATE:
444       self->fps_n = gst_value_get_fraction_numerator (value);
445       self->fps_d = gst_value_get_fraction_denominator (value);
446       break;
447     default:
448       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
449       break;
450   }
451   UNLOCK (self);
452 }
453 
454 static void
gst_image_sequence_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)455 gst_image_sequence_src_get_property (GObject * object, guint prop_id,
456     GValue * value, GParamSpec * pspec)
457 {
458   GstImageSequenceSrc *self = GST_IMAGE_SEQUENCE_SRC (object);
459 
460   LOCK (self);
461   switch (prop_id) {
462     case PROP_LOCATION:
463       g_value_set_string (value, self->path);
464       break;
465     case PROP_START_INDEX:
466       g_value_set_int (value, self->start_index);
467       break;
468     case PROP_STOP_INDEX:
469       g_value_set_int (value, self->stop_index);
470       break;
471     case PROP_FRAMERATE:
472       self->fps_n = gst_value_get_fraction_numerator (value);
473       self->fps_d = gst_value_get_fraction_denominator (value);
474       GST_DEBUG_OBJECT (self, "Set (framerate) property to (%d/%d)",
475           self->fps_n, self->fps_d);
476       break;
477     default:
478       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
479       break;
480   }
481   UNLOCK (self);
482 }
483 
484 /* Call with LOCK */
485 static gint
gst_image_sequence_src_count_frames(GstImageSequenceSrc * self,gboolean can_read)486 gst_image_sequence_src_count_frames (GstImageSequenceSrc * self,
487     gboolean can_read)
488 {
489   if (can_read && self->stop_index < 0 && self->path) {
490     gint i;
491 
492     for (i = self->start_index;; i++) {
493       gchar *filename = g_strdup_printf (self->path, i);
494 
495       if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
496         i--;
497         g_free (filename);
498         break;
499       }
500 
501       g_free (filename);
502     }
503     if (i > self->start_index)
504       self->stop_index = i;
505   }
506 
507   if (self->stop_index >= self->start_index)
508     self->n_frames = self->stop_index - self->start_index + 1;
509   return self->n_frames;
510 }
511 
512 static void
gst_image_sequence_src_set_caps(GstImageSequenceSrc * self,GstCaps * caps)513 gst_image_sequence_src_set_caps (GstImageSequenceSrc * self, GstCaps * caps)
514 {
515   GstCaps *new_caps;
516 
517   g_assert (caps != NULL);
518   new_caps = gst_caps_copy (caps);
519 
520   if (self->n_frames > 0) {
521     GValue fps = G_VALUE_INIT;
522     g_value_init (&fps, GST_TYPE_FRACTION);
523     gst_value_set_fraction (&fps, self->fps_n, self->fps_d);
524     gst_caps_set_value (new_caps, "framerate", &fps);
525     g_value_unset (&fps);
526   }
527 
528   gst_caps_replace (&self->caps, new_caps);
529   gst_pad_set_caps (GST_BASE_SRC_PAD (self), new_caps);
530 
531   GST_DEBUG_OBJECT (self, "Setting new caps: %" GST_PTR_FORMAT, new_caps);
532 }
533 
534 /* Call with LOCK */
535 static void
gst_image_sequence_src_set_duration(GstImageSequenceSrc * self)536 gst_image_sequence_src_set_duration (GstImageSequenceSrc * self)
537 {
538   GstClockTime old_duration = self->duration;
539 
540   if (self->n_frames <= 0)
541     return;
542 
543   /* Calculate duration */
544   self->duration =
545       gst_util_uint64_scale (GST_SECOND * self->n_frames, self->fps_d,
546       self->fps_n);
547 
548   if (self->duration != old_duration) {
549     UNLOCK (self);
550     gst_element_post_message (GST_ELEMENT (self),
551         gst_message_new_duration_changed (GST_OBJECT (self)));
552     LOCK (self);
553   }
554 }
555 
556 /* Call with LOCK */
557 static gchar *
gst_image_sequence_src_get_filename(GstImageSequenceSrc * self)558 gst_image_sequence_src_get_filename (GstImageSequenceSrc * self)
559 {
560   gchar *filename;
561 
562   GST_DEBUG ("Reading filename at index %d.", self->index);
563   filename = g_strdup_printf (self->path, self->index);
564 
565   return filename;
566 }
567 
568 static GstFlowReturn
gst_image_sequence_src_create(GstPushSrc * src,GstBuffer ** buffer)569 gst_image_sequence_src_create (GstPushSrc * src, GstBuffer ** buffer)
570 {
571   GstImageSequenceSrc *self;
572   gsize size;
573   gchar *data;
574   gchar *filename;
575   GstBuffer *buf;
576   gboolean ret;
577   GError *error = NULL;
578   gint fps_n, fps_d, start_index, stop_index;
579 
580   self = GST_IMAGE_SEQUENCE_SRC (src);
581 
582   LOCK (self);
583   start_index = self->start_index;
584   stop_index = self->stop_index;
585   if (self->index > stop_index && stop_index > 0) {
586     UNLOCK (self);
587 
588     return GST_FLOW_EOS;
589   }
590 
591   if (self->index < self->start_index)
592     self->index = self->start_index;
593 
594   g_assert (start_index <= self->index &&
595       (self->index <= stop_index || stop_index <= 0));
596 
597   filename = gst_image_sequence_src_get_filename (self);
598   fps_n = self->fps_n;
599   fps_d = self->fps_d;
600   UNLOCK (self);
601 
602   if (!filename)
603     goto handle_error;
604 
605   ret = g_file_get_contents (filename, &data, &size, &error);
606   if (!ret)
607     goto handle_error;
608 
609   buf = gst_buffer_new_wrapped_full (0, data, size, 0, size, NULL, g_free);
610 
611   if (!self->caps) {
612     GstCaps *caps;
613     caps = gst_type_find_helper_for_buffer (NULL, buf, NULL);
614     if (!caps) {
615       GST_ELEMENT_ERROR (self, STREAM, TYPE_NOT_FOUND, (NULL),
616           ("Could not determine image type."));
617 
618       return GST_FLOW_NOT_SUPPORTED;
619     }
620 
621     LOCK (self);
622     gst_image_sequence_src_count_frames (self, TRUE);
623     gst_image_sequence_src_set_duration (self);
624     UNLOCK (self);
625 
626     gst_image_sequence_src_set_caps (self, caps);
627     gst_caps_unref (caps);
628   }
629 
630   GST_BUFFER_PTS (buf) =
631       gst_util_uint64_scale_ceil ((self->index - start_index) * GST_SECOND,
632       fps_d, fps_n);
633   GST_BUFFER_DURATION (buf) = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n);
634   GST_BUFFER_OFFSET (buf) = self->index - start_index;
635   GST_LOG_OBJECT (self, "index: %d, %s - %" GST_PTR_FORMAT, self->index,
636       filename, buf);
637 
638   g_free (filename);
639   *buffer = buf;
640 
641   self->index += self->reverse ? -1 : 1;
642   return GST_FLOW_OK;
643 
644 handle_error:
645   {
646     if (error != NULL) {
647       GST_ELEMENT_ERROR (self, RESOURCE, READ,
648           ("Error while reading from file \"%s\".", filename),
649           ("%s", error->message));
650       g_error_free (error);
651     } else {
652       GST_ELEMENT_ERROR (self, RESOURCE, READ,
653           ("Error while reading from file \"%s\".", filename),
654           ("%s", g_strerror (errno)));
655     }
656     g_free (filename);
657     return GST_FLOW_ERROR;
658   }
659 }
660