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