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