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