configure.ac: Bump requirements of -base (videocrop test case needs this).
[platform/upstream/gst-plugins-good.git] / gst / videocrop / gstvideocrop.c
1 /* GStreamer video frame cropping
2  * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /**
21  * SECTION:element-videocrop
22  * @see_also: GstVideoBox
23  *
24  * <refsect2>
25  * <para>
26  * This element crops video frames, meaning it can remove parts of the
27  * picture on the left, right, top or bottom of the picture and output
28  * a smaller picture than the input picture, with the unwanted parts at the
29  * border removed.
30  * </para>
31  * <para>
32  * The videocrop element is similar to the videobox element, but its main
33  * goal is to support a multitude of formats as efficiently as possible.
34  * Unlike videbox, it cannot add borders to the picture and unlike videbox
35  * it will always output images in exactly the same format as the input image.
36  * </para>
37  * <para>
38  * If there is nothing to crop, the element will operate in pass-through mode.
39  * </para>
40  * <para>
41  * Note that no special efforts are made to handle chroma-subsampled formats
42  * in the case of odd-valued cropping and compensate for sub-unit chroma plane
43  * shifts for such formats in the case where the "left" or "top" property is
44  * set to an odd number. This doesn't matter for most use cases, but it might
45  * matter for yours.
46  * </para>
47  * <title>Example launch line</title>
48  * <para>
49  * <programlisting>
50  * gst-launch -v videotestsrc ! videocrop top=42 left=1 right=4 bottom=0 ! ximagesink
51  * </programlisting>
52  * </para>
53  * </refsect2>
54  */
55
56 /* TODO:
57  *  - for packed formats, we could avoid memcpy() in case crop_left
58  *    and crop_right are 0 and just create a sub-buffer of the input
59  *    buffer
60  */
61
62 #ifdef HAVE_CONFIG_H
63 #include "config.h"
64 #endif
65
66 #include <gst/gst.h>
67 #include <gst/video/video.h>
68
69 #include "gstvideocrop.h"
70
71 #include <string.h>
72
73 GST_DEBUG_CATEGORY_STATIC (videocrop_debug);
74 #define GST_CAT_DEFAULT videocrop_debug
75
76 static const GstElementDetails video_crop_details = GST_ELEMENT_DETAILS ("Crop",
77     "Filter/Effect/Video",
78     "Crops video into a user-defined region",
79     "Tim-Philipp Müller <tim centricular net>");
80
81 enum
82 {
83   ARG_0,
84   ARG_LEFT,
85   ARG_RIGHT,
86   ARG_TOP,
87   ARG_BOTTOM
88 };
89
90 /* the formats we support */
91 #define VIDEO_CROP_CAPS                          \
92   GST_VIDEO_CAPS_RGBx ";"                        \
93   GST_VIDEO_CAPS_xRGB ";"                        \
94   GST_VIDEO_CAPS_BGRx ";"                        \
95   GST_VIDEO_CAPS_xBGR ";"                        \
96   GST_VIDEO_CAPS_RGBA ";"                        \
97   GST_VIDEO_CAPS_ARGB ";"                        \
98   GST_VIDEO_CAPS_BGRA ";"                        \
99   GST_VIDEO_CAPS_ABGR ";"                        \
100   GST_VIDEO_CAPS_RGB ";"                         \
101   GST_VIDEO_CAPS_BGR ";"                         \
102   GST_VIDEO_CAPS_YUV ("AYUV") ";"                \
103   GST_VIDEO_CAPS_YUV ("YUY2") ";"                \
104   GST_VIDEO_CAPS_YUV ("YVYU") ";"                \
105   GST_VIDEO_CAPS_YUV ("UYVY") ";"                \
106   GST_VIDEO_CAPS_YUV ("Y800") ";"                \
107   GST_VIDEO_CAPS_YUV ("I420") ";"                \
108   GST_VIDEO_CAPS_YUV ("YV12") ";"                \
109   GST_VIDEO_CAPS_RGB_16 ";"                      \
110   GST_VIDEO_CAPS_RGB_15
111
112 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
113     GST_PAD_SRC,
114     GST_PAD_ALWAYS,
115     GST_STATIC_CAPS (VIDEO_CROP_CAPS)
116     );
117
118 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
119     GST_PAD_SINK,
120     GST_PAD_ALWAYS,
121     GST_STATIC_CAPS (VIDEO_CROP_CAPS)
122     );
123
124 GST_BOILERPLATE (GstVideoCrop, gst_video_crop, GstBaseTransform,
125     GST_TYPE_BASE_TRANSFORM);
126
127 static void gst_video_crop_set_property (GObject * object, guint prop_id,
128     const GValue * value, GParamSpec * pspec);
129 static void gst_video_crop_get_property (GObject * object, guint prop_id,
130     GValue * value, GParamSpec * pspec);
131
132 static GstCaps *gst_video_crop_transform_caps (GstBaseTransform * trans,
133     GstPadDirection direction, GstCaps * caps);
134 static GstFlowReturn gst_video_crop_transform (GstBaseTransform * trans,
135     GstBuffer * inbuf, GstBuffer * outbuf);
136 static gboolean gst_video_crop_get_unit_size (GstBaseTransform * trans,
137     GstCaps * caps, guint * size);
138 static gboolean gst_video_crop_set_caps (GstBaseTransform * trans,
139     GstCaps * in_caps, GstCaps * outcaps);
140
141 static void
142 gst_video_crop_base_init (gpointer g_class)
143 {
144   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
145
146   gst_element_class_set_details (element_class, &video_crop_details);
147
148   gst_element_class_add_pad_template (element_class,
149       gst_static_pad_template_get (&sink_template));
150   gst_element_class_add_pad_template (element_class,
151       gst_static_pad_template_get (&src_template));
152 }
153
154 static void
155 gst_video_crop_class_init (GstVideoCropClass * klass)
156 {
157   GObjectClass *gobject_class;
158   GstBaseTransformClass *basetransform_class;
159
160   gobject_class = (GObjectClass *) klass;
161   basetransform_class = (GstBaseTransformClass *) klass;
162
163   gobject_class->set_property = gst_video_crop_set_property;
164   gobject_class->get_property = gst_video_crop_get_property;
165
166   g_object_class_install_property (gobject_class, ARG_LEFT,
167       g_param_spec_int ("left", "Left", "Pixels to crop at left",
168           0, G_MAXINT, 0, G_PARAM_READWRITE));
169   g_object_class_install_property (gobject_class, ARG_RIGHT,
170       g_param_spec_int ("right", "Right", "Pixels to crop at right",
171           0, G_MAXINT, 0, G_PARAM_READWRITE));
172   g_object_class_install_property (gobject_class, ARG_TOP,
173       g_param_spec_int ("top", "Top", "Pixels to crop at top",
174           0, G_MAXINT, 0, G_PARAM_READWRITE));
175   g_object_class_install_property (gobject_class, ARG_BOTTOM,
176       g_param_spec_int ("bottom", "Bottom", "Pixels to crop at bottom",
177           0, G_MAXINT, 0, G_PARAM_READWRITE));
178
179   basetransform_class->transform = GST_DEBUG_FUNCPTR (gst_video_crop_transform);
180   basetransform_class->transform_caps =
181       GST_DEBUG_FUNCPTR (gst_video_crop_transform_caps);
182   basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_crop_set_caps);
183   basetransform_class->get_unit_size =
184       GST_DEBUG_FUNCPTR (gst_video_crop_get_unit_size);
185
186   basetransform_class->passthrough_on_same_caps = TRUE;
187 }
188
189 static void
190 gst_video_crop_init (GstVideoCrop * vcrop, GstVideoCropClass * klass)
191 {
192   vcrop->crop_right = 0;
193   vcrop->crop_left = 0;
194   vcrop->crop_top = 0;
195   vcrop->crop_bottom = 0;
196   vcrop->noop = TRUE;
197 }
198
199 static gboolean
200 gst_video_crop_get_image_details_from_caps (GstVideoCrop * vcrop,
201     GstVideoCropImageDetails * details, GstCaps * caps)
202 {
203   GstStructure *structure;
204   gint width, height;
205
206   structure = gst_caps_get_structure (caps, 0);
207   if (!gst_structure_get_int (structure, "width", &width) ||
208       !gst_structure_get_int (structure, "height", &height)) {
209     goto incomplete_format;
210   }
211
212   details->width = width;
213   details->height = height;
214
215   if (gst_structure_has_name (structure, "video/x-raw-rgb")) {
216     gint bpp = 0;
217
218     if (!gst_structure_get_int (structure, "bpp", &bpp) || (bpp & 0x07) != 0)
219       goto incomplete_format;
220
221     details->packed = TRUE;
222     details->bytes_per_pixel = bpp / 8;
223     details->stride = GST_ROUND_UP_4 (width * details->bytes_per_pixel);
224     details->size = details->stride * height;
225   } else if (gst_structure_has_name (structure, "video/x-raw-yuv")) {
226     guint32 format = 0;
227
228     if (!gst_structure_get_fourcc (structure, "format", &format))
229       goto incomplete_format;
230
231     switch (format) {
232       case GST_MAKE_FOURCC ('A', 'Y', 'U', 'V'):
233         details->packed = TRUE;
234         details->bytes_per_pixel = 4;
235         details->stride = GST_ROUND_UP_4 (width * 4);
236         details->size = details->stride * height;
237         break;
238       case GST_MAKE_FOURCC ('Y', 'V', 'Y', 'U'):
239       case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
240       case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
241         details->packed = TRUE;
242         details->bytes_per_pixel = 2;
243         details->stride = GST_ROUND_UP_4 (width * 2);
244         details->size = details->stride * height;
245         break;
246       case GST_MAKE_FOURCC ('Y', '8', '0', '0'):
247         details->packed = TRUE;
248         details->bytes_per_pixel = 1;
249         details->stride = GST_ROUND_UP_4 (width);
250         details->size = details->stride * height;
251         break;
252       case GST_MAKE_FOURCC ('I', '4', '2', '0'):
253       case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):{
254         details->packed = FALSE;
255
256         details->y_stride = GST_ROUND_UP_4 (width);
257         details->u_stride = GST_ROUND_UP_8 (width) / 2;
258         details->v_stride = GST_ROUND_UP_8 (width) / 2;
259
260         /* I420 and YV12 have U/V planes swapped, but doesn't matter for us */
261         details->y_off = 0;
262         details->u_off = 0 + details->y_stride * GST_ROUND_UP_2 (height);
263         details->v_off = details->u_off +
264             details->u_stride * (GST_ROUND_UP_2 (height) / 2);
265         details->size = details->v_off +
266             details->v_stride * (GST_ROUND_UP_2 (height) / 2);
267         break;
268       }
269       default:
270         goto unknown_format;
271     }
272   } else {
273     goto unknown_format;
274   }
275
276   return TRUE;
277
278   /* ERRORS */
279 unknown_format:
280   {
281     GST_ELEMENT_ERROR (vcrop, STREAM, NOT_IMPLEMENTED, (NULL),
282         ("Unsupported format"));
283     return FALSE;
284   }
285
286 incomplete_format:
287   {
288     GST_ELEMENT_ERROR (vcrop, CORE, NEGOTIATION, (NULL),
289         ("Incomplete caps, some required field is missing"));
290     return FALSE;
291   }
292 }
293
294 static gboolean
295 gst_video_crop_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
296     guint * size)
297 {
298   GstVideoCropImageDetails img_details = { 0, };
299   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
300
301   if (!gst_video_crop_get_image_details_from_caps (vcrop, &img_details, caps))
302     return FALSE;
303
304   *size = img_details.size;
305   return TRUE;
306 }
307
308 static void
309 gst_video_crop_transform_packed (GstVideoCrop * vcrop, GstBuffer * inbuf,
310     GstBuffer * outbuf)
311 {
312   guint8 *in_data, *out_data;
313   guint i, dx;
314
315   in_data = GST_BUFFER_DATA (inbuf);
316   out_data = GST_BUFFER_DATA (outbuf);
317
318   in_data += vcrop->crop_top * vcrop->in.stride;
319   in_data += vcrop->crop_left * vcrop->in.bytes_per_pixel;
320
321   dx = vcrop->out.width * vcrop->out.bytes_per_pixel;
322
323   for (i = 0; i < vcrop->out.height; ++i) {
324     memcpy (out_data, in_data, dx);
325     in_data += vcrop->in.stride;
326     out_data += vcrop->out.stride;
327   }
328 }
329
330 static void
331 gst_video_crop_transform_planar (GstVideoCrop * vcrop, GstBuffer * inbuf,
332     GstBuffer * outbuf)
333 {
334   guint8 *y_out, *u_out, *v_out;
335   guint8 *y_in, *u_in, *v_in;
336   guint i, dx;
337
338   /* Y plane */
339   y_in = GST_BUFFER_DATA (inbuf);
340   y_out = GST_BUFFER_DATA (outbuf);
341
342   y_in += (vcrop->crop_top * vcrop->in.y_stride) + vcrop->crop_left;
343   dx = vcrop->out.width * 1;
344
345   for (i = 0; i < vcrop->out.height; ++i) {
346     memcpy (y_out, y_in, dx);
347     y_in += vcrop->in.y_stride;
348     y_out += vcrop->out.y_stride;
349   }
350
351   /* U + V planes */
352   u_in = GST_BUFFER_DATA (inbuf) + vcrop->in.u_off;
353   u_out = GST_BUFFER_DATA (outbuf) + vcrop->out.u_off;
354
355   u_in += (vcrop->crop_top / 2) * vcrop->in.u_stride;
356   u_in += vcrop->crop_left / 2;
357
358   v_in = GST_BUFFER_DATA (inbuf) + vcrop->in.v_off;
359   v_out = GST_BUFFER_DATA (outbuf) + vcrop->out.v_off;
360
361   v_in += (vcrop->crop_top / 2) * vcrop->in.v_stride;
362   v_in += vcrop->crop_left / 2;
363
364   dx = GST_ROUND_UP_2 (vcrop->out.width) / 2;
365
366   for (i = 0; i < GST_ROUND_UP_2 (vcrop->out.height) / 2; ++i) {
367     memcpy (u_out, u_in, dx);
368     memcpy (v_out, v_in, dx);
369     u_in += vcrop->in.u_stride;
370     u_out += vcrop->out.u_stride;
371     v_in += vcrop->in.v_stride;
372     v_out += vcrop->out.v_stride;
373   }
374 }
375
376 static GstFlowReturn
377 gst_video_crop_transform (GstBaseTransform * trans, GstBuffer * inbuf,
378     GstBuffer * outbuf)
379 {
380   GstVideoCrop *vcrop = GST_VIDEO_CROP (trans);
381
382   /* we should be operating in passthrough mode if there's nothing to do */
383   g_assert (vcrop->noop == FALSE);
384
385   GST_OBJECT_LOCK (vcrop);
386
387   if (G_UNLIKELY ((vcrop->crop_left + vcrop->crop_right) >= vcrop->in.width ||
388           (vcrop->crop_top + vcrop->crop_bottom) >= vcrop->in.height)) {
389     GST_OBJECT_UNLOCK (vcrop);
390     goto cropping_too_much;
391   }
392
393   if (vcrop->in.packed) {
394     gst_video_crop_transform_packed (vcrop, inbuf, outbuf);
395   } else {
396     gst_video_crop_transform_planar (vcrop, inbuf, outbuf);
397   }
398
399   GST_OBJECT_UNLOCK (vcrop);
400
401   return GST_FLOW_OK;
402
403 cropping_too_much:
404   {
405     /* is there a better error code for this? */
406     GST_ELEMENT_ERROR (vcrop, LIBRARY, SETTINGS, (NULL),
407         ("Can't crop more pixels than there are"));
408     return GST_FLOW_ERROR;
409   }
410 }
411
412 static gint
413 gst_video_crop_transform_dimension (gint val, gint delta)
414 {
415   gint64 new_val = (gint64) val + (gint64) delta;
416
417   new_val = CLAMP (new_val, 1, G_MAXINT);
418
419   return (gint) new_val;
420 }
421
422 static gboolean
423 gst_video_crop_transform_dimension_value (const GValue * src_val,
424     gint delta, GValue * dest_val)
425 {
426   gboolean ret = TRUE;
427
428   g_value_init (dest_val, G_VALUE_TYPE (src_val));
429
430   if (G_VALUE_HOLDS_INT (src_val)) {
431     gint ival = g_value_get_int (src_val);
432
433     ival = gst_video_crop_transform_dimension (ival, delta);
434     g_value_set_int (dest_val, ival);
435   } else if (GST_VALUE_HOLDS_INT_RANGE (src_val)) {
436     gint min = gst_value_get_int_range_min (src_val);
437     gint max = gst_value_get_int_range_max (src_val);
438
439     min = gst_video_crop_transform_dimension (min, delta);
440     max = gst_video_crop_transform_dimension (max, delta);
441     gst_value_set_int_range (dest_val, min, max);
442   } else if (GST_VALUE_HOLDS_LIST (src_val)) {
443     gint i;
444
445     for (i = 0; i < gst_value_list_get_size (src_val); ++i) {
446       const GValue *list_val;
447       GValue newval = { 0, };
448
449       list_val = gst_value_list_get_value (src_val, i);
450       if (gst_video_crop_transform_dimension_value (list_val, delta, &newval))
451         gst_value_list_append_value (dest_val, &newval);
452       g_value_unset (&newval);
453     }
454
455     if (gst_value_list_get_size (dest_val) == 0) {
456       g_value_unset (dest_val);
457       ret = FALSE;
458     }
459   } else {
460     g_value_unset (dest_val);
461     ret = FALSE;
462   }
463
464   return ret;
465 }
466
467 static GstCaps *
468 gst_video_crop_transform_caps (GstBaseTransform * trans,
469     GstPadDirection direction, GstCaps * caps)
470 {
471   GstVideoCrop *vcrop;
472   GstCaps *other_caps;
473   gint dy, dx, i;
474
475   vcrop = GST_VIDEO_CROP (trans);
476
477   if (vcrop->noop)
478     return gst_caps_ref (caps);
479
480   GST_OBJECT_LOCK (vcrop);
481   if (direction == GST_PAD_SRC) {
482     dx = vcrop->crop_left + vcrop->crop_right;
483     dy = vcrop->crop_top + vcrop->crop_bottom;
484   } else {
485     dx = 0 - (vcrop->crop_left + vcrop->crop_right);
486     dy = 0 - (vcrop->crop_top + vcrop->crop_bottom);
487   }
488   GST_OBJECT_UNLOCK (vcrop);
489
490   GST_LOG_OBJECT (vcrop, "transforming caps %" GST_PTR_FORMAT, caps);
491
492   other_caps = gst_caps_new_empty ();
493
494   for (i = 0; i < gst_caps_get_size (caps); ++i) {
495     const GValue *v;
496     GstStructure *structure, *new_structure;
497     GValue w_val = { 0, }, h_val = {
498     0,};
499
500     structure = gst_caps_get_structure (caps, i);
501
502     v = gst_structure_get_value (structure, "width");
503     if (!gst_video_crop_transform_dimension_value (v, dx, &w_val)) {
504       GST_WARNING_OBJECT (vcrop, "could not tranform width value with dx=%d"
505           ", caps structure=%" GST_PTR_FORMAT, dx, structure);
506       continue;
507     }
508
509     v = gst_structure_get_value (structure, "height");
510     if (!gst_video_crop_transform_dimension_value (v, dy, &h_val)) {
511       g_value_unset (&w_val);
512       GST_WARNING_OBJECT (vcrop, "could not tranform height value with dy=%d"
513           ", caps structure=%" GST_PTR_FORMAT, dy, structure);
514       continue;
515     }
516
517     new_structure = gst_structure_copy (structure);
518     gst_structure_set_value (new_structure, "width", &w_val);
519     gst_structure_set_value (new_structure, "height", &h_val);
520     g_value_unset (&w_val);
521     g_value_unset (&h_val);
522     GST_LOG_OBJECT (vcrop, "transformed structure %2d: %" GST_PTR_FORMAT
523         " => %" GST_PTR_FORMAT, i, structure, new_structure);
524     gst_caps_append_structure (other_caps, new_structure);
525   }
526
527   if (gst_caps_is_empty (other_caps)) {
528     gst_caps_unref (other_caps);
529     other_caps = NULL;
530   }
531
532   return other_caps;
533 }
534
535 static gboolean
536 gst_video_crop_set_caps (GstBaseTransform * trans, GstCaps * incaps,
537     GstCaps * outcaps)
538 {
539   GstVideoCrop *crop = GST_VIDEO_CROP (trans);
540
541   if (!gst_video_crop_get_image_details_from_caps (crop, &crop->in, incaps)) {
542     GST_DEBUG_OBJECT (crop, "failed to parse input caps %" GST_PTR_FORMAT,
543         incaps);
544     return FALSE;
545   }
546
547   if (!gst_video_crop_get_image_details_from_caps (crop, &crop->out, outcaps)) {
548     GST_DEBUG_OBJECT (crop, "failed to parse output caps %" GST_PTR_FORMAT,
549         outcaps);
550     return FALSE;
551   }
552
553   return TRUE;
554 }
555
556 static void
557 gst_video_crop_set_property (GObject * object, guint prop_id,
558     const GValue * value, GParamSpec * pspec)
559 {
560   GstVideoCrop *video_crop;
561
562   video_crop = GST_VIDEO_CROP (object);
563
564   GST_OBJECT_LOCK (video_crop);
565   switch (prop_id) {
566     case ARG_LEFT:
567       video_crop->crop_left = g_value_get_int (value);
568       break;
569     case ARG_RIGHT:
570       video_crop->crop_right = g_value_get_int (value);
571       break;
572     case ARG_TOP:
573       video_crop->crop_top = g_value_get_int (value);
574       break;
575     case ARG_BOTTOM:
576       video_crop->crop_bottom = g_value_get_int (value);
577       break;
578     default:
579       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
580       break;
581   }
582
583   video_crop->noop = ((video_crop->crop_left | video_crop->crop_right |
584           video_crop->crop_top | video_crop->crop_bottom) == 0);
585
586   GST_OBJECT_UNLOCK (video_crop);
587 }
588
589 static void
590 gst_video_crop_get_property (GObject * object, guint prop_id, GValue * value,
591     GParamSpec * pspec)
592 {
593   GstVideoCrop *video_crop;
594
595   video_crop = GST_VIDEO_CROP (object);
596
597   GST_OBJECT_LOCK (video_crop);
598   switch (prop_id) {
599     case ARG_LEFT:
600       g_value_set_int (value, video_crop->crop_left);
601       break;
602     case ARG_RIGHT:
603       g_value_set_int (value, video_crop->crop_right);
604       break;
605     case ARG_TOP:
606       g_value_set_int (value, video_crop->crop_top);
607       break;
608     case ARG_BOTTOM:
609       g_value_set_int (value, video_crop->crop_bottom);
610       break;
611     default:
612       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
613       break;
614   }
615   GST_OBJECT_UNLOCK (video_crop);
616 }
617
618 static gboolean
619 plugin_init (GstPlugin * plugin)
620 {
621   GST_DEBUG_CATEGORY_INIT (videocrop_debug, "videocrop", 0, "videocrop");
622
623   return gst_element_register (plugin, "videocrop", GST_RANK_NONE,
624       GST_TYPE_VIDEO_CROP);
625 }
626
627 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
628     GST_VERSION_MINOR,
629     "videocrop",
630     "Crops video into a user-defined region",
631     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)