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