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