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