pango: Don't modify the original attributes list.
[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;
1343
1344     origin_attr =
1345         pango_attr_list_copy (pango_layout_get_attributes (overlay->layout));
1346     filtered_attr =
1347         pango_attr_list_filter (origin_attr,
1348         gst_text_overlay_filter_foreground_attr, NULL);
1349
1350     cairo_save (cr);
1351     cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1352     cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1353     pango_layout_set_attributes (overlay->layout, filtered_attr);
1354     pango_cairo_show_layout (cr, overlay->layout);
1355     pango_layout_set_attributes (overlay->layout, origin_attr);
1356     pango_attr_list_unref (filtered_attr);
1357     pango_attr_list_unref (origin_attr);
1358     cairo_restore (cr);
1359   }
1360
1361   a = (overlay->outline_color >> 24) & 0xff;
1362   r = (overlay->outline_color >> 16) & 0xff;
1363   g = (overlay->outline_color >> 8) & 0xff;
1364   b = (overlay->outline_color >> 0) & 0xff;
1365
1366   /* draw outline text */
1367   cairo_save (cr);
1368   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1369   cairo_set_line_width (cr, overlay->outline_offset);
1370   pango_cairo_layout_path (cr, overlay->layout);
1371   cairo_stroke (cr);
1372   cairo_restore (cr);
1373
1374   a = (overlay->color >> 24) & 0xff;
1375   r = (overlay->color >> 16) & 0xff;
1376   g = (overlay->color >> 8) & 0xff;
1377   b = (overlay->color >> 0) & 0xff;
1378
1379   /* draw text */
1380   cairo_save (cr);
1381   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1382   pango_cairo_show_layout (cr, overlay->layout);
1383   cairo_restore (cr);
1384
1385   cairo_destroy (cr);
1386   cairo_surface_destroy (surface);
1387   gst_buffer_unmap (buffer, &map);
1388   overlay->image_width = width;
1389   overlay->image_height = height;
1390   overlay->baseline_y = ink_rect.y;
1391   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1392
1393   gst_base_text_overlay_set_composition (overlay);
1394 }
1395
1396 static inline void
1397 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1398     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1399 {
1400   gint i, j, dest_stride;
1401   guint8 *dest_ptr;
1402
1403   dest_stride = dest->info.stride[0];
1404   dest_ptr = dest->data[0];
1405
1406   for (i = y0; i < y1; ++i) {
1407     for (j = x0; j < x1; ++j) {
1408       gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1409
1410       dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1411     }
1412   }
1413 }
1414
1415 static inline void
1416 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1417     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1418 {
1419   gint i, j;
1420   guint dest_stride, pixel_stride;
1421   guint8 *dest_ptr;
1422
1423   dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1424   dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1425   pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1426
1427   if (x0 != 0)
1428     x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1429   if (x1 != 0)
1430     x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1431
1432   if (y0 != 0)
1433     y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1434   if (y1 != 0)
1435     y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1436
1437   for (i = y0; i < y1; i++) {
1438     for (j = x0; j < x1; j++) {
1439       gint y;
1440       gint y_pos;
1441
1442       y_pos = (i * dest_stride) + j * pixel_stride;
1443       y = dest_ptr[y_pos] + overlay->shading_value;
1444
1445       dest_ptr[y_pos] = CLAMP (y, 0, 255);
1446     }
1447   }
1448 }
1449
1450 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1451 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1452 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1453 static inline void
1454 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1455     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1456 {
1457   gint i, j;
1458   guint8 *dest_ptr;
1459
1460   dest_ptr = dest->data[0];
1461
1462   for (i = y0; i < y1; i++) {
1463     for (j = x0; j < x1; j++) {
1464       gint y, y_pos, k;
1465
1466       y_pos = (i * 4 * overlay->width) + j * 4;
1467       for (k = 0; k < 4; k++) {
1468         y = dest_ptr[y_pos + k] + overlay->shading_value;
1469         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1470       }
1471     }
1472   }
1473 }
1474
1475 /* FIXME: orcify */
1476 static void
1477 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
1478     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1479 {
1480   const int pstride = 3;
1481   gint y, x, stride, shading_val, tmp;
1482   guint8 *p;
1483
1484   shading_val = overlay->shading_value;
1485   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1486
1487   for (y = y0; y < y1; ++y) {
1488     p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1489     p += (y * stride) + (x0 * pstride);
1490     for (x = x0; x < x1; ++x) {
1491       tmp = *p + shading_val;
1492       *p++ = CLAMP (tmp, 0, 255);
1493       tmp = *p + shading_val;
1494       *p++ = CLAMP (tmp, 0, 255);
1495       tmp = *p + shading_val;
1496       *p++ = CLAMP (tmp, 0, 255);
1497     }
1498   }
1499 }
1500
1501 static void
1502 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
1503     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1504 {
1505   gint y, x, stride, shading_val, tmp;
1506   guint8 *p;
1507
1508   shading_val = overlay->shading_value;
1509   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
1510
1511   /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
1512   for (y = y0; y < y1; ++y) {
1513     p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
1514     /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
1515     /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
1516      * unlikely anyone will notice.. */
1517     p += (y * stride) + ((x0 / 2) * 3) + 1;
1518     for (x = x0; x < x1; x += 2) {
1519       tmp = *p + shading_val;
1520       *p++ = CLAMP (tmp, 0, 255);
1521       tmp = *p + shading_val;
1522       *p++ = CLAMP (tmp, 0, 255);
1523       /* skip chroma */
1524       p++;
1525     }
1526   }
1527 }
1528
1529 #define ARGB_SHADE_FUNCTION(name, OFFSET)       \
1530 static inline void \
1531 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1532 gint x0, gint x1, gint y0, gint y1) \
1533 { \
1534   gint i, j;\
1535   guint8 *dest_ptr;\
1536   \
1537   dest_ptr = dest->data[0];\
1538   \
1539   for (i = y0; i < y1; i++) {\
1540     for (j = x0; j < x1; j++) {\
1541       gint y, y_pos, k;\
1542       y_pos = (i * 4 * overlay->width) + j * 4;\
1543       for (k = OFFSET; k < 3+OFFSET; k++) {\
1544         y = dest_ptr[y_pos + k] + overlay->shading_value;\
1545         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1546       }\
1547     }\
1548   }\
1549 }
1550 ARGB_SHADE_FUNCTION (ARGB, 1);
1551 ARGB_SHADE_FUNCTION (ABGR, 1);
1552 ARGB_SHADE_FUNCTION (RGBA, 0);
1553 ARGB_SHADE_FUNCTION (BGRA, 0);
1554
1555 static void
1556 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1557     const gchar * text, gint textlen)
1558 {
1559   gchar *string;
1560
1561   if (!overlay->need_render) {
1562     GST_DEBUG ("Using previously rendered text.");
1563     return;
1564   }
1565
1566   /* -1 is the whole string */
1567   if (text != NULL && textlen < 0) {
1568     textlen = strlen (text);
1569   }
1570
1571   if (text != NULL) {
1572     string = g_strndup (text, textlen);
1573   } else {                      /* empty string */
1574     string = g_strdup (" ");
1575   }
1576   g_strdelimit (string, "\r\t", ' ');
1577   textlen = strlen (string);
1578
1579   /* FIXME: should we check for UTF-8 here? */
1580
1581   GST_DEBUG ("Rendering '%s'", string);
1582   gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1583
1584   g_free (string);
1585
1586   overlay->need_render = FALSE;
1587 }
1588
1589 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
1590 #define BOX_XPAD  6
1591 #define BOX_YPAD  6
1592
1593 static void
1594 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
1595     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
1596 {
1597   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1598   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1599
1600   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1601   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1602
1603   switch (overlay->format) {
1604     case GST_VIDEO_FORMAT_I420:
1605     case GST_VIDEO_FORMAT_YV12:
1606     case GST_VIDEO_FORMAT_NV12:
1607     case GST_VIDEO_FORMAT_NV21:
1608     case GST_VIDEO_FORMAT_Y41B:
1609     case GST_VIDEO_FORMAT_Y42B:
1610     case GST_VIDEO_FORMAT_Y444:
1611     case GST_VIDEO_FORMAT_YUV9:
1612     case GST_VIDEO_FORMAT_YVU9:
1613     case GST_VIDEO_FORMAT_GRAY8:
1614     case GST_VIDEO_FORMAT_A420:
1615       gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
1616       break;
1617     case GST_VIDEO_FORMAT_AYUV:
1618     case GST_VIDEO_FORMAT_UYVY:
1619     case GST_VIDEO_FORMAT_YUY2:
1620     case GST_VIDEO_FORMAT_v308:
1621       gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
1622       break;
1623     case GST_VIDEO_FORMAT_xRGB:
1624       gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
1625       break;
1626     case GST_VIDEO_FORMAT_xBGR:
1627       gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
1628       break;
1629     case GST_VIDEO_FORMAT_BGRx:
1630       gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
1631       break;
1632     case GST_VIDEO_FORMAT_RGBx:
1633       gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
1634       break;
1635     case GST_VIDEO_FORMAT_ARGB:
1636       gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
1637       break;
1638     case GST_VIDEO_FORMAT_ABGR:
1639       gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
1640       break;
1641     case GST_VIDEO_FORMAT_RGBA:
1642       gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
1643       break;
1644     case GST_VIDEO_FORMAT_BGRA:
1645       gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
1646       break;
1647     case GST_VIDEO_FORMAT_BGR:
1648     case GST_VIDEO_FORMAT_RGB:
1649       gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
1650       break;
1651     case GST_VIDEO_FORMAT_IYU1:
1652       gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
1653       break;
1654     default:
1655       GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
1656           gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
1657       break;
1658   }
1659 }
1660
1661 static GstFlowReturn
1662 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1663     GstBuffer * video_frame)
1664 {
1665   GstVideoFrame frame;
1666
1667   if (overlay->composition == NULL)
1668     goto done;
1669
1670   if (gst_pad_check_reconfigure (overlay->srcpad))
1671     gst_base_text_overlay_negotiate (overlay);
1672
1673   video_frame = gst_buffer_make_writable (video_frame);
1674
1675   if (overlay->attach_compo_to_buffer) {
1676     GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1677     gst_buffer_add_video_overlay_composition_meta (video_frame,
1678         overlay->composition);
1679     /* FIXME: emulate shaded background box if want_shading=true */
1680     goto done;
1681   }
1682
1683   if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1684           GST_MAP_READWRITE))
1685     goto invalid_frame;
1686
1687   /* shaded background box */
1688   if (overlay->want_shading) {
1689     gint xpos, ypos;
1690
1691     gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1692
1693     gst_base_text_overlay_shade_background (overlay, &frame,
1694         xpos, xpos + overlay->image_width, ypos, ypos + overlay->image_height);
1695   }
1696
1697   gst_video_overlay_composition_blend (overlay->composition, &frame);
1698
1699   gst_video_frame_unmap (&frame);
1700
1701 done:
1702
1703   return gst_pad_push (overlay->srcpad, video_frame);
1704
1705   /* ERRORS */
1706 invalid_frame:
1707   {
1708     gst_buffer_unref (video_frame);
1709     GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1710     return GST_FLOW_OK;
1711   }
1712 }
1713
1714 static GstPadLinkReturn
1715 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
1716     GstPad * peer)
1717 {
1718   GstBaseTextOverlay *overlay;
1719
1720   overlay = GST_BASE_TEXT_OVERLAY (parent);
1721   if (G_UNLIKELY (!overlay))
1722     return GST_PAD_LINK_REFUSED;
1723
1724   GST_DEBUG_OBJECT (overlay, "Text pad linked");
1725
1726   overlay->text_linked = TRUE;
1727
1728   return GST_PAD_LINK_OK;
1729 }
1730
1731 static void
1732 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
1733 {
1734   GstBaseTextOverlay *overlay;
1735
1736   /* don't use gst_pad_get_parent() here, will deadlock */
1737   overlay = GST_BASE_TEXT_OVERLAY (parent);
1738
1739   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1740
1741   overlay->text_linked = FALSE;
1742
1743   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1744 }
1745
1746 static gboolean
1747 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1748     GstEvent * event)
1749 {
1750   gboolean ret = FALSE;
1751   GstBaseTextOverlay *overlay = NULL;
1752
1753   overlay = GST_BASE_TEXT_OVERLAY (parent);
1754
1755   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1756
1757   switch (GST_EVENT_TYPE (event)) {
1758     case GST_EVENT_CAPS:
1759     {
1760       GstCaps *caps;
1761
1762       gst_event_parse_caps (event, &caps);
1763       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1764       gst_event_unref (event);
1765       break;
1766     }
1767     case GST_EVENT_SEGMENT:
1768     {
1769       const GstSegment *segment;
1770
1771       overlay->text_eos = FALSE;
1772
1773       gst_event_parse_segment (event, &segment);
1774
1775       if (segment->format == GST_FORMAT_TIME) {
1776         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1777         gst_segment_copy_into (segment, &overlay->text_segment);
1778         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1779             &overlay->text_segment);
1780         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1781       } else {
1782         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1783             ("received non-TIME newsegment event on text input"));
1784       }
1785
1786       gst_event_unref (event);
1787       ret = TRUE;
1788
1789       /* wake up the video chain, it might be waiting for a text buffer or
1790        * a text segment update */
1791       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1792       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1793       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1794       break;
1795     }
1796     case GST_EVENT_GAP:
1797     {
1798       GstClockTime start, duration;
1799
1800       gst_event_parse_gap (event, &start, &duration);
1801       if (GST_CLOCK_TIME_IS_VALID (duration))
1802         start += duration;
1803       /* we do not expect another buffer until after gap,
1804        * so that is our position now */
1805       overlay->text_segment.position = start;
1806
1807       /* wake up the video chain, it might be waiting for a text buffer or
1808        * a text segment update */
1809       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1810       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1811       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1812       break;
1813     }
1814     case GST_EVENT_FLUSH_STOP:
1815       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1816       GST_INFO_OBJECT (overlay, "text flush stop");
1817       overlay->text_flushing = FALSE;
1818       overlay->text_eos = FALSE;
1819       gst_base_text_overlay_pop_text (overlay);
1820       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1821       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1822       gst_event_unref (event);
1823       ret = TRUE;
1824       break;
1825     case GST_EVENT_FLUSH_START:
1826       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1827       GST_INFO_OBJECT (overlay, "text flush start");
1828       overlay->text_flushing = TRUE;
1829       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1830       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1831       gst_event_unref (event);
1832       ret = TRUE;
1833       break;
1834     case GST_EVENT_EOS:
1835       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1836       overlay->text_eos = TRUE;
1837       GST_INFO_OBJECT (overlay, "text EOS");
1838       /* wake up the video chain, it might be waiting for a text buffer or
1839        * a text segment update */
1840       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1841       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1842       gst_event_unref (event);
1843       ret = TRUE;
1844       break;
1845     default:
1846       ret = gst_pad_event_default (pad, parent, event);
1847       break;
1848   }
1849
1850   return ret;
1851 }
1852
1853 static gboolean
1854 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1855     GstEvent * event)
1856 {
1857   gboolean ret = FALSE;
1858   GstBaseTextOverlay *overlay = NULL;
1859
1860   overlay = GST_BASE_TEXT_OVERLAY (parent);
1861
1862   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1863
1864   switch (GST_EVENT_TYPE (event)) {
1865     case GST_EVENT_CAPS:
1866     {
1867       GstCaps *caps;
1868
1869       gst_event_parse_caps (event, &caps);
1870       ret = gst_base_text_overlay_setcaps (overlay, caps);
1871       gst_event_unref (event);
1872       break;
1873     }
1874     case GST_EVENT_SEGMENT:
1875     {
1876       const GstSegment *segment;
1877
1878       GST_DEBUG_OBJECT (overlay, "received new segment");
1879
1880       gst_event_parse_segment (event, &segment);
1881
1882       if (segment->format == GST_FORMAT_TIME) {
1883         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1884             &overlay->segment);
1885
1886         gst_segment_copy_into (segment, &overlay->segment);
1887       } else {
1888         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1889             ("received non-TIME newsegment event on video input"));
1890       }
1891
1892       ret = gst_pad_event_default (pad, parent, event);
1893       break;
1894     }
1895     case GST_EVENT_EOS:
1896       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1897       GST_INFO_OBJECT (overlay, "video EOS");
1898       overlay->video_eos = TRUE;
1899       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1900       ret = gst_pad_event_default (pad, parent, event);
1901       break;
1902     case GST_EVENT_FLUSH_START:
1903       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1904       GST_INFO_OBJECT (overlay, "video flush start");
1905       overlay->video_flushing = TRUE;
1906       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1907       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1908       ret = gst_pad_event_default (pad, parent, event);
1909       break;
1910     case GST_EVENT_FLUSH_STOP:
1911       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1912       GST_INFO_OBJECT (overlay, "video flush stop");
1913       overlay->video_flushing = FALSE;
1914       overlay->video_eos = FALSE;
1915       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1916       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1917       ret = gst_pad_event_default (pad, parent, event);
1918       break;
1919     default:
1920       ret = gst_pad_event_default (pad, parent, event);
1921       break;
1922   }
1923
1924   return ret;
1925 }
1926
1927 static gboolean
1928 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1929     GstQuery * query)
1930 {
1931   gboolean ret = FALSE;
1932   GstBaseTextOverlay *overlay;
1933
1934   overlay = GST_BASE_TEXT_OVERLAY (parent);
1935
1936   switch (GST_QUERY_TYPE (query)) {
1937     case GST_QUERY_CAPS:
1938     {
1939       GstCaps *filter, *caps;
1940
1941       gst_query_parse_caps (query, &filter);
1942       caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1943       gst_query_set_caps_result (query, caps);
1944       gst_caps_unref (caps);
1945       ret = TRUE;
1946       break;
1947     }
1948     default:
1949       ret = gst_pad_query_default (pad, parent, query);
1950       break;
1951   }
1952
1953   return ret;
1954 }
1955
1956 /* Called with lock held */
1957 static void
1958 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1959 {
1960   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1961
1962   if (overlay->text_buffer) {
1963     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1964         overlay->text_buffer);
1965     gst_buffer_unref (overlay->text_buffer);
1966     overlay->text_buffer = NULL;
1967   }
1968
1969   /* Let the text task know we used that buffer */
1970   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1971 }
1972
1973 /* We receive text buffers here. If they are out of segment we just ignore them.
1974    If the buffer is in our segment we keep it internally except if another one
1975    is already waiting here, in that case we wait that it gets kicked out */
1976 static GstFlowReturn
1977 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1978     GstBuffer * buffer)
1979 {
1980   GstFlowReturn ret = GST_FLOW_OK;
1981   GstBaseTextOverlay *overlay = NULL;
1982   gboolean in_seg = FALSE;
1983   guint64 clip_start = 0, clip_stop = 0;
1984
1985   overlay = GST_BASE_TEXT_OVERLAY (parent);
1986
1987   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1988
1989   if (overlay->text_flushing) {
1990     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1991     ret = GST_FLOW_FLUSHING;
1992     GST_LOG_OBJECT (overlay, "text flushing");
1993     goto beach;
1994   }
1995
1996   if (overlay->text_eos) {
1997     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1998     ret = GST_FLOW_EOS;
1999     GST_LOG_OBJECT (overlay, "text EOS");
2000     goto beach;
2001   }
2002
2003   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2004       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2005       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2006       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2007           GST_BUFFER_DURATION (buffer)));
2008
2009   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2010     GstClockTime stop;
2011
2012     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2013       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2014     else
2015       stop = GST_CLOCK_TIME_NONE;
2016
2017     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2018         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2019   } else {
2020     in_seg = TRUE;
2021   }
2022
2023   if (in_seg) {
2024     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2025       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2026     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2027       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2028
2029     /* Wait for the previous buffer to go away */
2030     while (overlay->text_buffer != NULL) {
2031       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2032           GST_DEBUG_PAD_NAME (pad));
2033       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2034       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2035       if (overlay->text_flushing) {
2036         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2037         ret = GST_FLOW_FLUSHING;
2038         goto beach;
2039       }
2040     }
2041
2042     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2043       overlay->text_segment.position = clip_start;
2044
2045     overlay->text_buffer = buffer;
2046     /* That's a new text buffer we need to render */
2047     overlay->need_render = TRUE;
2048
2049     /* in case the video chain is waiting for a text buffer, wake it up */
2050     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2051   }
2052
2053   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2054
2055 beach:
2056
2057   return ret;
2058 }
2059
2060 static GstFlowReturn
2061 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2062     GstBuffer * buffer)
2063 {
2064   GstBaseTextOverlayClass *klass;
2065   GstBaseTextOverlay *overlay;
2066   GstFlowReturn ret = GST_FLOW_OK;
2067   gboolean in_seg = FALSE;
2068   guint64 start, stop, clip_start = 0, clip_stop = 0;
2069   gchar *text = NULL;
2070
2071   overlay = GST_BASE_TEXT_OVERLAY (parent);
2072   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2073
2074   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2075     goto missing_timestamp;
2076
2077   /* ignore buffers that are outside of the current segment */
2078   start = GST_BUFFER_TIMESTAMP (buffer);
2079
2080   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2081     stop = GST_CLOCK_TIME_NONE;
2082   } else {
2083     stop = start + GST_BUFFER_DURATION (buffer);
2084   }
2085
2086   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2087       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2088       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2089
2090   /* segment_clip() will adjust start unconditionally to segment_start if
2091    * no stop time is provided, so handle this ourselves */
2092   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2093     goto out_of_segment;
2094
2095   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2096       &clip_start, &clip_stop);
2097
2098   if (!in_seg)
2099     goto out_of_segment;
2100
2101   /* if the buffer is only partially in the segment, fix up stamps */
2102   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2103     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2104     buffer = gst_buffer_make_writable (buffer);
2105     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2106     if (stop != -1)
2107       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2108   }
2109
2110   /* now, after we've done the clipping, fix up end time if there's no
2111    * duration (we only use those estimated values internally though, we
2112    * don't want to set bogus values on the buffer itself) */
2113   if (stop == -1) {
2114     GstCaps *caps;
2115     GstStructure *s;
2116     gint fps_num, fps_denom;
2117
2118     /* FIXME, store this in setcaps */
2119     caps = gst_pad_get_current_caps (pad);
2120     s = gst_caps_get_structure (caps, 0);
2121     if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2122         fps_num && fps_denom) {
2123       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2124       stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2125     } else {
2126       GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2127       stop = start + 1;         /* we need to assume some interval */
2128     }
2129     gst_caps_unref (caps);
2130   }
2131
2132   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2133
2134 wait_for_text_buf:
2135
2136   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2137
2138   if (overlay->video_flushing)
2139     goto flushing;
2140
2141   if (overlay->video_eos)
2142     goto have_eos;
2143
2144   if (overlay->silent) {
2145     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2146     ret = gst_pad_push (overlay->srcpad, buffer);
2147
2148     /* Update position */
2149     overlay->segment.position = clip_start;
2150
2151     return ret;
2152   }
2153
2154   /* Text pad not linked, rendering internal text */
2155   if (!overlay->text_linked) {
2156     if (klass->get_text) {
2157       text = klass->get_text (overlay, buffer);
2158     } else {
2159       text = g_strdup (overlay->default_text);
2160     }
2161
2162     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2163         "text: '%s'", GST_STR_NULL (text));
2164
2165     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2166
2167     if (text != NULL && *text != '\0') {
2168       /* Render and push */
2169       gst_base_text_overlay_render_text (overlay, text, -1);
2170       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2171     } else {
2172       /* Invalid or empty string */
2173       ret = gst_pad_push (overlay->srcpad, buffer);
2174     }
2175   } else {
2176     /* Text pad linked, check if we have a text buffer queued */
2177     if (overlay->text_buffer) {
2178       gboolean pop_text = FALSE, valid_text_time = TRUE;
2179       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2180       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2181       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2182       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2183       GstClockTime vid_running_time, vid_running_time_end;
2184
2185       /* if the text buffer isn't stamped right, pop it off the
2186        * queue and display it for the current video frame only */
2187       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2188           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2189         GST_WARNING_OBJECT (overlay,
2190             "Got text buffer with invalid timestamp or duration");
2191         pop_text = TRUE;
2192         valid_text_time = FALSE;
2193       } else {
2194         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2195         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2196       }
2197
2198       vid_running_time =
2199           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2200           start);
2201       vid_running_time_end =
2202           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2203           stop);
2204
2205       /* If timestamp and duration are valid */
2206       if (valid_text_time) {
2207         text_running_time =
2208             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2209             text_start);
2210         text_running_time_end =
2211             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2212             text_end);
2213       }
2214
2215       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2216           GST_TIME_ARGS (text_running_time),
2217           GST_TIME_ARGS (text_running_time_end));
2218       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2219           GST_TIME_ARGS (vid_running_time),
2220           GST_TIME_ARGS (vid_running_time_end));
2221
2222       /* Text too old or in the future */
2223       if (valid_text_time && text_running_time_end <= vid_running_time) {
2224         /* text buffer too old, get rid of it and do nothing  */
2225         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2226         pop_text = FALSE;
2227         gst_base_text_overlay_pop_text (overlay);
2228         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2229         goto wait_for_text_buf;
2230       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2231         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2232         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2233         /* Push the video frame */
2234         ret = gst_pad_push (overlay->srcpad, buffer);
2235       } else {
2236         GstMapInfo map;
2237         gchar *in_text;
2238         gsize in_size;
2239
2240         gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2241         in_text = (gchar *) map.data;
2242         in_size = map.size;
2243
2244         if (in_size > 0) {
2245           /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2246            * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2247            * here on purpose, this is something that needs fixing upstream */
2248           if (!g_utf8_validate (in_text, in_size, NULL)) {
2249             const gchar *end = NULL;
2250
2251             GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2252             in_text = g_strndup (in_text, in_size);
2253             while (!g_utf8_validate (in_text, in_size, &end) && end)
2254               *((gchar *) end) = '*';
2255           }
2256
2257           /* Get the string */
2258           if (overlay->have_pango_markup) {
2259             text = g_strndup (in_text, in_size);
2260           } else {
2261             text = g_markup_escape_text (in_text, in_size);
2262           }
2263
2264           if (text != NULL && *text != '\0') {
2265             gint text_len = strlen (text);
2266
2267             while (text_len > 0 && (text[text_len - 1] == '\n' ||
2268                     text[text_len - 1] == '\r')) {
2269               --text_len;
2270             }
2271             GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2272             gst_base_text_overlay_render_text (overlay, text, text_len);
2273           } else {
2274             GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2275             gst_base_text_overlay_render_text (overlay, " ", 1);
2276           }
2277           if (in_text != (gchar *) map.data)
2278             g_free (in_text);
2279         } else {
2280           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2281           gst_base_text_overlay_render_text (overlay, " ", 1);
2282         }
2283
2284         gst_buffer_unmap (overlay->text_buffer, &map);
2285
2286         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2287         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2288
2289         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2290           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2291           pop_text = TRUE;
2292         }
2293       }
2294       if (pop_text) {
2295         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2296         gst_base_text_overlay_pop_text (overlay);
2297         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2298       }
2299     } else {
2300       gboolean wait_for_text_buf = TRUE;
2301
2302       if (overlay->text_eos)
2303         wait_for_text_buf = FALSE;
2304
2305       if (!overlay->wait_text)
2306         wait_for_text_buf = FALSE;
2307
2308       /* Text pad linked, but no text buffer available - what now? */
2309       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2310         GstClockTime text_start_running_time, text_position_running_time;
2311         GstClockTime vid_running_time;
2312
2313         vid_running_time =
2314             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2315             GST_BUFFER_TIMESTAMP (buffer));
2316         text_start_running_time =
2317             gst_segment_to_running_time (&overlay->text_segment,
2318             GST_FORMAT_TIME, overlay->text_segment.start);
2319         text_position_running_time =
2320             gst_segment_to_running_time (&overlay->text_segment,
2321             GST_FORMAT_TIME, overlay->text_segment.position);
2322
2323         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2324                 vid_running_time < text_start_running_time) ||
2325             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2326                 vid_running_time < text_position_running_time)) {
2327           wait_for_text_buf = FALSE;
2328         }
2329       }
2330
2331       if (wait_for_text_buf) {
2332         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2333         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2334         GST_DEBUG_OBJECT (overlay, "resuming");
2335         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2336         goto wait_for_text_buf;
2337       } else {
2338         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2339         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2340         ret = gst_pad_push (overlay->srcpad, buffer);
2341       }
2342     }
2343   }
2344
2345   g_free (text);
2346
2347   /* Update position */
2348   overlay->segment.position = clip_start;
2349
2350   return ret;
2351
2352 missing_timestamp:
2353   {
2354     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2355     gst_buffer_unref (buffer);
2356     return GST_FLOW_OK;
2357   }
2358
2359 flushing:
2360   {
2361     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2362     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2363     gst_buffer_unref (buffer);
2364     return GST_FLOW_FLUSHING;
2365   }
2366 have_eos:
2367   {
2368     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2369     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2370     gst_buffer_unref (buffer);
2371     return GST_FLOW_EOS;
2372   }
2373 out_of_segment:
2374   {
2375     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2376     gst_buffer_unref (buffer);
2377     return GST_FLOW_OK;
2378   }
2379 }
2380
2381 static GstStateChangeReturn
2382 gst_base_text_overlay_change_state (GstElement * element,
2383     GstStateChange transition)
2384 {
2385   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2386   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2387
2388   switch (transition) {
2389     case GST_STATE_CHANGE_PAUSED_TO_READY:
2390       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2391       overlay->text_flushing = TRUE;
2392       overlay->video_flushing = TRUE;
2393       /* pop_text will broadcast on the GCond and thus also make the video
2394        * chain exit if it's waiting for a text buffer */
2395       gst_base_text_overlay_pop_text (overlay);
2396       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2397       break;
2398     default:
2399       break;
2400   }
2401
2402   ret = parent_class->change_state (element, transition);
2403   if (ret == GST_STATE_CHANGE_FAILURE)
2404     return ret;
2405
2406   switch (transition) {
2407     case GST_STATE_CHANGE_READY_TO_PAUSED:
2408       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2409       overlay->text_flushing = FALSE;
2410       overlay->video_flushing = FALSE;
2411       overlay->video_eos = FALSE;
2412       overlay->text_eos = FALSE;
2413       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2414       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2415       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2416       break;
2417     default:
2418       break;
2419   }
2420
2421   return ret;
2422 }
2423
2424 static gboolean
2425 plugin_init (GstPlugin * plugin)
2426 {
2427   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2428           GST_TYPE_TEXT_OVERLAY) ||
2429       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2430           GST_TYPE_TIME_OVERLAY) ||
2431       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2432           GST_TYPE_CLOCK_OVERLAY) ||
2433       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2434           GST_TYPE_TEXT_RENDER)) {
2435     return FALSE;
2436   }
2437
2438   /*texttestsrc_plugin_init(module, plugin); */
2439
2440   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2441
2442   return TRUE;
2443 }
2444
2445 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2446     pango, "Pango-based text rendering and overlay", plugin_init,
2447     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)