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