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