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