pango: adjust to modified video overlay composition API
[platform/upstream/gstreamer.git] / ext / pango / gstbasetextoverlay.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2003> David Schleef <ds@schleef.org>
4  * Copyright (C) <2006> Julien Moutte <julien@moutte.net>
5  * Copyright (C) <2006> Zeeshan Ali <zeeshan.ali@nokia.com>
6  * Copyright (C) <2006-2008> Tim-Philipp Müller <tim centricular net>
7  * Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 /**
26  * SECTION:element-textoverlay
27  * @see_also: #GstTextRender, #GstClockOverlay, #GstTimeOverlay, #GstSubParse
28  *
29  * This plugin renders text on top of a video stream. This can be either
30  * static text or text from buffers received on the text sink pad, e.g.
31  * as produced by the subparse element. If the text sink pad is not linked,
32  * the text set via the "text" property will be rendered. If the text sink
33  * pad is linked, text will be rendered as it is received on that pad,
34  * honouring and matching the buffer timestamps of both input streams.
35  *
36  * The text can contain newline characters and text wrapping is enabled by
37  * default.
38  *
39  * <refsect2>
40  * <title>Example launch lines</title>
41  * |[
42  * gst-launch -v videotestsrc ! textoverlay text="Room A" valign=top halign=left ! xvimagesink
43  * ]| Here is a simple pipeline that displays a static text in the top left
44  * corner of the video picture
45  * |[
46  * gst-launch -v filesrc location=subtitles.srt ! subparse ! txt.   videotestsrc ! timeoverlay ! textoverlay name=txt shaded-background=yes ! xvimagesink
47  * ]| Here is another pipeline that displays subtitles from an .srt subtitle
48  * file, centered at the bottom of the picture and with a rectangular shading
49  * around the text in the background:
50  * <para>
51  * If you do not have such a subtitle file, create one looking like this
52  * in a text editor:
53  * |[
54  * 1
55  * 00:00:03,000 --> 00:00:05,000
56  * Hello? (3-5s)
57  *
58  * 2
59  * 00:00:08,000 --> 00:00:13,000
60  * Yes, this is a subtitle. Don&apos;t
61  * you like it? (8-13s)
62  *
63  * 3
64  * 00:00:18,826 --> 00:01:02,886
65  * Uh? What are you talking about?
66  * I don&apos;t understand  (18-62s)
67  * ]|
68  * </para>
69  * </refsect2>
70  */
71
72 /* FIXME: alloc segment as part of instance struct */
73
74 #ifdef HAVE_CONFIG_H
75 #include <config.h>
76 #endif
77
78 #include <gst/video/video.h>
79 #include <gst/video/gstvideometa.h>
80
81 #include "gstbasetextoverlay.h"
82 #include "gsttextoverlay.h"
83 #include "gsttimeoverlay.h"
84 #include "gstclockoverlay.h"
85 #include "gsttextrender.h"
86 #include <string.h>
87
88 /* FIXME:
89  *  - use proper strides and offset for I420
90  *  - if text is wider than the video picture, it does not get
91  *    clipped properly during blitting (if wrapping is disabled)
92  *  - make 'shading_value' a property (or enum:  light/normal/dark/verydark)?
93  */
94
95 GST_DEBUG_CATEGORY (pango_debug);
96 #define GST_CAT_DEFAULT pango_debug
97
98 #define DEFAULT_PROP_TEXT       ""
99 #define DEFAULT_PROP_SHADING    FALSE
100 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
101 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
102 #define DEFAULT_PROP_XPAD       25
103 #define DEFAULT_PROP_YPAD       25
104 #define DEFAULT_PROP_DELTAX     0
105 #define DEFAULT_PROP_DELTAY     0
106 #define DEFAULT_PROP_XPOS       0.5
107 #define DEFAULT_PROP_YPOS       0.5
108 #define DEFAULT_PROP_WRAP_MODE  GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
109 #define DEFAULT_PROP_FONT_DESC  ""
110 #define DEFAULT_PROP_SILENT     FALSE
111 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
112 #define DEFAULT_PROP_WAIT_TEXT  TRUE
113 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
114 #define DEFAULT_PROP_VERTICAL_RENDER  FALSE
115 #define DEFAULT_PROP_COLOR      0xffffffff
116 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
117
118 /* make a property of me */
119 #define DEFAULT_SHADING_VALUE    -80
120
121 #define MINIMUM_OUTLINE_OFFSET 1.0
122 #define DEFAULT_SCALE_BASIS    640
123
124 #define COMP_Y(ret, r, g, b) \
125 { \
126    ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
127    ret = CLAMP (ret, 0, 255); \
128 }
129
130 #define COMP_U(ret, r, g, b) \
131 { \
132    ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
133    ret = CLAMP (ret, 0, 255); \
134 }
135
136 #define COMP_V(ret, r, g, b) \
137 { \
138    ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
139    ret = CLAMP (ret, 0, 255); \
140 }
141
142 #define BLEND(ret, alpha, v0, v1) \
143 { \
144         ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
145 }
146
147 #define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew)     \
148 { \
149     gint _tmp; \
150     _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \
151     ret = CLAMP (_tmp, 0, 255); \
152 }
153
154 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
155 # define CAIRO_ARGB_A 3
156 # define CAIRO_ARGB_R 2
157 # define CAIRO_ARGB_G 1
158 # define CAIRO_ARGB_B 0
159 #else
160 # define CAIRO_ARGB_A 0
161 # define CAIRO_ARGB_R 1
162 # define CAIRO_ARGB_G 2
163 # define CAIRO_ARGB_B 3
164 #endif
165
166 enum
167 {
168   PROP_0,
169   PROP_TEXT,
170   PROP_SHADING,
171   PROP_HALIGNMENT,
172   PROP_VALIGNMENT,
173   PROP_XPAD,
174   PROP_YPAD,
175   PROP_DELTAX,
176   PROP_DELTAY,
177   PROP_XPOS,
178   PROP_YPOS,
179   PROP_WRAP_MODE,
180   PROP_FONT_DESC,
181   PROP_SILENT,
182   PROP_LINE_ALIGNMENT,
183   PROP_WAIT_TEXT,
184   PROP_AUTO_ADJUST_SIZE,
185   PROP_VERTICAL_RENDER,
186   PROP_COLOR,
187   PROP_SHADOW,
188   PROP_OUTLINE_COLOR,
189   PROP_LAST
190 };
191
192 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
193     I420, YV12, AYUV, YUY2, UYVY, v308, v210, v216, Y41B, Y42B, Y444, \
194     Y800, Y16, NV12, NV21, UYVP, A420, YUV9, IYU1 }"
195
196 static GstStaticPadTemplate src_template_factory =
197 GST_STATIC_PAD_TEMPLATE ("src",
198     GST_PAD_SRC,
199     GST_PAD_ALWAYS,
200     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
201     );
202
203 static GstStaticPadTemplate video_sink_template_factory =
204 GST_STATIC_PAD_TEMPLATE ("video_sink",
205     GST_PAD_SINK,
206     GST_PAD_ALWAYS,
207     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
208     );
209
210 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
211 static GType
212 gst_base_text_overlay_valign_get_type (void)
213 {
214   static GType base_text_overlay_valign_type = 0;
215   static const GEnumValue base_text_overlay_valign[] = {
216     {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
217     {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
218     {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
219     {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
220     {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
221     {0, NULL, NULL},
222   };
223
224   if (!base_text_overlay_valign_type) {
225     base_text_overlay_valign_type =
226         g_enum_register_static ("GstBaseTextOverlayVAlign",
227         base_text_overlay_valign);
228   }
229   return base_text_overlay_valign_type;
230 }
231
232 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
233 static GType
234 gst_base_text_overlay_halign_get_type (void)
235 {
236   static GType base_text_overlay_halign_type = 0;
237   static const GEnumValue base_text_overlay_halign[] = {
238     {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
239     {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
240     {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
241     {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
242     {0, NULL, NULL},
243   };
244
245   if (!base_text_overlay_halign_type) {
246     base_text_overlay_halign_type =
247         g_enum_register_static ("GstBaseTextOverlayHAlign",
248         base_text_overlay_halign);
249   }
250   return base_text_overlay_halign_type;
251 }
252
253
254 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
255 static GType
256 gst_base_text_overlay_wrap_mode_get_type (void)
257 {
258   static GType base_text_overlay_wrap_mode_type = 0;
259   static const GEnumValue base_text_overlay_wrap_mode[] = {
260     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
261     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
262     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
263     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
264     {0, NULL, NULL},
265   };
266
267   if (!base_text_overlay_wrap_mode_type) {
268     base_text_overlay_wrap_mode_type =
269         g_enum_register_static ("GstBaseTextOverlayWrapMode",
270         base_text_overlay_wrap_mode);
271   }
272   return base_text_overlay_wrap_mode_type;
273 }
274
275 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
276 static GType
277 gst_base_text_overlay_line_align_get_type (void)
278 {
279   static GType base_text_overlay_line_align_type = 0;
280   static const GEnumValue base_text_overlay_line_align[] = {
281     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
282     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
283     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
284     {0, NULL, NULL}
285   };
286
287   if (!base_text_overlay_line_align_type) {
288     base_text_overlay_line_align_type =
289         g_enum_register_static ("GstBaseTextOverlayLineAlign",
290         base_text_overlay_line_align);
291   }
292   return base_text_overlay_line_align_type;
293 }
294
295 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
296 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
297 #define GST_BASE_TEXT_OVERLAY_LOCK(ov)     (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
298 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov)   (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
299 #define GST_BASE_TEXT_OVERLAY_WAIT(ov)     (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
300 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov)   (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
301 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
302
303 static GstElementClass *parent_class = NULL;
304 static void gst_base_text_overlay_base_init (gpointer g_class);
305 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
306 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
307     GstBaseTextOverlayClass * klass);
308
309 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
310     element, GstStateChange transition);
311
312 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
313     GstBaseTextOverlay * overlay, GstCaps * filter);
314 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
315     GstCaps * caps);
316 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
317     GstCaps * caps);
318 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
319     GstObject * parent, GstEvent * event);
320 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
321     GstObject * parent, GstQuery * query);
322
323 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
324     GstObject * parent, GstEvent * event);
325 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
326     GstObject * parent, GstQuery * query);
327 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
328     GstObject * parent, GstBuffer * buffer);
329
330 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
331     GstObject * parent, GstEvent * event);
332 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
333     GstObject * parent, GstBuffer * buffer);
334 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
335     GstPad * peer);
336 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad);
337 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
338 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
339     overlay);
340
341 static void gst_base_text_overlay_finalize (GObject * object);
342 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
343     const GValue * value, GParamSpec * pspec);
344 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
345     GValue * value, GParamSpec * pspec);
346 static void
347 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
348     PangoFontDescription * desc);
349
350 GType
351 gst_base_text_overlay_get_type (void)
352 {
353   static GType type = 0;
354
355   if (g_once_init_enter ((gsize *) & type)) {
356     static const GTypeInfo info = {
357       sizeof (GstBaseTextOverlayClass),
358       (GBaseInitFunc) gst_base_text_overlay_base_init,
359       NULL,
360       (GClassInitFunc) gst_base_text_overlay_class_init,
361       NULL,
362       NULL,
363       sizeof (GstBaseTextOverlay),
364       0,
365       (GInstanceInitFunc) gst_base_text_overlay_init,
366     };
367
368     g_once_init_leave ((gsize *) & type,
369         g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
370             0));
371   }
372
373   return type;
374 }
375
376 static gchar *
377 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
378     GstBuffer * video_frame)
379 {
380   return g_strdup (overlay->default_text);
381 }
382
383 static void
384 gst_base_text_overlay_base_init (gpointer g_class)
385 {
386   GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
387   PangoFontMap *fontmap;
388
389   /* Only lock for the subclasses here, the base class
390    * doesn't have this mutex yet and it's not necessary
391    * here */
392   if (klass->pango_lock)
393     g_mutex_lock (klass->pango_lock);
394   fontmap = pango_cairo_font_map_get_default ();
395   klass->pango_context =
396       pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
397   if (klass->pango_lock)
398     g_mutex_unlock (klass->pango_lock);
399 }
400
401 static void
402 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
403 {
404   GObjectClass *gobject_class;
405   GstElementClass *gstelement_class;
406
407   gobject_class = (GObjectClass *) klass;
408   gstelement_class = (GstElementClass *) klass;
409
410   parent_class = g_type_class_peek_parent (klass);
411
412   gobject_class->finalize = gst_base_text_overlay_finalize;
413   gobject_class->set_property = gst_base_text_overlay_set_property;
414   gobject_class->get_property = gst_base_text_overlay_get_property;
415
416   gst_element_class_add_pad_template (gstelement_class,
417       gst_static_pad_template_get (&src_template_factory));
418   gst_element_class_add_pad_template (gstelement_class,
419       gst_static_pad_template_get (&video_sink_template_factory));
420
421   gstelement_class->change_state =
422       GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
423
424   klass->pango_lock = g_slice_new (GMutex);
425   g_mutex_init (klass->pango_lock);
426
427   klass->get_text = gst_base_text_overlay_get_text;
428
429   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
430       g_param_spec_string ("text", "text",
431           "Text to be display.", DEFAULT_PROP_TEXT,
432           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
433   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
434       g_param_spec_boolean ("shaded-background", "shaded background",
435           "Whether to shade the background under the text area",
436           DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
437   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
438       g_param_spec_enum ("valignment", "vertical alignment",
439           "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
440           DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
441   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
442       g_param_spec_enum ("halignment", "horizontal alignment",
443           "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
444           DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
445   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
446       g_param_spec_int ("xpad", "horizontal paddding",
447           "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
448           DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
449   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
450       g_param_spec_int ("ypad", "vertical padding",
451           "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
452           DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
453   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
454       g_param_spec_int ("deltax", "X position modifier",
455           "Shift X position to the left or to the right. Unit is pixels.",
456           G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
457           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
458   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
459       g_param_spec_int ("deltay", "Y position modifier",
460           "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
461           DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
462   /**
463    * GstBaseTextOverlay:xpos
464    *
465    * Horizontal position of the rendered text when using positioned alignment.
466    *
467    * Since: 0.10.31
468    **/
469   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
470       g_param_spec_double ("xpos", "horizontal position",
471           "Horizontal position when using position alignment", 0, 1.0,
472           DEFAULT_PROP_XPOS,
473           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
474   /**
475    * GstBaseTextOverlay:ypos
476    *
477    * Vertical position of the rendered text when using positioned alignment.
478    *
479    * Since: 0.10.31
480    **/
481   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
482       g_param_spec_double ("ypos", "vertical position",
483           "Vertical position when using position alignment", 0, 1.0,
484           DEFAULT_PROP_YPOS,
485           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
486   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
487       g_param_spec_enum ("wrap-mode", "wrap mode",
488           "Whether to wrap the text and if so how.",
489           GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
490           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
491   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
492       g_param_spec_string ("font-desc", "font description",
493           "Pango font description of font to be used for rendering. "
494           "See documentation of pango_font_description_from_string "
495           "for syntax.", DEFAULT_PROP_FONT_DESC,
496           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
497   /**
498    * GstBaseTextOverlay:color
499    *
500    * Color of the rendered text.
501    *
502    * Since: 0.10.31
503    **/
504   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
505       g_param_spec_uint ("color", "Color",
506           "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
507           DEFAULT_PROP_COLOR,
508           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
509   /**
510    * GstTextOverlay:outline-color
511    *
512    * Color of the outline of the rendered text.
513    *
514    * Since: 0.10.35
515    **/
516   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
517       g_param_spec_uint ("outline-color", "Text Outline Color",
518           "Color to use for outline the text (big-endian ARGB).", 0,
519           G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
520           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
521
522   /**
523    * GstBaseTextOverlay:line-alignment
524    *
525    * Alignment of text lines relative to each other (for multi-line text)
526    *
527    * Since: 0.10.15
528    **/
529   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
530       g_param_spec_enum ("line-alignment", "line alignment",
531           "Alignment of text lines relative to each other.",
532           GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
533           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
534   /**
535    * GstBaseTextOverlay:silent
536    *
537    * If set, no text is rendered. Useful to switch off text rendering
538    * temporarily without removing the textoverlay element from the pipeline.
539    *
540    * Since: 0.10.15
541    **/
542   /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
543   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
544       g_param_spec_boolean ("silent", "silent",
545           "Whether to render the text string",
546           DEFAULT_PROP_SILENT,
547           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
548   /**
549    * GstBaseTextOverlay:wait-text
550    *
551    * If set, the video will block until a subtitle is received on the text pad.
552    * If video and subtitles are sent in sync, like from the same demuxer, this
553    * property should be set.
554    *
555    * Since: 0.10.20
556    **/
557   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
558       g_param_spec_boolean ("wait-text", "Wait Text",
559           "Whether to wait for subtitles",
560           DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
561
562   g_object_class_install_property (G_OBJECT_CLASS (klass),
563       PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
564           "Automatically adjust font size to screen-size.",
565           DEFAULT_PROP_AUTO_ADJUST_SIZE,
566           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
567
568   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
569       g_param_spec_boolean ("vertical-render", "vertical render",
570           "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
571           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
572 }
573
574 static void
575 gst_base_text_overlay_finalize (GObject * object)
576 {
577   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
578
579   g_free (overlay->default_text);
580
581   if (overlay->composition) {
582     gst_video_overlay_composition_unref (overlay->composition);
583     overlay->composition = NULL;
584   }
585
586   if (overlay->text_image) {
587     gst_buffer_unref (overlay->text_image);
588     overlay->text_image = NULL;
589   }
590
591   if (overlay->layout) {
592     g_object_unref (overlay->layout);
593     overlay->layout = NULL;
594   }
595
596   if (overlay->text_buffer) {
597     gst_buffer_unref (overlay->text_buffer);
598     overlay->text_buffer = NULL;
599   }
600
601   g_mutex_clear (&overlay->lock);
602   g_cond_clear (&overlay->cond);
603
604   G_OBJECT_CLASS (parent_class)->finalize (object);
605 }
606
607 static void
608 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
609     GstBaseTextOverlayClass * klass)
610 {
611   GstPadTemplate *template;
612   PangoFontDescription *desc;
613
614   /* video sink */
615   template = gst_static_pad_template_get (&video_sink_template_factory);
616   overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
617   gst_object_unref (template);
618   gst_pad_set_event_function (overlay->video_sinkpad,
619       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
620   gst_pad_set_chain_function (overlay->video_sinkpad,
621       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
622   gst_pad_set_query_function (overlay->video_sinkpad,
623       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
624   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
625
626   template =
627       gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
628       "text_sink");
629   if (template) {
630     /* text sink */
631     overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
632
633     gst_pad_set_event_function (overlay->text_sinkpad,
634         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
635     gst_pad_set_chain_function (overlay->text_sinkpad,
636         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
637     gst_pad_set_link_function (overlay->text_sinkpad,
638         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
639     gst_pad_set_unlink_function (overlay->text_sinkpad,
640         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
641     gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
642   }
643
644   /* (video) source */
645   template = gst_static_pad_template_get (&src_template_factory);
646   overlay->srcpad = gst_pad_new_from_template (template, "src");
647   gst_object_unref (template);
648   gst_pad_set_event_function (overlay->srcpad,
649       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
650   gst_pad_set_query_function (overlay->srcpad,
651       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
652   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
653
654   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
655   overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
656   overlay->layout =
657       pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
658       (overlay)->pango_context);
659   desc =
660       pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
661       (overlay)->pango_context);
662   gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
663
664   overlay->color = DEFAULT_PROP_COLOR;
665   overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
666   overlay->halign = DEFAULT_PROP_HALIGNMENT;
667   overlay->valign = DEFAULT_PROP_VALIGNMENT;
668   overlay->xpad = DEFAULT_PROP_XPAD;
669   overlay->ypad = DEFAULT_PROP_YPAD;
670   overlay->deltax = DEFAULT_PROP_DELTAX;
671   overlay->deltay = DEFAULT_PROP_DELTAY;
672   overlay->xpos = DEFAULT_PROP_XPOS;
673   overlay->ypos = DEFAULT_PROP_YPOS;
674
675   overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
676
677   overlay->want_shading = DEFAULT_PROP_SHADING;
678   overlay->shading_value = DEFAULT_SHADING_VALUE;
679   overlay->silent = DEFAULT_PROP_SILENT;
680   overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
681   overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
682
683   overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
684   overlay->need_render = TRUE;
685   overlay->text_image = NULL;
686   overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
687   gst_base_text_overlay_update_render_mode (overlay);
688
689   overlay->text_buffer = NULL;
690   overlay->text_linked = FALSE;
691   g_mutex_init (&overlay->lock);
692   g_cond_init (&overlay->cond);
693   gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
694   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
695 }
696
697 static void
698 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
699 {
700   if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
701     GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
702     pango_layout_set_width (overlay->layout, -1);
703   } else {
704     int width;
705
706     if (overlay->auto_adjust_size) {
707       width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
708       if (overlay->use_vertical_render) {
709         width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
710       }
711     } else {
712       width =
713           (overlay->use_vertical_render ? overlay->height : overlay->width) *
714           PANGO_SCALE;
715     }
716
717     GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
718     GST_DEBUG_OBJECT (overlay, "Set wrap mode    %d", overlay->wrap_mode);
719     pango_layout_set_width (overlay->layout, width);
720     pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
721   }
722 }
723
724 static void
725 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
726 {
727   PangoMatrix matrix = PANGO_MATRIX_INIT;
728   PangoContext *context = pango_layout_get_context (overlay->layout);
729
730   if (overlay->use_vertical_render) {
731     pango_matrix_rotate (&matrix, -90);
732     pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
733     pango_context_set_matrix (context, &matrix);
734     pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
735   } else {
736     pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
737     pango_context_set_matrix (context, &matrix);
738     pango_layout_set_alignment (overlay->layout,
739         (PangoAlignment) overlay->line_align);
740   }
741 }
742
743 static gboolean
744 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
745 {
746   GstStructure *structure;
747
748   structure = gst_caps_get_structure (caps, 0);
749   overlay->have_pango_markup =
750       gst_structure_has_name (structure, "text/x-pango-markup");
751
752   return TRUE;
753 }
754
755 /* FIXME: upstream nego (e.g. when the video window is resized) */
756
757 /* only negotiate/query video overlay composition support for now */
758 static gboolean
759 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
760 {
761   GstCaps *target;
762   GstQuery *query;
763   gboolean attach = FALSE;
764
765   GST_DEBUG_OBJECT (overlay, "performing negotiation");
766
767   target = gst_pad_get_current_caps (overlay->srcpad);
768
769   if (!target || gst_caps_is_empty (target))
770     goto no_format;
771
772   /* find supported meta */
773   query = gst_query_new_allocation (target, TRUE);
774
775   if (!gst_pad_peer_query (overlay->srcpad, query)) {
776     /* no problem, we use the query defaults */
777     GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
778   }
779
780   if (gst_query_find_allocation_meta (query,
781           GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
782     attach = TRUE;
783
784   overlay->attach_compo_to_buffer = attach;
785
786   gst_query_unref (query);
787   gst_caps_unref (target);
788
789   return TRUE;
790
791 no_format:
792   {
793     if (target)
794       gst_caps_unref (target);
795     return FALSE;
796   }
797 }
798
799 static gboolean
800 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
801 {
802   GstVideoInfo info;
803   gboolean ret = FALSE;
804
805   if (!gst_video_info_from_caps (&info, caps))
806     goto invalid_caps;
807
808   overlay->info = info;
809   overlay->format = GST_VIDEO_INFO_FORMAT (&info);
810   overlay->width = GST_VIDEO_INFO_WIDTH (&info);
811   overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
812
813   ret = gst_pad_set_caps (overlay->srcpad, caps);
814
815   if (ret) {
816     GST_BASE_TEXT_OVERLAY_LOCK (overlay);
817     g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
818     gst_base_text_overlay_negotiate (overlay);
819     gst_base_text_overlay_update_wrap_mode (overlay);
820     g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
821     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
822   }
823
824   return ret;
825
826   /* ERRORS */
827 invalid_caps:
828   {
829     GST_DEBUG_OBJECT (overlay, "could not parse caps");
830     return FALSE;
831   }
832 }
833
834 static void
835 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
836     const GValue * value, GParamSpec * pspec)
837 {
838   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
839
840   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
841   switch (prop_id) {
842     case PROP_TEXT:
843       g_free (overlay->default_text);
844       overlay->default_text = g_value_dup_string (value);
845       overlay->need_render = TRUE;
846       break;
847     case PROP_SHADING:
848       overlay->want_shading = g_value_get_boolean (value);
849       break;
850     case PROP_XPAD:
851       overlay->xpad = g_value_get_int (value);
852       break;
853     case PROP_YPAD:
854       overlay->ypad = g_value_get_int (value);
855       break;
856     case PROP_DELTAX:
857       overlay->deltax = g_value_get_int (value);
858       break;
859     case PROP_DELTAY:
860       overlay->deltay = g_value_get_int (value);
861       break;
862     case PROP_XPOS:
863       overlay->xpos = g_value_get_double (value);
864       break;
865     case PROP_YPOS:
866       overlay->ypos = g_value_get_double (value);
867       break;
868     case PROP_VALIGNMENT:
869       overlay->valign = g_value_get_enum (value);
870       break;
871     case PROP_HALIGNMENT:
872       overlay->halign = g_value_get_enum (value);
873       break;
874     case PROP_WRAP_MODE:
875       overlay->wrap_mode = g_value_get_enum (value);
876       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
877       gst_base_text_overlay_update_wrap_mode (overlay);
878       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
879       break;
880     case PROP_FONT_DESC:
881     {
882       PangoFontDescription *desc;
883       const gchar *fontdesc_str;
884
885       fontdesc_str = g_value_get_string (value);
886       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
887       desc = pango_font_description_from_string (fontdesc_str);
888       if (desc) {
889         GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
890         pango_layout_set_font_description (overlay->layout, desc);
891         gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
892         pango_font_description_free (desc);
893       } else {
894         GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
895             fontdesc_str);
896       }
897       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
898       break;
899     }
900     case PROP_COLOR:
901       overlay->color = g_value_get_uint (value);
902       break;
903     case PROP_OUTLINE_COLOR:
904       overlay->outline_color = g_value_get_uint (value);
905       break;
906     case PROP_SILENT:
907       overlay->silent = g_value_get_boolean (value);
908       break;
909     case PROP_LINE_ALIGNMENT:
910       overlay->line_align = g_value_get_enum (value);
911       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
912       pango_layout_set_alignment (overlay->layout,
913           (PangoAlignment) overlay->line_align);
914       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
915       break;
916     case PROP_WAIT_TEXT:
917       overlay->wait_text = g_value_get_boolean (value);
918       break;
919     case PROP_AUTO_ADJUST_SIZE:
920       overlay->auto_adjust_size = g_value_get_boolean (value);
921       overlay->need_render = TRUE;
922       break;
923     case PROP_VERTICAL_RENDER:
924       overlay->use_vertical_render = g_value_get_boolean (value);
925       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
926       gst_base_text_overlay_update_render_mode (overlay);
927       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
928       overlay->need_render = TRUE;
929       break;
930     default:
931       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
932       break;
933   }
934
935   overlay->need_render = TRUE;
936   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
937 }
938
939 static void
940 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
941     GValue * value, GParamSpec * pspec)
942 {
943   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
944
945   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
946   switch (prop_id) {
947     case PROP_TEXT:
948       g_value_set_string (value, overlay->default_text);
949       break;
950     case PROP_SHADING:
951       g_value_set_boolean (value, overlay->want_shading);
952       break;
953     case PROP_XPAD:
954       g_value_set_int (value, overlay->xpad);
955       break;
956     case PROP_YPAD:
957       g_value_set_int (value, overlay->ypad);
958       break;
959     case PROP_DELTAX:
960       g_value_set_int (value, overlay->deltax);
961       break;
962     case PROP_DELTAY:
963       g_value_set_int (value, overlay->deltay);
964       break;
965     case PROP_XPOS:
966       g_value_set_double (value, overlay->xpos);
967       break;
968     case PROP_YPOS:
969       g_value_set_double (value, overlay->ypos);
970       break;
971     case PROP_VALIGNMENT:
972       g_value_set_enum (value, overlay->valign);
973       break;
974     case PROP_HALIGNMENT:
975       g_value_set_enum (value, overlay->halign);
976       break;
977     case PROP_WRAP_MODE:
978       g_value_set_enum (value, overlay->wrap_mode);
979       break;
980     case PROP_SILENT:
981       g_value_set_boolean (value, overlay->silent);
982       break;
983     case PROP_LINE_ALIGNMENT:
984       g_value_set_enum (value, overlay->line_align);
985       break;
986     case PROP_WAIT_TEXT:
987       g_value_set_boolean (value, overlay->wait_text);
988       break;
989     case PROP_AUTO_ADJUST_SIZE:
990       g_value_set_boolean (value, overlay->auto_adjust_size);
991       break;
992     case PROP_VERTICAL_RENDER:
993       g_value_set_boolean (value, overlay->use_vertical_render);
994       break;
995     case PROP_COLOR:
996       g_value_set_uint (value, overlay->color);
997       break;
998     case PROP_OUTLINE_COLOR:
999       g_value_set_uint (value, overlay->outline_color);
1000       break;
1001     default:
1002       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1003       break;
1004   }
1005
1006   overlay->need_render = TRUE;
1007   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1008 }
1009
1010 static gboolean
1011 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1012     GstQuery * query)
1013 {
1014   gboolean ret = FALSE;
1015   GstBaseTextOverlay *overlay;
1016
1017   overlay = GST_BASE_TEXT_OVERLAY (parent);
1018
1019   switch (GST_QUERY_TYPE (query)) {
1020     case GST_QUERY_CAPS:
1021     {
1022       GstCaps *filter, *caps;
1023
1024       gst_query_parse_caps (query, &filter);
1025       caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1026       gst_query_set_caps_result (query, caps);
1027       gst_caps_unref (caps);
1028       ret = TRUE;
1029       break;
1030     }
1031     default:
1032       ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1033       break;
1034   }
1035
1036   return ret;
1037 }
1038
1039 static gboolean
1040 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1041     GstEvent * event)
1042 {
1043   gboolean ret = FALSE;
1044   GstBaseTextOverlay *overlay = NULL;
1045
1046   overlay = GST_BASE_TEXT_OVERLAY (parent);
1047
1048   switch (GST_EVENT_TYPE (event)) {
1049     case GST_EVENT_SEEK:{
1050       GstSeekFlags flags;
1051
1052       /* We don't handle seek if we have not text pad */
1053       if (!overlay->text_linked) {
1054         GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1055         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1056         goto beach;
1057       }
1058
1059       GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1060
1061       gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1062
1063       /* Flush downstream, only for flushing seek */
1064       if (flags & GST_SEEK_FLAG_FLUSH)
1065         gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1066
1067       /* Mark ourself as flushing, unblock chains */
1068       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1069       overlay->video_flushing = TRUE;
1070       overlay->text_flushing = TRUE;
1071       gst_base_text_overlay_pop_text (overlay);
1072       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1073
1074       /* Seek on each sink pad */
1075       gst_event_ref (event);
1076       ret = gst_pad_push_event (overlay->video_sinkpad, event);
1077       if (ret) {
1078         ret = gst_pad_push_event (overlay->text_sinkpad, event);
1079       } else {
1080         gst_event_unref (event);
1081       }
1082       break;
1083     }
1084     default:
1085       if (overlay->text_linked) {
1086         gst_event_ref (event);
1087         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1088         gst_pad_push_event (overlay->text_sinkpad, event);
1089       } else {
1090         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1091       }
1092       break;
1093   }
1094
1095 beach:
1096
1097   return ret;
1098 }
1099
1100 static GstCaps *
1101 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1102     GstCaps * filter)
1103 {
1104   GstPad *otherpad;
1105   GstCaps *caps;
1106
1107   if (G_UNLIKELY (!overlay))
1108     return gst_pad_get_pad_template_caps (pad);
1109
1110   if (pad == overlay->srcpad)
1111     otherpad = overlay->video_sinkpad;
1112   else
1113     otherpad = overlay->srcpad;
1114
1115   /* we can do what the peer can */
1116   caps = gst_pad_peer_query_caps (otherpad, filter);
1117   if (caps) {
1118     GstCaps *temp, *templ;
1119
1120     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, caps);
1121
1122     /* filtered against our padtemplate */
1123     templ = gst_pad_get_pad_template_caps (otherpad);
1124     GST_DEBUG_OBJECT (pad, "our template  %" GST_PTR_FORMAT, templ);
1125     temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1126     GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1127     gst_caps_unref (caps);
1128     gst_caps_unref (templ);
1129     /* this is what we can do */
1130     caps = temp;
1131   } else {
1132     /* no peer, our padtemplate is enough then */
1133     caps = gst_pad_get_pad_template_caps (pad);
1134     if (filter) {
1135       GstCaps *intersection;
1136
1137       intersection =
1138           gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1139       gst_caps_unref (caps);
1140       caps = intersection;
1141     }
1142   }
1143
1144   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
1145
1146   return caps;
1147 }
1148
1149 static void
1150 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1151     PangoFontDescription * desc)
1152 {
1153   gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1154   overlay->shadow_offset = (double) (font_size) / 13.0;
1155   overlay->outline_offset = (double) (font_size) / 15.0;
1156   if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1157     overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1158 }
1159
1160 static void
1161 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1162     gint * xpos, gint * ypos)
1163 {
1164   gint width, height;
1165   GstBaseTextOverlayVAlign valign;
1166   GstBaseTextOverlayHAlign halign;
1167
1168   width = overlay->image_width;
1169   height = overlay->image_height;
1170
1171   if (overlay->use_vertical_render)
1172     halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1173   else
1174     halign = overlay->halign;
1175
1176   switch (halign) {
1177     case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1178       *xpos = overlay->xpad;
1179       break;
1180     case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1181       *xpos = (overlay->width - width) / 2;
1182       break;
1183     case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1184       *xpos = overlay->width - width - overlay->xpad;
1185       break;
1186     case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1187       *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1188       *xpos = CLAMP (*xpos, 0, overlay->width - width);
1189       if (*xpos < 0)
1190         *xpos = 0;
1191       break;
1192     default:
1193       *xpos = 0;
1194   }
1195   *xpos += overlay->deltax;
1196
1197   if (overlay->use_vertical_render)
1198     valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1199   else
1200     valign = overlay->valign;
1201
1202   switch (valign) {
1203     case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1204       *ypos = overlay->height - height - overlay->ypad;
1205       break;
1206     case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1207       *ypos = overlay->height - (height + overlay->ypad);
1208       break;
1209     case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1210       *ypos = overlay->ypad;
1211       break;
1212     case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1213       *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1214       *ypos = CLAMP (*ypos, 0, overlay->height - height);
1215       break;
1216     case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1217       *ypos = (overlay->height - height) / 2;
1218       break;
1219     default:
1220       *ypos = overlay->ypad;
1221       break;
1222   }
1223   *ypos += overlay->deltay;
1224 }
1225
1226 static inline void
1227 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1228 {
1229   gint xpos, ypos;
1230   GstVideoOverlayRectangle *rectangle;
1231
1232   gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1233
1234   if (overlay->text_image) {
1235     gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1236         GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1237         overlay->image_width, overlay->image_height);
1238     rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1239         xpos, ypos, overlay->image_width, overlay->image_height,
1240         GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1241
1242     if (overlay->composition)
1243       gst_video_overlay_composition_unref (overlay->composition);
1244     overlay->composition = gst_video_overlay_composition_new (rectangle);
1245     gst_video_overlay_rectangle_unref (rectangle);
1246
1247   } else if (overlay->composition) {
1248     gst_video_overlay_composition_unref (overlay->composition);
1249     overlay->composition = NULL;
1250   }
1251 }
1252
1253 static void
1254 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1255     const gchar * string, gint textlen)
1256 {
1257   cairo_t *cr;
1258   cairo_surface_t *surface;
1259   PangoRectangle ink_rect, logical_rect;
1260   cairo_matrix_t cairo_matrix;
1261   int width, height;
1262   double scalef = 1.0;
1263   double a, r, g, b;
1264   GstBuffer *buffer;
1265   GstMapInfo map;
1266
1267   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1268
1269   if (overlay->auto_adjust_size) {
1270     /* 640 pixel is default */
1271     scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1272   }
1273   pango_layout_set_width (overlay->layout, -1);
1274   /* set text on pango layout */
1275   pango_layout_set_markup (overlay->layout, string, textlen);
1276
1277   /* get subtitle image size */
1278   pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1279
1280   width = (logical_rect.width + overlay->shadow_offset) * scalef;
1281
1282   if (width + overlay->deltax >
1283       (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1284     /*
1285      * subtitle image width is larger then overlay width
1286      * so rearrange overlay wrap mode.
1287      */
1288     gst_base_text_overlay_update_wrap_mode (overlay);
1289     pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1290     width = overlay->width;
1291   }
1292
1293   height =
1294       (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1295   if (height > overlay->height) {
1296     height = overlay->height;
1297   }
1298   if (overlay->use_vertical_render) {
1299     PangoRectangle rect;
1300     PangoContext *context;
1301     PangoMatrix matrix = PANGO_MATRIX_INIT;
1302     int tmp;
1303
1304     context = pango_layout_get_context (overlay->layout);
1305
1306     pango_matrix_rotate (&matrix, -90);
1307
1308     rect.x = rect.y = 0;
1309     rect.width = width;
1310     rect.height = height;
1311     pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1312     matrix.x0 = -rect.x;
1313     matrix.y0 = -rect.y;
1314
1315     pango_context_set_matrix (context, &matrix);
1316
1317     cairo_matrix.xx = matrix.xx;
1318     cairo_matrix.yx = matrix.yx;
1319     cairo_matrix.xy = matrix.xy;
1320     cairo_matrix.yy = matrix.yy;
1321     cairo_matrix.x0 = matrix.x0;
1322     cairo_matrix.y0 = matrix.y0;
1323     cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1324
1325     tmp = height;
1326     height = width;
1327     width = tmp;
1328   } else {
1329     cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1330   }
1331
1332   /* reallocate overlay buffer */
1333   buffer = gst_buffer_new_and_alloc (4 * width * height);
1334   gst_buffer_replace (&overlay->text_image, buffer);
1335   gst_buffer_unref (buffer);
1336
1337   gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1338   surface = cairo_image_surface_create_for_data (map.data,
1339       CAIRO_FORMAT_ARGB32, width, height, width * 4);
1340   cr = cairo_create (surface);
1341
1342   /* clear surface */
1343   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1344   cairo_paint (cr);
1345
1346   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1347
1348   if (overlay->want_shading)
1349     cairo_paint_with_alpha (cr, overlay->shading_value);
1350
1351   /* apply transformations */
1352   cairo_set_matrix (cr, &cairo_matrix);
1353
1354   /* FIXME: We use show_layout everywhere except for the surface
1355    * because it's really faster and internally does all kinds of
1356    * caching. Unfortunately we have to paint to a cairo path for
1357    * the outline and this is slow. Once Pango supports user fonts
1358    * we should use them, see
1359    * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1360    *
1361    * Idea would the be, to create a cairo user font that
1362    * does shadow, outline, text painting in the
1363    * render_glyph function.
1364    */
1365
1366   /* draw shadow text */
1367   cairo_save (cr);
1368   cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1369   cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1370   pango_cairo_show_layout (cr, overlay->layout);
1371   cairo_restore (cr);
1372
1373   a = (overlay->outline_color >> 24) & 0xff;
1374   r = (overlay->outline_color >> 16) & 0xff;
1375   g = (overlay->outline_color >> 8) & 0xff;
1376   b = (overlay->outline_color >> 0) & 0xff;
1377
1378   /* draw outline text */
1379   cairo_save (cr);
1380   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1381   cairo_set_line_width (cr, overlay->outline_offset);
1382   pango_cairo_layout_path (cr, overlay->layout);
1383   cairo_stroke (cr);
1384   cairo_restore (cr);
1385
1386   a = (overlay->color >> 24) & 0xff;
1387   r = (overlay->color >> 16) & 0xff;
1388   g = (overlay->color >> 8) & 0xff;
1389   b = (overlay->color >> 0) & 0xff;
1390
1391   /* draw text */
1392   cairo_save (cr);
1393   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1394   pango_cairo_show_layout (cr, overlay->layout);
1395   cairo_restore (cr);
1396
1397   cairo_destroy (cr);
1398   cairo_surface_destroy (surface);
1399   gst_buffer_unmap (buffer, &map);
1400   overlay->image_width = width;
1401   overlay->image_height = height;
1402   overlay->baseline_y = ink_rect.y;
1403   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1404
1405   gst_base_text_overlay_set_composition (overlay);
1406 }
1407
1408 #define BOX_XPAD         6
1409 #define BOX_YPAD         6
1410
1411 static inline void
1412 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1413     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1414 {
1415   gint i, j, dest_stride;
1416   guint8 *dest_ptr;
1417
1418   dest_stride = dest->info.stride[0];
1419   dest_ptr = dest->data[0];
1420
1421   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1422   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1423
1424   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1425   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1426
1427   for (i = y0; i < y1; ++i) {
1428     for (j = x0; j < x1; ++j) {
1429       gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1430
1431       dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1432     }
1433   }
1434 }
1435
1436 static inline void
1437 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1438     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1439 {
1440   gint i, j;
1441   guint dest_stride, pixel_stride;
1442   guint8 *dest_ptr;
1443
1444   dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1445   dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1446   pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1447
1448   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1449   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1450
1451   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1452   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1453
1454   if (x0 != 0)
1455     x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1456   if (x1 != 0)
1457     x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1458
1459   if (y0 != 0)
1460     y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1461   if (y1 != 0)
1462     y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1463
1464   for (i = y0; i < y1; i++) {
1465     for (j = x0; j < x1; j++) {
1466       gint y;
1467       gint y_pos;
1468
1469       y_pos = (i * dest_stride) + j * pixel_stride;
1470       y = dest_ptr[y_pos] + overlay->shading_value;
1471
1472       dest_ptr[y_pos] = CLAMP (y, 0, 255);
1473     }
1474   }
1475 }
1476
1477 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1478 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1479 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1480 static inline void
1481 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1482     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1483 {
1484   gint i, j;
1485   guint8 *dest_ptr;
1486
1487   dest_ptr = dest->data[0];
1488
1489   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1490   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1491
1492   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1493   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1494
1495   for (i = y0; i < y1; i++) {
1496     for (j = x0; j < x1; j++) {
1497       gint y, y_pos, k;
1498
1499       y_pos = (i * 4 * overlay->width) + j * 4;
1500       for (k = 0; k < 4; k++) {
1501         y = dest_ptr[y_pos + k] + overlay->shading_value;
1502         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1503       }
1504     }
1505   }
1506 }
1507
1508 #define ARGB_SHADE_FUNCTION(name, OFFSET)       \
1509 static inline void \
1510 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1511 gint x0, gint x1, gint y0, gint y1) \
1512 { \
1513   gint i, j;\
1514   guint8 *dest_ptr;\
1515   \
1516   dest_ptr = dest->data[0];\
1517   \
1518   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1519   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1520   \
1521   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1522   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1523   \
1524   for (i = y0; i < y1; i++) {\
1525     for (j = x0; j < x1; j++) {\
1526       gint y, y_pos, k;\
1527       y_pos = (i * 4 * overlay->width) + j * 4;\
1528       for (k = OFFSET; k < 3+OFFSET; k++) {\
1529         y = dest_ptr[y_pos + k] + overlay->shading_value;\
1530         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1531       }\
1532     }\
1533   }\
1534 }
1535 ARGB_SHADE_FUNCTION (ARGB, 1);
1536 ARGB_SHADE_FUNCTION (ABGR, 1);
1537 ARGB_SHADE_FUNCTION (RGBA, 0);
1538 ARGB_SHADE_FUNCTION (BGRA, 0);
1539
1540 static void
1541 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1542     const gchar * text, gint textlen)
1543 {
1544   gchar *string;
1545
1546   if (!overlay->need_render) {
1547     GST_DEBUG ("Using previously rendered text.");
1548     return;
1549   }
1550
1551   /* -1 is the whole string */
1552   if (text != NULL && textlen < 0) {
1553     textlen = strlen (text);
1554   }
1555
1556   if (text != NULL) {
1557     string = g_strndup (text, textlen);
1558   } else {                      /* empty string */
1559     string = g_strdup (" ");
1560   }
1561   g_strdelimit (string, "\r\t", ' ');
1562   textlen = strlen (string);
1563
1564   /* FIXME: should we check for UTF-8 here? */
1565
1566   GST_DEBUG ("Rendering '%s'", string);
1567   gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1568
1569   g_free (string);
1570
1571   overlay->need_render = FALSE;
1572 }
1573
1574 static GstFlowReturn
1575 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1576     GstBuffer * video_frame)
1577 {
1578   gint xpos, ypos;
1579   GstVideoFrame frame;
1580
1581   if (overlay->composition == NULL)
1582     goto done;
1583
1584   if (gst_pad_check_reconfigure (overlay->srcpad))
1585     gst_base_text_overlay_negotiate (overlay);
1586
1587   video_frame = gst_buffer_make_writable (video_frame);
1588
1589   if (overlay->attach_compo_to_buffer) {
1590     GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1591     gst_buffer_add_video_overlay_composition_meta (video_frame,
1592         overlay->composition);
1593     /* FIXME: emulate shaded background box if want_shading=true */
1594     goto done;
1595   }
1596
1597   if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1598           GST_MAP_READWRITE))
1599     goto invalid_frame;
1600
1601   gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1602
1603   /* shaded background box */
1604   if (overlay->want_shading) {
1605     switch (overlay->format) {
1606       case GST_VIDEO_FORMAT_I420:
1607       case GST_VIDEO_FORMAT_NV12:
1608       case GST_VIDEO_FORMAT_NV21:
1609         gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1610             xpos, xpos + overlay->image_width,
1611             ypos, ypos + overlay->image_height);
1612         break;
1613       case GST_VIDEO_FORMAT_AYUV:
1614       case GST_VIDEO_FORMAT_UYVY:
1615         gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1616             xpos, xpos + overlay->image_width,
1617             ypos, ypos + overlay->image_height);
1618         break;
1619       case GST_VIDEO_FORMAT_xRGB:
1620         gst_base_text_overlay_shade_xRGB (overlay, &frame,
1621             xpos, xpos + overlay->image_width,
1622             ypos, ypos + overlay->image_height);
1623         break;
1624       case GST_VIDEO_FORMAT_xBGR:
1625         gst_base_text_overlay_shade_xBGR (overlay, &frame,
1626             xpos, xpos + overlay->image_width,
1627             ypos, ypos + overlay->image_height);
1628         break;
1629       case GST_VIDEO_FORMAT_BGRx:
1630         gst_base_text_overlay_shade_BGRx (overlay, &frame,
1631             xpos, xpos + overlay->image_width,
1632             ypos, ypos + overlay->image_height);
1633         break;
1634       case GST_VIDEO_FORMAT_RGBx:
1635         gst_base_text_overlay_shade_RGBx (overlay, &frame,
1636             xpos, xpos + overlay->image_width,
1637             ypos, ypos + overlay->image_height);
1638         break;
1639       case GST_VIDEO_FORMAT_ARGB:
1640         gst_base_text_overlay_shade_ARGB (overlay, &frame,
1641             xpos, xpos + overlay->image_width,
1642             ypos, ypos + overlay->image_height);
1643         break;
1644       case GST_VIDEO_FORMAT_ABGR:
1645         gst_base_text_overlay_shade_ABGR (overlay, &frame,
1646             xpos, xpos + overlay->image_width,
1647             ypos, ypos + overlay->image_height);
1648         break;
1649       case GST_VIDEO_FORMAT_RGBA:
1650         gst_base_text_overlay_shade_RGBA (overlay, &frame,
1651             xpos, xpos + overlay->image_width,
1652             ypos, ypos + overlay->image_height);
1653         break;
1654       case GST_VIDEO_FORMAT_BGRA:
1655         gst_base_text_overlay_shade_BGRA (overlay, &frame,
1656             xpos, xpos + overlay->image_width,
1657             ypos, ypos + overlay->image_height);
1658         break;
1659       default:
1660         g_assert_not_reached ();
1661     }
1662   }
1663
1664   gst_video_overlay_composition_blend (overlay->composition, &frame);
1665
1666   gst_video_frame_unmap (&frame);
1667
1668 done:
1669
1670   return gst_pad_push (overlay->srcpad, video_frame);
1671
1672   /* ERRORS */
1673 invalid_frame:
1674   {
1675     gst_buffer_unref (video_frame);
1676     GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1677     return GST_FLOW_OK;
1678   }
1679 }
1680
1681 static GstPadLinkReturn
1682 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1683 {
1684   GstBaseTextOverlay *overlay;
1685
1686   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1687   if (G_UNLIKELY (!overlay))
1688     return GST_PAD_LINK_REFUSED;
1689
1690   GST_DEBUG_OBJECT (overlay, "Text pad linked");
1691
1692   overlay->text_linked = TRUE;
1693
1694   gst_object_unref (overlay);
1695
1696   return GST_PAD_LINK_OK;
1697 }
1698
1699 static void
1700 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
1701 {
1702   GstBaseTextOverlay *overlay;
1703
1704   /* don't use gst_pad_get_parent() here, will deadlock */
1705   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1706
1707   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1708
1709   overlay->text_linked = FALSE;
1710
1711   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1712 }
1713
1714 static gboolean
1715 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1716     GstEvent * event)
1717 {
1718   gboolean ret = FALSE;
1719   GstBaseTextOverlay *overlay = NULL;
1720
1721   overlay = GST_BASE_TEXT_OVERLAY (parent);
1722
1723   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1724
1725   switch (GST_EVENT_TYPE (event)) {
1726     case GST_EVENT_CAPS:
1727     {
1728       GstCaps *caps;
1729
1730       gst_event_parse_caps (event, &caps);
1731       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1732       gst_event_unref (event);
1733       break;
1734     }
1735     case GST_EVENT_SEGMENT:
1736     {
1737       const GstSegment *segment;
1738
1739       overlay->text_eos = FALSE;
1740
1741       gst_event_parse_segment (event, &segment);
1742
1743       if (segment->format == GST_FORMAT_TIME) {
1744         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1745         gst_segment_copy_into (segment, &overlay->text_segment);
1746         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1747             &overlay->text_segment);
1748         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1749       } else {
1750         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1751             ("received non-TIME newsegment event on text input"));
1752       }
1753
1754       gst_event_unref (event);
1755       ret = TRUE;
1756
1757       /* wake up the video chain, it might be waiting for a text buffer or
1758        * a text segment update */
1759       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1760       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1761       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1762       break;
1763     }
1764     case GST_EVENT_FLUSH_STOP:
1765       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1766       GST_INFO_OBJECT (overlay, "text flush stop");
1767       overlay->text_flushing = FALSE;
1768       overlay->text_eos = FALSE;
1769       gst_base_text_overlay_pop_text (overlay);
1770       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1771       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1772       gst_event_unref (event);
1773       ret = TRUE;
1774       break;
1775     case GST_EVENT_FLUSH_START:
1776       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1777       GST_INFO_OBJECT (overlay, "text flush start");
1778       overlay->text_flushing = TRUE;
1779       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1780       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1781       gst_event_unref (event);
1782       ret = TRUE;
1783       break;
1784     case GST_EVENT_EOS:
1785       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1786       overlay->text_eos = TRUE;
1787       GST_INFO_OBJECT (overlay, "text EOS");
1788       /* wake up the video chain, it might be waiting for a text buffer or
1789        * a text segment update */
1790       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1791       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1792       gst_event_unref (event);
1793       ret = TRUE;
1794       break;
1795     default:
1796       ret = gst_pad_event_default (pad, parent, event);
1797       break;
1798   }
1799
1800   return ret;
1801 }
1802
1803 static gboolean
1804 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1805     GstEvent * event)
1806 {
1807   gboolean ret = FALSE;
1808   GstBaseTextOverlay *overlay = NULL;
1809
1810   overlay = GST_BASE_TEXT_OVERLAY (parent);
1811
1812   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1813
1814   switch (GST_EVENT_TYPE (event)) {
1815     case GST_EVENT_CAPS:
1816     {
1817       GstCaps *caps;
1818
1819       gst_event_parse_caps (event, &caps);
1820       ret = gst_base_text_overlay_setcaps (overlay, caps);
1821       gst_event_unref (event);
1822       break;
1823     }
1824     case GST_EVENT_SEGMENT:
1825     {
1826       const GstSegment *segment;
1827
1828       GST_DEBUG_OBJECT (overlay, "received new segment");
1829
1830       gst_event_parse_segment (event, &segment);
1831
1832       if (segment->format == GST_FORMAT_TIME) {
1833         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1834             &overlay->segment);
1835
1836         gst_segment_copy_into (segment, &overlay->segment);
1837       } else {
1838         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1839             ("received non-TIME newsegment event on video input"));
1840       }
1841
1842       ret = gst_pad_event_default (pad, parent, event);
1843       break;
1844     }
1845     case GST_EVENT_EOS:
1846       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1847       GST_INFO_OBJECT (overlay, "video EOS");
1848       overlay->video_eos = TRUE;
1849       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1850       ret = gst_pad_event_default (pad, parent, event);
1851       break;
1852     case GST_EVENT_FLUSH_START:
1853       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1854       GST_INFO_OBJECT (overlay, "video flush start");
1855       overlay->video_flushing = TRUE;
1856       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1857       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1858       ret = gst_pad_event_default (pad, parent, event);
1859       break;
1860     case GST_EVENT_FLUSH_STOP:
1861       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1862       GST_INFO_OBJECT (overlay, "video flush stop");
1863       overlay->video_flushing = FALSE;
1864       overlay->video_eos = FALSE;
1865       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1866       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1867       ret = gst_pad_event_default (pad, parent, event);
1868       break;
1869     default:
1870       ret = gst_pad_event_default (pad, parent, event);
1871       break;
1872   }
1873
1874   return ret;
1875 }
1876
1877 static gboolean
1878 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1879     GstQuery * query)
1880 {
1881   gboolean ret = FALSE;
1882   GstBaseTextOverlay *overlay;
1883
1884   overlay = GST_BASE_TEXT_OVERLAY (parent);
1885
1886   switch (GST_QUERY_TYPE (query)) {
1887     case GST_QUERY_CAPS:
1888     {
1889       GstCaps *filter, *caps;
1890
1891       gst_query_parse_caps (query, &filter);
1892       caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1893       gst_query_set_caps_result (query, caps);
1894       gst_caps_unref (caps);
1895       ret = TRUE;
1896       break;
1897     }
1898     default:
1899       ret = gst_pad_query_default (pad, parent, query);
1900       break;
1901   }
1902
1903   return ret;
1904 }
1905
1906 /* Called with lock held */
1907 static void
1908 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1909 {
1910   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1911
1912   if (overlay->text_buffer) {
1913     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1914         overlay->text_buffer);
1915     gst_buffer_unref (overlay->text_buffer);
1916     overlay->text_buffer = NULL;
1917   }
1918
1919   /* Let the text task know we used that buffer */
1920   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1921 }
1922
1923 /* We receive text buffers here. If they are out of segment we just ignore them.
1924    If the buffer is in our segment we keep it internally except if another one
1925    is already waiting here, in that case we wait that it gets kicked out */
1926 static GstFlowReturn
1927 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1928     GstBuffer * buffer)
1929 {
1930   GstFlowReturn ret = GST_FLOW_OK;
1931   GstBaseTextOverlay *overlay = NULL;
1932   gboolean in_seg = FALSE;
1933   guint64 clip_start = 0, clip_stop = 0;
1934
1935   overlay = GST_BASE_TEXT_OVERLAY (parent);
1936
1937   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1938
1939   if (overlay->text_flushing) {
1940     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1941     ret = GST_FLOW_FLUSHING;
1942     GST_LOG_OBJECT (overlay, "text flushing");
1943     goto beach;
1944   }
1945
1946   if (overlay->text_eos) {
1947     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1948     ret = GST_FLOW_EOS;
1949     GST_LOG_OBJECT (overlay, "text EOS");
1950     goto beach;
1951   }
1952
1953   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
1954       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1955       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1956       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1957           GST_BUFFER_DURATION (buffer)));
1958
1959   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1960     GstClockTime stop;
1961
1962     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1963       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1964     else
1965       stop = GST_CLOCK_TIME_NONE;
1966
1967     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
1968         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1969   } else {
1970     in_seg = TRUE;
1971   }
1972
1973   if (in_seg) {
1974     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1975       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1976     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1977       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1978
1979     /* Wait for the previous buffer to go away */
1980     while (overlay->text_buffer != NULL) {
1981       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
1982           GST_DEBUG_PAD_NAME (pad));
1983       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
1984       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
1985       if (overlay->text_flushing) {
1986         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1987         ret = GST_FLOW_FLUSHING;
1988         goto beach;
1989       }
1990     }
1991
1992     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1993       overlay->text_segment.position = clip_start;
1994
1995     overlay->text_buffer = buffer;
1996     /* That's a new text buffer we need to render */
1997     overlay->need_render = TRUE;
1998
1999     /* in case the video chain is waiting for a text buffer, wake it up */
2000     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2001   }
2002
2003   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2004
2005 beach:
2006
2007   return ret;
2008 }
2009
2010 static GstFlowReturn
2011 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2012     GstBuffer * buffer)
2013 {
2014   GstBaseTextOverlayClass *klass;
2015   GstBaseTextOverlay *overlay;
2016   GstFlowReturn ret = GST_FLOW_OK;
2017   gboolean in_seg = FALSE;
2018   guint64 start, stop, clip_start = 0, clip_stop = 0;
2019   gchar *text = NULL;
2020
2021   overlay = GST_BASE_TEXT_OVERLAY (parent);
2022   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2023
2024   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2025     goto missing_timestamp;
2026
2027   /* ignore buffers that are outside of the current segment */
2028   start = GST_BUFFER_TIMESTAMP (buffer);
2029
2030   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2031     stop = GST_CLOCK_TIME_NONE;
2032   } else {
2033     stop = start + GST_BUFFER_DURATION (buffer);
2034   }
2035
2036   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2037       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2038       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2039
2040   /* segment_clip() will adjust start unconditionally to segment_start if
2041    * no stop time is provided, so handle this ourselves */
2042   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2043     goto out_of_segment;
2044
2045   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2046       &clip_start, &clip_stop);
2047
2048   if (!in_seg)
2049     goto out_of_segment;
2050
2051   /* if the buffer is only partially in the segment, fix up stamps */
2052   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2053     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2054     buffer = gst_buffer_make_writable (buffer);
2055     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2056     if (stop != -1)
2057       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2058   }
2059
2060   /* now, after we've done the clipping, fix up end time if there's no
2061    * duration (we only use those estimated values internally though, we
2062    * don't want to set bogus values on the buffer itself) */
2063   if (stop == -1) {
2064     GstCaps *caps;
2065     GstStructure *s;
2066     gint fps_num, fps_denom;
2067
2068     /* FIXME, store this in setcaps */
2069     caps = gst_pad_get_current_caps (pad);
2070     s = gst_caps_get_structure (caps, 0);
2071     if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2072         fps_num && fps_denom) {
2073       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2074       stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2075     } else {
2076       GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2077       stop = start + 1;         /* we need to assume some interval */
2078     }
2079     gst_caps_unref (caps);
2080   }
2081
2082   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2083
2084 wait_for_text_buf:
2085
2086   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2087
2088   if (overlay->video_flushing)
2089     goto flushing;
2090
2091   if (overlay->video_eos)
2092     goto have_eos;
2093
2094   if (overlay->silent) {
2095     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2096     ret = gst_pad_push (overlay->srcpad, buffer);
2097
2098     /* Update position */
2099     overlay->segment.position = clip_start;
2100
2101     return ret;
2102   }
2103
2104   /* Text pad not linked, rendering internal text */
2105   if (!overlay->text_linked) {
2106     if (klass->get_text) {
2107       text = klass->get_text (overlay, buffer);
2108     } else {
2109       text = g_strdup (overlay->default_text);
2110     }
2111
2112     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2113         "text: '%s'", GST_STR_NULL (text));
2114
2115     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2116
2117     if (text != NULL && *text != '\0') {
2118       /* Render and push */
2119       gst_base_text_overlay_render_text (overlay, text, -1);
2120       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2121     } else {
2122       /* Invalid or empty string */
2123       ret = gst_pad_push (overlay->srcpad, buffer);
2124     }
2125   } else {
2126     /* Text pad linked, check if we have a text buffer queued */
2127     if (overlay->text_buffer) {
2128       gboolean pop_text = FALSE, valid_text_time = TRUE;
2129       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2130       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2131       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2132       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2133       GstClockTime vid_running_time, vid_running_time_end;
2134
2135       /* if the text buffer isn't stamped right, pop it off the
2136        * queue and display it for the current video frame only */
2137       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2138           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2139         GST_WARNING_OBJECT (overlay,
2140             "Got text buffer with invalid timestamp or duration");
2141         pop_text = TRUE;
2142         valid_text_time = FALSE;
2143       } else {
2144         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2145         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2146       }
2147
2148       vid_running_time =
2149           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2150           start);
2151       vid_running_time_end =
2152           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2153           stop);
2154
2155       /* If timestamp and duration are valid */
2156       if (valid_text_time) {
2157         text_running_time =
2158             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2159             text_start);
2160         text_running_time_end =
2161             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2162             text_end);
2163       }
2164
2165       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2166           GST_TIME_ARGS (text_running_time),
2167           GST_TIME_ARGS (text_running_time_end));
2168       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2169           GST_TIME_ARGS (vid_running_time),
2170           GST_TIME_ARGS (vid_running_time_end));
2171
2172       /* Text too old or in the future */
2173       if (valid_text_time && text_running_time_end <= vid_running_time) {
2174         /* text buffer too old, get rid of it and do nothing  */
2175         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2176         pop_text = FALSE;
2177         gst_base_text_overlay_pop_text (overlay);
2178         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2179         goto wait_for_text_buf;
2180       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2181         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2182         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2183         /* Push the video frame */
2184         ret = gst_pad_push (overlay->srcpad, buffer);
2185       } else {
2186         GstMapInfo map;
2187         gchar *in_text;
2188         gsize in_size;
2189
2190         gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2191         in_text = (gchar *) map.data;
2192         in_size = map.size;
2193
2194         /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2195          * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2196          * here on purpose, this is something that needs fixing upstream */
2197         if (!g_utf8_validate (in_text, in_size, NULL)) {
2198           const gchar *end = NULL;
2199
2200           GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2201           in_text = g_strndup (in_text, in_size);
2202           while (!g_utf8_validate (in_text, in_size, &end) && end)
2203             *((gchar *) end) = '*';
2204         }
2205
2206         /* Get the string */
2207         if (overlay->have_pango_markup) {
2208           text = g_strndup (in_text, in_size);
2209         } else {
2210           text = g_markup_escape_text (in_text, in_size);
2211         }
2212
2213         if (text != NULL && *text != '\0') {
2214           gint text_len = strlen (text);
2215
2216           while (text_len > 0 && (text[text_len - 1] == '\n' ||
2217                   text[text_len - 1] == '\r')) {
2218             --text_len;
2219           }
2220           GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2221           gst_base_text_overlay_render_text (overlay, text, text_len);
2222         } else {
2223           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2224           gst_base_text_overlay_render_text (overlay, " ", 1);
2225         }
2226         if (in_text != (gchar *) map.data)
2227           g_free (in_text);
2228
2229         gst_buffer_unmap (overlay->text_buffer, &map);
2230
2231         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2232         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2233
2234         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2235           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2236           pop_text = TRUE;
2237         }
2238       }
2239       if (pop_text) {
2240         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2241         gst_base_text_overlay_pop_text (overlay);
2242         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2243       }
2244     } else {
2245       gboolean wait_for_text_buf = TRUE;
2246
2247       if (overlay->text_eos)
2248         wait_for_text_buf = FALSE;
2249
2250       if (!overlay->wait_text)
2251         wait_for_text_buf = FALSE;
2252
2253       /* Text pad linked, but no text buffer available - what now? */
2254       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2255         GstClockTime text_start_running_time, text_position_running_time;
2256         GstClockTime vid_running_time;
2257
2258         vid_running_time =
2259             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2260             GST_BUFFER_TIMESTAMP (buffer));
2261         text_start_running_time =
2262             gst_segment_to_running_time (&overlay->text_segment,
2263             GST_FORMAT_TIME, overlay->text_segment.start);
2264         text_position_running_time =
2265             gst_segment_to_running_time (&overlay->text_segment,
2266             GST_FORMAT_TIME, overlay->text_segment.position);
2267
2268         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2269                 vid_running_time < text_start_running_time) ||
2270             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2271                 vid_running_time < text_position_running_time)) {
2272           wait_for_text_buf = FALSE;
2273         }
2274       }
2275
2276       if (wait_for_text_buf) {
2277         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2278         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2279         GST_DEBUG_OBJECT (overlay, "resuming");
2280         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2281         goto wait_for_text_buf;
2282       } else {
2283         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2284         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2285         ret = gst_pad_push (overlay->srcpad, buffer);
2286       }
2287     }
2288   }
2289
2290   g_free (text);
2291
2292   /* Update position */
2293   overlay->segment.position = clip_start;
2294
2295   return ret;
2296
2297 missing_timestamp:
2298   {
2299     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2300     gst_buffer_unref (buffer);
2301     return GST_FLOW_OK;
2302   }
2303
2304 flushing:
2305   {
2306     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2307     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2308     gst_buffer_unref (buffer);
2309     return GST_FLOW_FLUSHING;
2310   }
2311 have_eos:
2312   {
2313     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2314     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2315     gst_buffer_unref (buffer);
2316     return GST_FLOW_EOS;
2317   }
2318 out_of_segment:
2319   {
2320     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2321     gst_buffer_unref (buffer);
2322     return GST_FLOW_OK;
2323   }
2324 }
2325
2326 static GstStateChangeReturn
2327 gst_base_text_overlay_change_state (GstElement * element,
2328     GstStateChange transition)
2329 {
2330   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2331   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2332
2333   switch (transition) {
2334     case GST_STATE_CHANGE_PAUSED_TO_READY:
2335       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2336       overlay->text_flushing = TRUE;
2337       overlay->video_flushing = TRUE;
2338       /* pop_text will broadcast on the GCond and thus also make the video
2339        * chain exit if it's waiting for a text buffer */
2340       gst_base_text_overlay_pop_text (overlay);
2341       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2342       break;
2343     default:
2344       break;
2345   }
2346
2347   ret = parent_class->change_state (element, transition);
2348   if (ret == GST_STATE_CHANGE_FAILURE)
2349     return ret;
2350
2351   switch (transition) {
2352     case GST_STATE_CHANGE_READY_TO_PAUSED:
2353       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2354       overlay->text_flushing = FALSE;
2355       overlay->video_flushing = FALSE;
2356       overlay->video_eos = FALSE;
2357       overlay->text_eos = FALSE;
2358       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2359       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2360       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2361       break;
2362     default:
2363       break;
2364   }
2365
2366   return ret;
2367 }
2368
2369 static gboolean
2370 plugin_init (GstPlugin * plugin)
2371 {
2372   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2373           GST_TYPE_TEXT_OVERLAY) ||
2374       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2375           GST_TYPE_TIME_OVERLAY) ||
2376       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2377           GST_TYPE_CLOCK_OVERLAY) ||
2378       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2379           GST_TYPE_TEXT_RENDER)) {
2380     return FALSE;
2381   }
2382
2383   /*texttestsrc_plugin_init(module, plugin); */
2384
2385   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2386
2387   return TRUE;
2388 }
2389
2390 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2391     pango, "Pango-based text rendering and overlay", plugin_init,
2392     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)