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