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