pango: delete foreground color in shadow text
[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 gboolean
1256 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1257 {
1258   if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1259     return FALSE;
1260   } else {
1261     return TRUE;
1262   }
1263 }
1264
1265 static void
1266 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1267     const gchar * string, gint textlen)
1268 {
1269   cairo_t *cr;
1270   cairo_surface_t *surface;
1271   PangoRectangle ink_rect, logical_rect;
1272   cairo_matrix_t cairo_matrix;
1273   int width, height;
1274   double scalef = 1.0;
1275   double a, r, g, b;
1276   GstBuffer *buffer;
1277   GstMapInfo map;
1278
1279   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1280
1281   if (overlay->auto_adjust_size) {
1282     /* 640 pixel is default */
1283     scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1284   }
1285   pango_layout_set_width (overlay->layout, -1);
1286   /* set text on pango layout */
1287   pango_layout_set_markup (overlay->layout, string, textlen);
1288
1289   /* get subtitle image size */
1290   pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1291
1292   width = (logical_rect.width + overlay->shadow_offset) * scalef;
1293
1294   if (width + overlay->deltax >
1295       (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1296     /*
1297      * subtitle image width is larger then overlay width
1298      * so rearrange overlay wrap mode.
1299      */
1300     gst_base_text_overlay_update_wrap_mode (overlay);
1301     pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1302     width = overlay->width;
1303   }
1304
1305   height =
1306       (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1307   if (height > overlay->height) {
1308     height = overlay->height;
1309   }
1310   if (overlay->use_vertical_render) {
1311     PangoRectangle rect;
1312     PangoContext *context;
1313     PangoMatrix matrix = PANGO_MATRIX_INIT;
1314     int tmp;
1315
1316     context = pango_layout_get_context (overlay->layout);
1317
1318     pango_matrix_rotate (&matrix, -90);
1319
1320     rect.x = rect.y = 0;
1321     rect.width = width;
1322     rect.height = height;
1323     pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1324     matrix.x0 = -rect.x;
1325     matrix.y0 = -rect.y;
1326
1327     pango_context_set_matrix (context, &matrix);
1328
1329     cairo_matrix.xx = matrix.xx;
1330     cairo_matrix.yx = matrix.yx;
1331     cairo_matrix.xy = matrix.xy;
1332     cairo_matrix.yy = matrix.yy;
1333     cairo_matrix.x0 = matrix.x0;
1334     cairo_matrix.y0 = matrix.y0;
1335     cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1336
1337     tmp = height;
1338     height = width;
1339     width = tmp;
1340   } else {
1341     cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1342   }
1343
1344   /* reallocate overlay buffer */
1345   buffer = gst_buffer_new_and_alloc (4 * width * height);
1346   gst_buffer_replace (&overlay->text_image, buffer);
1347   gst_buffer_unref (buffer);
1348
1349   gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1350   surface = cairo_image_surface_create_for_data (map.data,
1351       CAIRO_FORMAT_ARGB32, width, height, width * 4);
1352   cr = cairo_create (surface);
1353
1354   /* clear surface */
1355   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1356   cairo_paint (cr);
1357
1358   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1359
1360   if (overlay->want_shading)
1361     cairo_paint_with_alpha (cr, overlay->shading_value);
1362
1363   /* apply transformations */
1364   cairo_set_matrix (cr, &cairo_matrix);
1365
1366   /* FIXME: We use show_layout everywhere except for the surface
1367    * because it's really faster and internally does all kinds of
1368    * caching. Unfortunately we have to paint to a cairo path for
1369    * the outline and this is slow. Once Pango supports user fonts
1370    * we should use them, see
1371    * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1372    *
1373    * Idea would the be, to create a cairo user font that
1374    * does shadow, outline, text painting in the
1375    * render_glyph function.
1376    */
1377
1378   /* draw shadow text */
1379   {
1380     PangoAttrList *origin_attr, *filtered_attr;
1381
1382     origin_attr =
1383         pango_attr_list_copy (pango_layout_get_attributes (overlay->layout));
1384     filtered_attr =
1385         pango_attr_list_filter (pango_attr_list_copy (origin_attr),
1386         gst_text_overlay_filter_foreground_attr, NULL);
1387
1388     cairo_save (cr);
1389     cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1390     cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1391     pango_layout_set_attributes (overlay->layout, filtered_attr);
1392     pango_cairo_show_layout (cr, overlay->layout);
1393     pango_layout_set_attributes (overlay->layout, origin_attr);
1394     pango_attr_list_unref (filtered_attr);
1395     pango_attr_list_unref (origin_attr);
1396     cairo_restore (cr);
1397   }
1398
1399   a = (overlay->outline_color >> 24) & 0xff;
1400   r = (overlay->outline_color >> 16) & 0xff;
1401   g = (overlay->outline_color >> 8) & 0xff;
1402   b = (overlay->outline_color >> 0) & 0xff;
1403
1404   /* draw outline text */
1405   cairo_save (cr);
1406   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1407   cairo_set_line_width (cr, overlay->outline_offset);
1408   pango_cairo_layout_path (cr, overlay->layout);
1409   cairo_stroke (cr);
1410   cairo_restore (cr);
1411
1412   a = (overlay->color >> 24) & 0xff;
1413   r = (overlay->color >> 16) & 0xff;
1414   g = (overlay->color >> 8) & 0xff;
1415   b = (overlay->color >> 0) & 0xff;
1416
1417   /* draw text */
1418   cairo_save (cr);
1419   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1420   pango_cairo_show_layout (cr, overlay->layout);
1421   cairo_restore (cr);
1422
1423   cairo_destroy (cr);
1424   cairo_surface_destroy (surface);
1425   gst_buffer_unmap (buffer, &map);
1426   overlay->image_width = width;
1427   overlay->image_height = height;
1428   overlay->baseline_y = ink_rect.y;
1429   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1430
1431   gst_base_text_overlay_set_composition (overlay);
1432 }
1433
1434 #define BOX_XPAD         6
1435 #define BOX_YPAD         6
1436
1437 static inline void
1438 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1439     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1440 {
1441   gint i, j, dest_stride;
1442   guint8 *dest_ptr;
1443
1444   dest_stride = dest->info.stride[0];
1445   dest_ptr = dest->data[0];
1446
1447   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1448   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1449
1450   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1451   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1452
1453   for (i = y0; i < y1; ++i) {
1454     for (j = x0; j < x1; ++j) {
1455       gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1456
1457       dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1458     }
1459   }
1460 }
1461
1462 static inline void
1463 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1464     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1465 {
1466   gint i, j;
1467   guint dest_stride, pixel_stride;
1468   guint8 *dest_ptr;
1469
1470   dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1471   dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1472   pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1473
1474   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1475   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1476
1477   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1478   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1479
1480   if (x0 != 0)
1481     x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1482   if (x1 != 0)
1483     x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1484
1485   if (y0 != 0)
1486     y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1487   if (y1 != 0)
1488     y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1489
1490   for (i = y0; i < y1; i++) {
1491     for (j = x0; j < x1; j++) {
1492       gint y;
1493       gint y_pos;
1494
1495       y_pos = (i * dest_stride) + j * pixel_stride;
1496       y = dest_ptr[y_pos] + overlay->shading_value;
1497
1498       dest_ptr[y_pos] = CLAMP (y, 0, 255);
1499     }
1500   }
1501 }
1502
1503 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1504 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1505 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1506 static inline void
1507 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1508     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1509 {
1510   gint i, j;
1511   guint8 *dest_ptr;
1512
1513   dest_ptr = dest->data[0];
1514
1515   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1516   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1517
1518   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1519   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1520
1521   for (i = y0; i < y1; i++) {
1522     for (j = x0; j < x1; j++) {
1523       gint y, y_pos, k;
1524
1525       y_pos = (i * 4 * overlay->width) + j * 4;
1526       for (k = 0; k < 4; k++) {
1527         y = dest_ptr[y_pos + k] + overlay->shading_value;
1528         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1529       }
1530     }
1531   }
1532 }
1533
1534 #define ARGB_SHADE_FUNCTION(name, OFFSET)       \
1535 static inline void \
1536 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1537 gint x0, gint x1, gint y0, gint y1) \
1538 { \
1539   gint i, j;\
1540   guint8 *dest_ptr;\
1541   \
1542   dest_ptr = dest->data[0];\
1543   \
1544   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1545   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1546   \
1547   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1548   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1549   \
1550   for (i = y0; i < y1; i++) {\
1551     for (j = x0; j < x1; j++) {\
1552       gint y, y_pos, k;\
1553       y_pos = (i * 4 * overlay->width) + j * 4;\
1554       for (k = OFFSET; k < 3+OFFSET; k++) {\
1555         y = dest_ptr[y_pos + k] + overlay->shading_value;\
1556         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1557       }\
1558     }\
1559   }\
1560 }
1561 ARGB_SHADE_FUNCTION (ARGB, 1);
1562 ARGB_SHADE_FUNCTION (ABGR, 1);
1563 ARGB_SHADE_FUNCTION (RGBA, 0);
1564 ARGB_SHADE_FUNCTION (BGRA, 0);
1565
1566 static void
1567 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1568     const gchar * text, gint textlen)
1569 {
1570   gchar *string;
1571
1572   if (!overlay->need_render) {
1573     GST_DEBUG ("Using previously rendered text.");
1574     return;
1575   }
1576
1577   /* -1 is the whole string */
1578   if (text != NULL && textlen < 0) {
1579     textlen = strlen (text);
1580   }
1581
1582   if (text != NULL) {
1583     string = g_strndup (text, textlen);
1584   } else {                      /* empty string */
1585     string = g_strdup (" ");
1586   }
1587   g_strdelimit (string, "\r\t", ' ');
1588   textlen = strlen (string);
1589
1590   /* FIXME: should we check for UTF-8 here? */
1591
1592   GST_DEBUG ("Rendering '%s'", string);
1593   gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1594
1595   g_free (string);
1596
1597   overlay->need_render = FALSE;
1598 }
1599
1600 static GstFlowReturn
1601 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1602     GstBuffer * video_frame)
1603 {
1604   gint xpos, ypos;
1605   GstVideoFrame frame;
1606
1607   if (overlay->composition == NULL)
1608     goto done;
1609
1610   if (gst_pad_check_reconfigure (overlay->srcpad))
1611     gst_base_text_overlay_negotiate (overlay);
1612
1613   video_frame = gst_buffer_make_writable (video_frame);
1614
1615   if (overlay->attach_compo_to_buffer) {
1616     GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1617     gst_buffer_add_video_overlay_composition_meta (video_frame,
1618         overlay->composition);
1619     /* FIXME: emulate shaded background box if want_shading=true */
1620     goto done;
1621   }
1622
1623   if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1624           GST_MAP_READWRITE))
1625     goto invalid_frame;
1626
1627   gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1628
1629   /* shaded background box */
1630   if (overlay->want_shading) {
1631     switch (overlay->format) {
1632       case GST_VIDEO_FORMAT_I420:
1633       case GST_VIDEO_FORMAT_NV12:
1634       case GST_VIDEO_FORMAT_NV21:
1635         gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1636             xpos, xpos + overlay->image_width,
1637             ypos, ypos + overlay->image_height);
1638         break;
1639       case GST_VIDEO_FORMAT_AYUV:
1640       case GST_VIDEO_FORMAT_UYVY:
1641         gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1642             xpos, xpos + overlay->image_width,
1643             ypos, ypos + overlay->image_height);
1644         break;
1645       case GST_VIDEO_FORMAT_xRGB:
1646         gst_base_text_overlay_shade_xRGB (overlay, &frame,
1647             xpos, xpos + overlay->image_width,
1648             ypos, ypos + overlay->image_height);
1649         break;
1650       case GST_VIDEO_FORMAT_xBGR:
1651         gst_base_text_overlay_shade_xBGR (overlay, &frame,
1652             xpos, xpos + overlay->image_width,
1653             ypos, ypos + overlay->image_height);
1654         break;
1655       case GST_VIDEO_FORMAT_BGRx:
1656         gst_base_text_overlay_shade_BGRx (overlay, &frame,
1657             xpos, xpos + overlay->image_width,
1658             ypos, ypos + overlay->image_height);
1659         break;
1660       case GST_VIDEO_FORMAT_RGBx:
1661         gst_base_text_overlay_shade_RGBx (overlay, &frame,
1662             xpos, xpos + overlay->image_width,
1663             ypos, ypos + overlay->image_height);
1664         break;
1665       case GST_VIDEO_FORMAT_ARGB:
1666         gst_base_text_overlay_shade_ARGB (overlay, &frame,
1667             xpos, xpos + overlay->image_width,
1668             ypos, ypos + overlay->image_height);
1669         break;
1670       case GST_VIDEO_FORMAT_ABGR:
1671         gst_base_text_overlay_shade_ABGR (overlay, &frame,
1672             xpos, xpos + overlay->image_width,
1673             ypos, ypos + overlay->image_height);
1674         break;
1675       case GST_VIDEO_FORMAT_RGBA:
1676         gst_base_text_overlay_shade_RGBA (overlay, &frame,
1677             xpos, xpos + overlay->image_width,
1678             ypos, ypos + overlay->image_height);
1679         break;
1680       case GST_VIDEO_FORMAT_BGRA:
1681         gst_base_text_overlay_shade_BGRA (overlay, &frame,
1682             xpos, xpos + overlay->image_width,
1683             ypos, ypos + overlay->image_height);
1684         break;
1685       default:
1686         g_assert_not_reached ();
1687     }
1688   }
1689
1690   gst_video_overlay_composition_blend (overlay->composition, &frame);
1691
1692   gst_video_frame_unmap (&frame);
1693
1694 done:
1695
1696   return gst_pad_push (overlay->srcpad, video_frame);
1697
1698   /* ERRORS */
1699 invalid_frame:
1700   {
1701     gst_buffer_unref (video_frame);
1702     GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1703     return GST_FLOW_OK;
1704   }
1705 }
1706
1707 static GstPadLinkReturn
1708 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
1709     GstPad * peer)
1710 {
1711   GstBaseTextOverlay *overlay;
1712
1713   overlay = GST_BASE_TEXT_OVERLAY (parent);
1714   if (G_UNLIKELY (!overlay))
1715     return GST_PAD_LINK_REFUSED;
1716
1717   GST_DEBUG_OBJECT (overlay, "Text pad linked");
1718
1719   overlay->text_linked = TRUE;
1720
1721   return GST_PAD_LINK_OK;
1722 }
1723
1724 static void
1725 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
1726 {
1727   GstBaseTextOverlay *overlay;
1728
1729   /* don't use gst_pad_get_parent() here, will deadlock */
1730   overlay = GST_BASE_TEXT_OVERLAY (parent);
1731
1732   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1733
1734   overlay->text_linked = FALSE;
1735
1736   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1737 }
1738
1739 static gboolean
1740 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1741     GstEvent * event)
1742 {
1743   gboolean ret = FALSE;
1744   GstBaseTextOverlay *overlay = NULL;
1745
1746   overlay = GST_BASE_TEXT_OVERLAY (parent);
1747
1748   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1749
1750   switch (GST_EVENT_TYPE (event)) {
1751     case GST_EVENT_CAPS:
1752     {
1753       GstCaps *caps;
1754
1755       gst_event_parse_caps (event, &caps);
1756       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1757       gst_event_unref (event);
1758       break;
1759     }
1760     case GST_EVENT_SEGMENT:
1761     {
1762       const GstSegment *segment;
1763
1764       overlay->text_eos = FALSE;
1765
1766       gst_event_parse_segment (event, &segment);
1767
1768       if (segment->format == GST_FORMAT_TIME) {
1769         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1770         gst_segment_copy_into (segment, &overlay->text_segment);
1771         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1772             &overlay->text_segment);
1773         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1774       } else {
1775         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1776             ("received non-TIME newsegment event on text input"));
1777       }
1778
1779       gst_event_unref (event);
1780       ret = TRUE;
1781
1782       /* wake up the video chain, it might be waiting for a text buffer or
1783        * a text segment update */
1784       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1785       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1786       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1787       break;
1788     }
1789     case GST_EVENT_GAP:
1790     {
1791       GstClockTime start, duration;
1792
1793       gst_event_parse_gap (event, &start, &duration);
1794       if (GST_CLOCK_TIME_IS_VALID (duration))
1795         start += duration;
1796       /* we do not expect another buffer until after gap,
1797        * so that is our position now */
1798       overlay->text_segment.position = start;
1799
1800       /* wake up the video chain, it might be waiting for a text buffer or
1801        * a text segment update */
1802       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1803       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1804       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1805       break;
1806     }
1807     case GST_EVENT_FLUSH_STOP:
1808       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1809       GST_INFO_OBJECT (overlay, "text flush stop");
1810       overlay->text_flushing = FALSE;
1811       overlay->text_eos = FALSE;
1812       gst_base_text_overlay_pop_text (overlay);
1813       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1814       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1815       gst_event_unref (event);
1816       ret = TRUE;
1817       break;
1818     case GST_EVENT_FLUSH_START:
1819       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1820       GST_INFO_OBJECT (overlay, "text flush start");
1821       overlay->text_flushing = TRUE;
1822       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1823       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1824       gst_event_unref (event);
1825       ret = TRUE;
1826       break;
1827     case GST_EVENT_EOS:
1828       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1829       overlay->text_eos = TRUE;
1830       GST_INFO_OBJECT (overlay, "text EOS");
1831       /* wake up the video chain, it might be waiting for a text buffer or
1832        * a text segment update */
1833       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1834       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1835       gst_event_unref (event);
1836       ret = TRUE;
1837       break;
1838     default:
1839       ret = gst_pad_event_default (pad, parent, event);
1840       break;
1841   }
1842
1843   return ret;
1844 }
1845
1846 static gboolean
1847 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1848     GstEvent * event)
1849 {
1850   gboolean ret = FALSE;
1851   GstBaseTextOverlay *overlay = NULL;
1852
1853   overlay = GST_BASE_TEXT_OVERLAY (parent);
1854
1855   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1856
1857   switch (GST_EVENT_TYPE (event)) {
1858     case GST_EVENT_CAPS:
1859     {
1860       GstCaps *caps;
1861
1862       gst_event_parse_caps (event, &caps);
1863       ret = gst_base_text_overlay_setcaps (overlay, caps);
1864       gst_event_unref (event);
1865       break;
1866     }
1867     case GST_EVENT_SEGMENT:
1868     {
1869       const GstSegment *segment;
1870
1871       GST_DEBUG_OBJECT (overlay, "received new segment");
1872
1873       gst_event_parse_segment (event, &segment);
1874
1875       if (segment->format == GST_FORMAT_TIME) {
1876         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1877             &overlay->segment);
1878
1879         gst_segment_copy_into (segment, &overlay->segment);
1880       } else {
1881         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1882             ("received non-TIME newsegment event on video input"));
1883       }
1884
1885       ret = gst_pad_event_default (pad, parent, event);
1886       break;
1887     }
1888     case GST_EVENT_EOS:
1889       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1890       GST_INFO_OBJECT (overlay, "video EOS");
1891       overlay->video_eos = TRUE;
1892       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1893       ret = gst_pad_event_default (pad, parent, event);
1894       break;
1895     case GST_EVENT_FLUSH_START:
1896       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1897       GST_INFO_OBJECT (overlay, "video flush start");
1898       overlay->video_flushing = TRUE;
1899       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1900       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1901       ret = gst_pad_event_default (pad, parent, event);
1902       break;
1903     case GST_EVENT_FLUSH_STOP:
1904       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1905       GST_INFO_OBJECT (overlay, "video flush stop");
1906       overlay->video_flushing = FALSE;
1907       overlay->video_eos = FALSE;
1908       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1909       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1910       ret = gst_pad_event_default (pad, parent, event);
1911       break;
1912     default:
1913       ret = gst_pad_event_default (pad, parent, event);
1914       break;
1915   }
1916
1917   return ret;
1918 }
1919
1920 static gboolean
1921 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1922     GstQuery * query)
1923 {
1924   gboolean ret = FALSE;
1925   GstBaseTextOverlay *overlay;
1926
1927   overlay = GST_BASE_TEXT_OVERLAY (parent);
1928
1929   switch (GST_QUERY_TYPE (query)) {
1930     case GST_QUERY_CAPS:
1931     {
1932       GstCaps *filter, *caps;
1933
1934       gst_query_parse_caps (query, &filter);
1935       caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1936       gst_query_set_caps_result (query, caps);
1937       gst_caps_unref (caps);
1938       ret = TRUE;
1939       break;
1940     }
1941     default:
1942       ret = gst_pad_query_default (pad, parent, query);
1943       break;
1944   }
1945
1946   return ret;
1947 }
1948
1949 /* Called with lock held */
1950 static void
1951 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1952 {
1953   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1954
1955   if (overlay->text_buffer) {
1956     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1957         overlay->text_buffer);
1958     gst_buffer_unref (overlay->text_buffer);
1959     overlay->text_buffer = NULL;
1960   }
1961
1962   /* Let the text task know we used that buffer */
1963   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1964 }
1965
1966 /* We receive text buffers here. If they are out of segment we just ignore them.
1967    If the buffer is in our segment we keep it internally except if another one
1968    is already waiting here, in that case we wait that it gets kicked out */
1969 static GstFlowReturn
1970 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1971     GstBuffer * buffer)
1972 {
1973   GstFlowReturn ret = GST_FLOW_OK;
1974   GstBaseTextOverlay *overlay = NULL;
1975   gboolean in_seg = FALSE;
1976   guint64 clip_start = 0, clip_stop = 0;
1977
1978   overlay = GST_BASE_TEXT_OVERLAY (parent);
1979
1980   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1981
1982   if (overlay->text_flushing) {
1983     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1984     ret = GST_FLOW_FLUSHING;
1985     GST_LOG_OBJECT (overlay, "text flushing");
1986     goto beach;
1987   }
1988
1989   if (overlay->text_eos) {
1990     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1991     ret = GST_FLOW_EOS;
1992     GST_LOG_OBJECT (overlay, "text EOS");
1993     goto beach;
1994   }
1995
1996   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
1997       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1998       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1999       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2000           GST_BUFFER_DURATION (buffer)));
2001
2002   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2003     GstClockTime stop;
2004
2005     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2006       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2007     else
2008       stop = GST_CLOCK_TIME_NONE;
2009
2010     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2011         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2012   } else {
2013     in_seg = TRUE;
2014   }
2015
2016   if (in_seg) {
2017     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2018       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2019     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2020       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2021
2022     /* Wait for the previous buffer to go away */
2023     while (overlay->text_buffer != NULL) {
2024       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2025           GST_DEBUG_PAD_NAME (pad));
2026       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2027       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2028       if (overlay->text_flushing) {
2029         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2030         ret = GST_FLOW_FLUSHING;
2031         goto beach;
2032       }
2033     }
2034
2035     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2036       overlay->text_segment.position = clip_start;
2037
2038     overlay->text_buffer = buffer;
2039     /* That's a new text buffer we need to render */
2040     overlay->need_render = TRUE;
2041
2042     /* in case the video chain is waiting for a text buffer, wake it up */
2043     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2044   }
2045
2046   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2047
2048 beach:
2049
2050   return ret;
2051 }
2052
2053 static GstFlowReturn
2054 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2055     GstBuffer * buffer)
2056 {
2057   GstBaseTextOverlayClass *klass;
2058   GstBaseTextOverlay *overlay;
2059   GstFlowReturn ret = GST_FLOW_OK;
2060   gboolean in_seg = FALSE;
2061   guint64 start, stop, clip_start = 0, clip_stop = 0;
2062   gchar *text = NULL;
2063
2064   overlay = GST_BASE_TEXT_OVERLAY (parent);
2065   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2066
2067   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2068     goto missing_timestamp;
2069
2070   /* ignore buffers that are outside of the current segment */
2071   start = GST_BUFFER_TIMESTAMP (buffer);
2072
2073   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2074     stop = GST_CLOCK_TIME_NONE;
2075   } else {
2076     stop = start + GST_BUFFER_DURATION (buffer);
2077   }
2078
2079   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2080       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2081       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2082
2083   /* segment_clip() will adjust start unconditionally to segment_start if
2084    * no stop time is provided, so handle this ourselves */
2085   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2086     goto out_of_segment;
2087
2088   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2089       &clip_start, &clip_stop);
2090
2091   if (!in_seg)
2092     goto out_of_segment;
2093
2094   /* if the buffer is only partially in the segment, fix up stamps */
2095   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2096     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2097     buffer = gst_buffer_make_writable (buffer);
2098     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2099     if (stop != -1)
2100       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2101   }
2102
2103   /* now, after we've done the clipping, fix up end time if there's no
2104    * duration (we only use those estimated values internally though, we
2105    * don't want to set bogus values on the buffer itself) */
2106   if (stop == -1) {
2107     GstCaps *caps;
2108     GstStructure *s;
2109     gint fps_num, fps_denom;
2110
2111     /* FIXME, store this in setcaps */
2112     caps = gst_pad_get_current_caps (pad);
2113     s = gst_caps_get_structure (caps, 0);
2114     if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2115         fps_num && fps_denom) {
2116       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2117       stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2118     } else {
2119       GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2120       stop = start + 1;         /* we need to assume some interval */
2121     }
2122     gst_caps_unref (caps);
2123   }
2124
2125   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2126
2127 wait_for_text_buf:
2128
2129   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2130
2131   if (overlay->video_flushing)
2132     goto flushing;
2133
2134   if (overlay->video_eos)
2135     goto have_eos;
2136
2137   if (overlay->silent) {
2138     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2139     ret = gst_pad_push (overlay->srcpad, buffer);
2140
2141     /* Update position */
2142     overlay->segment.position = clip_start;
2143
2144     return ret;
2145   }
2146
2147   /* Text pad not linked, rendering internal text */
2148   if (!overlay->text_linked) {
2149     if (klass->get_text) {
2150       text = klass->get_text (overlay, buffer);
2151     } else {
2152       text = g_strdup (overlay->default_text);
2153     }
2154
2155     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2156         "text: '%s'", GST_STR_NULL (text));
2157
2158     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2159
2160     if (text != NULL && *text != '\0') {
2161       /* Render and push */
2162       gst_base_text_overlay_render_text (overlay, text, -1);
2163       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2164     } else {
2165       /* Invalid or empty string */
2166       ret = gst_pad_push (overlay->srcpad, buffer);
2167     }
2168   } else {
2169     /* Text pad linked, check if we have a text buffer queued */
2170     if (overlay->text_buffer) {
2171       gboolean pop_text = FALSE, valid_text_time = TRUE;
2172       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2173       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2174       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2175       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2176       GstClockTime vid_running_time, vid_running_time_end;
2177
2178       /* if the text buffer isn't stamped right, pop it off the
2179        * queue and display it for the current video frame only */
2180       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2181           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2182         GST_WARNING_OBJECT (overlay,
2183             "Got text buffer with invalid timestamp or duration");
2184         pop_text = TRUE;
2185         valid_text_time = FALSE;
2186       } else {
2187         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2188         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2189       }
2190
2191       vid_running_time =
2192           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2193           start);
2194       vid_running_time_end =
2195           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2196           stop);
2197
2198       /* If timestamp and duration are valid */
2199       if (valid_text_time) {
2200         text_running_time =
2201             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2202             text_start);
2203         text_running_time_end =
2204             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2205             text_end);
2206       }
2207
2208       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2209           GST_TIME_ARGS (text_running_time),
2210           GST_TIME_ARGS (text_running_time_end));
2211       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2212           GST_TIME_ARGS (vid_running_time),
2213           GST_TIME_ARGS (vid_running_time_end));
2214
2215       /* Text too old or in the future */
2216       if (valid_text_time && text_running_time_end <= vid_running_time) {
2217         /* text buffer too old, get rid of it and do nothing  */
2218         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2219         pop_text = FALSE;
2220         gst_base_text_overlay_pop_text (overlay);
2221         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2222         goto wait_for_text_buf;
2223       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2224         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2225         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2226         /* Push the video frame */
2227         ret = gst_pad_push (overlay->srcpad, buffer);
2228       } else {
2229         GstMapInfo map;
2230         gchar *in_text;
2231         gsize in_size;
2232
2233         gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2234         in_text = (gchar *) map.data;
2235         in_size = map.size;
2236
2237         if (in_size > 0) {
2238           /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2239            * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2240            * here on purpose, this is something that needs fixing upstream */
2241           if (!g_utf8_validate (in_text, in_size, NULL)) {
2242             const gchar *end = NULL;
2243
2244             GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2245             in_text = g_strndup (in_text, in_size);
2246             while (!g_utf8_validate (in_text, in_size, &end) && end)
2247               *((gchar *) end) = '*';
2248           }
2249
2250           /* Get the string */
2251           if (overlay->have_pango_markup) {
2252             text = g_strndup (in_text, in_size);
2253           } else {
2254             text = g_markup_escape_text (in_text, in_size);
2255           }
2256
2257           if (text != NULL && *text != '\0') {
2258             gint text_len = strlen (text);
2259
2260             while (text_len > 0 && (text[text_len - 1] == '\n' ||
2261                     text[text_len - 1] == '\r')) {
2262               --text_len;
2263             }
2264             GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2265             gst_base_text_overlay_render_text (overlay, text, text_len);
2266           } else {
2267             GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2268             gst_base_text_overlay_render_text (overlay, " ", 1);
2269           }
2270           if (in_text != (gchar *) map.data)
2271             g_free (in_text);
2272         } else {
2273           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2274           gst_base_text_overlay_render_text (overlay, " ", 1);
2275         }
2276
2277         gst_buffer_unmap (overlay->text_buffer, &map);
2278
2279         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2280         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2281
2282         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2283           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2284           pop_text = TRUE;
2285         }
2286       }
2287       if (pop_text) {
2288         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2289         gst_base_text_overlay_pop_text (overlay);
2290         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2291       }
2292     } else {
2293       gboolean wait_for_text_buf = TRUE;
2294
2295       if (overlay->text_eos)
2296         wait_for_text_buf = FALSE;
2297
2298       if (!overlay->wait_text)
2299         wait_for_text_buf = FALSE;
2300
2301       /* Text pad linked, but no text buffer available - what now? */
2302       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2303         GstClockTime text_start_running_time, text_position_running_time;
2304         GstClockTime vid_running_time;
2305
2306         vid_running_time =
2307             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2308             GST_BUFFER_TIMESTAMP (buffer));
2309         text_start_running_time =
2310             gst_segment_to_running_time (&overlay->text_segment,
2311             GST_FORMAT_TIME, overlay->text_segment.start);
2312         text_position_running_time =
2313             gst_segment_to_running_time (&overlay->text_segment,
2314             GST_FORMAT_TIME, overlay->text_segment.position);
2315
2316         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2317                 vid_running_time < text_start_running_time) ||
2318             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2319                 vid_running_time < text_position_running_time)) {
2320           wait_for_text_buf = FALSE;
2321         }
2322       }
2323
2324       if (wait_for_text_buf) {
2325         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2326         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2327         GST_DEBUG_OBJECT (overlay, "resuming");
2328         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2329         goto wait_for_text_buf;
2330       } else {
2331         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2332         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2333         ret = gst_pad_push (overlay->srcpad, buffer);
2334       }
2335     }
2336   }
2337
2338   g_free (text);
2339
2340   /* Update position */
2341   overlay->segment.position = clip_start;
2342
2343   return ret;
2344
2345 missing_timestamp:
2346   {
2347     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2348     gst_buffer_unref (buffer);
2349     return GST_FLOW_OK;
2350   }
2351
2352 flushing:
2353   {
2354     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2355     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2356     gst_buffer_unref (buffer);
2357     return GST_FLOW_FLUSHING;
2358   }
2359 have_eos:
2360   {
2361     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2362     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2363     gst_buffer_unref (buffer);
2364     return GST_FLOW_EOS;
2365   }
2366 out_of_segment:
2367   {
2368     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2369     gst_buffer_unref (buffer);
2370     return GST_FLOW_OK;
2371   }
2372 }
2373
2374 static GstStateChangeReturn
2375 gst_base_text_overlay_change_state (GstElement * element,
2376     GstStateChange transition)
2377 {
2378   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2379   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2380
2381   switch (transition) {
2382     case GST_STATE_CHANGE_PAUSED_TO_READY:
2383       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2384       overlay->text_flushing = TRUE;
2385       overlay->video_flushing = TRUE;
2386       /* pop_text will broadcast on the GCond and thus also make the video
2387        * chain exit if it's waiting for a text buffer */
2388       gst_base_text_overlay_pop_text (overlay);
2389       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2390       break;
2391     default:
2392       break;
2393   }
2394
2395   ret = parent_class->change_state (element, transition);
2396   if (ret == GST_STATE_CHANGE_FAILURE)
2397     return ret;
2398
2399   switch (transition) {
2400     case GST_STATE_CHANGE_READY_TO_PAUSED:
2401       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2402       overlay->text_flushing = FALSE;
2403       overlay->video_flushing = FALSE;
2404       overlay->video_eos = FALSE;
2405       overlay->text_eos = FALSE;
2406       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2407       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2408       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2409       break;
2410     default:
2411       break;
2412   }
2413
2414   return ret;
2415 }
2416
2417 static gboolean
2418 plugin_init (GstPlugin * plugin)
2419 {
2420   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2421           GST_TYPE_TEXT_OVERLAY) ||
2422       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2423           GST_TYPE_TIME_OVERLAY) ||
2424       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2425           GST_TYPE_CLOCK_OVERLAY) ||
2426       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2427           GST_TYPE_TEXT_RENDER)) {
2428     return FALSE;
2429   }
2430
2431   /*texttestsrc_plugin_init(module, plugin); */
2432
2433   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2434
2435   return TRUE;
2436 }
2437
2438 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2439     pango, "Pango-based text rendering and overlay", plugin_init,
2440     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)