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