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